Skip to content

feat: print TXT record values quoted, matching dig - #83

Merged
toshi0806 merged 1 commit into
mainfrom
issue-82-quote-txt-rdata
Aug 11, 2026
Merged

feat: print TXT record values quoted, matching dig#83
toshi0806 merged 1 commit into
mainfrom
issue-82-quote-txt-rdata

Conversation

@toshi0806

Copy link
Copy Markdown
Member

概要

TXT レコードの値を dig と同じく引用符付きで出力するようにします。

# before
google.com.  285  IN  TXT  v=spf1 include:_spf.google.com ~all

# after
google.com.  285  IN  TXT  "v=spf1 include:_spf.google.com ~all"

Resolves #82

実装

rdata_to_string(rdata, :txt) を新設の quote_character_string/1 経由に変更しました。

escape/1 自体は変更していません。 この関数は :ns / :ptr / :cname / :mx / :soa のドメイン名でも共用されており、dig はドメイン名を引用しないためです。引用符の付与と " のエスケープは TXT 専用の経路に閉じています。

defp quote_character_string(string) when is_binary(string) do
  escaped = for <<byte <- string>>, into: "", do: escape_character_string_byte(byte)
  <<?", escaped::binary, ?">>
end

defp escape_character_string_byte(?"), do: "\\\""
defp escape_character_string_byte(byte), do: escape_byte(byte)

escape_character_string_byte/1" 以外を既存の escape_byte/1 に委譲するので、\\\ と非印字バイト → \DDD のルールは 1 箇所に保たれ、重複実装がありません。

テスト(TDD)

実装前に red を確認してから着手しました。既存の 2 件は引用符なしを期待していたため更新しています。

  • formats TXT records — 引用符付きの期待値に更新(既存テストの更新)
  • escapes double quotes inside TXT recordsa"b"a\"b"
  • escapes backslashes inside TXT recordsa\b"a\\b"
  • escapes non-printable bytes inside TXT records — ESC を含む値が "\027[31m" になる
  • renders an empty TXT record as empty quotes""
  • leaves domain names unquoted回帰防止。TXT の引用が :ns / :cname / :mx に漏れていないことを確認
  • escapes untrusted TXT and name bytes — 引用符付きの期待値に更新(ESC エスケープの検証意図は維持)

スコープ外: 複数 character-string の個別引用

dig は複数 character-string を "abc" "def" と個別に引用しますが、本 PR では連結して "abcdef" と表示します。

tenbin_dns の decode(rdata, :txt, _, _) が全 character-string を連結した単一バイナリを返し、境界情報を保持しないためです。これは smkwlab/tenbin_dns#95 で 3 案を比較したうえで選択された設計で、境界非保持は受容されたトレードオフとして記録されています(RFC 7208 §3.3 のとおり SPF 等では連結値が論理値)。tenbin_dns 側での再検討も提起しましたが、この判断を覆すには至らないと結論しています。

値そのものは完全に表示されるため情報の欠落はありません。 README の dig 比較表に既知の差異として記載し、コード側にもコメントで経緯を残しました。

動作確認

実機で dig と比較し、単一 character-string のケースで出力が完全に一致することを確認しました。

$ dig +noall +answer google.com TXT @8.8.8.8 | sort | head -3
google.com.		300	IN	TXT	"apple-domain-verification=30afIBcvSuDV2PLX"
google.com.		300	IN	TXT	"cisco-ci-domain-verification=47c38bc8c4b74b7233e9053220c1bbe76bcc1cd33c7acf7acd36cd6a5332004b"
google.com.		300	IN	TXT	"docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e"

$ ./tdig google.com TXT @8.8.8.8 | grep 'IN	TXT' | sort | head -3
google.com.		300	IN	TXT	"apple-domain-verification=30afIBcvSuDV2PLX"
google.com.		300	IN	TXT	"cisco-ci-domain-verification=47c38bc8c4b74b7233e9053220c1bbe76bcc1cd33c7acf7acd36cd6a5332004b"
google.com.		300	IN	TXT	"docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e"

品質チェック:

  • mix test: 60 passed
  • mix credo --strict: no issues
  • mix format --check-formatted: OK
  • mix compile --force --warnings-as-errors: 警告なし

補足

引用符が付いていなかったのは tenbin_dns 0.8.0 以前からの挙動で、今回の依存更新による退行ではありません。なお 0.7.1 では TXT の最初の character-string しか取得できず(残りは破棄)、255 バイト超の TXT は値が途中で切れて表示されていました。0.8.0 で全体が表示されるようになっています。

dig wraps every TXT character-string in double quotes; tdig printed the
value bare, so its output diverged from dig for every TXT lookup.

Quote the value and escape `"` on top of the existing escaping. The
quoting is deliberately kept out of escape/1, which is shared with the
domain-name types (:ns, :ptr, :cname, :mx, :soa) that dig leaves
unquoted; escape_character_string_byte/1 delegates to escape_byte/1 so
the `\\` and `\DDD` rules stay in one place.

Multi-character-string records still print as a single quoted value
because tenbin_dns returns their concatenation and deliberately does not
preserve boundaries (smkwlab/tenbin_dns#95), where dig prints
`"abc" "def"`. No data is lost, and the difference is now documented in
the README's dig comparison.

Verified against `dig +noall +answer google.com TXT @8.8.8.8`: output
matches for single-character-string records.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

全体として非常に丁寧に実装・テストされたPRです。TXTレコードの引用符付き出力、"のエスケープ、既存のescape_byte/1への委譲による重複回避、そしてドメイン名への引用漏れを防ぐ回帰テストまで、設計判断が明確でコメントも充実しています。複数character-stringの連結挙動もREADME・コードコメントで既知の差異として記録されており、トレードオフの説明が適切です。特に指摘すべき重大な問題は見当たりません。

Comment thread lib/tdig.ex
end

defp escape_character_string_byte(?"), do: "\\\""
defp escape_character_string_byte(byte), do: escape_byte(byte)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ [POSITIVE] 実装・コメント・委譲設計とも良好です。escape_character_string_byte/1"以外をescape_byte/1に委譲することで、\\\や非印字バイト→\DDDのルールが1箇所に集約され、重複実装を回避できている点は保守性の観点で優れています。ドメイン名の非引用を守るためにTXT専用経路に閉じている判断も適切です。

@toshi0806
toshi0806 merged commit c3936ba into main Aug 11, 2026
8 checks passed
@toshi0806
toshi0806 deleted the issue-82-quote-txt-rdata branch August 11, 2026 08:14
toshi0806 added a commit that referenced this pull request Aug 11, 2026
Minor rather than patch: this release changes tdig's output, so anyone
parsing it is affected.

- TXT values are now wrapped in double quotes, matching dig (#83)
- Section records print in wire order; they were reversed before
  (via tenbin_dns 0.8.0, #80)
- Long TXT values print in full; only the first character-string used to
  be read (via tenbin_dns 0.8.0, #80)
- dig-style argument order is accepted and unknown types are rejected
  (#78)

The release workflow refuses to publish when mix.exs and the pushed tag
disagree, so this has to land before tagging 0.5.0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Print TXT record values quoted, matching dig output

1 participant