package gemini import ( "bufio" "errors" "io" "net/url" "strconv" "strings" "tildegit.org/tjp/sliderule/internal/types" ) // InvalidRequestLineEnding indicates that a gemini request didn't end with "\r\n". var InvalidRequestLineEnding = errors.New("invalid request line ending") // ParseRequest parses a single gemini/titan request from a reader. // // If the reader argument is a *bufio.Reader, it will only read a single line from it. func ParseRequest(rdr io.Reader) (*types.Request, 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, err } if len(line) < 2 || line[len(line)-2:] != "\r\n" { return nil, InvalidRequestLineEnding } u, err := url.Parse(line[:len(line)-2]) if err != nil { return nil, err } if u.Scheme == "" { u.Scheme = "gemini" } req := &types.Request{URL: u} if u.Scheme == "titan" { length, err := sizeParam(u.Path) if err != nil { return nil, err } req.Meta = io.LimitReader(bufrdr, int64(length)) } return req, nil } // GetTitanRequestBody fetches the request body from a titan request. // // It returns nil if the argument is not a titan request or it otherwise // does not have a request body set. func GetTitanRequestBody(request *types.Request) io.Reader { if request.Scheme != "titan" { return nil } if rdr, ok := request.Meta.(io.Reader); ok { return rdr } return nil } func sizeParam(path string) (int, error) { _, rest, found := strings.Cut(path, ";") if !found { return 0, errors.New("no params in titan request path") } for _, piece := range strings.Split(rest, ";") { key, val, _ := strings.Cut(piece, "=") if key == "size" { return strconv.Atoi(val) } } return 0, errors.New("no size param found in titan request") }