package request import ( "bytes" "fmt" "regexp" ) // httpMethod represents available http methods type httpMethod byte const ( OPTIONS httpMethod = iota GET HEAD POST PUT DELETE ) // RequestLine represents the HTTP Request line // defined in rfc-2616 : https://tools.ietf.org/html/rfc2616#section-5.1 type RequestLine struct { method httpMethod uri string version byte } // parseRequestLine parses the first HTTP request line func (r *RequestLine) Parse(b []byte) error { /* (1) Split by ' ' */ parts := bytes.Split(b, []byte(" ")) /* (2) Fail when missing parts */ if len(parts) != 3 { return fmt.Errorf("expected 3 space-separated elements, got %d elements", len(parts)) } /* (3) Extract HTTP method */ err := r.extractHttpMethod(parts[0]) if err != nil { return err } /* (4) Extract URI */ err = r.extractURI(parts[1]) if err != nil { return err } /* (5) Extract version */ err = r.extractHttpVersion(parts[2]) if err != nil { return err } return nil } // GetURI returns the actual URI func (r RequestLine) GetURI() string { return r.uri } // extractHttpMethod extracts the HTTP method from a []byte // and checks for errors // allowed format: OPTIONS|GET|HEAD|POST|PUT|DELETE func (r *RequestLine) extractHttpMethod(b []byte) error { switch string(b) { // case "OPTIONS": r.method = OPTIONS case "GET": r.method = GET // case "HEAD": r.method = HEAD // case "POST": r.method = POST // case "PUT": r.method = PUT // case "DELETE": r.method = DELETE default: return fmt.Errorf("Invalid HTTP method '%s', expected 'GET'", b) } return nil } // extractURI extracts the URI from a []byte and checks for errors // allowed format: /([^/]/)*/? func (r *RequestLine) extractURI(b []byte) error { /* (1) Check format */ checker := regexp.MustCompile("^(?:/[^/]+)*/?$") if !checker.Match(b) { return fmt.Errorf("invalid URI, expected an absolute path, got '%s'", b) } /* (2) Store */ r.uri = string(b) return nil } // extractHttpVersion extracts the version and checks for errors // allowed format: [1-9] or [1.9].[0-9] func (r *RequestLine) extractHttpVersion(b []byte) error { /* (1) Extract version parts */ extractor := regexp.MustCompile(`^HTTP/([1-9])(?:\.([0-9]))?$`) if !extractor.Match(b) { return fmt.Errorf("HTTP version, expected INT or INT.INT, got '%s'", b) } /* (2) Extract version number */ matches := extractor.FindSubmatch(b) var version byte = matches[1][0] - '0' /* (3) Extract subversion (if exists) */ var subVersion byte = 0 if len(matches[2]) > 0 { subVersion = matches[2][0] - '0' } /* (4) Store version (x 10 to fit uint8) */ r.version = version*10 + subVersion return nil }