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/v8/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
	Cookie             = "Cookie"              // RFC7873
)

// 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
}
