bytesconv.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. //go:generate go run bytesconv_table_gen.go
  2. package fasthttp
  3. import (
  4. "bufio"
  5. "bytes"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "math"
  10. "net"
  11. "reflect"
  12. "sync"
  13. "time"
  14. "unsafe"
  15. )
  16. // AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
  17. func AppendHTMLEscape(dst []byte, s string) []byte {
  18. var (
  19. prev int
  20. sub string
  21. )
  22. for i, n := 0, len(s); i < n; i++ {
  23. sub = ""
  24. switch s[i] {
  25. case '&':
  26. sub = "&amp;"
  27. case '<':
  28. sub = "&lt;"
  29. case '>':
  30. sub = "&gt;"
  31. case '"':
  32. sub = "&#34;" // "&#34;" is shorter than "&quot;".
  33. case '\'':
  34. sub = "&#39;" // "&#39;" is shorter than "&apos;" and apos was not in HTML until HTML5.
  35. }
  36. if len(sub) > 0 {
  37. dst = append(dst, s[prev:i]...)
  38. dst = append(dst, sub...)
  39. prev = i + 1
  40. }
  41. }
  42. return append(dst, s[prev:]...)
  43. }
  44. // AppendHTMLEscapeBytes appends html-escaped s to dst and returns
  45. // the extended dst.
  46. func AppendHTMLEscapeBytes(dst, s []byte) []byte {
  47. return AppendHTMLEscape(dst, b2s(s))
  48. }
  49. // AppendIPv4 appends string representation of the given ip v4 to dst
  50. // and returns the extended dst.
  51. func AppendIPv4(dst []byte, ip net.IP) []byte {
  52. ip = ip.To4()
  53. if ip == nil {
  54. return append(dst, "non-v4 ip passed to AppendIPv4"...)
  55. }
  56. dst = AppendUint(dst, int(ip[0]))
  57. for i := 1; i < 4; i++ {
  58. dst = append(dst, '.')
  59. dst = AppendUint(dst, int(ip[i]))
  60. }
  61. return dst
  62. }
  63. var errEmptyIPStr = errors.New("empty ip address string")
  64. // ParseIPv4 parses ip address from ipStr into dst and returns the extended dst.
  65. func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
  66. if len(ipStr) == 0 {
  67. return dst, errEmptyIPStr
  68. }
  69. if len(dst) < net.IPv4len {
  70. dst = make([]byte, net.IPv4len)
  71. }
  72. copy(dst, net.IPv4zero)
  73. dst = dst.To4()
  74. if dst == nil {
  75. panic("BUG: dst must not be nil")
  76. }
  77. b := ipStr
  78. for i := 0; i < 3; i++ {
  79. n := bytes.IndexByte(b, '.')
  80. if n < 0 {
  81. return dst, fmt.Errorf("cannot find dot in ipStr %q", ipStr)
  82. }
  83. v, err := ParseUint(b[:n])
  84. if err != nil {
  85. return dst, fmt.Errorf("cannot parse ipStr %q: %w", ipStr, err)
  86. }
  87. if v > 255 {
  88. return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
  89. }
  90. dst[i] = byte(v)
  91. b = b[n+1:]
  92. }
  93. v, err := ParseUint(b)
  94. if err != nil {
  95. return dst, fmt.Errorf("cannot parse ipStr %q: %w", ipStr, err)
  96. }
  97. if v > 255 {
  98. return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
  99. }
  100. dst[3] = byte(v)
  101. return dst, nil
  102. }
  103. // AppendHTTPDate appends HTTP-compliant (RFC1123) representation of date
  104. // to dst and returns the extended dst.
  105. func AppendHTTPDate(dst []byte, date time.Time) []byte {
  106. dst = date.In(time.UTC).AppendFormat(dst, time.RFC1123)
  107. copy(dst[len(dst)-3:], strGMT)
  108. return dst
  109. }
  110. // ParseHTTPDate parses HTTP-compliant (RFC1123) date.
  111. func ParseHTTPDate(date []byte) (time.Time, error) {
  112. return time.Parse(time.RFC1123, b2s(date))
  113. }
  114. // AppendUint appends n to dst and returns the extended dst.
  115. func AppendUint(dst []byte, n int) []byte {
  116. if n < 0 {
  117. panic("BUG: int must be positive")
  118. }
  119. var b [20]byte
  120. buf := b[:]
  121. i := len(buf)
  122. var q int
  123. for n >= 10 {
  124. i--
  125. q = n / 10
  126. buf[i] = '0' + byte(n-q*10)
  127. n = q
  128. }
  129. i--
  130. buf[i] = '0' + byte(n)
  131. dst = append(dst, buf[i:]...)
  132. return dst
  133. }
  134. // ParseUint parses uint from buf.
  135. func ParseUint(buf []byte) (int, error) {
  136. v, n, err := parseUintBuf(buf)
  137. if n != len(buf) {
  138. return -1, errUnexpectedTrailingChar
  139. }
  140. return v, err
  141. }
  142. var (
  143. errEmptyInt = errors.New("empty integer")
  144. errUnexpectedFirstChar = errors.New("unexpected first char found. Expecting 0-9")
  145. errUnexpectedTrailingChar = errors.New("unexpected trailing char found. Expecting 0-9")
  146. errTooLongInt = errors.New("too long int")
  147. )
  148. func parseUintBuf(b []byte) (int, int, error) {
  149. n := len(b)
  150. if n == 0 {
  151. return -1, 0, errEmptyInt
  152. }
  153. v := 0
  154. for i := 0; i < n; i++ {
  155. c := b[i]
  156. k := c - '0'
  157. if k > 9 {
  158. if i == 0 {
  159. return -1, i, errUnexpectedFirstChar
  160. }
  161. return v, i, nil
  162. }
  163. vNew := 10*v + int(k)
  164. // Test for overflow.
  165. if vNew < v {
  166. return -1, i, errTooLongInt
  167. }
  168. v = vNew
  169. }
  170. return v, n, nil
  171. }
  172. var (
  173. errEmptyFloat = errors.New("empty float number")
  174. errDuplicateFloatPoint = errors.New("duplicate point found in float number")
  175. errUnexpectedFloatEnd = errors.New("unexpected end of float number")
  176. errInvalidFloatExponent = errors.New("invalid float number exponent")
  177. errUnexpectedFloatChar = errors.New("unexpected char found in float number")
  178. )
  179. // ParseUfloat parses unsigned float from buf.
  180. func ParseUfloat(buf []byte) (float64, error) {
  181. if len(buf) == 0 {
  182. return -1, errEmptyFloat
  183. }
  184. b := buf
  185. var v uint64
  186. var offset = 1.0
  187. var pointFound bool
  188. for i, c := range b {
  189. if c < '0' || c > '9' {
  190. if c == '.' {
  191. if pointFound {
  192. return -1, errDuplicateFloatPoint
  193. }
  194. pointFound = true
  195. continue
  196. }
  197. if c == 'e' || c == 'E' {
  198. if i+1 >= len(b) {
  199. return -1, errUnexpectedFloatEnd
  200. }
  201. b = b[i+1:]
  202. minus := -1
  203. switch b[0] {
  204. case '+':
  205. b = b[1:]
  206. minus = 1
  207. case '-':
  208. b = b[1:]
  209. default:
  210. minus = 1
  211. }
  212. vv, err := ParseUint(b)
  213. if err != nil {
  214. return -1, errInvalidFloatExponent
  215. }
  216. return float64(v) * offset * math.Pow10(minus*int(vv)), nil
  217. }
  218. return -1, errUnexpectedFloatChar
  219. }
  220. v = 10*v + uint64(c-'0')
  221. if pointFound {
  222. offset /= 10
  223. }
  224. }
  225. return float64(v) * offset, nil
  226. }
  227. var (
  228. errEmptyHexNum = errors.New("empty hex number")
  229. errTooLargeHexNum = errors.New("too large hex number")
  230. )
  231. func readHexInt(r *bufio.Reader) (int, error) {
  232. var k, i, n int
  233. for {
  234. c, err := r.ReadByte()
  235. if err != nil {
  236. if err == io.EOF && i > 0 {
  237. return n, nil
  238. }
  239. return -1, err
  240. }
  241. k = int(hex2intTable[c])
  242. if k == 16 {
  243. if i == 0 {
  244. return -1, errEmptyHexNum
  245. }
  246. if err := r.UnreadByte(); err != nil {
  247. return -1, err
  248. }
  249. return n, nil
  250. }
  251. if i >= maxHexIntChars {
  252. return -1, errTooLargeHexNum
  253. }
  254. n = (n << 4) | k
  255. i++
  256. }
  257. }
  258. var hexIntBufPool sync.Pool
  259. func writeHexInt(w *bufio.Writer, n int) error {
  260. if n < 0 {
  261. panic("BUG: int must be positive")
  262. }
  263. v := hexIntBufPool.Get()
  264. if v == nil {
  265. v = make([]byte, maxHexIntChars+1)
  266. }
  267. buf := v.([]byte)
  268. i := len(buf) - 1
  269. for {
  270. buf[i] = lowerhex[n&0xf]
  271. n >>= 4
  272. if n == 0 {
  273. break
  274. }
  275. i--
  276. }
  277. _, err := w.Write(buf[i:])
  278. hexIntBufPool.Put(v)
  279. return err
  280. }
  281. const (
  282. upperhex = "0123456789ABCDEF"
  283. lowerhex = "0123456789abcdef"
  284. )
  285. func lowercaseBytes(b []byte) {
  286. for i := 0; i < len(b); i++ {
  287. p := &b[i]
  288. *p = toLowerTable[*p]
  289. }
  290. }
  291. // b2s converts byte slice to a string without memory allocation.
  292. // See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
  293. //
  294. // Note it may break if string and/or slice header will change
  295. // in the future go versions.
  296. func b2s(b []byte) string {
  297. /* #nosec G103 */
  298. return *(*string)(unsafe.Pointer(&b))
  299. }
  300. // s2b converts string to a byte slice without memory allocation.
  301. //
  302. // Note it may break if string and/or slice header will change
  303. // in the future go versions.
  304. func s2b(s string) (b []byte) {
  305. /* #nosec G103 */
  306. bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
  307. /* #nosec G103 */
  308. sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
  309. bh.Data = sh.Data
  310. bh.Cap = sh.Len
  311. bh.Len = sh.Len
  312. return b
  313. }
  314. // AppendUnquotedArg appends url-decoded src to dst and returns appended dst.
  315. //
  316. // dst may point to src. In this case src will be overwritten.
  317. func AppendUnquotedArg(dst, src []byte) []byte {
  318. return decodeArgAppend(dst, src)
  319. }
  320. // AppendQuotedArg appends url-encoded src to dst and returns appended dst.
  321. func AppendQuotedArg(dst, src []byte) []byte {
  322. for _, c := range src {
  323. switch {
  324. case c == ' ':
  325. dst = append(dst, '+')
  326. case quotedArgShouldEscapeTable[int(c)] != 0:
  327. dst = append(dst, '%', upperhex[c>>4], upperhex[c&0xf])
  328. default:
  329. dst = append(dst, c)
  330. }
  331. }
  332. return dst
  333. }
  334. func appendQuotedPath(dst, src []byte) []byte {
  335. // Fix issue in https://github.com/golang/go/issues/11202
  336. if len(src) == 1 && src[0] == '*' {
  337. return append(dst, '*')
  338. }
  339. for _, c := range src {
  340. if quotedPathShouldEscapeTable[int(c)] != 0 {
  341. dst = append(dst, '%', upperhex[c>>4], upperhex[c&0xf])
  342. } else {
  343. dst = append(dst, c)
  344. }
  345. }
  346. return dst
  347. }