mimeheader

package module
v0.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 13, 2021 License: MIT Imports: 4 Imported by: 3

README

Mime header

Go (tests and linters) GitHub GitHub Workflow Status GitHub issues GitHub release (latest SemVer)

RFC reference

Implementation of mime types and Accept header support (RFC 2616 Sec 14.1).

Motivation

This library created to help people to parse media type data, like headers, and store and match it. The main features of the library are to:

  • Save a type as a structure (it can help pass mime type as an argument to a function or use as a field of a structure)
  • Match mime types by a wildcard. It can be helpful for Accept header negotiation
  • MUST be covered by tests to provide guarantees it works well
  • Zero dependencies

Examples

Match a header with text
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

// Content-Type - application/json;q=0.9;p=2
func parse(contentType string) {
	mtype, err := mimeheader.ParseMediaType(contentType)

	fmt.Println(err)                                 // nil
	fmt.Println(mtype.MatchText("application/json; param=1")) // true
	fmt.Println(mtype.MatchText("application/xml; param=1"))  // false
	fmt.Println(mtype.MatchText("*/plain; param=1"))          // false
	fmt.Println(mtype.MatchText("text/plain; param=1"))       // false
}
Match accept header
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

// Accept - application/json;q=1.0,*/*;q=1.0; param=wild,image/png;q=1.0;param=test
func parse(acceptHeader string) {
	ah := mimeheader.ParseAcceptHeader(acceptHeader)

	fmt.Println(ah.Negotiate([]string{"application/json;param=1", "image/png"}, "text/javascript")) // image/png, image/png, true
}
Accept header HTTP middleware

OWASP suggests using this middleware in all applications.

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	r := http.NewServeMux()

	r.HandleFunc("/", acceptHeaderMiddleware([]string{"application/json", "text/html"})(handlerTestFunc))

	err := http.ListenAndServe(":8080", r)
	if err != nil {
		log.Fatalln(err)
	}
}

func acceptHeaderMiddleware(acceptMimeTypes []string) func(http.HandlerFunc) http.HandlerFunc {
	return func(next http.HandlerFunc) http.HandlerFunc {
		return func(rw http.ResponseWriter, r *http.Request) {
			header := r.Header.Get("Accept")
			ah := mimeheader.ParseAcceptHeader(header)

			// We do not need default mime type.
			_, mtype, m := ah.Negotiate(acceptMimeTypes, "")
			if !m {
				// If not matched accept mim type, return 406.
				rw.WriteHeader(http.StatusNotAcceptable)

				return
			}

			// Add matched mime type to context.
			ctx := context.WithValue(r.Context(), "resp_content_type", mtype)
			// New requet from new context.
			rc := r.WithContext(ctx)

			// Call next middleware or handler.
			next(rw, rc)
		}
	}
}

func handlerTestFunc(rw http.ResponseWriter, r *http.Request) {
	mtype := r.Context().Value("resp_content_type").(string)
	rw.Write([]byte(mtype))
}

Requests example

GET http://localhost:8080/
Accept: text/*; q=0.9,application/json; q=1;

# HTTP/1.1 200 OK
# Date: Sat, 03 Jul 2021 19:14:58 GMT
# Content-Length: 16
# Content-Type: text/plain; charset=utf-8
# Connection: close

# application/json

###

GET http://localhost:8080/
Accept: text/*; q=1,application/json; q=1;

# HTTP/1.1 200 OK
# Date: Sat, 03 Jul 2021 19:15:51 GMT
# Content-Length: 16
# Content-Type: text/plain; charset=utf-8
# Connection: close

# application/json

###
GET http://localhost:8080/
Accept: text/html; q=1,application/*; q=1;

# HTTP/1.1 200 OK
# Date: Sat, 03 Jul 2021 19:16:15 GMT
# Content-Length: 9
# Content-Type: text/plain; charset=utf-8
# Connection: close

# text/html

###
GET http://localhost:8080/
Accept: text/*; q=1,application/*; q=0.9;

# HTTP/1.1 200 OK
# Date: Sat, 03 Jul 2021 19:16:48 GMT
# Content-Length: 9
# Content-Type: text/plain; charset=utf-8
# Connection: close

# text/html

###
GET http://localhost:8080/
Accept: text/plain; q=1,application/xml; q=1;

# HTTP/1.1 406 Not Acceptable
# Date: Sat, 03 Jul 2021 19:17:28 GMT
# Content-Length: 0
# Connection: close

Current benchmark results

$ go test -bench=.
goos: darwin
goarch: arm64
pkg: github.com/aohorodnyk/mimeheader
BenchmarkParseAcceptHeaderLong-8                        	  595474	      1979 ns/op	    1672 B/op	      16 allocs/op
BenchmarkParseAcceptHeaderThreeWithWights-8             	  879034	      1364 ns/op	    1480 B/op	      14 allocs/op
BenchmarkParseAcceptHeaderOne-8                         	 4268950	       279.7 ns/op	     232 B/op	       7 allocs/op
BenchmarkParseAcceptHeaderAndCompareLong-8              	  295642	      4052 ns/op	    2344 B/op	      34 allocs/op
BenchmarkParseAcceptHeaderAndCompareThreeWithWights-8   	  408138	      2943 ns/op	    1992 B/op	      28 allocs/op
BenchmarkParseAcceptHeaderAndCompareOne-8               	 1399750	       856.0 ns/op	     411 B/op	      13 allocs/op
PASS
ok  	github.com/aohorodnyk/mimeheader	9.252s

Contributing

All contributions have to follow the CONTRIBUTING.md document If you have any questions/issues/feature requests do not hesitate to create a ticket.

Documentation

Index

Examples

Constants

View Source
const (
	MimeParseErrMsg        = "error in a parse media type"
	MimeTypePartsErrMsg    = "wrong number of mime type parts"
	MimeTypeWildcardErrMsg = "mimetype cannot be as */plain"
)

Error messages.

View Source
const DefaultQuality float32 = 1.0
View Source
const MimeAny = "*"

MimeAny represented as the asterisk.

View Source
const MimeParts = 2

MimeParts MUST contain two parts <MIME_type>/<MIME_subtype>.

View Source
const MimeSeparator = "/"

MimeSeparator MUST be separated forward slash.

Variables

This section is empty.

Functions

This section is empty.

Types

type AcceptHeader

type AcceptHeader struct {
	MHeaders []MimeHeader
}

func NewAcceptHeader

func NewAcceptHeader(mheaders []MimeHeader) AcceptHeader

func NewAcceptHeaderPlain

func NewAcceptHeaderPlain(mheaders []MimeHeader) AcceptHeader

func ParseAcceptHeader

func ParseAcceptHeader(header string) AcceptHeader
Example
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	header := "image/*; q=0.9; s=4, application/json; q=0.9; b=3;, text/plain,image/png;q=0.9, image/jpeg,image/svg;q=0.8"
	ah := mimeheader.ParseAcceptHeader(header)

	fmt.Println(ah.Negotiate([]string{"application/xml", "image/tiff"}, "text/javascript"))
	fmt.Println(ah.Negotiate([]string{"application/xml", "image/png"}, "text/javascript"))
	fmt.Println(ah.Negotiate([]string{"application/xml", "image/svg"}, "text/javascript"))
	fmt.Println(ah.Negotiate([]string{"text/dart", "application/dart"}, "text/javascript"))

	fmt.Println(ah.Match("image/svg"))
	fmt.Println(ah.Match("text/javascript"))
}
Output:

image/* image/tiff true
image/png image/png true
image/* image/svg true
 text/javascript false
true
false

func (*AcceptHeader) Add

func (ah *AcceptHeader) Add(mh MimeHeader)

Add mime header to accept header. MimeHeader will be validated and added ONLY if valid. AcceptHeader will be sorted. For performance reasons better to use Set, instead of Add.

Example
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	ah := mimeheader.ParseAcceptHeader("image/png")
	fmt.Println(ah.Match("application/json"))

	ah.Add(mimeheader.MimeHeader{MimeType: mimeheader.MimeType{Type: "application", Subtype: "*"}})
	fmt.Println(ah.Match("application/json"))
}
Output:

false
true

func (AcceptHeader) Len

func (ah AcceptHeader) Len() int

Len function for sort.Interface interface.

func (AcceptHeader) Less

func (ah AcceptHeader) Less(i, j int) bool

Less function for sort.Interface interface.

func (AcceptHeader) Match

func (ah AcceptHeader) Match(mtype string) bool

Match is the same function as AcceptHeader.Negotiate. It implements simplified interface to match only one type and return only matched or not information.

Example
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	ah := mimeheader.ParseAcceptHeader("image/png")
	fmt.Println(ah.Match("application/json"))

	ah.Add(mimeheader.MimeHeader{MimeType: mimeheader.MimeType{Type: "application", Subtype: "*"}})
	fmt.Println(ah.Match("application/json"))
}
Output:

false
true

func (AcceptHeader) Negotiate

func (ah AcceptHeader) Negotiate(ctypes []string, dtype string) (accept MimeHeader, mimeType string, matched bool)

Negotiate return appropriate type fot current accept list from supported (common) mime types. First parameter returns matched value from accept header. Second parameter returns matched common type. Third parameter returns matched common type or default type applied.

Example
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	header := "image/*; q=0.9; s=4, application/json; q=0.9; b=3;, text/plain,image/png;q=0.9, image/jpeg,image/svg;q=0.8"
	ah := mimeheader.ParseAcceptHeader(header)

	fmt.Println(ah.Negotiate([]string{"application/xml", "image/tiff"}, "text/javascript"))
	fmt.Println(ah.Negotiate([]string{"application/xml", "image/png"}, "text/javascript"))
	fmt.Println(ah.Negotiate([]string{"application/xml", "image/svg"}, "text/javascript"))
	fmt.Println(ah.Negotiate([]string{"text/dart", "application/dart"}, "text/javascript"))
}
Output:

image/* image/tiff true
image/png image/png true
image/* image/svg true
 text/javascript false

func (*AcceptHeader) Set

func (ah *AcceptHeader) Set(mhs []MimeHeader)

Set all valid headers to AcceprHeader (override old ones). Sorting will be applied.

Example
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	ah := mimeheader.ParseAcceptHeader("image/png")
	fmt.Println(ah.Match("application/json"))

	ah.Add(mimeheader.MimeHeader{MimeType: mimeheader.MimeType{Type: "application", Subtype: "*"}})
	fmt.Println(ah.Match("application/json"))
}
Output:

func (*AcceptHeader) Swap

func (ah *AcceptHeader) Swap(i, j int)

Swap function for sort.Interface interface.

type MimeHeader

type MimeHeader struct {
	MimeType
	Quality float32
}

type MimeParseErr added in v0.0.2

type MimeParseErr struct {
	Err error
	Msg string
}

func (MimeParseErr) Error added in v0.0.2

func (e MimeParseErr) Error() string

func (MimeParseErr) Unwrap added in v0.0.2

func (e MimeParseErr) Unwrap() error

type MimeType

type MimeType struct {
	Type    string
	Subtype string
	Params  map[string]string
}

MimeType structure for media type (mime type).

func ParseMediaType

func ParseMediaType(mtype string) (MimeType, error)

ParseMediaType parses media type to MimeType structure.

Example (Exact)
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	// Parse media type
	mediaType := "application/json; q=1; param=test;"

	mimeType, err := mimeheader.ParseMediaType(mediaType)
	if err != nil {
		panic(err)
	}

	// Print string without params.
	fmt.Println(mimeType.String())
	// Print string with params.
	fmt.Println(mimeType.StringWithParams())
	// Parse input and match it.
	fmt.Println(mimeType.MatchText("application/json; param=test"))
	fmt.Println(mimeType.MatchText("application/xml; q=1"))
	fmt.Println(mimeType.MatchText("text/plain"))

	// Parse mime type.
	tmtype, err := mimeheader.ParseMediaType("application/json;q=0.3")
	if err != nil {
		panic(err)
	}

	// Match mime types.
	fmt.Println(mimeType.Match(tmtype))
}
Output:

application/json
application/json; param=test; q=1
true
false
false
true
Example (Wildcard)
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	// Parse media type
	mediaType := "application/*; q=1; param=test;"

	mimeType, err := mimeheader.ParseMediaType(mediaType)
	if err != nil {
		panic(err)
	}

	// Print string without params.
	fmt.Println(mimeType.String())
	// Print string with params.
	fmt.Println(mimeType.StringWithParams())
	// Parse input and match it.
	fmt.Println(mimeType.MatchText("application/json; param=test"))
	fmt.Println(mimeType.MatchText("application/xml; q=1"))
	fmt.Println(mimeType.MatchText("text/plain"))

	// Parse mime type.
	tmtype, err := mimeheader.ParseMediaType("application/json;q=0.3")
	if err != nil {
		panic(err)
	}

	// Match mime types.
	fmt.Println(mimeType.Match(tmtype))
}
Output:

application/*
application/*; param=test; q=1
true
true
false
true

func (MimeType) Match

func (mt MimeType) Match(target MimeType) bool

Match matches current structure with possible wildcards. MimeType structure (current) can be wildcard or specific type, like "text/*", "*/*", "text/plain".

Example
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	// Parse media type
	mediaType := "application/xml; q=1; param=test;"

	mimeType, err := mimeheader.ParseMediaType(mediaType)
	if err != nil {
		panic(err)
	}

	// Parse input and match it.
	fmt.Println(mimeType.Match(mimeheader.MimeType{Type: "application", Subtype: "xml"}))
	fmt.Println(mimeType.Match(mimeheader.MimeType{Type: "application", Subtype: "json"}))
}
Output:

true
false

func (MimeType) MatchText

func (mt MimeType) MatchText(target string) bool

MatchText matches current structure with possible wildcards. Target MUST be specific type, like "application/json", "text/plain" MimeType structure (current) can be wildcard or specific type, like "text/*", "*/*", "text/plain".

Example
package main

import (
	"fmt"

	"github.com/aohorodnyk/mimeheader"
)

func main() {
	// Parse media type
	mediaType := "application/json; q=1; param=test;"

	mimeType, err := mimeheader.ParseMediaType(mediaType)
	if err != nil {
		panic(err)
	}

	// Parse input and match it.
	fmt.Println(mimeType.MatchText("application/json; param=test"))
	fmt.Println(mimeType.MatchText("application/xml; q=1"))
	fmt.Println(mimeType.MatchText("text/plain"))
}
Output:

true
false
false

func (MimeType) String

func (mt MimeType) String() string

String builds mime type from type and subtype.

func (MimeType) StringWithParams

func (mt MimeType) StringWithParams() string

StringWithParams builds mime type from type and subtype with params.

func (MimeType) Valid

func (mt MimeType) Valid() bool

type MimeTypePartsErr added in v0.0.2

type MimeTypePartsErr struct {
	Msg string
}

func (MimeTypePartsErr) Error added in v0.0.2

func (e MimeTypePartsErr) Error() string

type MimeTypeWildcardErr added in v0.0.2

type MimeTypeWildcardErr struct {
	Msg string
}

func (MimeTypeWildcardErr) Error added in v0.0.2

func (e MimeTypeWildcardErr) Error() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL