@@ -10,7 +10,6 @@ import (
1010 "strings"
1111 "unicode/utf8"
1212
13- "code.gitea.io/gitea/modules/log"
1413 "code.gitea.io/gitea/modules/setting"
1514 "code.gitea.io/gitea/modules/util"
1615
@@ -26,9 +25,11 @@ type ConvertOpts struct {
2625 KeepBOM bool
2726}
2827
28+ var ToUTF8WithFallbackReaderPrefetchSize = 16 * 1024
29+
2930// ToUTF8WithFallbackReader detects the encoding of content and converts to UTF-8 reader if possible
3031func ToUTF8WithFallbackReader (rd io.Reader , opts ConvertOpts ) io.Reader {
31- buf := make ([]byte , 2048 )
32+ buf := make ([]byte , ToUTF8WithFallbackReaderPrefetchSize )
3233 n , err := util .ReadAtMost (rd , buf )
3334 if err != nil {
3435 return io .MultiReader (bytes .NewReader (MaybeRemoveBOM (buf [:n ], opts )), rd )
@@ -54,17 +55,17 @@ func ToUTF8WithFallbackReader(rd io.Reader, opts ConvertOpts) io.Reader {
5455}
5556
5657// ToUTF8 converts content to UTF8 encoding
57- func ToUTF8 (content []byte , opts ConvertOpts ) (string , error ) {
58+ func ToUTF8 (content []byte , opts ConvertOpts ) ([] byte , error ) {
5859 charsetLabel , err := DetectEncoding (content )
5960 if err != nil {
60- return "" , err
61+ return content , err
6162 } else if charsetLabel == "UTF-8" {
62- return string ( MaybeRemoveBOM (content , opts ) ), nil
63+ return MaybeRemoveBOM (content , opts ), nil
6364 }
6465
6566 encoding , _ := charset .Lookup (charsetLabel )
6667 if encoding == nil {
67- return string ( content ) , fmt .Errorf ("Unknown encoding: %s" , charsetLabel )
68+ return content , fmt .Errorf ("unknown encoding: %s" , charsetLabel )
6869 }
6970
7071 // If there is an error, we concatenate the nicely decoded part and the
@@ -76,7 +77,7 @@ func ToUTF8(content []byte, opts ConvertOpts) (string, error) {
7677
7778 result = MaybeRemoveBOM (result , opts )
7879
79- return string ( result ) , err
80+ return result , err
8081}
8182
8283// ToUTF8WithFallback detects the encoding of content and converts to UTF-8 if possible
@@ -130,28 +131,33 @@ func MaybeRemoveBOM(content []byte, opts ConvertOpts) []byte {
130131}
131132
132133// DetectEncoding detect the encoding of content
133- func DetectEncoding (content []byte ) (string , error ) {
134+ // it always returns a detected or guessed "encoding" string, no matter error happens or not
135+ func DetectEncoding (content []byte ) (encoding string , _ error ) {
134136 // First we check if the content represents valid utf8 content excepting a truncated character at the end.
135137
136138 // 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
139+ // instead we walk backwards from the end to trim off the incomplete character
138140 toValidate := content
139141 end := len (toValidate ) - 1
140142
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 ]
143+ // U+0000 U+007F 0yyyzzzz
144+ // U+0080 U+07FF 110xxxyy 10yyzzzz
145+ // U+0800 U+FFFF 1110wwww 10xxxxyy 10yyzzzz
146+ // U+010000 U+10FFFF 11110uvv 10vvwwww 10xxxxyy 10yyzzzz
147+ cnt := 0
148+ for end >= 0 && cnt < 4 {
149+ c := toValidate [end ]
150+ if c >> 6 == 0b10 {
151+ end --
152+ }
153+ if c >> 5 == 0b110 || c >> 4 == 0b1110 || c >> 3 == 0b11110 {
154+ toValidate = toValidate [:end ]
155+ break
156+ }
157+ cnt ++
152158 }
159+
153160 if utf8 .Valid (toValidate ) {
154- log .Debug ("Detected encoding: utf-8 (fast)" )
155161 return "UTF-8" , nil
156162 }
157163
@@ -160,7 +166,7 @@ func DetectEncoding(content []byte) (string, error) {
160166 if len (content ) < 1024 {
161167 // Check if original content is valid
162168 if _ , err := textDetector .DetectBest (content ); err != nil {
163- return "" , err
169+ return util . IfZero ( setting . Repository . AnsiCharset , "UTF-8" ) , err
164170 }
165171 times := 1024 / len (content )
166172 detectContent = make ([]byte , 0 , times * len (content ))
@@ -171,14 +177,10 @@ func DetectEncoding(content []byte) (string, error) {
171177 detectContent = content
172178 }
173179
174- // Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie break
180+ // Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie- break
175181 results , err := textDetector .DetectAll (detectContent )
176182 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
183+ return util .IfZero (setting .Repository .AnsiCharset , "UTF-8" ), err
182184 }
183185
184186 topConfidence := results [0 ].Confidence
@@ -201,11 +203,9 @@ func DetectEncoding(content []byte) (string, error) {
201203 }
202204
203205 // 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 )
206+ if topResult .Charset != "UTF-8" && setting .Repository .AnsiCharset != "" {
206207 return setting .Repository .AnsiCharset , err
207208 }
208209
209- log .Debug ("Detected encoding: %s" , topResult .Charset )
210- return topResult .Charset , err
210+ return topResult .Charset , nil
211211}
0 commit comments