* Fix chardet test and add ordering option Signed-off-by: Andrew Thornton <art27@cantab.net> * minor fixes Signed-off-by: Andrew Thornton <art27@cantab.net> * remove log Signed-off-by: Andrew Thornton <art27@cantab.net> * remove log2 Signed-off-by: Andrew Thornton <art27@cantab.net> * only iterate through top results Signed-off-by: Andrew Thornton <art27@cantab.net> * Update docs/content/doc/advanced/config-cheat-sheet.en-us.md * slight restructure of for loop Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: techknowlogick <techknowlogick@gitea.io>
		
			
				
	
	
		
			180 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2014 The Gogs Authors. All rights reserved.
 | |
| // Use of this source code is governed by a MIT-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package charset
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 	"unicode/utf8"
 | |
| 
 | |
| 	"code.gitea.io/gitea/modules/log"
 | |
| 	"code.gitea.io/gitea/modules/setting"
 | |
| 
 | |
| 	"github.com/gogs/chardet"
 | |
| 	"golang.org/x/net/html/charset"
 | |
| 	"golang.org/x/text/transform"
 | |
| )
 | |
| 
 | |
| // UTF8BOM is the utf-8 byte-order marker
 | |
| var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'}
 | |
| 
 | |
| // ToUTF8WithErr converts content to UTF8 encoding
 | |
| func ToUTF8WithErr(content []byte) (string, error) {
 | |
| 	charsetLabel, err := DetectEncoding(content)
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	} else if charsetLabel == "UTF-8" {
 | |
| 		return string(RemoveBOMIfPresent(content)), nil
 | |
| 	}
 | |
| 
 | |
| 	encoding, _ := charset.Lookup(charsetLabel)
 | |
| 	if encoding == nil {
 | |
| 		return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
 | |
| 	}
 | |
| 
 | |
| 	// If there is an error, we concatenate the nicely decoded part and the
 | |
| 	// original left over. This way we won't lose much data.
 | |
| 	result, n, err := transform.Bytes(encoding.NewDecoder(), content)
 | |
| 	if err != nil {
 | |
| 		result = append(result, content[n:]...)
 | |
| 	}
 | |
| 
 | |
| 	result = RemoveBOMIfPresent(result)
 | |
| 
 | |
| 	return string(result), err
 | |
| }
 | |
| 
 | |
| // ToUTF8WithFallback detects the encoding of content and coverts to UTF-8 if possible
 | |
| func ToUTF8WithFallback(content []byte) []byte {
 | |
| 	charsetLabel, err := DetectEncoding(content)
 | |
| 	if err != nil || charsetLabel == "UTF-8" {
 | |
| 		return RemoveBOMIfPresent(content)
 | |
| 	}
 | |
| 
 | |
| 	encoding, _ := charset.Lookup(charsetLabel)
 | |
| 	if encoding == nil {
 | |
| 		return content
 | |
| 	}
 | |
| 
 | |
| 	// If there is an error, we concatenate the nicely decoded part and the
 | |
| 	// original left over. This way we won't lose data.
 | |
| 	result, n, err := transform.Bytes(encoding.NewDecoder(), content)
 | |
| 	if err != nil {
 | |
| 		return append(result, content[n:]...)
 | |
| 	}
 | |
| 
 | |
| 	return RemoveBOMIfPresent(result)
 | |
| }
 | |
| 
 | |
| // ToUTF8 converts content to UTF8 encoding and ignore error
 | |
| func ToUTF8(content string) string {
 | |
| 	res, _ := ToUTF8WithErr([]byte(content))
 | |
| 	return res
 | |
| }
 | |
| 
 | |
| // ToUTF8DropErrors makes sure the return string is valid utf-8; attempts conversion if possible
 | |
| func ToUTF8DropErrors(content []byte) []byte {
 | |
| 	charsetLabel, err := DetectEncoding(content)
 | |
| 	if err != nil || charsetLabel == "UTF-8" {
 | |
| 		return RemoveBOMIfPresent(content)
 | |
| 	}
 | |
| 
 | |
| 	encoding, _ := charset.Lookup(charsetLabel)
 | |
| 	if encoding == nil {
 | |
| 		return content
 | |
| 	}
 | |
| 
 | |
| 	// We ignore any non-decodable parts from the file.
 | |
| 	// Some parts might be lost
 | |
| 	var decoded []byte
 | |
| 	decoder := encoding.NewDecoder()
 | |
| 	idx := 0
 | |
| 	for {
 | |
| 		result, n, err := transform.Bytes(decoder, content[idx:])
 | |
| 		decoded = append(decoded, result...)
 | |
| 		if err == nil {
 | |
| 			break
 | |
| 		}
 | |
| 		decoded = append(decoded, ' ')
 | |
| 		idx = idx + n + 1
 | |
| 		if idx >= len(content) {
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return RemoveBOMIfPresent(decoded)
 | |
| }
 | |
| 
 | |
| // RemoveBOMIfPresent removes a UTF-8 BOM from a []byte
 | |
| func RemoveBOMIfPresent(content []byte) []byte {
 | |
| 	if len(content) > 2 && bytes.Equal(content[0:3], UTF8BOM) {
 | |
| 		return content[3:]
 | |
| 	}
 | |
| 	return content
 | |
| }
 | |
| 
 | |
| // DetectEncoding detect the encoding of content
 | |
| func DetectEncoding(content []byte) (string, error) {
 | |
| 	if utf8.Valid(content) {
 | |
| 		log.Debug("Detected encoding: utf-8 (fast)")
 | |
| 		return "UTF-8", nil
 | |
| 	}
 | |
| 
 | |
| 	textDetector := chardet.NewTextDetector()
 | |
| 	var detectContent []byte
 | |
| 	if len(content) < 1024 {
 | |
| 		// Check if original content is valid
 | |
| 		if _, err := textDetector.DetectBest(content); err != nil {
 | |
| 			return "", err
 | |
| 		}
 | |
| 		times := 1024 / len(content)
 | |
| 		detectContent = make([]byte, 0, times*len(content))
 | |
| 		for i := 0; i < times; i++ {
 | |
| 			detectContent = append(detectContent, content...)
 | |
| 		}
 | |
| 	} else {
 | |
| 		detectContent = content
 | |
| 	}
 | |
| 
 | |
| 	// Now we can't use DetectBest or just results[0] because the result isn't stable - so we need a tie break
 | |
| 	results, err := textDetector.DetectAll(detectContent)
 | |
| 	if err != nil {
 | |
| 		if err == chardet.NotDetectedError && len(setting.Repository.AnsiCharset) > 0 {
 | |
| 			log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
 | |
| 			return setting.Repository.AnsiCharset, nil
 | |
| 		}
 | |
| 		return "", err
 | |
| 	}
 | |
| 
 | |
| 	topConfidence := results[0].Confidence
 | |
| 	topResult := results[0]
 | |
| 	priority, has := setting.Repository.DetectedCharsetScore[strings.ToLower(strings.TrimSpace(topResult.Charset))]
 | |
| 	for _, result := range results {
 | |
| 		// As results are sorted in confidence order - if we have a different confidence
 | |
| 		// we know it's less than the current confidence and can break out of the loop early
 | |
| 		if result.Confidence != topConfidence {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		// Otherwise check if this results is earlier in the DetectedCharsetOrder than our current top guesss
 | |
| 		resultPriority, resultHas := setting.Repository.DetectedCharsetScore[strings.ToLower(strings.TrimSpace(result.Charset))]
 | |
| 		if resultHas && (!has || resultPriority < priority) {
 | |
| 			topResult = result
 | |
| 			priority = resultPriority
 | |
| 			has = true
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument
 | |
| 	if topResult.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
 | |
| 		log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
 | |
| 		return setting.Repository.AnsiCharset, err
 | |
| 	}
 | |
| 
 | |
| 	log.Debug("Detected encoding: %s", topResult.Charset)
 | |
| 	return topResult.Charset, err
 | |
| }
 |