package http import ( "bufio" "encoding/base64" "errors" "fmt" "io" "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 } 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 io.Reader, init func(string) error, f func(string) error) error { scanner := bufio.NewScanner(r) ok := false if scanner.Scan() { if err := init(scanner.Text()); err != nil { return err } } for scanner.Scan() { if scanner.Text() == "" { ok = true break } if err := f(scanner.Text()); err != nil { return err } } if err := scanner.Err(); err != nil { return err } else if !ok { return io.ErrUnexpectedEOF } 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 io.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 io.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 }