// Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package mime import ( "bytes" "strings" "unicode" ) // ParseMediaType parses a media type value and any optional // parameters, per RFC 1531. Media types are the values in // Content-Type and Content-Disposition headers (RFC 2183). On // success, ParseMediaType returns the media type converted to // lowercase and trimmed of white space and a non-nil params. On // error, it returns an empty string and a nil params. func ParseMediaType(v string) (mediatype string, params map[string]string) { i := strings.Index(v, ";") if i == -1 { i = len(v) } mediatype = strings.TrimSpace(strings.ToLower(v[0:i])) params = make(map[string]string) v = v[i:] for len(v) > 0 { v = strings.TrimLeftFunc(v, unicode.IsSpace) if len(v) == 0 { return } key, value, rest := consumeMediaParam(v) if key == "" { // Parse error. return "", nil } params[key] = value v = rest } return } func isNotTokenChar(rune int) bool { return !IsTokenChar(rune) } // consumeToken consumes a token from the beginning of provided // string, per RFC 2045 section 5.1 (referenced from 2183), and return // the token consumed and the rest of the string. Returns ("", v) on // failure to consume at least one character. func consumeToken(v string) (token, rest string) { notPos := strings.IndexFunc(v, isNotTokenChar) if notPos == -1 { return v, "" } if notPos == 0 { return "", v } return v[0:notPos], v[notPos:] } // consumeValue consumes a "value" per RFC 2045, where a value is // either a 'token' or a 'quoted-string'. On success, consumeValue // returns the value consumed (and de-quoted/escaped, if a // quoted-string) and the rest of the string. On failure, returns // ("", v). func consumeValue(v string) (value, rest string) { if !strings.HasPrefix(v, `"`) { return consumeToken(v) } // parse a quoted-string rest = v[1:] // consume the leading quote buffer := new(bytes.Buffer) var idx, rune int var nextIsLiteral bool for idx, rune = range rest { switch { case nextIsLiteral: if rune >= 0x80 { return "", v } buffer.WriteRune(rune) nextIsLiteral = false case rune == '"': return buffer.String(), rest[idx+1:] case IsQText(rune): buffer.WriteRune(rune) case rune == '\\': nextIsLiteral = true default: return "", v } } return "", v } func consumeMediaParam(v string) (param, value, rest string) { rest = strings.TrimLeftFunc(v, unicode.IsSpace) if !strings.HasPrefix(rest, ";") { return "", "", v } rest = rest[1:] // consume semicolon rest = strings.TrimLeftFunc(rest, unicode.IsSpace) param, rest = consumeToken(rest) if param == "" { return "", "", v } if !strings.HasPrefix(rest, "=") { return "", "", v } rest = rest[1:] // consume equals sign value, rest = consumeValue(rest) if value == "" { return "", "", v } return param, value, rest }