blob: 85d9505d968aa845cd473c46eb17e5b54e493c58 [file] [log] [blame]
package rfc
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import "mime"
import "sort"
import "strings"
import "strconv"
/*
MimeType represents a "Media Type" as defined by RFC6838, along with some ease-of-use functionality.
Note that this structure is in no way guaranteed to represent a *real* MIME Type, only one that
is *syntactically valid*. The hope is that it will make content negotiation easier for developers,
and should not be considered a security measure by any standard.
*/
type MimeType struct {
// Name is the full name of the MIME Type, e.g. 'application/json'.
// Usually for printing, it's better to call MimeType.String
Name string
// Parameters contains a map of provided parameter names to corresponding values. Note that for
// MimeTypes constructed with NewMimeType, this will always be initialized, even when empty.
Parameters map[string]string
}
/*
Quality retrieves and parses the "quality" parameter of a MimeType.
As specified in RFC7231, the quality parameter's name is "q", not actually "quality". To obtain
a literal "quality" parameter value, access MimeType.Parameters directly.
MimeTypes with no "q" parameter implicitly have a "quality" of 1.0.
*/
func (m MimeType) Quality() float64 {
if m.Parameters == nil {
return 1
}
fs, ok := m.Parameters["q"]
if !ok {
return 1
}
ret, err := strconv.ParseFloat(fs, 64)
if err != nil {
return 1
}
return ret
}
/*
Charset retrieves the "charset" parameter of a MimeType.
Returns an empty string if no charset exists in the parameters, or if the parameters themselves are
not initialized.
*/
func (m MimeType) Charset() string {
if m.Parameters == nil {
return ""
}
c, ok := m.Parameters["charset"]
if !ok {
return ""
}
return c
}
// Type returns only the "main" type of a MimeType.
func (m MimeType) Type() string {
return strings.SplitN(m.Name, "/", 2)[0]
}
// SubType returns only the "sub" type of a MimeType.
func (m MimeType) SubType() string {
s := strings.SplitN(m.Name, "/", 2)
if len(s) != 2 {
return ""
}
return s[1]
}
// Facet returns the MimeType's "facet" if one exists, otherwise an empty string.
func (m MimeType) Facet() string {
s := m.SubType()
if fx := strings.SplitN(s, ".", 2); len(fx) == 2 {
return fx[0]
}
return ""
}
// Syntax returns the MimeType's "syntax suffix" if one exists, otherwise an empty string.
func (m MimeType) Syntax() string {
s := m.SubType()
if fx := strings.Split(s, "+"); len(fx) > 1 {
return fx[len(fx)-1]
}
return ""
}
// String implements the Stringer interface using mime.FormatMediaType.
func (m MimeType) String() string {
return mime.FormatMediaType(m.Name, m.Parameters)
}
// Satisfy checks whether or not the MimeType "satifies" some other MimeType, o.
//
// Note that this does not check if the two are literally the *same*. Specifically, if the Type or
// SubType of the given MimeType o is the special '*' name, then this will instead check whether or
// not this MimeType can *satisfy* the other according to RFC7231. This means that this satisfaction
// check is NOT associative - that is a.Satisfy(b) does not imply b.Satisfy(a).
//
// See Also: The MDN documentation on the Accept Header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept
func (m MimeType) Satisfy(o MimeType) bool {
// literally anything will satisfy this
if o.Type() == "*" && o.SubType() == "*" {
return true
}
// it's not syntactically valid to have a "*" type and a non-"*" subtype, e.g. "*/foo", so we're
// done here
if o.Type() != "*" && o.SubType() == "*" {
return o.Type() == m.Type()
}
if o.Type() != m.Type() || o.SubType() != m.SubType() {
return false
}
for k, v := range o.Parameters {
if k == "q" {
continue
}
if mv, ok := m.Parameters[k]; !ok || mv != v {
return false
}
}
return true
}
/*
Less checks whether or not this MimeType is "less than" some other MimeType, o.
This is done using a comparison of "quality value" of the two MimeTypes, as specified in RFC7231.
See Also: The MDN documentation on "quality value" comparisons: https://developer.mozilla.org/en-US/docs/Glossary/Quality_values
*/
func (m MimeType) Less(o MimeType) bool {
mq := m.Quality()
oq := o.Quality()
if mq < oq {
return true
} else if oq < mq {
return false
}
mType := m.Type()
mSub := m.SubType()
oType := o.Type()
oSub := o.SubType()
if mSub == "*" {
if oSub != "*" {
return true
}
if mType == "*" {
if oType != "*" {
return true
}
} else if oType == "*" {
return false
}
} else if oSub == "*" {
return false
}
return len(m.Parameters) < len(o.Parameters)
}
/*
NewMimeType creates a new MimeType, initializing its Parameters map.
If v cannot be parsed as a valid MIME (by mime.ParseMediaType), returns an error.
*/
func NewMimeType(v string) (m MimeType, err error) {
m.Name, m.Parameters, err = mime.ParseMediaType(v)
return m, err
}
/*
MimeTypesFromAccept constructs a *sorted* list of MimeTypes from the provided text, which is assumed
to be from an HTTP 'Accept' header. The list is sorted using to SortMimeTypes.
If a is an empty string, this will return an empty slice and no error.
*/
func MimeTypesFromAccept(a string) ([]MimeType, error) {
mimes := []MimeType{}
if a == "" {
return mimes, nil
}
for _, raw := range strings.Split(a, ",") {
m, err := NewMimeType(raw)
if err != nil {
return mimes, err
}
mimes = append(mimes, m)
}
SortMimeTypes(mimes)
return mimes, nil
}
/*
SortMimeTypes sorts the passed MimeTypes according to their "quality value". See MimeType.Less for
more information on how MimeTypes are compared.
*/
func SortMimeTypes(m []MimeType) {
// using !Less because default sort order is ascending
sort.SliceStable(m, func(i, j int) bool { return !m[i].Less(m[j]) })
}
// MIME_JSON is a pre-defined MimeType for JSON data.
var MIME_JSON = MimeType{
Name: "application/json",
Parameters: map[string]string{},
}
// MIME_PLAINTEXT is a pre-defined MimeType for plain text data.
var MIME_PLAINTEXT = MimeType{
Name: "text/plain",
Parameters: map[string]string{"charset": "utf-8"},
}
// MIME_HTML is a pre-defined MimeType for HTML data.
var MIME_HTML = MimeType{
Name: "text/html",
Parameters: map[string]string{"charset": "utf-8"},
}
// MIME_CSS is a pre-defined MimeType for CSS data.
var MIME_CSS = MimeType{
Name: "text/css",
Parameters: map[string]string{"charset": "utf-8"},
}
// MIME_JS is a pre-defined MimeType for JavaScript data.
var MIME_JS = MimeType{
Name: "text/javascript",
Parameters: map[string]string{"charset": "utf-8"},
}