Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

accept_headers

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

accept_headers

  • 0.1.0
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

Build Status

AcceptHeaders

AcceptHeaders is a ruby library that does content negotiation and parses and sorts http accept headers.

Some features of the library are:

  • Strict adherence to RFC 2616, specifically section 14
  • Full support for the Accept, Accept-Encoding, and Accept-Language HTTP request headers
  • Accept-Charset is not supported because it's obsolete
  • Parser tested against all IANA registered media types and encodings
  • A comprehensive spec suite that covers many edge cases

This library is optimistic when parsing headers. If a specific media type, encoding, or language can't be parsed, is in an invalid format, or contains invalid characters, it will skip that specific entry when constructing the sorted list. If a q value can't be read or is in the wrong format (more than 3 decimal places), it will default it to 0.001 so it still has a chance to match. Lack of an explicit q value of course defaults to 1.

Installation

Add this line to your application's Gemfile:

gem 'accept_headers'

And then execute:

$ bundle

Or install it yourself as:

$ gem install accept_headers

Usage

Accept

AcceptHeaders::MediaType::Negotiator is a class that is initialized with an Accept header string and will internally store an array of MediaTypes in descending order according to the spec, which takes into account q value, type/subtype and extensions specificity.

accept_header = 'Accept: text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5'
media_types = AcceptHeaders::MediaType::Negotiator.new(accept_header)

media_types.list

# Returns:

[
  AcceptHeaders::MediaType.new('text', 'html', extensions: { 'level' => '1' }),
  AcceptHeaders::MediaType.new('text', 'html', q: 0.7),
  AcceptHeaders::MediaType.new('*', '*', q: 0.5),
  AcceptHeaders::MediaType.new('text', 'html', q: 0.4, extensions: { 'level' => '2' }),
  AcceptHeaders::MediaType.new('text', '*', q: 0.3)
]

#negotiate takes an array of media range strings supported (by your API or route/controller) and returns the best supported MediaType and the extensions params from the matching internal media type.

This will first check the available list for any matching media types with a q of 0 and skip any matches. It does this because the RFC specifies that if the q value is 0, then content with this parameter is not acceptable. Then it'll look to the highest q values and look for matches in descending q value order and return the first match (accounting for wildcards). Finally, if there are no matches, it returns nil.

# The same media_types variable as above
media_types.negotiate(['text/html', 'text/plain'])

# Returns this equivalent:

AcceptHeader::MediaType.new('text', 'html', extensions: { 'level' => '1' })

It returns the matching MediaType, so you can see which one matched and also access the extensions params. For example, if you wanted to put your API version in the extensions, you could then retrieve the value.

versions_header = 'Accept: application/json;version=2,application/json;version=1;q=0.8'
media_types = AcceptHeaders::MediaType::Negotiator.new(versions_header)

m = media_types.negotiate('application/json')
puts m.extensions['version'] # returns '2'

#accept?:

media_types.accept?('text/html') # true

Accept-Encoding

AcceptHeader::Encoding::Encoding:

accept_encoding = 'Accept-Encoding: deflate; q=0.5, gzip, compress; q=0.8, identity'
encodings = AcceptHeaders::Encoding::Negotiator.new(accept_encoding)

encodings.list

# Returns:

[
  AcceptHeaders::Encoding.new('gzip'),
  AcceptHeaders::Encoding.new('compress', q: 0.8),
  AcceptHeaders::Encoding.new('deflate', q: 0.5)
]

#negotiate:

encodings.negotiate(['gzip', 'compress'])

# Returns this equivalent:

AcceptHeader::Encoding.new('gzip')

#accept?:

encodings.accept?('gzip') # true

# Identity is accepted as long as it's not explicitly rejected 'identity;q=0'

encodings.accept?('identity') # true

Accept-Language

Accept::Language::Negotiator:

accept_language = 'Accept-Language: en-*, en-us, *;q=0.8'
languages = AcceptHeaders::Language::Negotiator.new(accept_language)

languages.list

# Returns:

[
  AcceptHeaders::Language.new('en', 'us'),
  AcceptHeaders::Language.new('en', '*'),
  AcceptHeaders::Language.new('*', '*', q: 0.8)
]

#negotiate:

languages.negotiate(['en-us', 'zh-Hant'])

# Returns this equivalent:

AcceptHeaders::Language.new('en', 'us')

#accept?:

languages.accept?('en-gb') # true

Rack Middleware

Add the middleware:

require 'accept_headers/middleware'
use AcceptHeaders::Middleware
run YourApp

Simple way to set the content response headers based on the request accept headers and the supported media types, encodings, and languages provided by the app or route.

class YourApp
  def initialize(app)
    @app = app
  end

  def call(env)
    # List your arrays of supported media types, encodings, languages. This can be global or per route/controller
    supported_media_types = %w[application/json application/xml text/html text/plain]
    supported_encodings = %w[gzip identify]
    supported_languages = %w[en-US en-GB]

    # Call the Negotiators and pass in the supported arrays and it'll return the best match
    matched_media_type = env["accept_headers.media_types"].negotiate(supported_media_types)
    matched_encoding = env["accept_headers.encodings"].negotiate(supported_encodings)
    matched_language = env["accept_headers.languages"].negotiate(supported_languages)

    # Set a default, in this case an empty string, in case of a bad header that cannot be parsed
    # The return value is a MediaType, Encoding, or Language depending on the case:
    # On MediaType, you can call #type ('text'), #subtype ('html'), #media_range ('text/html') to get the stringified parts
    # On Encoding, you can call #encoding to get the string encoding ('gzip')
    # On Language, you can call #primary_tag ('en'), #subtag ('us'), or #language_tag ('en-us')
    headers = {
      'Content-Type' => matched_media_type ? matched_media_type.media_range : '',
      'Content-Encoding' => matched_encoding ? matched_encoding.encoding : '',
      'Content-Language' => matched_language ? matched_language.language_tag : '',
    }

    [200, headers, ["Hello World!"]]
  end
end

Contributing

  1. Fork it ( https://github.com/[my-github-username]/accept_headers/fork )
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create a new Pull Request

FAQs

Package last updated on 19 Apr 2019

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc