package spartan import ( "bufio" "bytes" "errors" "io" "net/url" "strconv" "strings" "tildegit.org/tjp/sliderule/internal/types" ) var ( // InvalidRequestLine indicates a malformed first-line of a spartan request. InvalidRequestLine = errors.New("invalid request line") // InvalidRequestLineEnding says that a spartan request's first line wasn't terminated with CRLF. InvalidRequestLineEnding = errors.New("invalid request line ending") ) // ParseRequest parses a single spartan request and the indicated content-length from a reader. // // If ther reader artument is a *bufio.Reader, it will only read a single line from it. func ParseRequest(rdr io.Reader) (*types.Request, int, error) { bufrdr, ok := rdr.(*bufio.Reader) if !ok { bufrdr = bufio.NewReader(rdr) } line, err := bufrdr.ReadString('\n') if err != io.EOF && err != nil { return nil, 0, err } host, rest, ok := strings.Cut(line, " ") if !ok { return nil, 0, InvalidRequestLine } path, rest, ok := strings.Cut(rest, " ") if !ok { return nil, 0, InvalidRequestLine } if len(rest) < 2 || line[len(line)-2:] != "\r\n" { return nil, 0, InvalidRequestLineEnding } contentlen, err := strconv.Atoi(rest[:len(rest)-2]) if err != nil { return nil, 0, err } return &types.Request{ URL: &url.URL{ Scheme: "spartan", Host: host, Path: path, RawPath: path, }, }, contentlen, nil } // GetRequestContentLength reads the remaining un-read number of bytes in a request body. // // It will immediately return 0 if there is no request body. func GetRequestContentLength(request *types.Request) int { if lr, ok := request.Meta.(*io.LimitedReader); ok { return int(lr.N) } return 0 } // GetRequestBody returns a reader of the spartan request body. // // It will return nil if the request has no body. func GetRequestBody(request *types.Request) io.Reader { if rdr, ok := request.Meta.(io.Reader); ok { return rdr } return nil } // SetRequestBody adds an io.Reader as a request body. // // It is for use in clients, preparing the request to be sent. // // This function will read the entire contents into memory unless // the reader is already an *io.LimitedReader. func SetRequestBody(request *types.Request, body io.Reader) error { if rdr, ok := body.(*io.LimitedReader); ok { request.Meta = rdr return nil } buf, err := io.ReadAll(body) if err != nil { return err } request.Meta = io.LimitReader(bytes.NewBuffer(buf), int64(len(buf))) return nil }