summaryrefslogtreecommitdiff
path: root/pkg/http
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/http')
-rw-r--r--pkg/http/http.go166
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
+}