| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- //go:generate go run bytesconv_table_gen.go
- package fasthttp
- import (
- "bufio"
- "bytes"
- "errors"
- "fmt"
- "io"
- "net"
- "net/http"
- "strconv"
- "sync"
- "time"
- )
- // AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
- func AppendHTMLEscape(dst []byte, s string) []byte {
- var (
- prev int
- sub string
- )
- for i, n := 0, len(s); i < n; i++ {
- sub = ""
- switch s[i] {
- case '&':
- sub = "&"
- case '<':
- sub = "<"
- case '>':
- sub = ">"
- case '"':
- sub = """ // """ is shorter than """.
- case '\'':
- sub = "'" // "'" is shorter than "'" and apos was not in HTML until HTML5.
- }
- if sub != "" {
- dst = append(dst, s[prev:i]...)
- dst = append(dst, sub...)
- prev = i + 1
- }
- }
- return append(dst, s[prev:]...)
- }
- // AppendHTMLEscapeBytes appends html-escaped s to dst and returns
- // the extended dst.
- func AppendHTMLEscapeBytes(dst, s []byte) []byte {
- return AppendHTMLEscape(dst, b2s(s))
- }
- // AppendIPv4 appends string representation of the given ip v4 to dst
- // and returns the extended dst.
- func AppendIPv4(dst []byte, ip net.IP) []byte {
- ip = ip.To4()
- if ip == nil {
- return append(dst, "non-v4 ip passed to AppendIPv4"...)
- }
- dst = AppendUint(dst, int(ip[0]))
- for i := 1; i < 4; i++ {
- dst = append(dst, '.')
- dst = AppendUint(dst, int(ip[i]))
- }
- return dst
- }
- var errEmptyIPStr = errors.New("empty ip address string")
- var httpDateGMT = time.FixedZone("GMT", 0)
- // ParseIPv4 parses ip address from ipStr into dst and returns the extended dst.
- func ParseIPv4(dst net.IP, ipStr []byte) (net.IP, error) {
- if len(ipStr) == 0 {
- return dst, errEmptyIPStr
- }
- if len(dst) < net.IPv4len || len(dst) > net.IPv4len {
- dst = make([]byte, net.IPv4len)
- }
- copy(dst, net.IPv4zero)
- dst = dst.To4() // dst is always non-nil here
- b := ipStr
- for i := range 3 {
- n := bytes.IndexByte(b, '.')
- if n < 0 {
- return dst, fmt.Errorf("cannot find dot in ipStr %q", ipStr)
- }
- octet, parsed, err := parseIPv4Octet(b[:n])
- if err != nil {
- if errors.Is(err, errIPv4PartTooLarge) {
- return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, parsed)
- }
- return dst, fmt.Errorf("cannot parse ipStr %q: %w", ipStr, err)
- }
- dst[i] = octet
- b = b[n+1:]
- }
- octet, parsed, err := parseIPv4Octet(b)
- if err != nil {
- if errors.Is(err, errIPv4PartTooLarge) {
- return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, parsed)
- }
- return dst, fmt.Errorf("cannot parse ipStr %q: %w", ipStr, err)
- }
- dst[3] = octet
- return dst, nil
- }
- // AppendHTTPDate appends HTTP-compliant (RFC1123) representation of date
- // to dst and returns the extended dst.
- func AppendHTTPDate(dst []byte, date time.Time) []byte {
- dst = date.In(time.UTC).AppendFormat(dst, time.RFC1123)
- copy(dst[len(dst)-3:], strGMT)
- return dst
- }
- // ParseHTTPDate parses HTTP-compliant (RFC1123) date.
- func ParseHTTPDate(date []byte) (time.Time, error) {
- if t, ok := parseRFC1123DateGMT(date); ok {
- return t, nil
- }
- return time.Parse(http.TimeFormat, b2s(date))
- }
- func parseRFC1123DateGMT(b []byte) (time.Time, bool) {
- // Expects "Mon, 02 Jan 2006 15:04:05 GMT".
- if len(b) != 29 {
- return time.Time{}, false
- }
- if !isWeekday3(b[0], b[1], b[2]) {
- return time.Time{}, false
- }
- if b[3] != ',' || b[4] != ' ' || b[7] != ' ' || b[11] != ' ' ||
- b[16] != ' ' || b[19] != ':' || b[22] != ':' || b[25] != ' ' {
- return time.Time{}, false
- }
- if b[26] != 'G' || b[27] != 'M' || b[28] != 'T' {
- return time.Time{}, false
- }
- day, ok := parse2Digits(b[5], b[6])
- if !ok || day < 1 || day > 31 {
- return time.Time{}, false
- }
- month, ok := parseMonth3(b[8], b[9], b[10])
- if !ok {
- return time.Time{}, false
- }
- year, ok := parse4Digits(b[12], b[13], b[14], b[15])
- if !ok {
- return time.Time{}, false
- }
- hour, ok := parse2Digits(b[17], b[18])
- if !ok || hour > 23 {
- return time.Time{}, false
- }
- minute, ok := parse2Digits(b[20], b[21])
- if !ok || minute > 59 {
- return time.Time{}, false
- }
- second, ok := parse2Digits(b[23], b[24])
- if !ok || second > 59 {
- return time.Time{}, false
- }
- t := time.Date(year, month, day, hour, minute, second, 0, httpDateGMT)
- // Reject calendar-invalid dates like "31 Feb", which time.Date normalizes.
- if t.Year() != year || t.Month() != month || t.Day() != day {
- return time.Time{}, false
- }
- return t, true
- }
- func isWeekday3(a, b, c byte) bool {
- a |= 0x20
- b |= 0x20
- c |= 0x20
- k := uint32(a)<<16 | uint32(b)<<8 | uint32(c)
- switch k {
- case uint32('m')<<16 | uint32('o')<<8 | uint32('n'),
- uint32('t')<<16 | uint32('u')<<8 | uint32('e'),
- uint32('w')<<16 | uint32('e')<<8 | uint32('d'),
- uint32('t')<<16 | uint32('h')<<8 | uint32('u'),
- uint32('f')<<16 | uint32('r')<<8 | uint32('i'),
- uint32('s')<<16 | uint32('a')<<8 | uint32('t'),
- uint32('s')<<16 | uint32('u')<<8 | uint32('n'):
- return true
- default:
- return false
- }
- }
- func parse2Digits(a, b byte) (int, bool) {
- if a < '0' || a > '9' || b < '0' || b > '9' {
- return 0, false
- }
- return int(a-'0')*10 + int(b-'0'), true
- }
- func parse4Digits(a, b, c, d byte) (int, bool) {
- v1, ok := parse2Digits(a, b)
- if !ok {
- return 0, false
- }
- v2, ok := parse2Digits(c, d)
- if !ok {
- return 0, false
- }
- return v1*100 + v2, true
- }
- func parseMonth3(a, b, c byte) (time.Month, bool) {
- a |= 0x20
- b |= 0x20
- c |= 0x20
- k := uint32(a)<<16 | uint32(b)<<8 | uint32(c)
- switch k {
- case uint32('j')<<16 | uint32('a')<<8 | uint32('n'):
- return time.January, true
- case uint32('f')<<16 | uint32('e')<<8 | uint32('b'):
- return time.February, true
- case uint32('m')<<16 | uint32('a')<<8 | uint32('r'):
- return time.March, true
- case uint32('a')<<16 | uint32('p')<<8 | uint32('r'):
- return time.April, true
- case uint32('m')<<16 | uint32('a')<<8 | uint32('y'):
- return time.May, true
- case uint32('j')<<16 | uint32('u')<<8 | uint32('n'):
- return time.June, true
- case uint32('j')<<16 | uint32('u')<<8 | uint32('l'):
- return time.July, true
- case uint32('a')<<16 | uint32('u')<<8 | uint32('g'):
- return time.August, true
- case uint32('s')<<16 | uint32('e')<<8 | uint32('p'):
- return time.September, true
- case uint32('o')<<16 | uint32('c')<<8 | uint32('t'):
- return time.October, true
- case uint32('n')<<16 | uint32('o')<<8 | uint32('v'):
- return time.November, true
- case uint32('d')<<16 | uint32('e')<<8 | uint32('c'):
- return time.December, true
- }
- return 0, false
- }
- // AppendUint appends n to dst and returns the extended dst.
- func AppendUint(dst []byte, n int) []byte {
- if n < 0 {
- // developer sanity-check
- panic("BUG: int must be positive")
- }
- return strconv.AppendUint(dst, uint64(n), 10)
- }
- // ParseUint parses uint from buf.
- func ParseUint(buf []byte) (int, error) {
- v, n, err := parseUintBuf(buf)
- if n != len(buf) {
- return -1, errUnexpectedTrailingChar
- }
- return v, err
- }
- var (
- errEmptyInt = errors.New("empty integer")
- errIPv4PartTooLarge = errors.New("ip part cannot exceed 255")
- errUnexpectedFirstChar = errors.New("unexpected first char found. Expecting 0-9")
- errUnexpectedTrailingChar = errors.New("unexpected trailing char found. Expecting 0-9")
- errTooLongInt = errors.New("too long int")
- )
- func parseUintBuf(b []byte) (int, int, error) {
- n := len(b)
- if n == 0 {
- return -1, 0, errEmptyInt
- }
- v := 0
- for i := range n {
- c := b[i]
- k := c - '0'
- if k > 9 {
- if i == 0 {
- return -1, i, errUnexpectedFirstChar
- }
- return v, i, nil
- }
- vNew := 10*v + int(k)
- // Test for overflow.
- if vNew < v {
- return -1, i, errTooLongInt
- }
- v = vNew
- }
- return v, n, nil
- }
- func parseIPv4Octet(b []byte) (byte, int, error) {
- if len(b) == 0 {
- return 0, 0, errEmptyInt
- }
- var (
- octet byte
- parsed int
- )
- for i := range len(b) {
- c := b[i]
- k := c - '0'
- if k > 9 {
- if i == 0 {
- return 0, parsed, errUnexpectedFirstChar
- }
- return 0, parsed, errUnexpectedTrailingChar
- }
- parsed = parsed*10 + int(k)
- if octet > 25 || (octet == 25 && k > 5) {
- return 0, parsed, errIPv4PartTooLarge
- }
- octet = octet*10 + k
- }
- return octet, parsed, nil
- }
- // ParseUfloat parses unsigned float from buf.
- func ParseUfloat(buf []byte) (float64, error) {
- // The implementation of parsing a float string is not easy.
- // We believe that the conservative approach is to call strconv.ParseFloat.
- // https://github.com/valyala/fasthttp/pull/1865
- res, err := strconv.ParseFloat(b2s(buf), 64)
- if res < 0 {
- return -1, errors.New("negative input is invalid")
- }
- if err != nil {
- return -1, err
- }
- return res, err
- }
- var (
- errEmptyHexNum = errors.New("empty hex number")
- errTooLargeHexNum = errors.New("too large hex number")
- )
- func readHexInt(r *bufio.Reader) (int, error) {
- var k, i, n int
- for {
- c, err := r.ReadByte()
- if err != nil {
- if err == io.EOF && i > 0 {
- return n, nil
- }
- return -1, err
- }
- k = int(hex2intTable[c])
- if k == 16 {
- if i == 0 {
- return -1, errEmptyHexNum
- }
- if err := r.UnreadByte(); err != nil {
- return -1, err
- }
- return n, nil
- }
- if i >= maxHexIntChars {
- return -1, errTooLargeHexNum
- }
- n = (n << 4) | k
- i++
- }
- }
- var hexIntBufPool sync.Pool
- func writeHexInt(w *bufio.Writer, n int) error {
- if n < 0 {
- // developer sanity-check
- panic("BUG: int must be positive")
- }
- v := hexIntBufPool.Get()
- if v == nil {
- v = make([]byte, maxHexIntChars+1)
- }
- buf := v.([]byte)
- i := len(buf) - 1
- for {
- buf[i] = lowerhex[n&0xf]
- n >>= 4
- if n == 0 {
- break
- }
- i--
- }
- _, err := w.Write(buf[i:])
- hexIntBufPool.Put(v)
- return err
- }
- const (
- upperhex = "0123456789ABCDEF"
- lowerhex = "0123456789abcdef"
- )
- func lowercaseBytes(b []byte) {
- for i := range b {
- p := &b[i]
- *p = toLowerTable[*p]
- }
- }
- // AppendUnquotedArg appends url-decoded src to dst and returns appended dst.
- //
- // dst may point to src. In this case src will be overwritten.
- func AppendUnquotedArg(dst, src []byte) []byte {
- return decodeArgAppend(dst, src)
- }
- // AppendQuotedArg appends url-encoded src to dst and returns appended dst.
- func AppendQuotedArg(dst, src []byte) []byte {
- for _, c := range src {
- switch {
- case c == ' ':
- dst = append(dst, '+')
- case quotedArgShouldEscapeTable[int(c)] != 0:
- dst = append(dst, '%', upperhex[c>>4], upperhex[c&0xf])
- default:
- dst = append(dst, c)
- }
- }
- return dst
- }
- func appendQuotedPath(dst, src []byte) []byte {
- // Fix issue in https://github.com/golang/go/issues/11202
- if len(src) == 1 && src[0] == '*' {
- return append(dst, '*')
- }
- for _, c := range src {
- if quotedPathShouldEscapeTable[int(c)] != 0 {
- dst = append(dst, '%', upperhex[c>>4], upperhex[c&0xf])
- } else {
- dst = append(dst, c)
- }
- }
- return dst
- }
|