github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/resource/api/internal/mime/mediatype.go (about) 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 }