package upgrade import ( "bytes" "fmt" "regexp" ) // Line represents the HTTP Request line // defined in rfc-2616 : https://tools.ietf.org/html/rfc2616#section-5.1 type Line struct { method Method uri string version byte } // Parse parses the first HTTP request line func (r *Line) 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 Line) 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 *Line) 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 *Line) 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 *Line) 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 }