You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

charset.go 4.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. // Copyright 2014 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package charset
  5. import (
  6. "bytes"
  7. "fmt"
  8. "unicode/utf8"
  9. "code.gitea.io/gitea/modules/log"
  10. "code.gitea.io/gitea/modules/setting"
  11. "github.com/gogits/chardet"
  12. "golang.org/x/net/html/charset"
  13. "golang.org/x/text/transform"
  14. )
  15. // UTF8BOM is the utf-8 byte-order marker
  16. var UTF8BOM = []byte{'\xef', '\xbb', '\xbf'}
  17. // ToUTF8WithErr converts content to UTF8 encoding
  18. func ToUTF8WithErr(content []byte) (string, error) {
  19. charsetLabel, err := DetectEncoding(content)
  20. if err != nil {
  21. return "", err
  22. } else if charsetLabel == "UTF-8" {
  23. return string(RemoveBOMIfPresent(content)), nil
  24. }
  25. encoding, _ := charset.Lookup(charsetLabel)
  26. if encoding == nil {
  27. return string(content), fmt.Errorf("Unknown encoding: %s", charsetLabel)
  28. }
  29. // If there is an error, we concatenate the nicely decoded part and the
  30. // original left over. This way we won't lose data.
  31. result, n, err := transform.Bytes(encoding.NewDecoder(), content)
  32. if err != nil {
  33. result = append(result, content[n:]...)
  34. }
  35. result = RemoveBOMIfPresent(result)
  36. return string(result), err
  37. }
  38. // ToUTF8WithFallback detects the encoding of content and coverts to UTF-8 if possible
  39. func ToUTF8WithFallback(content []byte) []byte {
  40. charsetLabel, err := DetectEncoding(content)
  41. if err != nil || charsetLabel == "UTF-8" {
  42. return RemoveBOMIfPresent(content)
  43. }
  44. encoding, _ := charset.Lookup(charsetLabel)
  45. if encoding == nil {
  46. return content
  47. }
  48. // If there is an error, we concatenate the nicely decoded part and the
  49. // original left over. This way we won't lose data.
  50. result, n, err := transform.Bytes(encoding.NewDecoder(), content)
  51. if err != nil {
  52. return append(result, content[n:]...)
  53. }
  54. return RemoveBOMIfPresent(result)
  55. }
  56. // ToUTF8 converts content to UTF8 encoding and ignore error
  57. func ToUTF8(content string) string {
  58. res, _ := ToUTF8WithErr([]byte(content))
  59. return res
  60. }
  61. // ToUTF8DropErrors makes sure the return string is valid utf-8; attempts conversion if possible
  62. func ToUTF8DropErrors(content []byte) []byte {
  63. charsetLabel, err := DetectEncoding(content)
  64. if err != nil || charsetLabel == "UTF-8" {
  65. return RemoveBOMIfPresent(content)
  66. }
  67. encoding, _ := charset.Lookup(charsetLabel)
  68. if encoding == nil {
  69. return content
  70. }
  71. // We ignore any non-decodable parts from the file.
  72. // Some parts might be lost
  73. var decoded []byte
  74. decoder := encoding.NewDecoder()
  75. idx := 0
  76. for {
  77. result, n, err := transform.Bytes(decoder, content[idx:])
  78. decoded = append(decoded, result...)
  79. if err == nil {
  80. break
  81. }
  82. decoded = append(decoded, ' ')
  83. idx = idx + n + 1
  84. if idx >= len(content) {
  85. break
  86. }
  87. }
  88. return RemoveBOMIfPresent(decoded)
  89. }
  90. // RemoveBOMIfPresent removes a UTF-8 BOM from a []byte
  91. func RemoveBOMIfPresent(content []byte) []byte {
  92. if len(content) > 2 && bytes.Equal(content[0:3], UTF8BOM) {
  93. return content[3:]
  94. }
  95. return content
  96. }
  97. // DetectEncoding detect the encoding of content
  98. func DetectEncoding(content []byte) (string, error) {
  99. if utf8.Valid(content) {
  100. log.Debug("Detected encoding: utf-8 (fast)")
  101. return "UTF-8", nil
  102. }
  103. textDetector := chardet.NewTextDetector()
  104. var detectContent []byte
  105. if len(content) < 1024 {
  106. // Check if original content is valid
  107. if _, err := textDetector.DetectBest(content); err != nil {
  108. return "", err
  109. }
  110. times := 1024 / len(content)
  111. detectContent = make([]byte, 0, times*len(content))
  112. for i := 0; i < times; i++ {
  113. detectContent = append(detectContent, content...)
  114. }
  115. } else {
  116. detectContent = content
  117. }
  118. result, err := textDetector.DetectBest(detectContent)
  119. if err != nil {
  120. return "", err
  121. }
  122. // FIXME: to properly decouple this function the fallback ANSI charset should be passed as an argument
  123. if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
  124. log.Debug("Using default AnsiCharset: %s", setting.Repository.AnsiCharset)
  125. return setting.Repository.AnsiCharset, err
  126. }
  127. log.Debug("Detected encoding: %s", result.Charset)
  128. return result.Charset, err
  129. }