blob: 9590245d2d054e904077ea3cb9e50d0c4f6e9f7c [file] [log] [blame]
package rfc
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import (
"net/http"
"strconv"
"strings"
"time"
"github.com/apache/trafficcontrol/lib/go-util"
)
// These are the names of HTTP Headers, for convenience and so that typos are
// caught at compile-time.
const (
AcceptEncoding = "Accept-Encoding" // RFC7231§5.3.4
CacheControl = "Cache-Control" // RFC7234§5.2
ContentDisposition = "Content-Disposition" // RFC6266
ContentEncoding = "Content-Encoding" // RFC7231§3.1.2.2
ContentType = "Content-Type" // RFC7231§3.1.1.5
PermissionsPolicy = "Permissions-Policy" // W3C "Permissions Policy"
Server = "Server" // RFC7231§7.4.2
UserAgent = "User-Agent" // RFC7231§5.5.3
Vary = "Vary" // RFC7231§7.1.4
Age = "Age" // RFC7234§5.1
Location = "Location" // RFC7231§7.1.2
Authorization = "Authorization" // RFC7235§4.2
)
// These are (some) valid values for content encoding and MIME types, for
// convenience and so that typos are caught at compile-time.
const (
ApplicationJSON = "application/json" // RFC4627§6
ApplicationOctetStream = "application/octet-stream" // RFC2046§4.5.2
ContentTypeMultiPartMixed = "multipart/mixed" // RFC1341§7.2
ContentTypeTextPlain = "text/plain" // RFC2046§4.1
ContentTypeURIList = "text/uri-list" // RFC2483§5
Gzip = "gzip" // RFC7230§4.2.3
)
// LastModifiedFormat is the format used by dates in the HTTP Last-Modified
// header.
const LastModifiedFormat = "Mon, 02 Jan 2006 15:04:05 MST" // RFC1123
// ValidHTTPCodes provides fast lookup of whether a HTTP response code is valid.
var ValidHTTPCodes = map[int]struct{}{
http.StatusContinue: {}, // RFC 7231, 6.2.1
http.StatusSwitchingProtocols: {}, // RFC 7231, 6.2.2
http.StatusProcessing: {}, // RFC 2518, 10.1
http.StatusEarlyHints: {}, // RFC 8297
http.StatusOK: {}, // RFC 7231, 6.3.1
http.StatusCreated: {}, // RFC 7231, 6.3.2
http.StatusAccepted: {}, // RFC 7231, 6.3.3
http.StatusNonAuthoritativeInfo: {}, // RFC 7231, 6.3.4
http.StatusNoContent: {}, // RFC 7231, 6.3.5
http.StatusResetContent: {}, // RFC 7231, 6.3.6
http.StatusPartialContent: {}, // RFC 7233, 4.1
http.StatusMultiStatus: {}, // RFC 4918, 11.1
http.StatusAlreadyReported: {}, // RFC 5842, 7.1
http.StatusIMUsed: {}, // RFC 3229, 10.4.1
http.StatusMultipleChoices: {}, // RFC 7231, 6.4.1
http.StatusMovedPermanently: {}, // RFC 7231, 6.4.2
http.StatusFound: {}, // RFC 7231, 6.4.3
http.StatusSeeOther: {}, // RFC 7231, 6.4.4
http.StatusNotModified: {}, // RFC 7232, 4.1
http.StatusUseProxy: {}, // RFC 7231, 6.4.5
306: {}, // 306 is reserved for future use - valid, but unused
http.StatusTemporaryRedirect: {}, // RFC 7231, 6.4.7
http.StatusPermanentRedirect: {}, // RFC 7538, 3
http.StatusBadRequest: {}, // RFC 7231, 6.5.1
http.StatusUnauthorized: {}, // RFC 7235, 3.1
http.StatusPaymentRequired: {}, // RFC 7231, 6.5.2
http.StatusForbidden: {}, // RFC 7231, 6.5.3
http.StatusNotFound: {}, // RFC 7231, 6.5.4
http.StatusMethodNotAllowed: {}, // RFC 7231, 6.5.5
http.StatusNotAcceptable: {}, // RFC 7231, 6.5.6
http.StatusProxyAuthRequired: {}, // RFC 7235, 3.2
http.StatusRequestTimeout: {}, // RFC 7231, 6.5.7
http.StatusConflict: {}, // RFC 7231, 6.5.8
http.StatusGone: {}, // RFC 7231, 6.5.9
http.StatusLengthRequired: {}, // RFC 7231, 6.5.10
http.StatusPreconditionFailed: {}, // RFC 7232, 4.2
http.StatusRequestEntityTooLarge: {}, // RFC 7231, 6.5.11
http.StatusRequestURITooLong: {}, // RFC 7231, 6.5.12
http.StatusUnsupportedMediaType: {}, // RFC 7231, 6.5.13
http.StatusRequestedRangeNotSatisfiable: {}, // RFC 7233, 4.4
http.StatusExpectationFailed: {}, // RFC 7231, 6.5.14
http.StatusTeapot: {}, // RFC 7168, 2.3.3
http.StatusMisdirectedRequest: {}, // RFC 7540, 9.1.2
http.StatusUnprocessableEntity: {}, // RFC 4918, 11.2
http.StatusLocked: {}, // RFC 4918, 11.3
http.StatusFailedDependency: {}, // RFC 4918, 11.4
http.StatusTooEarly: {}, // RFC 8470, 5.2.
http.StatusUpgradeRequired: {}, // RFC 7231, 6.5.15
http.StatusPreconditionRequired: {}, // RFC 6585, 3
http.StatusTooManyRequests: {}, // RFC 6585, 4
http.StatusRequestHeaderFieldsTooLarge: {}, // RFC 6585, 5
http.StatusUnavailableForLegalReasons: {}, // RFC 7725, 3
http.StatusInternalServerError: {}, // RFC 7231, 6.6.1
http.StatusNotImplemented: {}, // RFC 7231, 6.6.2
http.StatusBadGateway: {}, // RFC 7231, 6.6.3
http.StatusServiceUnavailable: {}, // RFC 7231, 6.6.4
http.StatusGatewayTimeout: {}, // RFC 7231, 6.6.5
http.StatusHTTPVersionNotSupported: {}, // RFC 7231, 6.6.6
http.StatusVariantAlsoNegotiates: {}, // RFC 2295, 8.1
http.StatusInsufficientStorage: {}, // RFC 4918, 11.5
http.StatusLoopDetected: {}, // RFC 5842, 7.2
http.StatusNotExtended: {}, // RFC 2774, 7
http.StatusNetworkAuthenticationRequired: {}, // RFC 6585, 6
}
// AcceptsGzip returns whether r accepts gzip encoding, per RFC7231§5.3.4.
func AcceptsGzip(r *http.Request) bool {
encodingHeaders := r.Header[AcceptEncoding] // headers are case-insensitive, but Go promises to Canonical-Case requests
for _, encodingHeader := range encodingHeaders {
encodingHeader = util.StripAllWhitespace(encodingHeader)
encodings := strings.Split(encodingHeader, ",")
for _, encoding := range encodings {
if strings.ToLower(encoding) == Gzip { // encoding is case-insensitive, per the RFC7231§3.1.2.1.
return true
}
}
}
return false
}
// GetHTTPDate is a helper function which gets an HTTP date from the given map
// (which is typically a `http.Header` or `CacheControl`.
//
// Returns false if the given key doesn't exist in the map, or if the value isn't
// a valid HTTP Date per RFC2616§3.3.
func GetHTTPDate(headers http.Header, key string) (time.Time, bool) {
maybeDate := headers.Get(key)
if maybeDate == "" {
return time.Time{}, false
}
return ParseHTTPDate(maybeDate)
}
// ParseHTTPDate parses the given RFC7231§7.1.1 HTTP-date.
func ParseHTTPDate(d string) (time.Time, bool) {
if t, err := time.Parse(time.RFC1123, d); err == nil {
return t, true
}
if t, err := time.Parse(time.RFC850, d); err == nil {
return t, true
}
if t, err := time.Parse(time.ANSIC, d); err == nil {
return t, true
}
return time.Time{}, false
}
// FormatHTTPDate formats t as an RFC7231§7.1.1 HTTP-date.
func FormatHTTPDate(t time.Time) string {
return t.Format(time.RFC1123)
}
// GetHTTPDeltaSeconds is a helper function which gets an HTTP Delta Seconds
// from the given map (which is typically a `http.Header` or `CacheControl`).
// Returns false if the given key doesn't exist in the map, or if the value
// isn't a valid Delta Seconds per RFC2616§3.3.2.
func GetHTTPDeltaSeconds(m map[string][]string, key string) (time.Duration, bool) {
maybeSeconds, ok := m[key]
if !ok {
return 0, false
}
if len(maybeSeconds) == 0 {
return 0, false
}
maybeSec := maybeSeconds[0]
seconds, err := strconv.ParseUint(maybeSec, 10, 64)
if err != nil {
return 0, false
}
return time.Duration(seconds) * time.Second, true
}