mirror of
https://github.com/sot-tech/mochi.git
synced 2026-04-27 08:00:00 -07:00
* remove `compact` from req/resp structures, because it used only in HTTP and only while response write
99 lines
2.9 KiB
Go
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)
|
|
}
|
|
}
|