448 lines
9.6 KiB
Go
448 lines
9.6 KiB
Go
|
package fasthttp
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"bytes"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"math"
|
||
|
"net"
|
||
|
"reflect"
|
||
|
"strings"
|
||
|
"sync"
|
||
|
"time"
|
||
|
"unsafe"
|
||
|
)
|
||
|
|
||
|
// AppendHTMLEscape appends html-escaped s to dst and returns the extended dst.
|
||
|
func AppendHTMLEscape(dst []byte, s string) []byte {
|
||
|
if strings.IndexByte(s, '<') < 0 &&
|
||
|
strings.IndexByte(s, '>') < 0 &&
|
||
|
strings.IndexByte(s, '"') < 0 &&
|
||
|
strings.IndexByte(s, '\'') < 0 {
|
||
|
|
||
|
// fast path - nothing to escape
|
||
|
return append(dst, s...)
|
||
|
}
|
||
|
|
||
|
// slow path
|
||
|
var prev int
|
||
|
var sub string
|
||
|
for i, n := 0, len(s); i < n; i++ {
|
||
|
sub = ""
|
||
|
switch s[i] {
|
||
|
case '<':
|
||
|
sub = "<"
|
||
|
case '>':
|
||
|
sub = ">"
|
||
|
case '"':
|
||
|
sub = """
|
||
|
case '\'':
|
||
|
sub = "'"
|
||
|
}
|
||
|
if len(sub) > 0 {
|
||
|
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")
|
||
|
|
||
|
// 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 {
|
||
|
dst = make([]byte, net.IPv4len)
|
||
|
}
|
||
|
copy(dst, net.IPv4zero)
|
||
|
dst = dst.To4()
|
||
|
if dst == nil {
|
||
|
panic("BUG: dst must not be nil")
|
||
|
}
|
||
|
|
||
|
b := ipStr
|
||
|
for i := 0; i < 3; i++ {
|
||
|
n := bytes.IndexByte(b, '.')
|
||
|
if n < 0 {
|
||
|
return dst, fmt.Errorf("cannot find dot in ipStr %q", ipStr)
|
||
|
}
|
||
|
v, err := ParseUint(b[:n])
|
||
|
if err != nil {
|
||
|
return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
|
||
|
}
|
||
|
if v > 255 {
|
||
|
return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
|
||
|
}
|
||
|
dst[i] = byte(v)
|
||
|
b = b[n+1:]
|
||
|
}
|
||
|
v, err := ParseUint(b)
|
||
|
if err != nil {
|
||
|
return dst, fmt.Errorf("cannot parse ipStr %q: %s", ipStr, err)
|
||
|
}
|
||
|
if v > 255 {
|
||
|
return dst, fmt.Errorf("cannot parse ipStr %q: ip part cannot exceed 255: parsed %d", ipStr, v)
|
||
|
}
|
||
|
dst[3] = byte(v)
|
||
|
|
||
|
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) {
|
||
|
return time.Parse(time.RFC1123, b2s(date))
|
||
|
}
|
||
|
|
||
|
// AppendUint appends n to dst and returns the extended dst.
|
||
|
func AppendUint(dst []byte, n int) []byte {
|
||
|
if n < 0 {
|
||
|
panic("BUG: int must be positive")
|
||
|
}
|
||
|
|
||
|
var b [20]byte
|
||
|
buf := b[:]
|
||
|
i := len(buf)
|
||
|
var q int
|
||
|
for n >= 10 {
|
||
|
i--
|
||
|
q = n / 10
|
||
|
buf[i] = '0' + byte(n-q*10)
|
||
|
n = q
|
||
|
}
|
||
|
i--
|
||
|
buf[i] = '0' + byte(n)
|
||
|
|
||
|
dst = append(dst, buf[i:]...)
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
// 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")
|
||
|
errUnexpectedFirstChar = errors.New("unexpected first char found. Expecting 0-9")
|
||
|
errUnexpectedTrailingChar = errors.New("unexpected traling 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 := 0; i < n; i++ {
|
||
|
c := b[i]
|
||
|
k := c - '0'
|
||
|
if k > 9 {
|
||
|
if i == 0 {
|
||
|
return -1, i, errUnexpectedFirstChar
|
||
|
}
|
||
|
return v, i, nil
|
||
|
}
|
||
|
if i >= maxIntChars {
|
||
|
return -1, i, errTooLongInt
|
||
|
}
|
||
|
v = 10*v + int(k)
|
||
|
}
|
||
|
return v, n, nil
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
errEmptyFloat = errors.New("empty float number")
|
||
|
errDuplicateFloatPoint = errors.New("duplicate point found in float number")
|
||
|
errUnexpectedFloatEnd = errors.New("unexpected end of float number")
|
||
|
errInvalidFloatExponent = errors.New("invalid float number exponent")
|
||
|
errUnexpectedFloatChar = errors.New("unexpected char found in float number")
|
||
|
)
|
||
|
|
||
|
// ParseUfloat parses unsigned float from buf.
|
||
|
func ParseUfloat(buf []byte) (float64, error) {
|
||
|
if len(buf) == 0 {
|
||
|
return -1, errEmptyFloat
|
||
|
}
|
||
|
b := buf
|
||
|
var v uint64
|
||
|
var offset = 1.0
|
||
|
var pointFound bool
|
||
|
for i, c := range b {
|
||
|
if c < '0' || c > '9' {
|
||
|
if c == '.' {
|
||
|
if pointFound {
|
||
|
return -1, errDuplicateFloatPoint
|
||
|
}
|
||
|
pointFound = true
|
||
|
continue
|
||
|
}
|
||
|
if c == 'e' || c == 'E' {
|
||
|
if i+1 >= len(b) {
|
||
|
return -1, errUnexpectedFloatEnd
|
||
|
}
|
||
|
b = b[i+1:]
|
||
|
minus := -1
|
||
|
switch b[0] {
|
||
|
case '+':
|
||
|
b = b[1:]
|
||
|
minus = 1
|
||
|
case '-':
|
||
|
b = b[1:]
|
||
|
default:
|
||
|
minus = 1
|
||
|
}
|
||
|
vv, err := ParseUint(b)
|
||
|
if err != nil {
|
||
|
return -1, errInvalidFloatExponent
|
||
|
}
|
||
|
return float64(v) * offset * math.Pow10(minus*int(vv)), nil
|
||
|
}
|
||
|
return -1, errUnexpectedFloatChar
|
||
|
}
|
||
|
v = 10*v + uint64(c-'0')
|
||
|
if pointFound {
|
||
|
offset /= 10
|
||
|
}
|
||
|
}
|
||
|
return float64(v) * offset, nil
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
errEmptyHexNum = errors.New("empty hex number")
|
||
|
errTooLargeHexNum = errors.New("too large hex number")
|
||
|
)
|
||
|
|
||
|
func readHexInt(r *bufio.Reader) (int, error) {
|
||
|
n := 0
|
||
|
i := 0
|
||
|
var k 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
|
||
|
}
|
||
|
r.UnreadByte()
|
||
|
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 {
|
||
|
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] = int2hexbyte(n & 0xf)
|
||
|
n >>= 4
|
||
|
if n == 0 {
|
||
|
break
|
||
|
}
|
||
|
i--
|
||
|
}
|
||
|
_, err := w.Write(buf[i:])
|
||
|
hexIntBufPool.Put(v)
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
func int2hexbyte(n int) byte {
|
||
|
if n < 10 {
|
||
|
return '0' + byte(n)
|
||
|
}
|
||
|
return 'a' + byte(n) - 10
|
||
|
}
|
||
|
|
||
|
func hexCharUpper(c byte) byte {
|
||
|
if c < 10 {
|
||
|
return '0' + c
|
||
|
}
|
||
|
return c - 10 + 'A'
|
||
|
}
|
||
|
|
||
|
var hex2intTable = func() []byte {
|
||
|
b := make([]byte, 256)
|
||
|
for i := 0; i < 256; i++ {
|
||
|
c := byte(16)
|
||
|
if i >= '0' && i <= '9' {
|
||
|
c = byte(i) - '0'
|
||
|
} else if i >= 'a' && i <= 'f' {
|
||
|
c = byte(i) - 'a' + 10
|
||
|
} else if i >= 'A' && i <= 'F' {
|
||
|
c = byte(i) - 'A' + 10
|
||
|
}
|
||
|
b[i] = c
|
||
|
}
|
||
|
return b
|
||
|
}()
|
||
|
|
||
|
const toLower = 'a' - 'A'
|
||
|
|
||
|
var toLowerTable = func() [256]byte {
|
||
|
var a [256]byte
|
||
|
for i := 0; i < 256; i++ {
|
||
|
c := byte(i)
|
||
|
if c >= 'A' && c <= 'Z' {
|
||
|
c += toLower
|
||
|
}
|
||
|
a[i] = c
|
||
|
}
|
||
|
return a
|
||
|
}()
|
||
|
|
||
|
var toUpperTable = func() [256]byte {
|
||
|
var a [256]byte
|
||
|
for i := 0; i < 256; i++ {
|
||
|
c := byte(i)
|
||
|
if c >= 'a' && c <= 'z' {
|
||
|
c -= toLower
|
||
|
}
|
||
|
a[i] = c
|
||
|
}
|
||
|
return a
|
||
|
}()
|
||
|
|
||
|
func lowercaseBytes(b []byte) {
|
||
|
for i := 0; i < len(b); i++ {
|
||
|
p := &b[i]
|
||
|
*p = toLowerTable[*p]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// b2s converts byte slice to a string without memory allocation.
|
||
|
// See https://groups.google.com/forum/#!msg/Golang-Nuts/ENgbUzYvCuU/90yGx7GUAgAJ .
|
||
|
//
|
||
|
// Note it may break if string and/or slice header will change
|
||
|
// in the future go versions.
|
||
|
func b2s(b []byte) string {
|
||
|
return *(*string)(unsafe.Pointer(&b))
|
||
|
}
|
||
|
|
||
|
// s2b converts string to a byte slice without memory allocation.
|
||
|
//
|
||
|
// Note it may break if string and/or slice header will change
|
||
|
// in the future go versions.
|
||
|
func s2b(s string) []byte {
|
||
|
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
||
|
bh := reflect.SliceHeader{
|
||
|
Data: sh.Data,
|
||
|
Len: sh.Len,
|
||
|
Cap: sh.Len,
|
||
|
}
|
||
|
return *(*[]byte)(unsafe.Pointer(&bh))
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
// See http://www.w3.org/TR/html5/forms.html#form-submission-algorithm
|
||
|
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
|
||
|
c == '*' || c == '-' || c == '.' || c == '_' {
|
||
|
dst = append(dst, c)
|
||
|
} else {
|
||
|
dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
|
||
|
}
|
||
|
}
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
func appendQuotedPath(dst, src []byte) []byte {
|
||
|
for _, c := range src {
|
||
|
if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' ||
|
||
|
c == '/' || c == '.' || c == ',' || c == '=' || c == ':' || c == '&' || c == '~' || c == '-' || c == '_' {
|
||
|
dst = append(dst, c)
|
||
|
} else {
|
||
|
dst = append(dst, '%', hexCharUpper(c>>4), hexCharUpper(c&15))
|
||
|
}
|
||
|
}
|
||
|
return dst
|
||
|
}
|
||
|
|
||
|
// EqualBytesStr returns true if string(b) == s.
|
||
|
//
|
||
|
// This function has no performance benefits comparing to string(b) == s.
|
||
|
// It is left here for backwards compatibility only.
|
||
|
//
|
||
|
// This function is deperecated and may be deleted soon.
|
||
|
func EqualBytesStr(b []byte, s string) bool {
|
||
|
return string(b) == s
|
||
|
}
|
||
|
|
||
|
// AppendBytesStr appends src to dst and returns the extended dst.
|
||
|
//
|
||
|
// This function has no performance benefits comparing to append(dst, src...).
|
||
|
// It is left here for backwards compatibility only.
|
||
|
//
|
||
|
// This function is deprecated and may be deleted soon.
|
||
|
func AppendBytesStr(dst []byte, src string) []byte {
|
||
|
return append(dst, src...)
|
||
|
}
|