Build machinery cleanup.
[official-gcc.git] / libgo / go / mime / mediatype.go
blobad63f9bb98e9a4197070f99b82d512b9f3812eb0
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.
5 package mime
7 import (
8 "bytes"
9 "errors"
10 "fmt"
11 "sort"
12 "strings"
13 "unicode"
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, "/")
23 if slash == -1 {
24 return ""
26 major, sub := t[:slash], t[slash+1:]
27 if !isToken(major) || !isToken(sub) {
28 return ""
30 var b bytes.Buffer
31 b.WriteString(strings.ToLower(major))
32 b.WriteByte('/')
33 b.WriteString(strings.ToLower(sub))
35 attrs := make([]string, 0, len(param))
36 for a := range param {
37 attrs = append(attrs, a)
39 sort.Strings(attrs)
41 for _, attribute := range attrs {
42 value := param[attribute]
43 b.WriteByte(';')
44 b.WriteByte(' ')
45 if !isToken(attribute) {
46 return ""
48 b.WriteString(strings.ToLower(attribute))
49 b.WriteByte('=')
50 if isToken(value) {
51 b.WriteString(value)
52 continue
55 b.WriteByte('"')
56 offset := 0
57 for index, character := range value {
58 if character == '"' || character == '\\' {
59 b.WriteString(value[offset:index])
60 offset = index
61 b.WriteByte('\\')
63 if character&0x80 != 0 {
64 return ""
67 b.WriteString(value[offset:])
68 b.WriteByte('"')
70 return b.String()
73 func checkMediaTypeDisposition(s string) error {
74 typ, rest := consumeToken(s)
75 if typ == "" {
76 return errors.New("mime: no media type")
78 if rest == "" {
79 return nil
81 if !strings.HasPrefix(rest, "/") {
82 return errors.New("mime: expected slash after first token")
84 subtype, rest := consumeToken(rest[1:])
85 if subtype == "" {
86 return errors.New("mime: expected token after slash")
88 if rest != "" {
89 return errors.New("mime: unexpected content after media subtype")
91 return nil
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, ";")
103 if i == -1 {
104 i = len(v)
106 mediatype = strings.TrimSpace(strings.ToLower(v[0:i]))
108 err = checkMediaTypeDisposition(mediatype)
109 if err != nil {
110 return "", nil, err
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
120 v = v[i:]
121 for len(v) > 0 {
122 v = strings.TrimLeftFunc(v, unicode.IsSpace)
123 if len(v) == 0 {
124 break
126 key, value, rest := consumeMediaParam(v)
127 if key == "" {
128 if strings.TrimSpace(rest) == ";" {
129 // Ignore trailing semicolons.
130 // Not an error.
131 return
133 // Parse error.
134 return "", nil, errors.New("mime: invalid media parameter")
137 pmap := params
138 if idx := strings.Index(key, "*"); idx != -1 {
139 baseName := key[:idx]
140 if continuation == nil {
141 continuation = make(map[string]map[string]string)
143 var ok bool
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")
153 pmap[key] = value
154 v = rest
157 // Stitch together any continuations or things with stars
158 // (i.e. RFC 2231 things with stars: "foo*0" or "foo*")
159 var buf bytes.Buffer
160 for key, pieceMap := range continuation {
161 singlePartKey := key + "*"
162 if v, ok := pieceMap[singlePartKey]; ok {
163 decv := decode2231Enc(v)
164 params[key] = decv
165 continue
168 buf.Reset()
169 valid := false
170 for n := 0; ; n++ {
171 simplePart := fmt.Sprintf("%s*%d", key, n)
172 if v, ok := pieceMap[simplePart]; ok {
173 valid = true
174 buf.WriteString(v)
175 continue
177 encodedPart := simplePart + "*"
178 if v, ok := pieceMap[encodedPart]; ok {
179 valid = true
180 if n == 0 {
181 buf.WriteString(decode2231Enc(v))
182 } else {
183 decv, _ := percentHexUnescape(v)
184 buf.WriteString(decv)
186 } else {
187 break
190 if valid {
191 params[key] = buf.String()
195 return
198 func decode2231Enc(v string) string {
199 sv := strings.SplitN(v, "'", 3)
200 if len(sv) != 3 {
201 return ""
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
209 return ""
211 encv, _ := percentHexUnescape(sv[2])
212 return encv
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)
225 if notPos == -1 {
226 return v, ""
228 if notPos == 0 {
229 return "", v
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
238 // ("", v).
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)
249 var idx int
250 var r rune
251 var nextIsLiteral bool
252 for idx, r = range rest {
253 switch {
254 case nextIsLiteral:
255 buffer.WriteRune(r)
256 nextIsLiteral = false
257 case r == leadQuote:
258 return buffer.String(), rest[idx+1:]
259 case r == '\\':
260 nextIsLiteral = true
261 case r != '\r' && r != '\n':
262 buffer.WriteRune(r)
263 default:
264 return "", v
267 return "", v
270 func consumeMediaParam(v string) (param, value, rest string) {
271 rest = strings.TrimLeftFunc(v, unicode.IsSpace)
272 if !strings.HasPrefix(rest, ";") {
273 return "", "", v
276 rest = rest[1:] // consume semicolon
277 rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
278 param, rest = consumeToken(rest)
279 param = strings.ToLower(param)
280 if param == "" {
281 return "", "", v
284 rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
285 if !strings.HasPrefix(rest, "=") {
286 return "", "", v
288 rest = rest[1:] // consume equals sign
289 rest = strings.TrimLeftFunc(rest, unicode.IsSpace)
290 value, rest = consumeValue(rest)
291 if value == "" {
292 return "", "", v
294 return param, value, rest
297 func percentHexUnescape(s string) (string, error) {
298 // Count %, check that they're well-formed.
299 percents := 0
300 for i := 0; i < len(s); {
301 if s[i] != '%' {
303 continue
305 percents++
306 if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
307 s = s[i:]
308 if len(s) > 3 {
309 s = s[0:3]
311 return "", fmt.Errorf("mime: bogus characters after %%: %q", s)
313 i += 3
315 if percents == 0 {
316 return s, nil
319 t := make([]byte, len(s)-2*percents)
320 j := 0
321 for i := 0; i < len(s); {
322 switch s[i] {
323 case '%':
324 t[j] = unhex(s[i+1])<<4 | unhex(s[i+2])
326 i += 3
327 default:
328 t[j] = s[i]
333 return string(t), nil
336 func ishex(c byte) bool {
337 switch {
338 case '0' <= c && c <= '9':
339 return true
340 case 'a' <= c && c <= 'f':
341 return true
342 case 'A' <= c && c <= 'F':
343 return true
345 return false
348 func unhex(c byte) byte {
349 switch {
350 case '0' <= c && c <= '9':
351 return c - '0'
352 case 'a' <= c && c <= 'f':
353 return c - 'a' + 10
354 case 'A' <= c && c <= 'F':
355 return c - 'A' + 10
357 return 0