implemented HTTP 1.0/1.1 server
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
package string_helper
|
||||
|
||||
import (
|
||||
"io"
|
||||
"slices"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type StringBuilder struct {
|
||||
// allocate it with size of estimated output
|
||||
Buf []byte
|
||||
}
|
||||
|
||||
// compile-time interface checks
|
||||
var (
|
||||
_ io.Writer = (*StringBuilder)(nil)
|
||||
_ io.StringWriter = (*StringBuilder)(nil)
|
||||
_ io.ByteWriter = (*StringBuilder)(nil)
|
||||
)
|
||||
|
||||
// Write implements [io.Writer]. It never returns an error.
|
||||
func (b *StringBuilder) Write(p []byte) (int, error) {
|
||||
b.Buf = append(b.Buf, p...)
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// WriteString implements [io.StringWriter]. It never returns an error.
|
||||
func (b *StringBuilder) WriteString(s string) (int, error) {
|
||||
b.Buf = append(b.Buf, s...)
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
// WriteByte implements [io.ByteWriter]. It never returns an error.
|
||||
func (b *StringBuilder) WriteByte(c byte) error {
|
||||
b.Buf = append(b.Buf, c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset empties the builder but keeps the allocated capacity for reuse.
|
||||
func (b *StringBuilder) Reset() { b.Buf = b.Buf[:0] }
|
||||
|
||||
// Grow ensures there is room for at least n more bytes
|
||||
func (b *StringBuilder) Grow(n int) { b.Buf = slices.Grow(b.Buf, n) }
|
||||
|
||||
func (b *StringBuilder) Len() int { return len(b.Buf) }
|
||||
|
||||
func (b *StringBuilder) AddS(v string) *StringBuilder {
|
||||
b.Buf = append(b.Buf, v...)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *StringBuilder) AddB(v byte) *StringBuilder {
|
||||
b.Buf = append(b.Buf, v)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *StringBuilder) AddR(v rune) *StringBuilder {
|
||||
b.Buf = utf8.AppendRune(b.Buf, v)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *StringBuilder) AddBool(v bool) *StringBuilder {
|
||||
b.Buf = strconv.AppendBool(b.Buf, v)
|
||||
return b
|
||||
}
|
||||
|
||||
// numberBase: 2 <= base <= 36
|
||||
func (b *StringBuilder) AddIExt(v int64, numberBase int) *StringBuilder {
|
||||
b.Buf = strconv.AppendInt(b.Buf, v, numberBase)
|
||||
return b
|
||||
}
|
||||
func (b *StringBuilder) AddI(v int64) *StringBuilder { return b.AddIExt(v, 10) }
|
||||
func (b *StringBuilder) AddI32(v int32) *StringBuilder { return b.AddI(int64(v)) }
|
||||
func (b *StringBuilder) AddI16(v int16) *StringBuilder { return b.AddI(int64(v)) }
|
||||
func (b *StringBuilder) AddI8(v int8) *StringBuilder { return b.AddI(int64(v)) }
|
||||
|
||||
// numberBase: 2 <= base <= 36
|
||||
func (b *StringBuilder) AddUExt(v uint64, numberBase int) *StringBuilder {
|
||||
b.Buf = strconv.AppendUint(b.Buf, v, numberBase)
|
||||
return b
|
||||
}
|
||||
func (b *StringBuilder) AddU(v uint64) *StringBuilder { return b.AddUExt(v, 10) }
|
||||
func (b *StringBuilder) AddU32(v uint32) *StringBuilder { return b.AddU(uint64(v)) }
|
||||
func (b *StringBuilder) AddU16(v uint16) *StringBuilder { return b.AddU(uint64(v)) }
|
||||
func (b *StringBuilder) AddU8(v uint8) *StringBuilder { return b.AddU(uint64(v)) }
|
||||
|
||||
// fmtChar: see [strconv.FormatFloat]
|
||||
// precision: number of digits after . (-1 for max precision)
|
||||
func (b *StringBuilder) AddF32Ext(v float32, fmtChar byte, precision int) *StringBuilder {
|
||||
b.Buf = strconv.AppendFloat(b.Buf, float64(v), fmtChar, precision, 32)
|
||||
return b
|
||||
}
|
||||
func (b *StringBuilder) AddF32(v float32) *StringBuilder {
|
||||
return b.AddF32Ext(v, 'g', -1)
|
||||
}
|
||||
|
||||
// fmtChar: see [strconv.FormatFloat]
|
||||
// precision: number of digits after . (-1 for max precision)
|
||||
func (b *StringBuilder) AddF64Ext(v float64, fmtChar byte, precision int) *StringBuilder {
|
||||
b.Buf = strconv.AppendFloat(b.Buf, v, fmtChar, precision, 64)
|
||||
return b
|
||||
}
|
||||
func (b *StringBuilder) AddF64(v float64) *StringBuilder {
|
||||
return b.AddF64Ext(v, 'g', -1)
|
||||
}
|
||||
|
||||
// creates copy of buffer
|
||||
func (b *StringBuilder) String() string {
|
||||
return string(b.Buf)
|
||||
}
|
||||
|
||||
// doesn't copy buffer,
|
||||
// so you shouldn't modify b.Buf before using the string
|
||||
func (b *StringBuilder) StringUnsafe() string {
|
||||
return UnsafeBytesToStr(b.Buf)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package string_helper
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Returns path cleaned from "./", "/./", "//"
|
||||
// or empty string if path is not safe.
|
||||
// Path is considered unsafe if it:
|
||||
// - contains ".." segments
|
||||
// - ends with '.'
|
||||
// - contains `\:*?"<>|`
|
||||
// - contains windows-restricted names COM, CON, ...
|
||||
func ResolveSafePath(decodedPath string) string {
|
||||
var sb StringBuilder
|
||||
sb.Grow(len(decodedPath))
|
||||
var seg string
|
||||
for rest := decodedPath; rest != ""; {
|
||||
seg, rest = nextSegment(rest)
|
||||
if seg == "" || seg == "." {
|
||||
continue
|
||||
}
|
||||
if !isSafeSegment(seg) {
|
||||
return ""
|
||||
}
|
||||
sb.AddB('/')
|
||||
sb.AddS(seg)
|
||||
}
|
||||
if sb.Len() == 0 {
|
||||
return "/"
|
||||
}
|
||||
return sb.StringUnsafe()
|
||||
}
|
||||
|
||||
// nextSegment splits off the first '/'-separated segment of s.
|
||||
func nextSegment(s string) (seg, rest string) {
|
||||
seg, rest, _ = strings.Cut(s, "/")
|
||||
return
|
||||
}
|
||||
|
||||
// isSafeSegment reports whether a single non-empty path segment is safe to
|
||||
// use as a file name.
|
||||
func isSafeSegment(seg string) bool {
|
||||
if seg == ".." {
|
||||
return false
|
||||
}
|
||||
// Windows strips trailing dots "a.txt." would open "a.txt"
|
||||
if last := seg[len(seg)-1]; last == '.' {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(seg); i++ {
|
||||
c := seg[i]
|
||||
if c < 0x20 || c == 0x7F {
|
||||
return false
|
||||
}
|
||||
// invalid in Windows file names, ':' also selects NTFS streams
|
||||
if strings.IndexByte(`\:*?"<>|`, c) >= 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return !isReservedWindowsName(seg)
|
||||
}
|
||||
|
||||
// isReservedWindowsName reports whether seg names a Windows device:
|
||||
// "CON", "con.txt", "COM1.tar.gz", ...
|
||||
func isReservedWindowsName(seg string) bool {
|
||||
base := seg
|
||||
if i := strings.IndexByte(seg, '.'); i >= 0 {
|
||||
base = seg[:i]
|
||||
}
|
||||
switch len(base) {
|
||||
case 3:
|
||||
return asciiEqualUpper(base, "CON") || asciiEqualUpper(base, "PRN") ||
|
||||
asciiEqualUpper(base, "AUX") || asciiEqualUpper(base, "NUL")
|
||||
case 4:
|
||||
prefix := base[:3]
|
||||
return (asciiEqualUpper(prefix, "COM") || asciiEqualUpper(prefix, "LPT")) &&
|
||||
'0' <= base[3] && base[3] <= '9'
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// asciiUpper upper-cases ASCII letters only (like std::toupper in the "C" locale).
|
||||
func asciiUpper(c byte) byte {
|
||||
if 'a' <= c && c <= 'z' {
|
||||
return c - ('a' - 'A')
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// asciiEqualUpper compares s with upper (which must be upper-case ASCII), ignoring
|
||||
// ASCII case. Unlike strings.EqualFold it does no Unicode folding, so e.g. the
|
||||
// Kelvin sign 'K' (U+212A) does not match 'K'.
|
||||
func asciiEqualUpper(s, upper string) bool {
|
||||
if len(s) != len(upper) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s); i++ {
|
||||
if asciiUpper(s[i]) != upper[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package string_helper
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func UnsafeGetStrBytes(s string) []byte {
|
||||
pointr := unsafe.StringData(s)
|
||||
return unsafe.Slice(pointr, len(s))
|
||||
}
|
||||
|
||||
func UnsafeBytesToStr(b []byte) string {
|
||||
pointr := unsafe.SliceData(b)
|
||||
return unsafe.String(pointr, len(b))
|
||||
}
|
||||
Reference in New Issue
Block a user