~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/resource/api/internal/mime/mediatype.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
 
5
//+build !go1.6
 
6
 
 
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.
 
9
 
 
10
package mime
 
11
 
 
12
import (
 
13
        "bytes"
 
14
        "errors"
 
15
        "fmt"
 
16
        "sort"
 
17
        "strings"
 
18
        "unicode"
 
19
)
 
20
 
 
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 {
 
27
        var b bytes.Buffer
 
28
        if slash := strings.Index(t, "/"); slash == -1 {
 
29
                if !isToken(t) {
 
30
                        return ""
 
31
                }
 
32
                b.WriteString(strings.ToLower(t))
 
33
        } else {
 
34
                major, sub := t[:slash], t[slash+1:]
 
35
                if !isToken(major) || !isToken(sub) {
 
36
                        return ""
 
37
                }
 
38
                b.WriteString(strings.ToLower(major))
 
39
                b.WriteByte('/')
 
40
                b.WriteString(strings.ToLower(sub))
 
41
        }
 
42
 
 
43
        attrs := make([]string, 0, len(param))
 
44
        for a := range param {
 
45
                attrs = append(attrs, a)
 
46
        }
 
47
        sort.Strings(attrs)
 
48
 
 
49
        for _, attribute := range attrs {
 
50
                value := param[attribute]
 
51
                b.WriteByte(';')
 
52
                b.WriteByte(' ')
 
53
                if !isToken(attribute) {
 
54
                        return ""
 
55
                }
 
56
                b.WriteString(strings.ToLower(attribute))
 
57
                b.WriteByte('=')
 
58
                if isToken(value) {
 
59
                        b.WriteString(value)
 
60
                        continue
 
61
                }
 
62
 
 
63
                b.WriteByte('"')
 
64
                offset := 0
 
65
                for index, character := range value {
 
66
                        if character == '"' || character == '\\' {
 
67
                                b.WriteString(value[offset:index])
 
68
                                offset = index
 
69
                                b.WriteByte('\\')
 
70
                        }
 
71
                        if character&0x80 != 0 {
 
72
                                return ""
 
73
                        }
 
74
                }
 
75
                b.WriteString(value[offset:])
 
76
                b.WriteByte('"')
 
77
        }
 
78
        return b.String()
 
79
}
 
80
 
 
81
func checkMediaTypeDisposition(s string) error {
 
82
        typ, rest := consumeToken(s)
 
83
        if typ == "" {
 
84
                return errors.New("mime: no media type")
 
85
        }
 
86
        if rest == "" {
 
87
                return nil
 
88
        }
 
89
        if !strings.HasPrefix(rest, "/") {
 
90
                return errors.New("mime: expected slash after first token")
 
91
        }
 
92
        subtype, rest := consumeToken(rest[1:])
 
93
        if subtype == "" {
 
94
                return errors.New("mime: expected token after slash")
 
95
        }
 
96
        if rest != "" {
 
97
                return errors.New("mime: unexpected content after media subtype")
 
98
        }
 
99
        return nil
 
100
}
 
101
 
 
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, ";")
 
111
        if i == -1 {
 
112
                i = len(v)
 
113
        }
 
114
        mediatype = strings.TrimSpace(strings.ToLower(v[0:i]))
 
115
 
 
116
        err = checkMediaTypeDisposition(mediatype)
 
117
        if err != nil {
 
118
                return "", nil, err
 
119
        }
 
120
 
 
121
        params = make(map[string]string)
 
122
 
 
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
 
127
 
 
128
        v = v[i:]
 
129
        for len(v) > 0 {
 
130
                v = strings.TrimLeftFunc(v, unicode.IsSpace)
 
131
                if len(v) == 0 {
 
132
                        break
 
133
                }
 
134
                key, value, rest := consumeMediaParam(v)
 
135
                if key == "" {
 
136
                        if strings.TrimSpace(rest) == ";" {
 
137
                                // Ignore trailing semicolons.
 
138
                                // Not an error.
 
139
                                return
 
140
                        }
 
141
                        // Parse error.
 
142
                        return "", nil, errors.New("mime: invalid media parameter")
 
143
                }
 
144
 
 
145
                pmap := params
 
146
                if idx := strings.Index(key, "*"); idx != -1 {
 
147
                        baseName := key[:idx]
 
148
                        if continuation == nil {
 
149
                                continuation = make(map[string]map[string]string)
 
150
                        }
 
151
                        var ok bool
 
152
                        if pmap, ok = continuation[baseName]; !ok {
 
153
                                continuation[baseName] = make(map[string]string)
 
154
                                pmap = continuation[baseName]
 
155
                        }
 
156
                }
 
157
                if _, exists := pmap[key]; exists {
 
158
                        // Duplicate parameter name is bogus.
 
159
                        return "", nil, errors.New("mime: duplicate parameter name")
 
160
                }
 
161
                pmap[key] = value
 
162
                v = rest
 
163
        }
 
164
 
 
165
        // Stitch together any continuations or things with stars
 
166
        // (i.e. RFC 2231 things with stars: "foo*0" or "foo*")
 
167
        var buf bytes.Buffer
 
168
        for key, pieceMap := range continuation {
 
169
                singlePartKey := key + "*"
 
170
                if v, ok := pieceMap[singlePartKey]; ok {
 
171
                        decv := decode2231Enc(v)
 
172
                        params[key] = decv
 
173
                        continue
 
174
                }
 
175
 
 
176
                buf.Reset()
 
177
                valid := false
 
178
                for n := 0; ; n++ {
 
179
                        simplePart := fmt.Sprintf("%s*%d", key, n)
 
180
                        if v, ok := pieceMap[simplePart]; ok {
 
181
                                valid = true
 
182
                                buf.WriteString(v)
 
183
                                continue
 
184
                        }
 
185
                        encodedPart := simplePart + "*"
 
186
                        if v, ok := pieceMap[encodedPart]; ok {
 
187
                                valid = true
 
188
                                if n == 0 {
 
189
                                        buf.WriteString(decode2231Enc(v))
 
190
                                } else {
 
191
                                        decv, _ := percentHexUnescape(v)
 
192
                                        buf.WriteString(decv)
 
193
                                }
 
194
                        } else {
 
195
                                break
 
196
                        }
 
197
                }
 
198
                if valid {
 
199
                        params[key] = buf.String()
 
200
                }
 
201
        }
 
202
 
 
203
        return
 
204
}
 
205
 
 
206
func decode2231Enc(v string) string {
 
207
        sv := strings.SplitN(v, "'", 3)
 
208
        if len(sv) != 3 {
 
209
                return ""
 
210
        }
 
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
 
217
                return ""
 
218
        }
 
219
        encv, _ := percentHexUnescape(sv[2])
 
220
        return encv
 
221
}
 
222
 
 
223
func isNotTokenChar(r rune) bool {
 
224
        return !isTokenChar(r)
 
225
}
 
226
 
 
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)
 
233
        if notPos == -1 {
 
234
                return v, ""
 
235
        }
 
236
        if notPos == 0 {
 
237
                return "", v
 
238
        }
 
239
        return v[0:notPos], v[notPos:]
 
240
}
 
241
 
 
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
 
246
// ("", v).
 
247
func consumeValue(v string) (value, rest string) {
 
248
        if v == "" {
 
249
                return
 
250
        }
 
251
        if v[0] != '"' {
 
252
                return consumeToken(v)
 
253
        }
 
254
 
 
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 {
 
260
                switch {
 
261
                case nextIsLiteral:
 
262
                        buffer.WriteRune(r)
 
263
                        nextIsLiteral = false
 
264
                case r == '"':
 
265
                        return buffer.String(), rest[idx+1:]
 
266
                case r == '\\':
 
267
                        nextIsLiteral = true
 
268
                case r != '\r' && r != '\n':
 
269
                        buffer.WriteRune(r)
 
270
                default:
 
271
                        return "", v
 
272
                }
 
273
        }
 
274
        return "", v
 
275
}
 
276
 
 
277
func consumeMediaParam(v string) (param, value, rest string) {
 
278
        rest = strings.TrimLeftFunc(v, unicode.IsSpace)
 
279
        if !strings.HasPrefix(rest, ";") {
 
280
                return "", "", v
 
281
        }
 
282
 
 
283
        rest = rest[1:] // consume semicolon
 
284
        rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
 
285
        param, rest = consumeToken(rest)
 
286
        param = strings.ToLower(param)
 
287
        if param == "" {
 
288
                return "", "", v
 
289
        }
 
290
 
 
291
        rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
 
292
        if !strings.HasPrefix(rest, "=") {
 
293
                return "", "", v
 
294
        }
 
295
        rest = rest[1:] // consume equals sign
 
296
        rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
 
297
        value, rest2 := consumeValue(rest)
 
298
        if value == "" && rest2 == rest {
 
299
                return "", "", v
 
300
        }
 
301
        rest = rest2
 
302
        return param, value, rest
 
303
}
 
304
 
 
305
func percentHexUnescape(s string) (string, error) {
 
306
        // Count %, check that they're well-formed.
 
307
        percents := 0
 
308
        for i := 0; i < len(s); {
 
309
                if s[i] != '%' {
 
310
                        i++
 
311
                        continue
 
312
                }
 
313
                percents++
 
314
                if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
 
315
                        s = s[i:]
 
316
                        if len(s) > 3 {
 
317
                                s = s[0:3]
 
318
                        }
 
319
                        return "", fmt.Errorf("mime: bogus characters after %%: %q", s)
 
320
                }
 
321
                i += 3
 
322
        }
 
323
        if percents == 0 {
 
324
                return s, nil
 
325
        }
 
326
 
 
327
        t := make([]byte, len(s)-2*percents)
 
328
        j := 0
 
329
        for i := 0; i < len(s); {
 
330
                switch s[i] {
 
331
                case '%':
 
332
                        t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
 
333
                        j++
 
334
                        i += 3
 
335
                default:
 
336
                        t[j] = s[i]
 
337
                        j++
 
338
                        i++
 
339
                }
 
340
        }
 
341
        return string(t), nil
 
342
}
 
343
 
 
344
func ishex(c byte) bool {
 
345
        switch {
 
346
        case '0' <= c && c <= '9':
 
347
                return true
 
348
        case 'a' <= c && c <= 'f':
 
349
                return true
 
350
        case 'A' <= c && c <= 'F':
 
351
                return true
 
352
        }
 
353
        return false
 
354
}
 
355
 
 
356
func unhex(c byte) byte {
 
357
        switch {
 
358
        case '0' <= c && c <= '9':
 
359
                return c - '0'
 
360
        case 'a' <= c && c <= 'f':
 
361
                return c - 'a' + 10
 
362
        case 'A' <= c && c <= 'F':
 
363
                return c - 'A' + 10
 
364
        }
 
365
        return 0
 
366
}