@@ -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,33 @@ 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 >> 6 == 0b10 {
155+ end --
156+ }
157+ if c >> 5 == 0b110 || c >> 4 == 0b1110 || c >> 3 == 0b11110 {
158+ toValidate = toValidate [:end ]
159+ break
160+ }
161+ cnt ++
152162 }
163+
153164 if utf8 .Valid (toValidate ) {
154- log .Debug ("Detected encoding: utf-8 (fast)" )
155165 return "UTF-8" , nil
156166 }
157167
@@ -160,7 +170,7 @@ func DetectEncoding(content []byte) (string, error) {
160170 if len (content ) < 1024 {
161171 // Check if original content is valid
162172 if _ , err := textDetector .DetectBest (content ); err != nil {
163- return "" , err
173+ return util . IfZero ( setting . Repository . AnsiCharset , "UTF-8" ) , err
164174 }
165175 times := 1024 / len (content )
166176 detectContent = make ([]byte , 0 , times * len (content ))
@@ -171,14 +181,10 @@ func DetectEncoding(content []byte) (string, error) {
171181 detectContent = content
172182 }
173183
174- // Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie break
184+ // Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie- break
175185 results , err := textDetector .DetectAll (detectContent )
176186 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
187+ return util .IfZero (setting .Repository .AnsiCharset , "UTF-8" ), err
182188 }
183189
184190 topConfidence := results [0 ].Confidence
@@ -201,11 +207,9 @@ func DetectEncoding(content []byte) (string, error) {
201207 }
202208
203209 // 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 )
210+ if topResult .Charset != "UTF-8" && setting .Repository .AnsiCharset != "" {
206211 return setting .Repository .AnsiCharset , err
207212 }
208213
209- log .Debug ("Detected encoding: %s" , topResult .Charset )
210- return topResult .Charset , err
214+ return topResult .Charset , nil
211215}
0 commit comments