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.
7
// TODO(natefinch) remove this once we support building on go 1.6 for all platforms.
8
// This code was copied from the Go 1.6 sourcecode.
21
// FormatMediaType serializes mediatype t and the parameters
22
// param as a media type conforming to RFC 2045 and RFC 2616.
23
// The type and parameter names are written in lower-case.
24
// When any of the arguments result in a standard violation then
25
// FormatMediaType returns the empty string.
26
func FormatMediaType(t string, param map[string]string) string {
28
if slash := strings.Index(t, "/"); slash == -1 {
32
b.WriteString(strings.ToLower(t))
34
major, sub := t[:slash], t[slash+1:]
35
if !isToken(major) || !isToken(sub) {
38
b.WriteString(strings.ToLower(major))
40
b.WriteString(strings.ToLower(sub))
43
attrs := make([]string, 0, len(param))
44
for a := range param {
45
attrs = append(attrs, a)
49
for _, attribute := range attrs {
50
value := param[attribute]
53
if !isToken(attribute) {
56
b.WriteString(strings.ToLower(attribute))
65
for index, character := range value {
66
if character == '"' || character == '\\' {
67
b.WriteString(value[offset:index])
71
if character&0x80 != 0 {
75
b.WriteString(value[offset:])
81
func checkMediaTypeDisposition(s string) error {
82
typ, rest := consumeToken(s)
84
return errors.New("mime: no media type")
89
if !strings.HasPrefix(rest, "/") {
90
return errors.New("mime: expected slash after first token")
92
subtype, rest := consumeToken(rest[1:])
94
return errors.New("mime: expected token after slash")
97
return errors.New("mime: unexpected content after media subtype")
102
// ParseMediaType parses a media type value and any optional
103
// parameters, per RFC 1521. Media types are the values in
104
// Content-Type and Content-Disposition headers (RFC 2183).
105
// On success, ParseMediaType returns the media type converted
106
// to lowercase and trimmed of white space and a non-nil map.
107
// The returned map, params, maps from the lowercase
108
// attribute to the attribute value with its case preserved.
109
func ParseMediaType(v string) (mediatype string, params map[string]string, err error) {
110
i := strings.Index(v, ";")
114
mediatype = strings.TrimSpace(strings.ToLower(v[0:i]))
116
err = checkMediaTypeDisposition(mediatype)
121
params = make(map[string]string)
123
// Map of base parameter name -> parameter name -> value
124
// for parameters containing a '*' character.
125
// Lazily initialized.
126
var continuation map[string]map[string]string
130
v = strings.TrimLeftFunc(v, unicode.IsSpace)
134
key, value, rest := consumeMediaParam(v)
136
if strings.TrimSpace(rest) == ";" {
137
// Ignore trailing semicolons.
142
return "", nil, errors.New("mime: invalid media parameter")
146
if idx := strings.Index(key, "*"); idx != -1 {
147
baseName := key[:idx]
148
if continuation == nil {
149
continuation = make(map[string]map[string]string)
152
if pmap, ok = continuation[baseName]; !ok {
153
continuation[baseName] = make(map[string]string)
154
pmap = continuation[baseName]
157
if _, exists := pmap[key]; exists {
158
// Duplicate parameter name is bogus.
159
return "", nil, errors.New("mime: duplicate parameter name")
165
// Stitch together any continuations or things with stars
166
// (i.e. RFC 2231 things with stars: "foo*0" or "foo*")
168
for key, pieceMap := range continuation {
169
singlePartKey := key + "*"
170
if v, ok := pieceMap[singlePartKey]; ok {
171
decv := decode2231Enc(v)
179
simplePart := fmt.Sprintf("%s*%d", key, n)
180
if v, ok := pieceMap[simplePart]; ok {
185
encodedPart := simplePart + "*"
186
if v, ok := pieceMap[encodedPart]; ok {
189
buf.WriteString(decode2231Enc(v))
191
decv, _ := percentHexUnescape(v)
192
buf.WriteString(decv)
199
params[key] = buf.String()
206
func decode2231Enc(v string) string {
207
sv := strings.SplitN(v, "'", 3)
211
// TODO: ignoring lang in sv[1] for now. If anybody needs it we'll
212
// need to decide how to expose it in the API. But I'm not sure
213
// anybody uses it in practice.
214
charset := strings.ToLower(sv[0])
215
if charset != "us-ascii" && charset != "utf-8" {
216
// TODO: unsupported encoding
219
encv, _ := percentHexUnescape(sv[2])
223
func isNotTokenChar(r rune) bool {
224
return !isTokenChar(r)
227
// consumeToken consumes a token from the beginning of provided
228
// string, per RFC 2045 section 5.1 (referenced from 2183), and return
229
// the token consumed and the rest of the string. Returns ("", v) on
230
// failure to consume at least one character.
231
func consumeToken(v string) (token, rest string) {
232
notPos := strings.IndexFunc(v, isNotTokenChar)
239
return v[0:notPos], v[notPos:]
242
// consumeValue consumes a "value" per RFC 2045, where a value is
243
// either a 'token' or a 'quoted-string'. On success, consumeValue
244
// returns the value consumed (and de-quoted/escaped, if a
245
// quoted-string) and the rest of the string. On failure, returns
247
func consumeValue(v string) (value, rest string) {
252
return consumeToken(v)
255
// parse a quoted-string
256
rest = v[1:] // consume the leading quote
257
buffer := new(bytes.Buffer)
258
var nextIsLiteral bool
259
for idx, r := range rest {
263
nextIsLiteral = false
265
return buffer.String(), rest[idx+1:]
268
case r != '\r' && r != '\n':
277
func consumeMediaParam(v string) (param, value, rest string) {
278
rest = strings.TrimLeftFunc(v, unicode.IsSpace)
279
if !strings.HasPrefix(rest, ";") {
283
rest = rest[1:] // consume semicolon
284
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
285
param, rest = consumeToken(rest)
286
param = strings.ToLower(param)
291
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
292
if !strings.HasPrefix(rest, "=") {
295
rest = rest[1:] // consume equals sign
296
rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
297
value, rest2 := consumeValue(rest)
298
if value == "" && rest2 == rest {
302
return param, value, rest
305
func percentHexUnescape(s string) (string, error) {
306
// Count %, check that they're well-formed.
308
for i := 0; i < len(s); {
314
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
319
return "", fmt.Errorf("mime: bogus characters after %%: %q", s)
327
t := make([]byte, len(s)-2*percents)
329
for i := 0; i < len(s); {
332
t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
341
return string(t), nil
344
func ishex(c byte) bool {
346
case '0' <= c && c <= '9':
348
case 'a' <= c && c <= 'f':
350
case 'A' <= c && c <= 'F':
356
func unhex(c byte) byte {
358
case '0' <= c && c <= '9':
360
case 'a' <= c && c <= 'f':
362
case 'A' <= c && c <= 'F':