mirror of
				https://github.com/StackExchange/dnscontrol.git
				synced 2024-05-11 05:55:12 +00:00 
			
		
		
		
	REFACTOR: Opinion: TXT records are one long string (#2631)
Co-authored-by: Costas Drogos <costas.drogos@gmail.com> Co-authored-by: imlonghao <git@imlonghao.com> Co-authored-by: Jeffrey Cafferata <jeffrey@jcid.nl> Co-authored-by: Vincent Hagen <blackshadev@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										155
									
								
								pkg/txtutil/txtcode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								pkg/txtutil/txtcode.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,155 @@ | ||||
| //go:generate stringer -type=State | ||||
|  | ||||
| package txtutil | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| ) | ||||
|  | ||||
| // ParseQuoted parses a string of RFC1035-style quoted items. The resulting | ||||
| // items are then joined into one string. This is useful for parsing TXT | ||||
| // records. | ||||
| // Examples: | ||||
| // `foo` => foo | ||||
| // `"foo"` => foo | ||||
| // `"f\"oo"` => f"oo | ||||
| // `"f\\oo"` => f\oo | ||||
| // `"foo" "bar"` => foobar | ||||
| // `"foo" bar` => foobar | ||||
| func ParseQuoted(s string) (string, error) { | ||||
| 	return txtDecode(s) | ||||
| } | ||||
|  | ||||
| // EncodeQuoted encodes a string into a series of quoted 255-octet chunks. That | ||||
| // is, when decoded each chunk would be 255-octets with the remainder in the | ||||
| // last chunk. | ||||
| // | ||||
| // The output looks like: | ||||
| // | ||||
| //	`""`                                      empty | ||||
| //	`"255\"octets"`                           quotes are escaped | ||||
| //	`"255\\octets"`                           backslashes are escaped | ||||
| //	`"255octets" "255octets" "remainder"`     long strings are chunked | ||||
| func EncodeQuoted(t string) string { | ||||
| 	return txtEncode(ToChunks(t)) | ||||
| } | ||||
|  | ||||
| type State int | ||||
|  | ||||
| const ( | ||||
| 	StateStart     State = iota // Looking for a non-space | ||||
| 	StateUnquoted               // A run of unquoted text | ||||
| 	StateQuoted                 // Quoted text | ||||
| 	StateBackslash              // last char was backlash in a quoted string | ||||
| 	StateWantSpace              // expect space after closing quote | ||||
| ) | ||||
|  | ||||
| func isRemaining(s string, i, r int) bool { | ||||
| 	return (len(s) - 1 - i) > r | ||||
| } | ||||
|  | ||||
| // txtDecode decodes TXT strings quoted/escaped as Tom interprets RFC10225. | ||||
| func txtDecode(s string) (string, error) { | ||||
| 	// Parse according to RFC1035 zonefile specifications. | ||||
| 	// "foo"  -> one string: `foo`` | ||||
| 	// "foo" "bar"  -> two strings: `foo` and `bar` | ||||
| 	// quotes and backslashes are escaped using \ | ||||
|  | ||||
| 	/* | ||||
|  | ||||
| 		BNF: | ||||
| 			txttarget := `""`` | item | item ` ` item* | ||||
| 			item := quoteditem | unquoteditem | ||||
| 			quoteditem := quote innertxt quote | ||||
| 			quote := `"` | ||||
| 			innertxt := (escaped | printable )* | ||||
| 			escaped := `\\` | `\"` | ||||
| 			printable := (printable ASCII chars) | ||||
| 			unquoteditem := (printable ASCII chars but not `"` nor ' ') | ||||
|  | ||||
| 	*/ | ||||
|  | ||||
| 	//printer.Printf("DEBUG: txtDecode txt inboundv=%v\n", s) | ||||
|  | ||||
| 	b := &bytes.Buffer{} | ||||
| 	state := StateStart | ||||
| 	for i, c := range s { | ||||
|  | ||||
| 		//printer.Printf("DEBUG: state=%v rune=%v\n", state, string(c)) | ||||
|  | ||||
| 		switch state { | ||||
|  | ||||
| 		case StateStart: | ||||
| 			if c == ' ' { | ||||
| 				// skip whitespace | ||||
| 			} else if c == '"' { | ||||
| 				state = StateQuoted | ||||
| 			} else { | ||||
| 				state = StateUnquoted | ||||
| 				b.WriteRune(c) | ||||
| 			} | ||||
|  | ||||
| 		case StateUnquoted: | ||||
|  | ||||
| 			if c == ' ' { | ||||
| 				state = StateStart | ||||
| 			} else { | ||||
| 				b.WriteRune(c) | ||||
| 			} | ||||
|  | ||||
| 		case StateQuoted: | ||||
|  | ||||
| 			if c == '\\' { | ||||
| 				if isRemaining(s, i, 1) { | ||||
| 					state = StateBackslash | ||||
| 				} else { | ||||
| 					return "", fmt.Errorf("txtDecode quoted string ends with backslash q(%q)", s) | ||||
| 				} | ||||
| 			} else if c == '"' { | ||||
| 				state = StateWantSpace | ||||
| 			} else { | ||||
| 				b.WriteRune(c) | ||||
| 			} | ||||
|  | ||||
| 		case StateBackslash: | ||||
| 			b.WriteRune(c) | ||||
| 			state = StateQuoted | ||||
|  | ||||
| 		case StateWantSpace: | ||||
| 			if c == ' ' { | ||||
| 				state = StateStart | ||||
| 			} else { | ||||
| 				return "", fmt.Errorf("txtDecode expected whitespace after close quote q(%q)", s) | ||||
| 			} | ||||
|  | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r := b.String() | ||||
| 	//printer.Printf("DEBUG: txtDecode txt decodedv=%v\n", r) | ||||
| 	return r, nil | ||||
| } | ||||
|  | ||||
| // txtEncode encodes TXT strings in RFC1035 format as interpreted by Tom. | ||||
| func txtEncode(ts []string) string { | ||||
| 	//printer.Printf("DEBUG: txtEncode txt outboundv=%v\n", ts) | ||||
| 	if (len(ts) == 0) || (strings.Join(ts, "") == "") { | ||||
| 		return `""` | ||||
| 	} | ||||
|  | ||||
| 	var r []string | ||||
|  | ||||
| 	for i := range ts { | ||||
| 		tx := ts[i] | ||||
| 		tx = strings.ReplaceAll(tx, `\`, `\\`) | ||||
| 		tx = strings.ReplaceAll(tx, `"`, `\"`) | ||||
| 		tx = `"` + tx + `"` | ||||
| 		r = append(r, tx) | ||||
| 	} | ||||
| 	t := strings.Join(r, ` `) | ||||
|  | ||||
| 	//printer.Printf("DEBUG: txtEncode txt  encodedv=%v\n", t) | ||||
| 	return t | ||||
| } | ||||
							
								
								
									
										97
									
								
								pkg/txtutil/txtcode_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								pkg/txtutil/txtcode_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| package txtutil | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func r(s string, c int) string { return strings.Repeat(s, c) } | ||||
|  | ||||
| func TestTxtDecode(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		data     string | ||||
| 		expected []string | ||||
| 	}{ | ||||
| 		{``, []string{``}}, | ||||
| 		{`""`, []string{``}}, | ||||
| 		{`foo`, []string{`foo`}}, | ||||
| 		{`"foo"`, []string{`foo`}}, | ||||
| 		{`"foo bar"`, []string{`foo bar`}}, | ||||
| 		{`foo bar`, []string{`foo`, `bar`}}, | ||||
| 		{`"aaa" "bbb"`, []string{`aaa`, `bbb`}}, | ||||
| 		{`"a\"a" "bbb"`, []string{`a"a`, `bbb`}}, | ||||
| 		// Seen in live traffic: | ||||
| 		{"\"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\"", | ||||
| 			[]string{r("B", 254)}}, | ||||
| 		{"\"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\"", | ||||
| 			[]string{r("C", 255)}}, | ||||
| 		{"\"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD\" \"D\"", | ||||
| 			[]string{r("D", 255), "D"}}, | ||||
| 		{"\"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\" \"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\"", | ||||
| 			[]string{r("E", 255), r("E", 255)}}, | ||||
| 		{"\"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\" \"F\"", | ||||
| 			[]string{r("F", 255), r("F", 255), "F"}}, | ||||
| 		{"\"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\" \"GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\"", | ||||
| 			[]string{r("G", 255), r("G", 255), r("G", 255)}}, | ||||
| 		{"\"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH\" \"H\"", | ||||
| 			[]string{r("H", 255), r("H", 255), r("H", 255), "H"}}, | ||||
| 		{"\"quo'te\"", []string{`quo'te`}}, | ||||
| 		{"\"blah`blah\"", []string{"blah`blah"}}, | ||||
| 		{"\"quo\\\"te\"", []string{`quo"te`}}, | ||||
| 		{"\"q\\\"uo\\\"te\"", []string{`q"uo"te`}}, | ||||
| 		/// Backslashes are meaningless in unquoted strings. Unquoted strings run until they hit a space. | ||||
| 		{`1backs\lash`, []string{`1backs\lash`}}, | ||||
| 		{`2backs\\lash`, []string{`2backs\\lash`}}, | ||||
| 		{`3backs\\\lash`, []string{`3backs\\\lash`}}, | ||||
| 		{`4backs\\\\lash`, []string{`4backs\\\\lash`}}, | ||||
| 		/// Inside quotes, a backlash means take the next byte literally. | ||||
| 		{`"q1backs\lash"`, []string{`q1backslash`}}, | ||||
| 		{`"q2backs\\lash"`, []string{`q2backs\lash`}}, | ||||
| 		{`"q3backs\\\lash"`, []string{`q3backs\lash`}}, | ||||
| 		{`"q4backs\\\\lash"`, []string{`q4backs\\lash`}}, | ||||
| 		// HETZNER includes a space after the last quote. Make sure we handle that. | ||||
| 		{`"one" "more" `, []string{`one`, `more`}}, | ||||
| 	} | ||||
| 	for i, test := range tests { | ||||
| 		got, err := txtDecode(test.data) | ||||
| 		if err != nil { | ||||
| 			t.Error(err) | ||||
| 		} | ||||
|  | ||||
| 		want := strings.Join(test.expected, "") | ||||
| 		if got != want { | ||||
| 			t.Errorf("%v: expected TxtStrings=(%q) got (%q)", i, want, got) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestTxtEncode(t *testing.T) { | ||||
| 	tests := []struct { | ||||
| 		data     []string | ||||
| 		expected string | ||||
| 	}{ | ||||
| 		{[]string{}, `""`}, | ||||
| 		{[]string{``}, `""`}, | ||||
| 		{[]string{`foo`}, `"foo"`}, | ||||
| 		{[]string{`aaa`, `bbb`}, `"aaa" "bbb"`}, | ||||
| 		{[]string{`ccc`, `ddd`, `eee`}, `"ccc" "ddd" "eee"`}, | ||||
| 		{[]string{`a"a`, `bbb`}, `"a\"a" "bbb"`}, | ||||
| 		{[]string{`quo'te`}, "\"quo'te\""}, | ||||
| 		{[]string{"blah`blah"}, "\"blah`blah\""}, | ||||
| 		{[]string{`quo"te`}, "\"quo\\\"te\""}, | ||||
| 		{[]string{`quo"te`}, `"quo\"te"`}, | ||||
| 		{[]string{`q"uo"te`}, "\"q\\\"uo\\\"te\""}, | ||||
| 		{[]string{`1backs\lash`}, `"1backs\\lash"`}, | ||||
| 		{[]string{`2backs\\lash`}, `"2backs\\\\lash"`}, | ||||
| 		{[]string{`3backs\\\lash`}, `"3backs\\\\\\lash"`}, | ||||
| 		{[]string{strings.Repeat("M", 26), `quo"te`}, `"MMMMMMMMMMMMMMMMMMMMMMMMMM" "quo\"te"`}, | ||||
| 	} | ||||
| 	for i, test := range tests { | ||||
| 		got := txtEncode(test.data) | ||||
|  | ||||
| 		want := test.expected | ||||
| 		if got != want { | ||||
| 			t.Errorf("%v: expected TxtStrings=v(%v) got (%v)", i, want, got) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										53
									
								
								pkg/txtutil/txtcombined.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								pkg/txtutil/txtcombined.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| //go:generate stringer -type=State | ||||
|  | ||||
| package txtutil | ||||
|  | ||||
| // func ParseCombined(s string) (string, error) { | ||||
| // 	return txtDecodeCombined(s) | ||||
| // } | ||||
|  | ||||
| // // // txtDecode decodes TXT strings received from ROUTE53 and GCLOUD. | ||||
| // func txtDecodeCombined(s string) (string, error) { | ||||
|  | ||||
| // 	// The dns package doesn't expose the quote parser. Therefore we create a TXT record and extract the strings. | ||||
| // 	rr, err := dns.NewRR("example.com. IN TXT " + s) | ||||
| // 	if err != nil { | ||||
| // 		return "", fmt.Errorf("could not parse %q TXT: %w", s, err) | ||||
| // 	} | ||||
|  | ||||
| // 	return strings.Join(rr.(*dns.TXT).Txt, ""), nil | ||||
| // } | ||||
|  | ||||
| // func EncodeCombined(t string) string { | ||||
| // 	return txtEncodeCombined(ToChunks(t)) | ||||
| // } | ||||
|  | ||||
| // // txtEncode encodes TXT strings as the old GetTargetCombined() function did. | ||||
| // func txtEncodeCombined(ts []string) string { | ||||
| // 	//printer.Printf("DEBUG: route53 txt outboundv=%v\n", ts) | ||||
|  | ||||
| // 	// Don't call this on fake types. | ||||
| // 	rdtype := dns.StringToType["TXT"] | ||||
|  | ||||
| // 	// Magically create an RR of the correct type. | ||||
| // 	rr := dns.TypeToRR[rdtype]() | ||||
|  | ||||
| // 	// Fill in the header. | ||||
| // 	rr.Header().Name = "example.com." | ||||
| // 	rr.Header().Rrtype = rdtype | ||||
| // 	rr.Header().Class = dns.ClassINET | ||||
| // 	rr.Header().Ttl = 300 | ||||
|  | ||||
| // 	// Fill in the TXT data. | ||||
| // 	rr.(*dns.TXT).Txt = ts | ||||
|  | ||||
| // 	// Generate the quoted string: | ||||
| // 	header := rr.Header().String() | ||||
| // 	full := rr.String() | ||||
| // 	if !strings.HasPrefix(full, header) { | ||||
| // 		panic("assertion failed. dns.Hdr.String() behavior has changed in an incompatible way") | ||||
| // 	} | ||||
|  | ||||
| // 	//printer.Printf("DEBUG: route53 txt  encodedv=%v\n", t) | ||||
| // 	return full[len(header):] | ||||
| // } | ||||
| @@ -1,20 +1,13 @@ | ||||
| package txtutil | ||||
|  | ||||
| import "github.com/StackExchange/dnscontrol/v4/models" | ||||
| // SplitSingleLongTxt does nothing. | ||||
| // Deprecated: This is a no-op for backwards compatibility. | ||||
| func SplitSingleLongTxt(records any) { | ||||
| } | ||||
|  | ||||
| // SplitSingleLongTxt finds TXT records with a single long string and splits it | ||||
| // into 255-octet chunks. This is used by providers that, when a user specifies | ||||
| // one long TXT string, split it into smaller strings behind the scenes. | ||||
| // This should be called from GetZoneRecordsCorrections(). | ||||
| func SplitSingleLongTxt(records []*models.RecordConfig) { | ||||
| 	for _, rc := range records { | ||||
| 		if rc.HasFormatIdenticalToTXT() { | ||||
| 			s := rc.TxtStrings[0] | ||||
| 			if len(rc.TxtStrings) == 1 && len(s) > 255 { | ||||
| 				rc.SetTargetTXTs(splitChunks(s, 255)) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| // ToChunks returns the string as chunks of 255-octet strings (the last string being the remainder). | ||||
| func ToChunks(s string) []string { | ||||
| 	return splitChunks(s, 255) | ||||
| } | ||||
|  | ||||
| // Segment returns the string as 255-octet segments, the last being the remainder. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user