mirror of
https://github.com/valyala/fasthttp.git
synced 2026-06-14 15:56:44 +03:00
d0f2727a4d
* client: simplify (*HostClient).do() Remove an allocation in favour of deferring a call to release the response. * client: remove panic in dialAddr Return an error instead of panicking if the user supplied a nonsensical DialFunc. * compression: remove panic on invalid compression level If a compression level exceeding gzip's boundaries is provided, fasthttp will panic. Instead it would be better to handle this error for them by limiting it to the minimum or maximum value, depending on the direction the user has exceeded the limits. Clamp the value of gzip to always be between gzip.BestSpeed and gzip.BestCompression. * peripconn: remove panic on negative connection count When a negative count is reached when unregistering a connection, a panic is caused even though data-integrity is not at risk. Replace the panic() with a simple clamp on the value to ensure the value does not exceed it's expected lower bounds. References: #1504 * compress: remove error on failed nonblocking writes Since there is no way of handling or even logging non-critical errors in stateless non-blocking writecalls, just drop them and hope the user notices and tries again. * workerPool: remove panic on redundant Start and Stop calls Instead of panicking for invalid behaviour, it's preferable to just turn the function into a noop. * http: remove panic on invalid form boundary * http: remove panic on negative reads Since bufio already panics on negative reads, it is not necessary to do so as well. If the length is zero and for some reason no error is returned, readBodyIdentity and appendBodyFixedSize now errors in these cases. Link: https://github.com/golang/go/blob/851f6fd61425c810959c7ab51e6dc86f8a63c970/src/bufio/bufio.go#L246 * fs: remove panic on negative reader count When a negative count is reached when unregistering a reader, a panic is thrown even though data-integrity is not at risk. Replace the panic() with a simple clamp on the value to ensure the value does not exceed it's expected lower bounds. * server: remove panic in favour of a segfault Panicking with "BUG: " obscures the error. As the segfault causes a panic anyway, just let the chaos unfold. * server: remove panic in favour of returning an error Writing on a timed-out response is not endangering data integrity and just fails. * chore: add comments to all panics * chore: fix minor typo
358 lines
7.5 KiB
Go
358 lines
7.5 KiB
Go
//go:generate go run bytesconv_table_gen.go
|
|
|
|
package fasthttp
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"net"
|
|
"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 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 {
|
|
// developer sanity-check
|
|
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: %w", 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: %w", 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 {
|
|
// developer sanity-check
|
|
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 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 := 0; i < n; i++ {
|
|
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
|
|
}
|
|
|
|
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*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) {
|
|
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 := 0; i < len(b); i++ {
|
|
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
|
|
}
|