@@ -26,9 +26,11 @@ type ConvertOpts struct {
2626 KeepBOM bool
2727}
2828
29+ var ToUTF8WithFallbackReaderPrefetchSize = 16 * 1024
30+
2931// ToUTF8WithFallbackReader detects the encoding of content and converts to UTF-8 reader if possible
3032func ToUTF8WithFallbackReader (rd io.Reader , opts ConvertOpts ) io.Reader {
31- buf := make ([]byte , 2048 )
33+ buf := make ([]byte , ToUTF8WithFallbackReaderPrefetchSize )
3234 n , err := util .ReadAtMost (rd , buf )
3335 if err != nil {
3436 return io .MultiReader (bytes .NewReader (MaybeRemoveBOM (buf [:n ], opts )), rd )
@@ -41,6 +43,7 @@ func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
4143
4244 encoding , _ := charset .Lookup (charsetLabel )
4345 if encoding == nil {
46+ log .Error ("Unknown encoding: %s" , charsetLabel )
4447 return io .MultiReader (bytes .NewReader (buf [:n ]), rd )
4548 }
4649
@@ -54,17 +57,18 @@ func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
5457}
5558
5659// ToUTF8 converts content to UTF8 encoding
57- func ToUTF8 (content []byte , opts ConvertOpts ) (string , error ) {
60+ func ToUTF8 (content []byte , opts ConvertOpts ) ([] byte , error ) {
5861 charsetLabel , err := DetectEncoding (content )
5962 if err != nil {
60- return "" , err
63+ return content , err
6164 } else if charsetLabel == "UTF-8" {
62- return string ( MaybeRemoveBOM (content , opts ) ), nil
65+ return MaybeRemoveBOM (content , opts ), nil
6366 }
6467
6568 encoding , _ := charset .Lookup (charsetLabel )
6669 if encoding == nil {
67- return string (content ), fmt .Errorf ("Unknown encoding: %s" , charsetLabel )
70+ log .Error ("Unknown encoding: %s" , charsetLabel )
71+ return content , fmt .Errorf ("unknown encoding: %s" , charsetLabel )
6872 }
6973
7074 // If there is an error, we concatenate the nicely decoded part and the
@@ -76,7 +80,7 @@ func ToUTF8(content []byte, opts ConvertOpts) (string, error) {
7680
7781 result = MaybeRemoveBOM (result , opts )
7882
79- return string ( result ) , err
83+ return result , err
8084}
8185
8286// ToUTF8WithFallback detects the encoding of content and converts to UTF-8 if possible
@@ -94,6 +98,7 @@ func ToUTF8DropErrors(content []byte, opts ConvertOpts) []byte {
9498
9599 encoding , _ := charset .Lookup (charsetLabel )
96100 if encoding == nil {
101+ log .Error ("Unknown encoding: %s" , charsetLabel )
97102 return content
98103 }
99104
@@ -130,28 +135,37 @@ func MaybeRemoveBOM(content []byte, opts ConvertOpts) []byte {
130135}
131136
132137// DetectEncoding detect the encoding of content
133- func DetectEncoding (content []byte ) (string , error ) {
138+ // it always returns a detected or guessed "encoding" string, no matter error happens or not
139+ func DetectEncoding (content []byte ) (encoding string , _ error ) {
134140 // First we check if the content represents valid utf8 content excepting a truncated character at the end.
135141
136142 // Now we could decode all the runes in turn but this is not necessarily the cheapest thing to do
137- // instead we walk backwards from the end to trim off a the incomplete character
143+ // instead we walk backwards from the end to trim off the incomplete character
138144 toValidate := content
139145 end := len (toValidate ) - 1
140146
141- if end < 0 {
142- // no-op
143- } else if toValidate [end ]>> 5 == 0b110 {
144- // Incomplete 1 byte extension e.g. © <c2><a9> which has been truncated to <c2>
145- toValidate = toValidate [:end ]
146- } else if end > 0 && toValidate [end ]>> 6 == 0b10 && toValidate [end - 1 ]>> 4 == 0b1110 {
147- // Incomplete 2 byte extension e.g. ⛔ <e2><9b><94> which has been truncated to <e2><9b>
148- toValidate = toValidate [:end - 1 ]
149- } else if end > 1 && toValidate [end ]>> 6 == 0b10 && toValidate [end - 1 ]>> 6 == 0b10 && toValidate [end - 2 ]>> 3 == 0b11110 {
150- // Incomplete 3 byte extension e.g. 💩 <f0><9f><92><a9> which has been truncated to <f0><9f><92>
151- toValidate = toValidate [:end - 2 ]
147+ // U+0000 U+007F 0yyyzzzz
148+ // U+0080 U+07FF 110xxxyy 10yyzzzz
149+ // U+0800 U+FFFF 1110wwww 10xxxxyy 10yyzzzz
150+ // U+010000 U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz
151+ cnt := 0
152+ for end >= 0 && cnt < 4 {
153+ c := toValidate [end ]
154+ if c >> 5 == 0b110 || c >> 4 == 0b1110 || c >> 3 == 0b11110 {
155+ // a leading byte
156+ toValidate = toValidate [:end ]
157+ break
158+ } else if c >> 6 == 0b10 {
159+ // a continuation byte
160+ end --
161+ } else {
162+ // not an utf-8 byte
163+ break
164+ }
165+ cnt ++
152166 }
167+
153168 if utf8 .Valid (toValidate ) {
154- log .Debug ("Detected encoding: utf-8 (fast)" )
155169 return "UTF-8" , nil
156170 }
157171
@@ -160,7 +174,7 @@ func DetectEncoding(content []byte) (string, error) {
160174 if len (content ) < 1024 {
161175 // Check if original content is valid
162176 if _ , err := textDetector .DetectBest (content ); err != nil {
163- return "" , err
177+ return util . IfZero ( setting . Repository . AnsiCharset , "UTF-8" ) , err
164178 }
165179 times := 1024 / len (content )
166180 detectContent = make ([]byte , 0 , times * len (content ))
@@ -171,14 +185,10 @@ func DetectEncoding(content []byte) (string, error) {
171185 detectContent = content
172186 }
173187
174- // Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie break
188+ // Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie- break
175189 results , err := textDetector .DetectAll (detectContent )
176190 if err != nil {
177- if err == chardet .NotDetectedError && len (setting .Repository .AnsiCharset ) > 0 {
178- log .Debug ("Using default AnsiCharset: %s" , setting .Repository .AnsiCharset )
179- return setting .Repository .AnsiCharset , nil
180- }
181- return "" , err
191+ return util .IfZero (setting .Repository .AnsiCharset , "UTF-8" ), err
182192 }
183193
184194 topConfidence := results [0 ].Confidence
@@ -201,11 +211,9 @@ func DetectEncoding(content []byte) (string, error) {
201211 }
202212
203213 // FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument
204- if topResult .Charset != "UTF-8" && len (setting .Repository .AnsiCharset ) > 0 {
205- log .Debug ("Using default AnsiCharset: %s" , setting .Repository .AnsiCharset )
214+ if topResult .Charset != "UTF-8" && setting .Repository .AnsiCharset != "" {
206215 return setting .Repository .AnsiCharset , err
207216 }
208217
209- log .Debug ("Detected encoding: %s" , topResult .Charset )
210- return topResult .Charset , err
218+ return topResult .Charset , nil
211219}
0 commit comments