github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  }