| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- package webapi
- import (
- "context"
- "mime"
- "net/http"
- "strings"
- )
- // The Web API is method-agnostic: parameters may arrive on the query string or in a
- // form-encoded body, varying by client and by endpoint. The helpers here read either
- // location so a handler never has to care.
- const formContentType = "application/x-www-form-urlencoded"
- // binaryBodyKey marks a request whose body is a payload rather than form fields.
- type binaryBodyKey struct{}
- // WithBinaryBody marks a request body as a payload the parameter helpers must not
- // consume. expressions/upload POSTs a raw untyped image, and without the marker a
- // lookup that missed on the query string would hand it to ParseForm, which reads it
- // to EOF.
- func WithBinaryBody(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), binaryBodyKey{}, true)))
- })
- }
- func hasBinaryBody(r *http.Request) bool {
- marked, _ := r.Context().Value(binaryBodyKey{}).(bool)
- return marked
- }
- // parseBodyForm parses the request body as form fields, reporting whether the form
- // is available to read afterwards. It is safe to call repeatedly: ParseForm caches
- // its result on the request.
- func parseBodyForm(r *http.Request) bool {
- if r.Method != http.MethodPost && r.Method != http.MethodPut {
- return false
- }
- if hasBinaryBody(r) {
- return false
- }
- if ct := r.Header.Get("Content-Type"); ct == "" {
- // ParseForm ignores a body it cannot type, and form bodies often arrive
- // unannounced. A genuinely non-form body is marked by WithBinaryBody.
- r.Header.Set("Content-Type", formContentType)
- } else if mediaType, _, err := mime.ParseMediaType(ct); err != nil || mediaType != formContentType {
- return false
- }
- return r.ParseForm() == nil
- }
- // param returns a request parameter from the query string, falling back to the
- // form-encoded body.
- func param(r *http.Request, key string) string {
- if v := r.URL.Query().Get(key); v != "" {
- return v
- }
- if !parseBodyForm(r) {
- return ""
- }
- return r.PostFormValue(key)
- }
- // paramValues returns every value sent for a repeated parameter, from the query
- // string and the form-encoded body both.
- func paramValues(r *http.Request, key string) []string {
- return append(r.URL.Query()[key], bodyValues(r, key)...)
- }
- // bodyValues returns every value sent for a repeated parameter in the form-encoded
- // body, ignoring the query string. Most callers want paramValues instead.
- func bodyValues(r *http.Request, key string) []string {
- if !parseBodyForm(r) {
- return nil
- }
- return r.PostForm[key]
- }
- // targetNames returns the screen names a request's "t" parameter asks about.
- // Both spellings of the list are accepted and combined: one t carrying comma-
- // separated names, and t repeated once per name.
- func targetNames(r *http.Request) []string {
- var targets []string
- for _, value := range paramValues(r, "t") {
- for _, name := range strings.Split(value, ",") {
- if name = strings.TrimSpace(name); name != "" {
- targets = append(targets, name)
- }
- }
- }
- return targets
- }
- // isTrueParam reports whether a boolean-ish parameter is set. Clients spell these
- // inconsistently, so both "1" and "true" are accepted.
- func isTrueParam(v string) bool {
- switch strings.ToLower(strings.TrimSpace(v)) {
- case "1", "true", "yes":
- return true
- }
- return false
- }
|