Files
mochi/frontend/udp/params.go
Lawrence, Rendall 34c2921be8 add support for no_peer_id HTTP announce parameter
* remove `compact` from req/resp structures, because
it used only in HTTP and only while response write
2023-03-19 18:37:57 +03:00

99 lines
2.9 KiB
Go

package udp
import (
"bytes"
"net/url"
"strings"
"github.com/rs/zerolog"
"github.com/sot-tech/mochi/bittorrent"
)
// ErrInvalidQueryEscape is returned when a query string contains invalid
// escapes.
var ErrInvalidQueryEscape = bittorrent.ClientError("invalid query escape")
// queryParams parses a URL Query and implements the Params interface
type queryParams struct {
params map[string]string
}
// parseQuery parses a request URL or UDP URLData as defined in BEP41.
// It expects a concatenated string of the request's path and query parts as
// defined in RFC 3986. As both the udp: and http: scheme used by BitTorrent
// include an authority part the path part must always begin with a slash.
// An example of the expected URLData would be "/announce?port=1234&uploaded=0"
// or "/?auth=0x1337".
// HTTP servers should pass (*http.Request).RequestURI, UDP servers should
// pass the concatenated, unchanged URLData as defined in BEP41.
//
// Note that, in the case of a key occurring multiple times in the query, only
// the last value for that key is kept.
//
// Also note that any error that is encountered during parsing is returned as a
// ClientError, as this method is expected to be used to parse client-provided
// data.
func parseQuery(query []byte) (q *queryParams, err error) {
queryDelim := bytes.IndexRune(query, '?')
if queryDelim != -1 {
query = query[queryDelim+1:]
}
// This is basically url.ParseQuery, but with a map[string]string
// instead of map[string][]string for the values.
q = &queryParams{
params: make(map[string]string),
}
for len(query) > 0 {
key := query
if i := bytes.IndexAny(key, "&;"); i >= 0 {
key, query = key[:i], key[i+1:]
} else {
query = nil
}
if len(key) == 0 {
continue
}
var value []byte
if i := bytes.IndexRune(key, '='); i >= 0 {
key, value = key[:i], key[i+1:]
}
var k, v string
k, err = url.QueryUnescape(string(key))
if err != nil {
// QueryUnescape returns an error like "invalid escape: '%x'".
// But frontends record these errors to prometheus, which generates
// a lot of time series.
// We log it here for debugging instead.
return nil, ErrInvalidQueryEscape
}
v, err = url.QueryUnescape(string(value))
if err != nil {
// QueryUnescape returns an error like "invalid escape: '%x'".
// But frontends record these errors to prometheus, which generates
// a lot of time series.
// We log it here for debugging instead.
return nil, ErrInvalidQueryEscape
}
q.params[strings.ToLower(k)] = v
}
return q, nil
}
// String returns a string parsed from a query. Every key can be returned as a
// string because they are encoded in the URL as strings.
func (qp queryParams) String(key string) (string, bool) {
value, ok := qp.params[strings.ToLower(key)]
return value, ok
}
// MarshalZerologObject writes fields into zerolog event
func (qp queryParams) MarshalZerologObject(e *zerolog.Event) {
for k, v := range qp.params {
e.Str(k, v)
}
}