implemented HTTP 1.0/1.1 server

This commit is contained in:
Altair-sh
2026-11-01 20:43:08 +01:00
parent 2de1f038c7
commit e0cf8a12a9
13 changed files with 956 additions and 68 deletions
+117
View File
@@ -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)
}
+104
View File
@@ -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
}
+13
View File
@@ -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))
}