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.
16 // FormatMediaType serializes mediatype t and the parameters
17 // param as a media type conforming to RFC 2045 and RFC 2616.
18 // The type and parameter names are written in lower-case.
19 // When any of the arguments result in a standard violation then
20 // FormatMediaType returns the empty string.
21 func FormatMediaType(t
string, param
map[string]string) string {
22 slash
:= strings
.Index(t
, "/")
26 major
, sub
:= t
[:slash
], t
[slash
+1:]
27 if !isToken(major
) ||
!isToken(sub
) {
31 b
.WriteString(strings
.ToLower(major
))
33 b
.WriteString(strings
.ToLower(sub
))
35 attrs
:= make([]string, 0, len(param
))
36 for a
:= range param
{
37 attrs
= append(attrs
, a
)
41 for _
, attribute
:= range attrs
{
42 value
:= param
[attribute
]
45 if !isToken(attribute
) {
48 b
.WriteString(strings
.ToLower(attribute
))
57 for index
, character
:= range value
{
58 if character
== '"' || character
== '\\' {
59 b
.WriteString(value
[offset
:index
])
63 if character
&0x80 != 0 {
67 b
.WriteString(value
[offset
:])
73 func checkMediaTypeDisposition(s
string) error
{
74 typ
, rest
:= consumeToken(s
)
76 return errors
.New("mime: no media type")
81 if !strings
.HasPrefix(rest
, "/") {
82 return errors
.New("mime: expected slash after first token")
84 subtype
, rest
:= consumeToken(rest
[1:])
86 return errors
.New("mime: expected token after slash")
89 return errors
.New("mime: unexpected content after media subtype")
94 // ParseMediaType parses a media type value and any optional
95 // parameters, per RFC 1521. Media types are the values in
96 // Content-Type and Content-Disposition headers (RFC 2183).
97 // On success, ParseMediaType returns the media type converted
98 // to lowercase and trimmed of white space and a non-nil map.
99 // The returned map, params, maps from the lowercase
100 // attribute to the attribute value with its case preserved.
101 func ParseMediaType(v
string) (mediatype
string, params
map[string]string, err error
) {
102 i
:= strings
.Index(v
, ";")
106 mediatype
= strings
.TrimSpace(strings
.ToLower(v
[0:i
]))
108 err
= checkMediaTypeDisposition(mediatype
)
113 params
= make(map[string]string)
115 // Map of base parameter name -> parameter name -> value
116 // for parameters containing a '*' character.
117 // Lazily initialized.
118 var continuation
map[string]map[string]string
122 v
= strings
.TrimLeftFunc(v
, unicode
.IsSpace
)
126 key
, value
, rest
:= consumeMediaParam(v
)
128 if strings
.TrimSpace(rest
) == ";" {
129 // Ignore trailing semicolons.
134 return "", nil, errors
.New("mime: invalid media parameter")
138 if idx
:= strings
.Index(key
, "*"); idx
!= -1 {
139 baseName
:= key
[:idx
]
140 if continuation
== nil {
141 continuation
= make(map[string]map[string]string)
144 if pmap
, ok
= continuation
[baseName
]; !ok
{
145 continuation
[baseName
] = make(map[string]string)
146 pmap
= continuation
[baseName
]
149 if _
, exists
:= pmap
[key
]; exists
{
150 // Duplicate parameter name is bogus.
151 return "", nil, errors
.New("mime: duplicate parameter name")
157 // Stitch together any continuations or things with stars
158 // (i.e. RFC 2231 things with stars: "foo*0" or "foo*")
160 for key
, pieceMap
:= range continuation
{
161 singlePartKey
:= key
+ "*"
162 if v
, ok
:= pieceMap
[singlePartKey
]; ok
{
163 decv
:= decode2231Enc(v
)
171 simplePart
:= fmt
.Sprintf("%s*%d", key
, n
)
172 if v
, ok
:= pieceMap
[simplePart
]; ok
{
177 encodedPart
:= simplePart
+ "*"
178 if v
, ok
:= pieceMap
[encodedPart
]; ok
{
181 buf
.WriteString(decode2231Enc(v
))
183 decv
, _
:= percentHexUnescape(v
)
184 buf
.WriteString(decv
)
191 params
[key
] = buf
.String()
198 func decode2231Enc(v
string) string {
199 sv
:= strings
.SplitN(v
, "'", 3)
203 // TODO: ignoring lang in sv[1] for now. If anybody needs it we'll
204 // need to decide how to expose it in the API. But I'm not sure
205 // anybody uses it in practice.
206 charset
:= strings
.ToLower(sv
[0])
207 if charset
!= "us-ascii" && charset
!= "utf-8" {
208 // TODO: unsupported encoding
211 encv
, _
:= percentHexUnescape(sv
[2])
215 func isNotTokenChar(r rune
) bool {
216 return !isTokenChar(r
)
219 // consumeToken consumes a token from the beginning of provided
220 // string, per RFC 2045 section 5.1 (referenced from 2183), and return
221 // the token consumed and the rest of the string. Returns ("", v) on
222 // failure to consume at least one character.
223 func consumeToken(v
string) (token
, rest
string) {
224 notPos
:= strings
.IndexFunc(v
, isNotTokenChar
)
231 return v
[0:notPos
], v
[notPos
:]
234 // consumeValue consumes a "value" per RFC 2045, where a value is
235 // either a 'token' or a 'quoted-string'. On success, consumeValue
236 // returns the value consumed (and de-quoted/escaped, if a
237 // quoted-string) and the rest of the string. On failure, returns
239 func consumeValue(v
string) (value
, rest
string) {
240 if !strings
.HasPrefix(v
, `"`) && !strings
.HasPrefix(v
, `'`) {
241 return consumeToken(v
)
244 leadQuote
:= rune(v
[0])
246 // parse a quoted-string
247 rest
= v
[1:] // consume the leading quote
248 buffer
:= new(bytes
.Buffer
)
251 var nextIsLiteral
bool
252 for idx
, r
= range rest
{
256 nextIsLiteral
= false
258 return buffer
.String(), rest
[idx
+1:]
261 case r
!= '\r' && r
!= '\n':
270 func consumeMediaParam(v
string) (param
, value
, rest
string) {
271 rest
= strings
.TrimLeftFunc(v
, unicode
.IsSpace
)
272 if !strings
.HasPrefix(rest
, ";") {
276 rest
= rest
[1:] // consume semicolon
277 rest
= strings
.TrimLeftFunc(rest
, unicode
.IsSpace
)
278 param
, rest
= consumeToken(rest
)
279 param
= strings
.ToLower(param
)
284 rest
= strings
.TrimLeftFunc(rest
, unicode
.IsSpace
)
285 if !strings
.HasPrefix(rest
, "=") {
288 rest
= rest
[1:] // consume equals sign
289 rest
= strings
.TrimLeftFunc(rest
, unicode
.IsSpace
)
290 value
, rest
= consumeValue(rest
)
294 return param
, value
, rest
297 func percentHexUnescape(s
string) (string, error
) {
298 // Count %, check that they're well-formed.
300 for i
:= 0; i
< len(s
); {
306 if i
+2 >= len(s
) ||
!ishex(s
[i
+1]) ||
!ishex(s
[i
+2]) {
311 return "", fmt
.Errorf("mime: bogus characters after %%: %q", s
)
319 t
:= make([]byte, len(s
)-2*percents
)
321 for i
:= 0; i
< len(s
); {
324 t
[j
] = unhex(s
[i
+1])<<4 |
unhex(s
[i
+2])
333 return string(t
), nil
336 func ishex(c
byte) bool {
338 case '0' <= c
&& c
<= '9':
340 case 'a' <= c
&& c
<= 'f':
342 case 'A' <= c
&& c
<= 'F':
348 func unhex(c
byte) byte {
350 case '0' <= c
&& c
<= '9':
352 case 'a' <= c
&& c
<= 'f':
354 case 'A' <= c
&& c
<= 'F':