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.
15 // FormatMediaType serializes mediatype t and the parameters
16 // param as a media type conforming to RFC 2045 and RFC 2616.
17 // The type and parameter names are written in lower-case.
18 // When any of the arguments result in a standard violation then
19 // FormatMediaType returns the empty string.
20 func FormatMediaType(t
string, param
map[string]string) string {
21 slash
:= strings
.Index(t
, "/")
25 major
, sub
:= t
[:slash
], t
[slash
+1:]
26 if !isToken(major
) ||
!isToken(sub
) {
30 b
.WriteString(strings
.ToLower(major
))
32 b
.WriteString(strings
.ToLower(sub
))
34 for attribute
, value
:= range param
{
37 if !isToken(attribute
) {
40 b
.WriteString(strings
.ToLower(attribute
))
49 for index
, character
:= range value
{
50 if character
== '"' || character
== '\r' {
51 b
.WriteString(value
[offset
:index
])
55 if character
&0x80 != 0 {
59 b
.WriteString(value
[offset
:])
65 func checkMediaTypeDisposition(s
string) error
{
66 typ
, rest
:= consumeToken(s
)
68 return errors
.New("mime: no media type")
73 if !strings
.HasPrefix(rest
, "/") {
74 return errors
.New("mime: expected slash after first token")
76 subtype
, rest
:= consumeToken(rest
[1:])
78 return errors
.New("mime: expected token after slash")
81 return errors
.New("mime: unexpected content after media subtype")
86 // ParseMediaType parses a media type value and any optional
87 // parameters, per RFC 1521. Media types are the values in
88 // Content-Type and Content-Disposition headers (RFC 2183).
89 // On success, ParseMediaType returns the media type converted
90 // to lowercase and trimmed of white space and a non-nil map.
91 // The returned map, params, maps from the lowercase
92 // attribute to the attribute value with its case preserved.
93 func ParseMediaType(v
string) (mediatype
string, params
map[string]string, err error
) {
94 i
:= strings
.Index(v
, ";")
98 mediatype
= strings
.TrimSpace(strings
.ToLower(v
[0:i
]))
100 err
= checkMediaTypeDisposition(mediatype
)
105 params
= make(map[string]string)
107 // Map of base parameter name -> parameter name -> value
108 // for parameters containing a '*' character.
109 // Lazily initialized.
110 var continuation
map[string]map[string]string
114 v
= strings
.TrimLeftFunc(v
, unicode
.IsSpace
)
118 key
, value
, rest
:= consumeMediaParam(v
)
120 if strings
.TrimSpace(rest
) == ";" {
121 // Ignore trailing semicolons.
126 return "", nil, errors
.New("mime: invalid media parameter")
130 if idx
:= strings
.Index(key
, "*"); idx
!= -1 {
131 baseName
:= key
[:idx
]
132 if continuation
== nil {
133 continuation
= make(map[string]map[string]string)
136 if pmap
, ok
= continuation
[baseName
]; !ok
{
137 continuation
[baseName
] = make(map[string]string)
138 pmap
= continuation
[baseName
]
141 if _
, exists
:= pmap
[key
]; exists
{
142 // Duplicate parameter name is bogus.
143 return "", nil, errors
.New("mime: duplicate parameter name")
149 // Stitch together any continuations or things with stars
150 // (i.e. RFC 2231 things with stars: "foo*0" or "foo*")
152 for key
, pieceMap
:= range continuation
{
153 singlePartKey
:= key
+ "*"
154 if v
, ok
:= pieceMap
[singlePartKey
]; ok
{
155 decv
:= decode2231Enc(v
)
163 simplePart
:= fmt
.Sprintf("%s*%d", key
, n
)
164 if v
, ok
:= pieceMap
[simplePart
]; ok
{
169 encodedPart
:= simplePart
+ "*"
170 if v
, ok
:= pieceMap
[encodedPart
]; ok
{
173 buf
.WriteString(decode2231Enc(v
))
175 decv
, _
:= percentHexUnescape(v
)
176 buf
.WriteString(decv
)
183 params
[key
] = buf
.String()
190 func decode2231Enc(v
string) string {
191 sv
:= strings
.SplitN(v
, "'", 3)
195 // TODO: ignoring lang in sv[1] for now. If anybody needs it we'll
196 // need to decide how to expose it in the API. But I'm not sure
197 // anybody uses it in practice.
198 charset
:= strings
.ToLower(sv
[0])
199 if charset
!= "us-ascii" && charset
!= "utf-8" {
200 // TODO: unsupported encoding
203 encv
, _
:= percentHexUnescape(sv
[2])
207 func isNotTokenChar(r rune
) bool {
208 return !isTokenChar(r
)
211 // consumeToken consumes a token from the beginning of provided
212 // string, per RFC 2045 section 5.1 (referenced from 2183), and return
213 // the token consumed and the rest of the string. Returns ("", v) on
214 // failure to consume at least one character.
215 func consumeToken(v
string) (token
, rest
string) {
216 notPos
:= strings
.IndexFunc(v
, isNotTokenChar
)
223 return v
[0:notPos
], v
[notPos
:]
226 // consumeValue consumes a "value" per RFC 2045, where a value is
227 // either a 'token' or a 'quoted-string'. On success, consumeValue
228 // returns the value consumed (and de-quoted/escaped, if a
229 // quoted-string) and the rest of the string. On failure, returns
231 func consumeValue(v
string) (value
, rest
string) {
232 if !strings
.HasPrefix(v
, `"`) && !strings
.HasPrefix(v
, `'`) {
233 return consumeToken(v
)
236 leadQuote
:= rune(v
[0])
238 // parse a quoted-string
239 rest
= v
[1:] // consume the leading quote
240 buffer
:= new(bytes
.Buffer
)
243 var nextIsLiteral
bool
244 for idx
, r
= range rest
{
248 nextIsLiteral
= false
250 return buffer
.String(), rest
[idx
+1:]
253 case r
!= '\r' && r
!= '\n':
262 func consumeMediaParam(v
string) (param
, value
, rest
string) {
263 rest
= strings
.TrimLeftFunc(v
, unicode
.IsSpace
)
264 if !strings
.HasPrefix(rest
, ";") {
268 rest
= rest
[1:] // consume semicolon
269 rest
= strings
.TrimLeftFunc(rest
, unicode
.IsSpace
)
270 param
, rest
= consumeToken(rest
)
271 param
= strings
.ToLower(param
)
276 rest
= strings
.TrimLeftFunc(rest
, unicode
.IsSpace
)
277 if !strings
.HasPrefix(rest
, "=") {
280 rest
= rest
[1:] // consume equals sign
281 rest
= strings
.TrimLeftFunc(rest
, unicode
.IsSpace
)
282 value
, rest
= consumeValue(rest
)
286 return param
, value
, rest
289 func percentHexUnescape(s
string) (string, error
) {
290 // Count %, check that they're well-formed.
292 for i
:= 0; i
< len(s
); {
298 if i
+2 >= len(s
) ||
!ishex(s
[i
+1]) ||
!ishex(s
[i
+2]) {
303 return "", fmt
.Errorf("mime: bogus characters after %%: %q", s
)
311 t
:= make([]byte, len(s
)-2*percents
)
313 for i
:= 0; i
< len(s
); {
316 t
[j
] = unhex(s
[i
+1])<<4 |
unhex(s
[i
+2])
325 return string(t
), nil
328 func ishex(c
byte) bool {
330 case '0' <= c
&& c
<= '9':
332 case 'a' <= c
&& c
<= 'f':
334 case 'A' <= c
&& c
<= 'F':
340 func unhex(c
byte) byte {
342 case '0' <= c
&& c
<= '9':
344 case 'a' <= c
&& c
<= 'f':
346 case 'A' <= c
&& c
<= 'F':