| package http |
| |
| import ( |
| "encoding/base64" |
| "encoding/json" |
| "errors" |
| "log" |
| "net/http" |
| "net/url" |
| "path" |
| "strconv" |
| "strings" |
| "time" |
| ) |
| |
| func WriteError(w http.ResponseWriter, code int, msg string) { |
| e := struct { |
| Error string `json:"error"` |
| }{ |
| Error: msg, |
| } |
| b, err := json.Marshal(e) |
| if err != nil { |
| log.Printf("go-oidc: failed to marshal %#v: %v", e, err) |
| code = http.StatusInternalServerError |
| b = []byte(`{"error":"server_error"}`) |
| } |
| w.Header().Set("Content-Type", "application/json") |
| w.WriteHeader(code) |
| w.Write(b) |
| } |
| |
| // BasicAuth parses a username and password from the request's |
| // Authorization header. This was pulled from golang master: |
| // https://codereview.appspot.com/76540043 |
| func BasicAuth(r *http.Request) (username, password string, ok bool) { |
| auth := r.Header.Get("Authorization") |
| if auth == "" { |
| return |
| } |
| |
| if !strings.HasPrefix(auth, "Basic ") { |
| return |
| } |
| c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic ")) |
| if err != nil { |
| return |
| } |
| cs := string(c) |
| s := strings.IndexByte(cs, ':') |
| if s < 0 { |
| return |
| } |
| return cs[:s], cs[s+1:], true |
| } |
| |
| func cacheControlMaxAge(hdr string) (time.Duration, bool, error) { |
| for _, field := range strings.Split(hdr, ",") { |
| parts := strings.SplitN(strings.TrimSpace(field), "=", 2) |
| k := strings.ToLower(strings.TrimSpace(parts[0])) |
| if k != "max-age" { |
| continue |
| } |
| |
| if len(parts) == 1 { |
| return 0, false, errors.New("max-age has no value") |
| } |
| |
| v := strings.TrimSpace(parts[1]) |
| if v == "" { |
| return 0, false, errors.New("max-age has empty value") |
| } |
| |
| age, err := strconv.Atoi(v) |
| if err != nil { |
| return 0, false, err |
| } |
| |
| if age <= 0 { |
| return 0, false, nil |
| } |
| |
| return time.Duration(age) * time.Second, true, nil |
| } |
| |
| return 0, false, nil |
| } |
| |
| func expires(date, expires string) (time.Duration, bool, error) { |
| if date == "" || expires == "" { |
| return 0, false, nil |
| } |
| |
| var te time.Time |
| var err error |
| if expires == "0" { |
| return 0, false, nil |
| } |
| te, err = time.Parse(time.RFC1123, expires) |
| if err != nil { |
| return 0, false, err |
| } |
| |
| td, err := time.Parse(time.RFC1123, date) |
| if err != nil { |
| return 0, false, err |
| } |
| |
| ttl := te.Sub(td) |
| |
| // headers indicate data already expired, caller should not |
| // have to care about this case |
| if ttl <= 0 { |
| return 0, false, nil |
| } |
| |
| return ttl, true, nil |
| } |
| |
| func Cacheable(hdr http.Header) (time.Duration, bool, error) { |
| ttl, ok, err := cacheControlMaxAge(hdr.Get("Cache-Control")) |
| if err != nil || ok { |
| return ttl, ok, err |
| } |
| |
| return expires(hdr.Get("Date"), hdr.Get("Expires")) |
| } |
| |
| // MergeQuery appends additional query values to an existing URL. |
| func MergeQuery(u url.URL, q url.Values) url.URL { |
| uv := u.Query() |
| for k, vs := range q { |
| for _, v := range vs { |
| uv.Add(k, v) |
| } |
| } |
| u.RawQuery = uv.Encode() |
| return u |
| } |
| |
| // NewResourceLocation appends a resource id to the end of the requested URL path. |
| func NewResourceLocation(reqURL *url.URL, id string) string { |
| var u url.URL |
| u = *reqURL |
| u.Path = path.Join(u.Path, id) |
| u.RawQuery = "" |
| u.Fragment = "" |
| return u.String() |
| } |
| |
| // CopyRequest returns a clone of the provided *http.Request. |
| // The returned object is a shallow copy of the struct and a |
| // deep copy of its Header field. |
| func CopyRequest(r *http.Request) *http.Request { |
| r2 := *r |
| r2.Header = make(http.Header) |
| for k, s := range r.Header { |
| r2.Header[k] = s |
| } |
| return &r2 |
| } |