diff options
Diffstat (limited to 'pkg/http/http.go')
| -rw-r--r-- | pkg/http/http.go | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/pkg/http/http.go b/pkg/http/http.go new file mode 100644 index 0000000..eefe348 --- /dev/null +++ b/pkg/http/http.go @@ -0,0 +1,166 @@ +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 +} |
