// Package encoder provides functionality to translate file names// for usage on restrictive storage systems.//// The restricted set of characters are mapped to a unicode equivalent version// (most to their FULLWIDTH variant) to increase compatibility with other// storage systems.// See: http://unicode-search.net/unicode-namesearch.pl?term=FULLWIDTH//// Encoders will also quote reserved characters to differentiate between// the raw and encoded forms.package encoder import ( "bytes" "fmt" "io" "sort" "strconv" "strings" "unicode/utf8") const ( // adding this to any printable ASCII character turns it into the // FULLWIDTH variant fullOffset = 0xFEE0 // the first rune of the SYMBOL FOR block for control characters symbolOffset = '␀' // SYMBOL FOR NULL // QuoteRune is the rune used for quoting reserved characters QuoteRune = '‛' // SINGLE HIGH-REVERSED-9 QUOTATION MARK) // NB keep the tests in fstests/fstests/fstests.go FsEncoding up to date with this// NB keep the aliases up to date below also // Possible flags for the MultiEncoderconst ( EncodeZero MultiEncoder = 0 // NUL(0x00) EncodeRaw MultiEncoder = 1 << (iota - 1) EncodeSlash // / EncodeLtGt // <> EncodeDoubleQuote // " EncodeSingleQuote // ' EncodeBackQuote // ` EncodeDollar // $ EncodeColon // : EncodeQuestion // ? EncodeAsterisk // * EncodePipe // | EncodeHash // # EncodePercent // % EncodeBackSlash // \ EncodeCrLf // CR(0x0D), LF(0x0A) EncodeDel // DEL(0x7F) EncodeCtl // CTRL(0x01-0x1F) EncodeLeftSpace // Leading SPACE EncodeLeftPeriod // Leading . EncodeLeftTilde // Leading ~ EncodeLeftCrLfHtVt // Leading CR LF HT VT EncodeRightSpace // Trailing SPACE EncodeRightPeriod // Trailing . EncodeRightCrLfHtVt // Trailing CR LF HT VT EncodeInvalidUtf8 // Invalid UTF-8 bytes EncodeDot // . and .. names EncodeSquareBracket // [] EncodeSemicolon // ; EncodeExclamation // ! // Synthetic EncodeWin = EncodeColon | EncodeQuestion | EncodeDoubleQuote | EncodeAsterisk | EncodeLtGt | EncodePipe // :?"*<>| EncodeHashPercent = EncodeHash | EncodePercent // #%) // Has returns true if flag is contained in maskfunc (mask MultiEncoder) Has(flag MultiEncoder) bool { return mask&flag != 0} // Encoder can transform names to and from the original and translated version.type Encoder interface { // Encode takes a raw name and substitutes any reserved characters and // patterns in it Encode(string) string // Decode takes a name and undoes any substitutions made by Encode Decode(string) string // FromStandardPath takes a / separated path in Standard encoding // and converts it to a / separated path in this encoding. FromStandardPath(string) string // FromStandardName takes name in Standard encoding and converts // it in this encoding. FromStandardName(string) string // ToStandardPath takes a / separated path in this encoding // and converts it to a / separated path in Standard encoding. ToStandardPath(string) string // ToStandardName takes name in this encoding and converts // it in Standard encoding. ToStandardName(string) string} // MultiEncoder is a configurable Encoder. The Encode* constants in this// package can be combined using bitwise or (|) to enable handling of multiple// character classestype MultiEncoder uint // Aliases maps encodings to names and vice versavar ( encodingToName = map[MultiEncoder]string{} nameToEncoding = map[string]MultiEncoder{}) // alias adds an alias for MultiEncoder.String() and MultiEncoder.Set()func alias(name string, mask MultiEncoder) { nameToEncoding[name] = mask // don't overwrite existing reverse translations if _, ok := encodingToName[mask]; !ok { encodingToName[mask] = name }} func init() { alias("Raw", EncodeRaw) alias("None", EncodeZero) alias("Slash", EncodeSlash) alias("LtGt", EncodeLtGt) alias("SquareBracket", EncodeSquareBracket) alias("Semicolon", EncodeSemicolon) alias("Exclamation", EncodeExclamation) alias("DoubleQuote", EncodeDoubleQuote) alias("SingleQuote", EncodeSingleQuote) alias("BackQuote", EncodeBackQuote) alias("Dollar", EncodeDollar) alias("Colon", EncodeColon) alias("Question", EncodeQuestion) alias("Asterisk", EncodeAsterisk) alias("Pipe", EncodePipe) alias("Hash", EncodeHash) alias("Percent", EncodePercent) alias("BackSlash", EncodeBackSlash) alias("CrLf", EncodeCrLf) alias("Del", EncodeDel) alias("Ctl", EncodeCtl) alias("LeftSpace", EncodeLeftSpace) alias("LeftPeriod", EncodeLeftPeriod) alias("LeftTilde", EncodeLeftTilde) alias("LeftCrLfHtVt", EncodeLeftCrLfHtVt) alias("RightSpace", EncodeRightSpace) alias("RightPeriod", EncodeRightPeriod) alias("RightCrLfHtVt", EncodeRightCrLfHtVt) alias("InvalidUtf8", EncodeInvalidUtf8) alias("Dot", EncodeDot)} // ValidStrings returns all the valid MultiEncoder stringsfunc ValidStrings() string { var out []string for k := range nameToEncoding { out = append(out, k) } sort.Strings(out) return strings.Join(out, ", ")} // String converts the MultiEncoder into textfunc (mask MultiEncoder) String() string { // See if there is an exact translation - if so return that if name, ok := encodingToName[mask]; ok { return name } var out []string // Otherwise decompose bit by bit for bit := MultiEncoder(1); bit != 0; bit *= 2 { if (mask & bit) != 0 { if name, ok := encodingToName[bit]; ok { out = append(out, name) } else { out = append(out, fmt.Sprintf("0x%X", uint(bit))) } } } return strings.Join(out, ",")} // Set converts a string into a MultiEncoderfunc (mask *MultiEncoder) Set(in string) error { var out MultiEncoder parts := strings.SplitSeq(in, ",") for part := range parts { part = strings.TrimSpace(part) if bits, ok := nameToEncoding[part]; ok { out |= bits } else { i, err := strconv.ParseUint(part, 0, 0) if err != nil { return fmt.Errorf("bad encoding %q: possible values are: %s", part, ValidStrings()) } out |= MultiEncoder(i) } } *mask = out return nil} // Type returns a textual type of the MultiEncoder to satisfy the pflag.Value interfacefunc (mask MultiEncoder) Type() string { return "Encoding"} // Scan implements the fmt.Scanner interfacefunc (mask *MultiEncoder) Scan(s fmt.ScanState, ch rune) error { token, err := s.Token(true, nil) if err != nil { return err } return mask.Set(string(token))} // Encode takes a raw name and substitutes any reserved characters and// patterns in itfunc (mask MultiEncoder) Encode(in string) string { if mask == EncodeRaw { return in } if in == "" { return "" } if mask.Has(EncodeDot) { switch in { case ".": return "." case "..": return ".." case ".": return string(QuoteRune) + "." case "..": return string(QuoteRune) + "." + string(QuoteRune) + "." } } // handle prefix only replacements prefix := "" if mask.Has(EncodeLeftSpace) { // Leading SPACE if in[0] == ' ' { prefix, in = "␠", in[1:] // SYMBOL FOR SPACE } else if r, l := utf8.DecodeRuneInString(in); r == '␠' { // SYMBOL FOR SPACE prefix, in = string(QuoteRune)+"␠", in[l:] // SYMBOL FOR SPACE } } if mask.Has(EncodeLeftPeriod) && prefix == "" { // Leading PERIOD if in[0] == '.' { prefix, in = ".", in[1:] // FULLWIDTH FULL STOP } else if r, l := utf8.DecodeRuneInString(in); r == '.' { // FULLWIDTH FULL STOP prefix, in = string(QuoteRune)+".", in[l:] // FULLWIDTH FULL STOP } } if mask.Has(EncodeLeftTilde) && prefix == "" { // Leading ~ if in[0] == '~' { prefix, in = string('~'+fullOffset), in[1:] // FULLWIDTH TILDE } else if r, l := utf8.DecodeRuneInString(in); r == '~'+fullOffset { prefix, in = string(QuoteRune)+string('~'+fullOffset), in[l:] // FULLWIDTH TILDE } } if mask.Has(EncodeLeftCrLfHtVt) && prefix == "" { // Leading CR LF HT VT switch c := in[0]; c { case '\t', '\n', '\v', '\r': prefix, in = string('␀'+rune(c)), in[1:] // SYMBOL FOR NULL default: switch r, l := utf8.DecodeRuneInString(in); r { case '␀' + '\t', '␀' + '\n', '␀' + '\v', '␀' + '\r': prefix, in = string(QuoteRune)+string(r), in[l:] } } } // handle suffix only replacements suffix := "" if in != "" { if mask.Has(EncodeRightSpace) { // Trailing SPACE if in[len(in)-1] == ' ' { suffix, in = "␠", in[:len(in)-1] // SYMBOL FOR SPACE } else if r, l := utf8.DecodeLastRuneInString(in); r == '␠' { suffix, in = string(QuoteRune)+"␠", in[:len(in)-l] // SYMBOL FOR SPACE } } if mask.Has(EncodeRightPeriod) && suffix == "" { // Trailing . if in[len(in)-1] == '.' { suffix, in = ".", in[:len(in)-1] // FULLWIDTH FULL STOP } else if r, l := utf8.DecodeLastRuneInString(in); r == '.' { suffix, in = string(QuoteRune)+".", in[:len(in)-l] // FULLWIDTH FULL STOP } } if mask.Has(EncodeRightCrLfHtVt) && suffix == "" { // Trailing . switch c := in[len(in)-1]; c { case '\t', '\n', '\v', '\r': suffix, in = string('␀'+rune(c)), in[:len(in)-1] // FULLWIDTH FULL STOP default: switch r, l := utf8.DecodeLastRuneInString(in); r { case '␀' + '\t', '␀' + '\n', '␀' + '\v', '␀' + '\r': suffix, in = string(QuoteRune)+string(r), in[:len(in)-l] } } } } index := 0 if prefix == "" && suffix == "" { // find the first rune which (most likely) needs to be replaced index = strings.IndexFunc(in, func(r rune) bool { switch r { case 0, '␀', QuoteRune, utf8.RuneError: return true } if mask.Has(EncodeAsterisk) { // * switch r { case '*', '*': return true } } if mask.Has(EncodeLtGt) { // <> switch r { case '<', '>', '<', '>': return true } } if mask.Has(EncodeSquareBracket) { // [] switch r { case '[', ']', '[', ']': return true } } if mask.Has(EncodeSemicolon) { // ; switch r { case ';', ';': return true } } if mask.Has(EncodeExclamation) { // ! switch r { case '!', '!': return true } } if mask.Has(EncodeQuestion) { // ? switch r { case '?', '?': return true } } if mask.Has(EncodeColon) { // : switch r { case ':', ':': return true } } if mask.Has(EncodePipe) { // | switch r { case '|', '|': return true } } if mask.Has(EncodeDoubleQuote) { // " switch r { case '"', '"': return true } } if mask.Has(EncodeSingleQuote) { // ' switch r { case '\'', ''': return true } } if mask.Has(EncodeBackQuote) { // ` switch r { case '`', '`': return true } } if mask.Has(EncodeDollar) { // $ switch r { case '$', '$': return true } } if mask.Has(EncodeSlash) { // / switch r { case '/', '/': return true } } if mask.Has(EncodeBackSlash) { // \ switch r { case '\\', '\': return true } } if mask.Has(EncodeCrLf) { // CR LF switch r { case rune(0x0D), rune(0x0A), '␍', '␊': return true } } if mask.Has(EncodeHash) { // # switch r { case '#', '#': return true } } if mask.Has(EncodePercent) { // % switch r { case '%', '%': return true } } if mask.Has(EncodeDel) { // DEL(0x7F) switch r { case rune(0x7F), '␡': return true } } if mask.Has(EncodeCtl) { // CTRL(0x01-0x1F) if r >= 1 && r <= 0x1F { return true } else if r > symbolOffset && r <= symbolOffset+0x1F { return true } } return false }) } // nothing to replace, return input if index == -1 { return in } var out bytes.Buffer out.Grow(len(in) + len(prefix) + len(suffix)) out.WriteString(prefix) // copy the clean part of the input and skip it out.WriteString(in[:index]) in = in[index:] for i, r := range in { switch r { case 0: out.WriteRune(symbolOffset) continue case '␀', QuoteRune: out.WriteRune(QuoteRune) out.WriteRune(r) continue case utf8.RuneError: if mask.Has(EncodeInvalidUtf8) { // only encode invalid sequences and not utf8.RuneError if i+3 > len(in) || in[i:i+3] != string(utf8.RuneError) { _, l := utf8.DecodeRuneInString(in[i:]) appendQuotedBytes(&out, in[i:i+l]) continue } } else { // append the real bytes instead of utf8.RuneError _, l := utf8.DecodeRuneInString(in[i:]) out.WriteString(in[i : i+l]) continue } } if mask.Has(EncodeAsterisk) { // * switch r { case '*': out.WriteRune(r + fullOffset) continue case '*': out.WriteRune(QuoteRune) out.WriteRune(r) continue } } if mask.Has(EncodeLtGt) { // <> switch r { case '<', '>': out.WriteRune(r + fullOffset) continue case '<', '>': out.WriteRune(QuoteRune) out.WriteRune(r) continue } } if mask.Has(EncodeSquareBracket) { // [] switch r { case '[', ']': out.WriteRune(r + fullOffset) continue case '[', ']': out.WriteRune(QuoteRune) out.WriteRune(r)Lines 1–500 of 1268. Load more
// Package encoder provides functionality to translate file names // for usage on restrictive storage systems. // // The restricted set of characters are mapped to a unicode equivalent version // (most to their FULLWIDTH variant) to increase compatibility with other // storage systems. // See: http://unicode-search.net/unicode-namesearch.pl?term=FULLWIDTH // // Encoders will also quote reserved characters to differentiate between // the raw and encoded forms. package encoder import ( "bytes" "fmt" "io" "sort" "strconv" "strings" "unicode/utf8" ) const ( // adding this to any printable ASCII character turns it into the // FULLWIDTH variant fullOffset = 0xFEE0 // the first rune of the SYMBOL FOR block for control characters symbolOffset = '␀' // SYMBOL FOR NULL // QuoteRune is the rune used for quoting reserved characters QuoteRune = '‛' // SINGLE HIGH-REVERSED-9 QUOTATION MARK ) // NB keep the tests in fstests/fstests/fstests.go FsEncoding up to date with this // NB keep the aliases up to date below also // Possible flags for the MultiEncoder const ( EncodeZero MultiEncoder = 0 // NUL(0x00) EncodeRaw MultiEncoder = 1 << (iota - 1) EncodeSlash // / EncodeLtGt // <> EncodeDoubleQuote // " EncodeSingleQuote // ' EncodeBackQuote // ` EncodeDollar // $ EncodeColon // : EncodeQuestion // ? EncodeAsterisk // * EncodePipe // | EncodeHash // # EncodePercent // % EncodeBackSlash // \ EncodeCrLf // CR(0x0D), LF(0x0A) EncodeDel // DEL(0x7F) EncodeCtl // CTRL(0x01-0x1F) EncodeLeftSpace // Leading SPACE EncodeLeftPeriod // Leading . EncodeLeftTilde // Leading ~ EncodeLeftCrLfHtVt // Leading CR LF HT VT EncodeRightSpace // Trailing SPACE EncodeRightPeriod // Trailing . EncodeRightCrLfHtVt // Trailing CR LF HT VT EncodeInvalidUtf8 // Invalid UTF-8 bytes EncodeDot // . and .. names EncodeSquareBracket // [] EncodeSemicolon // ; EncodeExclamation // ! // Synthetic EncodeWin = EncodeColon | EncodeQuestion | EncodeDoubleQuote | EncodeAsterisk | EncodeLtGt | EncodePipe // :?"*<>| EncodeHashPercent = EncodeHash | EncodePercent // #% ) // Has returns true if flag is contained in mask func (mask MultiEncoder) Has(flag MultiEncoder) bool { return mask&flag != 0 } // Encoder can transform names to and from the original and translated version. type Encoder interface { // Encode takes a raw name and substitutes any reserved characters and // patterns in it Encode(string) string // Decode takes a name and undoes any substitutions made by Encode Decode(string) string // FromStandardPath takes a / separated path in Standard encoding // and converts it to a / separated path in this encoding. FromStandardPath(string) string // FromStandardName takes name in Standard encoding and converts // it in this encoding. FromStandardName(string) string // ToStandardPath takes a / separated path in this encoding // and converts it to a / separated path in Standard encoding. ToStandardPath(string) string // ToStandardName takes name in this encoding and converts // it in Standard encoding. ToStandardName(string) string } // MultiEncoder is a configurable Encoder. The Encode* constants in this // package can be combined using bitwise or (|) to enable handling of multiple // character classes type MultiEncoder uint // Aliases maps encodings to names and vice versa var ( encodingToName = map[MultiEncoder]string{} nameToEncoding = map[string]MultiEncoder{} ) // alias adds an alias for MultiEncoder.String() and MultiEncoder.Set() func alias(name string, mask MultiEncoder) { nameToEncoding[name] = mask // don't overwrite existing reverse translations if _, ok := encodingToName[mask]; !ok { encodingToName[mask] = name } } func init() { alias("Raw", EncodeRaw) alias("None", EncodeZero) alias("Slash", EncodeSlash) alias("LtGt", EncodeLtGt) alias("SquareBracket", EncodeSquareBracket) alias("Semicolon", EncodeSemicolon) alias("Exclamation", EncodeExclamation) alias("DoubleQuote", EncodeDoubleQuote) alias("SingleQuote", EncodeSingleQuote) alias("BackQuote", EncodeBackQuote) alias("Dollar", EncodeDollar) alias("Colon", EncodeColon) alias("Question", EncodeQuestion) alias("Asterisk", EncodeAsterisk) alias("Pipe", EncodePipe) alias("Hash", EncodeHash) alias("Percent", EncodePercent) alias("BackSlash", EncodeBackSlash) alias("CrLf", EncodeCrLf) alias("Del", EncodeDel) alias("Ctl", EncodeCtl) alias("LeftSpace", EncodeLeftSpace) alias("LeftPeriod", EncodeLeftPeriod) alias("LeftTilde", EncodeLeftTilde) alias("LeftCrLfHtVt", EncodeLeftCrLfHtVt) alias("RightSpace", EncodeRightSpace) alias("RightPeriod", EncodeRightPeriod) alias("RightCrLfHtVt", EncodeRightCrLfHtVt) alias("InvalidUtf8", EncodeInvalidUtf8) alias("Dot", EncodeDot) } // ValidStrings returns all the valid MultiEncoder strings func ValidStrings() string { var out []string for k := range nameToEncoding { out = append(out, k) } sort.Strings(out) return strings.Join(out, ", ") } // String converts the MultiEncoder into text func (mask MultiEncoder) String() string { // See if there is an exact translation - if so return that if name, ok := encodingToName[mask]; ok { return name } var out []string // Otherwise decompose bit by bit for bit := MultiEncoder(1); bit != 0; bit *= 2 { if (mask & bit) != 0 { if name, ok := encodingToName[bit]; ok { out = append(out, name) } else { out = append(out, fmt.Sprintf("0x%X", uint(bit))) } } } return strings.Join(out, ",") } // Set converts a string into a MultiEncoder func (mask *MultiEncoder) Set(in string) error { var out MultiEncoder parts := strings.SplitSeq(in, ",") for part := range parts { part = strings.TrimSpace(part) if bits, ok := nameToEncoding[part]; ok { out |= bits } else { i, err := strconv.ParseUint(part, 0, 0) if err != nil { return fmt.Errorf("bad encoding %q: possible values are: %s", part, ValidStrings()) } out |= MultiEncoder(i) } } *mask = out return nil } // Type returns a textual type of the MultiEncoder to satisfy the pflag.Value interface func (mask MultiEncoder) Type() string { return "Encoding" } // Scan implements the fmt.Scanner interface func (mask *MultiEncoder) Scan(s fmt.ScanState, ch rune) error { token, err := s.Token(true, nil) if err != nil { return err } return mask.Set(string(token)) } // Encode takes a raw name and substitutes any reserved characters and // patterns in it func (mask MultiEncoder) Encode(in string) string { if mask == EncodeRaw { return in } if in == "" { return "" } if mask.Has(EncodeDot) { switch in { case ".": return "." case "..": return ".." case ".": return string(QuoteRune) + "." case "..": return string(QuoteRune) + "." + string(QuoteRune) + "." } } // handle prefix only replacements prefix := "" if mask.Has(EncodeLeftSpace) { // Leading SPACE if in[0] == ' ' { prefix, in = "␠", in[1:] // SYMBOL FOR SPACE } else if r, l := utf8.DecodeRuneInString(in); r == '␠' { // SYMBOL FOR SPACE prefix, in = string(QuoteRune)+"␠", in[l:] // SYMBOL FOR SPACE } } if mask.Has(EncodeLeftPeriod) && prefix == "" { // Leading PERIOD if in[0] == '.' { prefix, in = ".", in[1:] // FULLWIDTH FULL STOP } else if r, l := utf8.DecodeRuneInString(in); r == '.' { // FULLWIDTH FULL STOP prefix, in = string(QuoteRune)+".", in[l:] // FULLWIDTH FULL STOP } } if mask.Has(EncodeLeftTilde) && prefix == "" { // Leading ~ if in[0] == '~' { prefix, in = string('~'+fullOffset), in[1:] // FULLWIDTH TILDE } else if r, l := utf8.DecodeRuneInString(in); r == '~'+fullOffset { prefix, in = string(QuoteRune)+string('~'+fullOffset), in[l:] // FULLWIDTH TILDE } } if mask.Has(EncodeLeftCrLfHtVt) && prefix == "" { // Leading CR LF HT VT switch c := in[0]; c { case '\t', '\n', '\v', '\r': prefix, in = string('␀'+rune(c)), in[1:] // SYMBOL FOR NULL default: switch r, l := utf8.DecodeRuneInString(in); r { case '␀' + '\t', '␀' + '\n', '␀' + '\v', '␀' + '\r': prefix, in = string(QuoteRune)+string(r), in[l:] } } } // handle suffix only replacements suffix := "" if in != "" { if mask.Has(EncodeRightSpace) { // Trailing SPACE if in[len(in)-1] == ' ' { suffix, in = "␠", in[:len(in)-1] // SYMBOL FOR SPACE } else if r, l := utf8.DecodeLastRuneInString(in); r == '␠' { suffix, in = string(QuoteRune)+"␠", in[:len(in)-l] // SYMBOL FOR SPACE } } if mask.Has(EncodeRightPeriod) && suffix == "" { // Trailing . if in[len(in)-1] == '.' { suffix, in = ".", in[:len(in)-1] // FULLWIDTH FULL STOP } else if r, l := utf8.DecodeLastRuneInString(in); r == '.' { suffix, in = string(QuoteRune)+".", in[:len(in)-l] // FULLWIDTH FULL STOP } } if mask.Has(EncodeRightCrLfHtVt) && suffix == "" { // Trailing . switch c := in[len(in)-1]; c { case '\t', '\n', '\v', '\r': suffix, in = string('␀'+rune(c)), in[:len(in)-1] // FULLWIDTH FULL STOP default: switch r, l := utf8.DecodeLastRuneInString(in); r { case '␀' + '\t', '␀' + '\n', '␀' + '\v', '␀' + '\r': suffix, in = string(QuoteRune)+string(r), in[:len(in)-l] } } } } index := 0 if prefix == "" && suffix == "" { // find the first rune which (most likely) needs to be replaced index = strings.IndexFunc(in, func(r rune) bool { switch r { case 0, '␀', QuoteRune, utf8.RuneError: return true } if mask.Has(EncodeAsterisk) { // * switch r { case '*', '*': return true } } if mask.Has(EncodeLtGt) { // <> switch r { case '<', '>', '<', '>': return true } } if mask.Has(EncodeSquareBracket) { // [] switch r { case '[', ']', '[', ']': return true } } if mask.Has(EncodeSemicolon) { // ; switch r { case ';', ';': return true } } if mask.Has(EncodeExclamation) { // ! switch r { case '!', '!': return true } } if mask.Has(EncodeQuestion) { // ? switch r { case '?', '?': return true } } if mask.Has(EncodeColon) { // : switch r { case ':', ':': return true } } if mask.Has(EncodePipe) { // | switch r { case '|', '|': return true } } if mask.Has(EncodeDoubleQuote) { // " switch r { case '"', '"': return true } } if mask.Has(EncodeSingleQuote) { // ' switch r { case '\'', ''': return true } } if mask.Has(EncodeBackQuote) { // ` switch r { case '`', '`': return true } } if mask.Has(EncodeDollar) { // $ switch r { case '$', '$': return true } } if mask.Has(EncodeSlash) { // / switch r { case '/', '/': return true } } if mask.Has(EncodeBackSlash) { // \ switch r { case '\\', '\': return true } } if mask.Has(EncodeCrLf) { // CR LF switch r { case rune(0x0D), rune(0x0A), '␍', '␊': return true } } if mask.Has(EncodeHash) { // # switch r { case '#', '#': return true } } if mask.Has(EncodePercent) { // % switch r { case '%', '%': return true } } if mask.Has(EncodeDel) { // DEL(0x7F) switch r { case rune(0x7F), '␡': return true } } if mask.Has(EncodeCtl) { // CTRL(0x01-0x1F) if r >= 1 && r <= 0x1F { return true } else if r > symbolOffset && r <= symbolOffset+0x1F { return true } } return false }) } // nothing to replace, return input if index == -1 { return in } var out bytes.Buffer out.Grow(len(in) + len(prefix) + len(suffix)) out.WriteString(prefix) // copy the clean part of the input and skip it out.WriteString(in[:index]) in = in[index:] for i, r := range in { switch r { case 0: out.WriteRune(symbolOffset) continue case '␀', QuoteRune: out.WriteRune(QuoteRune) out.WriteRune(r) continue case utf8.RuneError: if mask.Has(EncodeInvalidUtf8) { // only encode invalid sequences and not utf8.RuneError if i+3 > len(in) || in[i:i+3] != string(utf8.RuneError) { _, l := utf8.DecodeRuneInString(in[i:]) appendQuotedBytes(&out, in[i:i+l]) continue } } else { // append the real bytes instead of utf8.RuneError _, l := utf8.DecodeRuneInString(in[i:]) out.WriteString(in[i : i+l]) continue } } if mask.Has(EncodeAsterisk) { // * switch r { case '*': out.WriteRune(r + fullOffset) continue case '*': out.WriteRune(QuoteRune) out.WriteRune(r) continue } } if mask.Has(EncodeLtGt) { // <> switch r { case '<', '>': out.WriteRune(r + fullOffset) continue case '<', '>': out.WriteRune(QuoteRune) out.WriteRune(r) continue } } if mask.Has(EncodeSquareBracket) { // [] switch r { case '[', ']': out.WriteRune(r + fullOffset) continue case '[', ']': out.WriteRune(QuoteRune) out.WriteRune(r)