escape.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // Copyright (C) 2016 Kohei YOSHIDA. All rights reserved.
  2. //
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of The BSD 3-Clause License
  5. // that can be found in the LICENSE file.
  6. package uritemplate
  7. import (
  8. "strings"
  9. "unicode"
  10. "unicode/utf8"
  11. )
  12. var (
  13. hex = []byte("0123456789ABCDEF")
  14. // reserved = gen-delims / sub-delims
  15. // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
  16. // sub-delims = "!" / "$" / "&" / "’" / "(" / ")"
  17. // / "*" / "+" / "," / ";" / "="
  18. rangeReserved = &unicode.RangeTable{
  19. R16: []unicode.Range16{
  20. {Lo: 0x21, Hi: 0x21, Stride: 1}, // '!'
  21. {Lo: 0x23, Hi: 0x24, Stride: 1}, // '#' - '$'
  22. {Lo: 0x26, Hi: 0x2C, Stride: 1}, // '&' - ','
  23. {Lo: 0x2F, Hi: 0x2F, Stride: 1}, // '/'
  24. {Lo: 0x3A, Hi: 0x3B, Stride: 1}, // ':' - ';'
  25. {Lo: 0x3D, Hi: 0x3D, Stride: 1}, // '='
  26. {Lo: 0x3F, Hi: 0x40, Stride: 1}, // '?' - '@'
  27. {Lo: 0x5B, Hi: 0x5B, Stride: 1}, // '['
  28. {Lo: 0x5D, Hi: 0x5D, Stride: 1}, // ']'
  29. },
  30. LatinOffset: 9,
  31. }
  32. reReserved = `\x21\x23\x24\x26-\x2c\x2f\x3a\x3b\x3d\x3f\x40\x5b\x5d`
  33. // ALPHA = %x41-5A / %x61-7A
  34. // DIGIT = %x30-39
  35. // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  36. rangeUnreserved = &unicode.RangeTable{
  37. R16: []unicode.Range16{
  38. {Lo: 0x2D, Hi: 0x2E, Stride: 1}, // '-' - '.'
  39. {Lo: 0x30, Hi: 0x39, Stride: 1}, // '0' - '9'
  40. {Lo: 0x41, Hi: 0x5A, Stride: 1}, // 'A' - 'Z'
  41. {Lo: 0x5F, Hi: 0x5F, Stride: 1}, // '_'
  42. {Lo: 0x61, Hi: 0x7A, Stride: 1}, // 'a' - 'z'
  43. {Lo: 0x7E, Hi: 0x7E, Stride: 1}, // '~'
  44. },
  45. }
  46. reUnreserved = `\x2d\x2e\x30-\x39\x41-\x5a\x5f\x61-\x7a\x7e`
  47. )
  48. type runeClass uint8
  49. const (
  50. runeClassU runeClass = 1 << iota
  51. runeClassR
  52. runeClassPctE
  53. runeClassLast
  54. runeClassUR = runeClassU | runeClassR
  55. )
  56. var runeClassNames = []string{
  57. "U",
  58. "R",
  59. "pct-encoded",
  60. }
  61. func (rc runeClass) String() string {
  62. ret := make([]string, 0, len(runeClassNames))
  63. for i, j := 0, runeClass(1); j < runeClassLast; j <<= 1 {
  64. if rc&j == j {
  65. ret = append(ret, runeClassNames[i])
  66. }
  67. i++
  68. }
  69. return strings.Join(ret, "+")
  70. }
  71. func pctEncode(w *strings.Builder, r rune) {
  72. if s := r >> 24 & 0xff; s > 0 {
  73. w.Write([]byte{'%', hex[s/16], hex[s%16]})
  74. }
  75. if s := r >> 16 & 0xff; s > 0 {
  76. w.Write([]byte{'%', hex[s/16], hex[s%16]})
  77. }
  78. if s := r >> 8 & 0xff; s > 0 {
  79. w.Write([]byte{'%', hex[s/16], hex[s%16]})
  80. }
  81. if s := r & 0xff; s > 0 {
  82. w.Write([]byte{'%', hex[s/16], hex[s%16]})
  83. }
  84. }
  85. func unhex(c byte) byte {
  86. switch {
  87. case '0' <= c && c <= '9':
  88. return c - '0'
  89. case 'a' <= c && c <= 'f':
  90. return c - 'a' + 10
  91. case 'A' <= c && c <= 'F':
  92. return c - 'A' + 10
  93. }
  94. return 0
  95. }
  96. func ishex(c byte) bool {
  97. switch {
  98. case '0' <= c && c <= '9':
  99. return true
  100. case 'a' <= c && c <= 'f':
  101. return true
  102. case 'A' <= c && c <= 'F':
  103. return true
  104. default:
  105. return false
  106. }
  107. }
  108. func pctDecode(s string) string {
  109. size := len(s)
  110. for i := 0; i < len(s); {
  111. switch s[i] {
  112. case '%':
  113. size -= 2
  114. i += 3
  115. default:
  116. i++
  117. }
  118. }
  119. if size == len(s) {
  120. return s
  121. }
  122. buf := make([]byte, size)
  123. j := 0
  124. for i := 0; i < len(s); {
  125. switch c := s[i]; c {
  126. case '%':
  127. buf[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
  128. i += 3
  129. j++
  130. default:
  131. buf[j] = c
  132. i++
  133. j++
  134. }
  135. }
  136. return string(buf)
  137. }
  138. type escapeFunc func(*strings.Builder, string) error
  139. func escapeLiteral(w *strings.Builder, v string) error {
  140. w.WriteString(v)
  141. return nil
  142. }
  143. func escapeExceptU(w *strings.Builder, v string) error {
  144. for i := 0; i < len(v); {
  145. r, size := utf8.DecodeRuneInString(v[i:])
  146. if r == utf8.RuneError {
  147. return errorf(i, "invalid encoding")
  148. }
  149. if unicode.Is(rangeUnreserved, r) {
  150. w.WriteRune(r)
  151. } else {
  152. pctEncode(w, r)
  153. }
  154. i += size
  155. }
  156. return nil
  157. }
  158. func escapeExceptUR(w *strings.Builder, v string) error {
  159. for i := 0; i < len(v); {
  160. r, size := utf8.DecodeRuneInString(v[i:])
  161. if r == utf8.RuneError {
  162. return errorf(i, "invalid encoding")
  163. }
  164. // TODO(yosida95): is pct-encoded triplets allowed here?
  165. if unicode.In(r, rangeUnreserved, rangeReserved) {
  166. w.WriteRune(r)
  167. } else {
  168. pctEncode(w, r)
  169. }
  170. i += size
  171. }
  172. return nil
  173. }