package http import ( "bufio" "encoding/base64" "errors" "fmt" "io" "net/textproto" "regexp" "strconv" "strings" ) type Header map[string]string type Request struct { Method string URI string Proto string Header Header } type Response struct { Code int Desc string Proto string Header Header } type handler func(string) error var version = "HTTP/[0-9]+\\.[0-9]+" var requestLine = regexp.MustCompile("^([A-Z]+) +([^ ]+) +(" + version + ")$") var statusLine = regexp.MustCompile("^(" + version + ") +([0-9]+) +(.*)$") var header = regexp.MustCompile("^([^ ]+) *: *(.*)$") const OK = 200 func parseHeader(s string) (key, value string, ok bool) { if m := header.FindStringSubmatch(s); m == nil { return } else { return m[1], m[2], true } } func addHeader(m Header, k, v string) Header { if m == nil { m = make(Header) } m[strings.Title(k)] = v return m } func parse(r *bufio.Reader, init handler, f handler) error { proto := textproto.NewReader(r) first := true for { line, err := proto.ReadLine() if err != nil { if err == io.EOF { err = io.ErrUnexpectedEOF } return err } if first { if err := init(line); err != nil { return err } first = false continue } if line == "" { break } if err := f(line); err != nil { return err } } return nil } func ParseRequestLine(s string) (method, uri, proto string, ok bool) { if m := requestLine.FindStringSubmatch(s); m == nil { return } else { return m[1], m[2], m[3], true } } func (r *Request) parseRequestLine(s string) error { if method, uri, proto, ok := ParseRequestLine(s); !ok { return errors.New("request line parse failed") } else { r.Method, r.URI, r.Proto = method, uri, proto return nil } } func (r *Request) parseHeader(s string) error { if k, v, ok := parseHeader(s); !ok { return errors.New("header parse failed") } else { r.Header = addHeader(r.Header, k, v) return nil } } func ParseRequest(r *bufio.Reader) (*Request, error) { req := new(Request) if err := parse(r, req.parseRequestLine, req.parseHeader); err != nil { return nil, fmt.Errorf("http: bad request: %w", err) } return req, nil } func ParseStatusLine(s string) (proto string, code int, desc string, ok bool) { if m := statusLine.FindStringSubmatch(s); m == nil { return } else if n, err := strconv.Atoi(m[2]); err != nil { return } else { return m[1], n, m[3], true } } func (r *Response) parseStatusLine(s string) error { if proto, code, desc, ok := ParseStatusLine(s); !ok { return errors.New("status line parse failed") } else { r.Code, r.Desc, r.Proto = code, desc, proto return nil } } func (r *Response) parseHeader(s string) error { if k, v, ok := parseHeader(s); !ok { return errors.New("header parse failed") } else { r.Header = addHeader(r.Header, k, v) return nil } } func ParseResponse(r *bufio.Reader) (*Response, error) { resp := new(Response) if err := parse(r, resp.parseStatusLine, resp.parseHeader); err != nil { return nil, fmt.Errorf("http: bad response: %w", err) } return resp, nil } func BasicAuthEncode(s string) string { encoded := base64.StdEncoding.EncodeToString([]byte(s)) return "Basic " + encoded } func BasicAuthCheck(origin string, encoded string) bool { return BasicAuthEncode(origin) == encoded }