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.

387 lines
8.9 KiB

  1. // Copyright 2015 Unknwon
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. package ini
  15. import (
  16. "bufio"
  17. "bytes"
  18. "fmt"
  19. "io"
  20. "strconv"
  21. "strings"
  22. "unicode"
  23. )
  24. type tokenType int
  25. const (
  26. _TOKEN_INVALID tokenType = iota
  27. _TOKEN_COMMENT
  28. _TOKEN_SECTION
  29. _TOKEN_KEY
  30. )
  31. type parser struct {
  32. buf *bufio.Reader
  33. isEOF bool
  34. count int
  35. comment *bytes.Buffer
  36. }
  37. func newParser(r io.Reader) *parser {
  38. return &parser{
  39. buf: bufio.NewReader(r),
  40. count: 1,
  41. comment: &bytes.Buffer{},
  42. }
  43. }
  44. // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
  45. // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
  46. func (p *parser) BOM() error {
  47. mask, err := p.buf.Peek(2)
  48. if err != nil && err != io.EOF {
  49. return err
  50. } else if len(mask) < 2 {
  51. return nil
  52. }
  53. switch {
  54. case mask[0] == 254 && mask[1] == 255:
  55. fallthrough
  56. case mask[0] == 255 && mask[1] == 254:
  57. p.buf.Read(mask)
  58. case mask[0] == 239 && mask[1] == 187:
  59. mask, err := p.buf.Peek(3)
  60. if err != nil && err != io.EOF {
  61. return err
  62. } else if len(mask) < 3 {
  63. return nil
  64. }
  65. if mask[2] == 191 {
  66. p.buf.Read(mask)
  67. }
  68. }
  69. return nil
  70. }
  71. func (p *parser) readUntil(delim byte) ([]byte, error) {
  72. data, err := p.buf.ReadBytes(delim)
  73. if err != nil {
  74. if err == io.EOF {
  75. p.isEOF = true
  76. } else {
  77. return nil, err
  78. }
  79. }
  80. return data, nil
  81. }
  82. func cleanComment(in []byte) ([]byte, bool) {
  83. i := bytes.IndexAny(in, "#;")
  84. if i == -1 {
  85. return nil, false
  86. }
  87. return in[i:], true
  88. }
  89. func readKeyName(in []byte) (string, int, error) {
  90. line := string(in)
  91. // Check if key name surrounded by quotes.
  92. var keyQuote string
  93. if line[0] == '"' {
  94. if len(line) > 6 && string(line[0:3]) == `"""` {
  95. keyQuote = `"""`
  96. } else {
  97. keyQuote = `"`
  98. }
  99. } else if line[0] == '`' {
  100. keyQuote = "`"
  101. }
  102. // Get out key name
  103. endIdx := -1
  104. if len(keyQuote) > 0 {
  105. startIdx := len(keyQuote)
  106. // FIXME: fail case -> """"""name"""=value
  107. pos := strings.Index(line[startIdx:], keyQuote)
  108. if pos == -1 {
  109. return "", -1, fmt.Errorf("missing closing key quote: %s", line)
  110. }
  111. pos += startIdx
  112. // Find key-value delimiter
  113. i := strings.IndexAny(line[pos+startIdx:], "=:")
  114. if i < 0 {
  115. return "", -1, ErrDelimiterNotFound{line}
  116. }
  117. endIdx = pos + i
  118. return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
  119. }
  120. endIdx = strings.IndexAny(line, "=:")
  121. if endIdx < 0 {
  122. return "", -1, ErrDelimiterNotFound{line}
  123. }
  124. return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
  125. }
  126. func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
  127. for {
  128. data, err := p.readUntil('\n')
  129. if err != nil {
  130. return "", err
  131. }
  132. next := string(data)
  133. pos := strings.LastIndex(next, valQuote)
  134. if pos > -1 {
  135. val += next[:pos]
  136. comment, has := cleanComment([]byte(next[pos:]))
  137. if has {
  138. p.comment.Write(bytes.TrimSpace(comment))
  139. }
  140. break
  141. }
  142. val += next
  143. if p.isEOF {
  144. return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
  145. }
  146. }
  147. return val, nil
  148. }
  149. func (p *parser) readContinuationLines(val string) (string, error) {
  150. for {
  151. data, err := p.readUntil('\n')
  152. if err != nil {
  153. return "", err
  154. }
  155. next := strings.TrimSpace(string(data))
  156. if len(next) == 0 {
  157. break
  158. }
  159. val += next
  160. if val[len(val)-1] != '\\' {
  161. break
  162. }
  163. val = val[:len(val)-1]
  164. }
  165. return val, nil
  166. }
  167. // hasSurroundedQuote check if and only if the first and last characters
  168. // are quotes \" or \'.
  169. // It returns false if any other parts also contain same kind of quotes.
  170. func hasSurroundedQuote(in string, quote byte) bool {
  171. return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
  172. strings.IndexByte(in[1:], quote) == len(in)-2
  173. }
  174. func (p *parser) readValue(in []byte,
  175. ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols bool) (string, error) {
  176. line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
  177. if len(line) == 0 {
  178. return "", nil
  179. }
  180. var valQuote string
  181. if len(line) > 3 && string(line[0:3]) == `"""` {
  182. valQuote = `"""`
  183. } else if line[0] == '`' {
  184. valQuote = "`"
  185. } else if unescapeValueDoubleQuotes && line[0] == '"' {
  186. valQuote = `"`
  187. }
  188. if len(valQuote) > 0 {
  189. startIdx := len(valQuote)
  190. pos := strings.LastIndex(line[startIdx:], valQuote)
  191. // Check for multi-line value
  192. if pos == -1 {
  193. return p.readMultilines(line, line[startIdx:], valQuote)
  194. }
  195. if unescapeValueDoubleQuotes && valQuote == `"` {
  196. return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
  197. }
  198. return line[startIdx : pos+startIdx], nil
  199. }
  200. // Won't be able to reach here if value only contains whitespace
  201. line = strings.TrimSpace(line)
  202. // Check continuation lines when desired
  203. if !ignoreContinuation && line[len(line)-1] == '\\' {
  204. return p.readContinuationLines(line[:len(line)-1])
  205. }
  206. // Check if ignore inline comment
  207. if !ignoreInlineComment {
  208. i := strings.IndexAny(line, "#;")
  209. if i > -1 {
  210. p.comment.WriteString(line[i:])
  211. line = strings.TrimSpace(line[:i])
  212. }
  213. }
  214. // Trim single and double quotes
  215. if hasSurroundedQuote(line, '\'') ||
  216. hasSurroundedQuote(line, '"') {
  217. line = line[1 : len(line)-1]
  218. } else if len(valQuote) == 0 && unescapeValueCommentSymbols {
  219. if strings.Contains(line, `\;`) {
  220. line = strings.Replace(line, `\;`, ";", -1)
  221. }
  222. if strings.Contains(line, `\#`) {
  223. line = strings.Replace(line, `\#`, "#", -1)
  224. }
  225. }
  226. return line, nil
  227. }
  228. // parse parses data through an io.Reader.
  229. func (f *File) parse(reader io.Reader) (err error) {
  230. p := newParser(reader)
  231. if err = p.BOM(); err != nil {
  232. return fmt.Errorf("BOM: %v", err)
  233. }
  234. // Ignore error because default section name is never empty string.
  235. name := DEFAULT_SECTION
  236. if f.options.Insensitive {
  237. name = strings.ToLower(DEFAULT_SECTION)
  238. }
  239. section, _ := f.NewSection(name)
  240. var line []byte
  241. var inUnparseableSection bool
  242. for !p.isEOF {
  243. line, err = p.readUntil('\n')
  244. if err != nil {
  245. return err
  246. }
  247. line = bytes.TrimLeftFunc(line, unicode.IsSpace)
  248. if len(line) == 0 {
  249. continue
  250. }
  251. // Comments
  252. if line[0] == '#' || line[0] == ';' {
  253. // Note: we do not care ending line break,
  254. // it is needed for adding second line,
  255. // so just clean it once at the end when set to value.
  256. p.comment.Write(line)
  257. continue
  258. }
  259. // Section
  260. if line[0] == '[' {
  261. // Read to the next ']' (TODO: support quoted strings)
  262. // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
  263. closeIdx := bytes.LastIndex(line, []byte("]"))
  264. if closeIdx == -1 {
  265. return fmt.Errorf("unclosed section: %s", line)
  266. }
  267. name := string(line[1:closeIdx])
  268. section, err = f.NewSection(name)
  269. if err != nil {
  270. return err
  271. }
  272. comment, has := cleanComment(line[closeIdx+1:])
  273. if has {
  274. p.comment.Write(comment)
  275. }
  276. section.Comment = strings.TrimSpace(p.comment.String())
  277. // Reset aotu-counter and comments
  278. p.comment.Reset()
  279. p.count = 1
  280. inUnparseableSection = false
  281. for i := range f.options.UnparseableSections {
  282. if f.options.UnparseableSections[i] == name ||
  283. (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
  284. inUnparseableSection = true
  285. continue
  286. }
  287. }
  288. continue
  289. }
  290. if inUnparseableSection {
  291. section.isRawSection = true
  292. section.rawBody += string(line)
  293. continue
  294. }
  295. kname, offset, err := readKeyName(line)
  296. if err != nil {
  297. // Treat as boolean key when desired, and whole line is key name.
  298. if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
  299. kname, err := p.readValue(line,
  300. f.options.IgnoreContinuation,
  301. f.options.IgnoreInlineComment,
  302. f.options.UnescapeValueDoubleQuotes,
  303. f.options.UnescapeValueCommentSymbols)
  304. if err != nil {
  305. return err
  306. }
  307. key, err := section.NewBooleanKey(kname)
  308. if err != nil {
  309. return err
  310. }
  311. key.Comment = strings.TrimSpace(p.comment.String())
  312. p.comment.Reset()
  313. continue
  314. }
  315. return err
  316. }
  317. // Auto increment.
  318. isAutoIncr := false
  319. if kname == "-" {
  320. isAutoIncr = true
  321. kname = "#" + strconv.Itoa(p.count)
  322. p.count++
  323. }
  324. value, err := p.readValue(line[offset:],
  325. f.options.IgnoreContinuation,
  326. f.options.IgnoreInlineComment,
  327. f.options.UnescapeValueDoubleQuotes,
  328. f.options.UnescapeValueCommentSymbols)
  329. if err != nil {
  330. return err
  331. }
  332. key, err := section.NewKey(kname, value)
  333. if err != nil {
  334. return err
  335. }
  336. key.isAutoIncrement = isAutoIncr
  337. key.Comment = strings.TrimSpace(p.comment.String())
  338. p.comment.Reset()
  339. }
  340. return nil
  341. }