...

Source file src/github.com/munnerz/goautoneg/autoneg.go

Documentation: github.com/munnerz/goautoneg

     1  /*
     2  HTTP Content-Type Autonegotiation.
     3  
     4  The functions in this package implement the behaviour specified in
     5  http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
     6  
     7  Copyright (c) 2011, Open Knowledge Foundation Ltd.
     8  All rights reserved.
     9  
    10  Redistribution and use in source and binary forms, with or without
    11  modification, are permitted provided that the following conditions are
    12  met:
    13  
    14      Redistributions of source code must retain the above copyright
    15      notice, this list of conditions and the following disclaimer.
    16  
    17      Redistributions in binary form must reproduce the above copyright
    18      notice, this list of conditions and the following disclaimer in
    19      the documentation and/or other materials provided with the
    20      distribution.
    21  
    22      Neither the name of the Open Knowledge Foundation Ltd. nor the
    23      names of its contributors may be used to endorse or promote
    24      products derived from this software without specific prior written
    25      permission.
    26  
    27  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    28  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    29  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    30  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    31  HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    32  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    33  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    34  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    35  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    36  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    37  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    38  */
    39  
    40  package goautoneg
    41  
    42  import (
    43  	"sort"
    44  	"strconv"
    45  	"strings"
    46  )
    47  
    48  // Structure to represent a clause in an HTTP Accept Header
    49  type Accept struct {
    50  	Type, SubType string
    51  	Q             float64
    52  	Params        map[string]string
    53  }
    54  
    55  // acceptSlice is defined to implement sort interface.
    56  type acceptSlice []Accept
    57  
    58  func (slice acceptSlice) Len() int {
    59  	return len(slice)
    60  }
    61  
    62  func (slice acceptSlice) Less(i, j int) bool {
    63  	ai, aj := slice[i], slice[j]
    64  	if ai.Q > aj.Q {
    65  		return true
    66  	}
    67  	if ai.Type != "*" && aj.Type == "*" {
    68  		return true
    69  	}
    70  	if ai.SubType != "*" && aj.SubType == "*" {
    71  		return true
    72  	}
    73  	return false
    74  }
    75  
    76  func (slice acceptSlice) Swap(i, j int) {
    77  	slice[i], slice[j] = slice[j], slice[i]
    78  }
    79  
    80  func stringTrimSpaceCutset(r rune) bool {
    81  	return r == ' '
    82  }
    83  
    84  func nextSplitElement(s, sep string) (item string, remaining string) {
    85  	if index := strings.Index(s, sep); index != -1 {
    86  		return s[:index], s[index+1:]
    87  	}
    88  	return s, ""
    89  }
    90  
    91  // Parse an Accept Header string returning a sorted list
    92  // of clauses
    93  func ParseAccept(header string) acceptSlice {
    94  	partsCount := 0
    95  	remaining := header
    96  	for len(remaining) > 0 {
    97  		partsCount++
    98  		_, remaining = nextSplitElement(remaining, ",")
    99  	}
   100  	accept := make(acceptSlice, 0, partsCount)
   101  
   102  	remaining = header
   103  	var part string
   104  	for len(remaining) > 0 {
   105  		part, remaining = nextSplitElement(remaining, ",")
   106  		part = strings.TrimFunc(part, stringTrimSpaceCutset)
   107  
   108  		a := Accept{
   109  			Q: 1.0,
   110  		}
   111  
   112  		sp, remainingPart := nextSplitElement(part, ";")
   113  
   114  		sp0, spRemaining := nextSplitElement(sp, "/")
   115  		a.Type = strings.TrimFunc(sp0, stringTrimSpaceCutset)
   116  
   117  		switch {
   118  		case len(spRemaining) == 0:
   119  			if a.Type == "*" {
   120  				a.SubType = "*"
   121  			} else {
   122  				continue
   123  			}
   124  		default:
   125  			var sp1 string
   126  			sp1, spRemaining = nextSplitElement(spRemaining, "/")
   127  			if len(spRemaining) > 0 {
   128  				continue
   129  			}
   130  			a.SubType = strings.TrimFunc(sp1, stringTrimSpaceCutset)
   131  		}
   132  
   133  		if len(remainingPart) == 0 {
   134  			accept = append(accept, a)
   135  			continue
   136  		}
   137  
   138  		a.Params = make(map[string]string)
   139  		for len(remainingPart) > 0 {
   140  			sp, remainingPart = nextSplitElement(remainingPart, ";")
   141  			sp0, spRemaining = nextSplitElement(sp, "=")
   142  			if len(spRemaining) == 0 {
   143  				continue
   144  			}
   145  			var sp1 string
   146  			sp1, spRemaining = nextSplitElement(spRemaining, "=")
   147  			if len(spRemaining) != 0 {
   148  				continue
   149  			}
   150  			token := strings.TrimFunc(sp0, stringTrimSpaceCutset)
   151  			if token == "q" {
   152  				a.Q, _ = strconv.ParseFloat(sp1, 32)
   153  			} else {
   154  				a.Params[token] = strings.TrimFunc(sp1, stringTrimSpaceCutset)
   155  			}
   156  		}
   157  
   158  		accept = append(accept, a)
   159  	}
   160  
   161  	sort.Sort(accept)
   162  	return accept
   163  }
   164  
   165  // Negotiate the most appropriate content_type given the accept header
   166  // and a list of alternatives.
   167  func Negotiate(header string, alternatives []string) (content_type string) {
   168  	asp := make([][]string, 0, len(alternatives))
   169  	for _, ctype := range alternatives {
   170  		asp = append(asp, strings.SplitN(ctype, "/", 2))
   171  	}
   172  	for _, clause := range ParseAccept(header) {
   173  		for i, ctsp := range asp {
   174  			if clause.Type == ctsp[0] && clause.SubType == ctsp[1] {
   175  				content_type = alternatives[i]
   176  				return
   177  			}
   178  			if clause.Type == ctsp[0] && clause.SubType == "*" {
   179  				content_type = alternatives[i]
   180  				return
   181  			}
   182  			if clause.Type == "*" && clause.SubType == "*" {
   183  				content_type = alternatives[i]
   184  				return
   185  			}
   186  		}
   187  	}
   188  	return
   189  }
   190  

View as plain text