blob: 1dd1cad646634d7afe2a94427ecca93fbaaec5c3 [file] [log] [blame]
Abhay Kumar40252eb2025-10-13 13:25:53 +00001/*
2HTTP Content-Type Autonegotiation.
3
4The functions in this package implement the behaviour specified in
5http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
6
7Copyright (c) 2011, Open Knowledge Foundation Ltd.
8All rights reserved.
9
10Redistribution and use in source and binary forms, with or without
11modification, are permitted provided that the following conditions are
12met:
13
14 Redistributions of source code must retain the above copyright
15 notice, this list of conditions and the following disclaimer.
16
17 Redistributions in binary form must reproduce the above copyright
18 notice, this list of conditions and the following disclaimer in
19 the documentation and/or other materials provided with the
20 distribution.
21
22 Neither the name of the Open Knowledge Foundation Ltd. nor the
23 names of its contributors may be used to endorse or promote
24 products derived from this software without specific prior written
25 permission.
26
27THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
28"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
29LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
30A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
31HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
33LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
34DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
35THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
36(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
37OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38*/
39
40package goautoneg
41
42import (
43 "sort"
44 "strconv"
45 "strings"
46)
47
48// Structure to represent a clause in an HTTP Accept Header
49type Accept struct {
50 Type, SubType string
51 Q float64
52 Params map[string]string
53}
54
55// acceptSlice is defined to implement sort interface.
56type acceptSlice []Accept
57
58func (slice acceptSlice) Len() int {
59 return len(slice)
60}
61
62func (slice acceptSlice) Less(i, j int) bool {
63 ai, aj := slice[i], slice[j]
64 if ai.Q > aj.Q {
65 return true
66 }
67 if ai.Type != "*" && aj.Type == "*" {
68 return true
69 }
70 if ai.SubType != "*" && aj.SubType == "*" {
71 return true
72 }
73 return false
74}
75
76func (slice acceptSlice) Swap(i, j int) {
77 slice[i], slice[j] = slice[j], slice[i]
78}
79
80func stringTrimSpaceCutset(r rune) bool {
81 return r == ' '
82}
83
84func nextSplitElement(s, sep string) (item string, remaining string) {
85 if index := strings.Index(s, sep); index != -1 {
86 return s[:index], s[index+1:]
87 }
88 return s, ""
89}
90
91// Parse an Accept Header string returning a sorted list
92// of clauses
93func ParseAccept(header string) acceptSlice {
94 partsCount := 0
95 remaining := header
96 for len(remaining) > 0 {
97 partsCount++
98 _, remaining = nextSplitElement(remaining, ",")
99 }
100 accept := make(acceptSlice, 0, partsCount)
101
102 remaining = header
103 var part string
104 for len(remaining) > 0 {
105 part, remaining = nextSplitElement(remaining, ",")
106 part = strings.TrimFunc(part, stringTrimSpaceCutset)
107
108 a := Accept{
109 Q: 1.0,
110 }
111
112 sp, remainingPart := nextSplitElement(part, ";")
113
114 sp0, spRemaining := nextSplitElement(sp, "/")
115 a.Type = strings.TrimFunc(sp0, stringTrimSpaceCutset)
116
117 switch {
118 case len(spRemaining) == 0:
119 if a.Type == "*" {
120 a.SubType = "*"
121 } else {
122 continue
123 }
124 default:
125 var sp1 string
126 sp1, spRemaining = nextSplitElement(spRemaining, "/")
127 if len(spRemaining) > 0 {
128 continue
129 }
130 a.SubType = strings.TrimFunc(sp1, stringTrimSpaceCutset)
131 }
132
133 if len(remainingPart) == 0 {
134 accept = append(accept, a)
135 continue
136 }
137
138 a.Params = make(map[string]string)
139 for len(remainingPart) > 0 {
140 sp, remainingPart = nextSplitElement(remainingPart, ";")
141 sp0, spRemaining = nextSplitElement(sp, "=")
142 if len(spRemaining) == 0 {
143 continue
144 }
145 var sp1 string
146 sp1, spRemaining = nextSplitElement(spRemaining, "=")
147 if len(spRemaining) != 0 {
148 continue
149 }
150 token := strings.TrimFunc(sp0, stringTrimSpaceCutset)
151 if token == "q" {
152 a.Q, _ = strconv.ParseFloat(sp1, 32)
153 } else {
154 a.Params[token] = strings.TrimFunc(sp1, stringTrimSpaceCutset)
155 }
156 }
157
158 accept = append(accept, a)
159 }
160
161 sort.Sort(accept)
162 return accept
163}
164
165// Negotiate the most appropriate content_type given the accept header
166// and a list of alternatives.
167func Negotiate(header string, alternatives []string) (content_type string) {
168 asp := make([][]string, 0, len(alternatives))
169 for _, ctype := range alternatives {
170 asp = append(asp, strings.SplitN(ctype, "/", 2))
171 }
172 for _, clause := range ParseAccept(header) {
173 for i, ctsp := range asp {
174 if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
175 content_type = alternatives[i]
176 return
177 }
178 if clause.Type == ctsp[0] && clause.SubType == "*" {
179 content_type = alternatives[i]
180 return
181 }
182 if clause.Type == "*" && clause.SubType == "*" {
183 content_type = alternatives[i]
184 return
185 }
186 }
187 }
188 return
189}