1// Copyright 2010 The Go Authors.  All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package http
6
7import (
8	"io"
9	"net/textproto"
10	"sort"
11	"strings"
12	"time"
13)
14
15// A Header represents the key-value pairs in an HTTP header.
16type Header map[string][]string
17
18// Add adds the key, value pair to the header.
19// It appends to any existing values associated with key.
20func (h Header) Add(key, value string) {
21	textproto.MIMEHeader(h).Add(key, value)
22}
23
24// Set sets the header entries associated with key to
25// the single element value.  It replaces any existing
26// values associated with key.
27func (h Header) Set(key, value string) {
28	textproto.MIMEHeader(h).Set(key, value)
29}
30
31// Get gets the first value associated with the given key.
32// If there are no values associated with the key, Get returns "".
33// To access multiple values of a key, access the map directly
34// with CanonicalHeaderKey.
35func (h Header) Get(key string) string {
36	return textproto.MIMEHeader(h).Get(key)
37}
38
39// get is like Get, but key must already be in CanonicalHeaderKey form.
40func (h Header) get(key string) string {
41	if v := h[key]; len(v) > 0 {
42		return v[0]
43	}
44	return ""
45}
46
47// Del deletes the values associated with key.
48func (h Header) Del(key string) {
49	textproto.MIMEHeader(h).Del(key)
50}
51
52// Write writes a header in wire format.
53func (h Header) Write(w io.Writer) error {
54	return h.WriteSubset(w, nil)
55}
56
57func (h Header) clone() Header {
58	h2 := make(Header, len(h))
59	for k, vv := range h {
60		vv2 := make([]string, len(vv))
61		copy(vv2, vv)
62		h2[k] = vv2
63	}
64	return h2
65}
66
67var timeFormats = []string{
68	TimeFormat,
69	time.RFC850,
70	time.ANSIC,
71}
72
73// ParseTime parses a time header (such as the Date: header),
74// trying each of the three formats allowed by HTTP/1.1:
75// TimeFormat, time.RFC850, and time.ANSIC.
76func ParseTime(text string) (t time.Time, err error) {
77	for _, layout := range timeFormats {
78		t, err = time.Parse(layout, text)
79		if err == nil {
80			return
81		}
82	}
83	return
84}
85
86var headerNewlineToSpace = strings.NewReplacer("\n", " ", "\r", " ")
87
88type writeStringer interface {
89	WriteString(string) (int, error)
90}
91
92// stringWriter implements WriteString on a Writer.
93type stringWriter struct {
94	w io.Writer
95}
96
97func (w stringWriter) WriteString(s string) (n int, err error) {
98	return w.w.Write([]byte(s))
99}
100
101type keyValues struct {
102	key    string
103	values []string
104}
105
106type byKey []keyValues
107
108func (s byKey) Len() int           { return len(s) }
109func (s byKey) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
110func (s byKey) Less(i, j int) bool { return s[i].key < s[j].key }
111
112func (h Header) sortedKeyValues(exclude map[string]bool) []keyValues {
113	kvs := make([]keyValues, 0, len(h))
114	for k, vv := range h {
115		if !exclude[k] {
116			kvs = append(kvs, keyValues{k, vv})
117		}
118	}
119	sort.Sort(byKey(kvs))
120	return kvs
121}
122
123// WriteSubset writes a header in wire format.
124// If exclude is not nil, keys where exclude[key] == true are not written.
125func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error {
126	ws, ok := w.(writeStringer)
127	if !ok {
128		ws = stringWriter{w}
129	}
130	for _, kv := range h.sortedKeyValues(exclude) {
131		for _, v := range kv.values {
132			v = headerNewlineToSpace.Replace(v)
133			v = textproto.TrimString(v)
134			for _, s := range []string{kv.key, ": ", v, "\r\n"} {
135				if _, err := ws.WriteString(s); err != nil {
136					return err
137				}
138			}
139		}
140	}
141	return nil
142}
143
144// CanonicalHeaderKey returns the canonical format of the
145// header key s.  The canonicalization converts the first
146// letter and any letter following a hyphen to upper case;
147// the rest are converted to lowercase.  For example, the
148// canonical key for "accept-encoding" is "Accept-Encoding".
149func CanonicalHeaderKey(s string) string { return textproto.CanonicalMIMEHeaderKey(s) }
150
151// hasToken returns whether token appears with v, ASCII
152// case-insensitive, with space or comma boundaries.
153// token must be all lowercase.
154// v may contain mixed cased.
155func hasToken(v, token string) bool {
156	if len(token) > len(v) || token == "" {
157		return false
158	}
159	if v == token {
160		return true
161	}
162	for sp := 0; sp <= len(v)-len(token); sp++ {
163		// Check that first character is good.
164		// The token is ASCII, so checking only a single byte
165		// is sufficient.  We skip this potential starting
166		// position if both the first byte and its potential
167		// ASCII uppercase equivalent (b|0x20) don't match.
168		// False positives ('^' => '~') are caught by EqualFold.
169		if b := v[sp]; b != token[0] && b|0x20 != token[0] {
170			continue
171		}
172		// Check that start pos is on a valid token boundary.
173		if sp > 0 && !isTokenBoundary(v[sp-1]) {
174			continue
175		}
176		// Check that end pos is on a valid token boundary.
177		if endPos := sp + len(token); endPos != len(v) && !isTokenBoundary(v[endPos]) {
178			continue
179		}
180		if strings.EqualFold(v[sp:sp+len(token)], token) {
181			return true
182		}
183	}
184	return false
185}
186
187func isTokenBoundary(b byte) bool {
188	return b == ' ' || b == ',' || b == '\t'
189}
190