http-accept-2.2.1/0000755000004100000410000000000014560735316013772 5ustar www-datawww-datahttp-accept-2.2.1/lib/0000755000004100000410000000000014560735316014540 5ustar www-datawww-datahttp-accept-2.2.1/lib/.DS_Store0000644000004100000410000001400414560735316016222 0ustar www-datawww-dataBud1lg1Scomphttplg1ScompghttpmoDDblobl:AhttpmodDblobl:Ahttpph1Scomp  @ @ @ @ EDSDB ` @ @ @http-accept-2.2.1/lib/http/0000755000004100000410000000000014560735316015517 5ustar www-datawww-datahttp-accept-2.2.1/lib/http/accept.rb0000644000004100000410000000073214560735316017305 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. # Copyright, 2016, by Matthew Kerwin. # Copyright, 2017, by Andy Brody. require_relative 'accept/version' # Accept: header require_relative 'accept/media_types' require_relative 'accept/content_type' # Accept-Encoding: header require_relative 'accept/encodings' # Accept-Language: header require_relative 'accept/languages' module HTTP module Accept end end http-accept-2.2.1/lib/http/accept/0000755000004100000410000000000014560735316016756 5ustar www-datawww-datahttp-accept-2.2.1/lib/http/accept/media_types.rb0000644000004100000410000000700214560735316021605 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. require 'strscan' require_relative 'parse_error' require_relative 'quoted_string' require_relative 'sort' require_relative 'media_types/map' module HTTP module Accept # Parse and process the HTTP Accept: header. module MediaTypes # According to https://tools.ietf.org/html/rfc7231#section-5.3.2 MIME_TYPE = /(?#{TOKEN})\/(?#{TOKEN})/ PARAMETER = /\s*;\s*(?#{TOKEN})=((?#{TOKEN})|(?#{QUOTED_STRING}))/ # A single entry in the Accept: header, which includes a mime type and associated parameters. MediaRange = Struct.new(:type, :subtype, :parameters) do def initialize(type, subtype = '*', parameters = {}) super(type, subtype, parameters) end def parameters_string return '' if parameters == nil or parameters.empty? parameters.collect do |key, value| "; #{key.to_s}=#{QuotedString.quote(value.to_s)}" end.join end def === other if other.is_a? self.class super else return self.mime_type === other end end def mime_type "#{type}/#{subtype}" end def to_s "#{type}/#{subtype}#{parameters_string}" end alias to_str to_s def quality_factor parameters.fetch('q', 1.0).to_f end def split(*args) return [type, subtype] end def self.parse_parameters(scanner, normalize_whitespace) parameters = {} while scanner.scan(PARAMETER) key = scanner[:key] # If the regular expression PARAMETER matched, it must be one of these two: if value = scanner[:value] parameters[key] = value elsif quoted_value = scanner[:quoted_value] parameters[key] = QuotedString.unquote(quoted_value, normalize_whitespace) end end return parameters end def self.parse(scanner, normalize_whitespace = true) return to_enum(:parse, scanner, normalize_whitespace) unless block_given? while scanner.scan(MIME_TYPE) type = scanner[:type] subtype = scanner[:subtype] parameters = parse_parameters(scanner, normalize_whitespace) yield self.new(type, subtype, parameters) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new("Could not parse entire string!") unless scanner.eos? end end def self.parse(text, normalize_whitespace = true) scanner = StringScanner.new(text) media_types = MediaRange.parse(scanner, normalize_whitespace) return Sort.by_quality_factor(media_types) end HTTP_ACCEPT = 'HTTP_ACCEPT'.freeze WILDCARD_MEDIA_RANGE = MediaRange.new("*", "*", {}).freeze # Parse the list of browser preferred content types and return ordered by priority. If no `Accept:` header is specified, the behaviour is the same as if `Accept: */*` was provided (according to RFC). def self.browser_preferred_media_types(env) if accept_content_types = env[HTTP_ACCEPT]&.strip unless accept_content_types.empty? return HTTP::Accept::MediaTypes.parse(accept_content_types) end end # According to http://tools.ietf.org/html/rfc7231#section-5.3.2: # A request without any Accept header field implies that the user agent will accept any media type in response. # You should treat a non-existent Accept header as */*. return [WILDCARD_MEDIA_RANGE] end end end end http-accept-2.2.1/lib/http/accept/sort.rb0000644000004100000410000000067414560735316020301 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. module HTTP module Accept module Sort # This sorts items with higher priority first, and keeps items with the same priority in the same relative order. def self.by_quality_factor(items) # We do this to get a stable sort: items.sort_by.with_index{|object, index| [-object.quality_factor, index]} end end end end http-accept-2.2.1/lib/http/accept/languages.rb0000644000004100000410000000500514560735316021251 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. # Copyright, 2021, by Khaled Hassan Hussein. require 'strscan' require_relative 'parse_error' require_relative 'sort' module HTTP module Accept module Languages # https://tools.ietf.org/html/rfc3066#section-2.1 LOCALE = /\*|[A-Z]{1,8}(-[A-Z0-9]{1,8})*/i # https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.9 QVALUE = /0(\.[0-9]{0,6})?|1(\.[0]{0,6})?/ # https://greenbytes.de/tech/webdav/rfc7231.html#quality.values LANGUAGE_RANGE = /(?#{LOCALE})(\s*;\s*q=(?#{QVALUE}))?/ # Provides an efficient data-structure for matching the Accept-Languages header to set of available locales according to https://tools.ietf.org/html/rfc7231#section-5.3.5 and https://tools.ietf.org/html/rfc4647#section-2.3 class Locales def self.expand(locale, into) parts = locale.split('-') while parts.size > 0 key = parts.join('-') into[key] ||= locale parts.pop end end def initialize(names) @names = names @patterns = {} @names.each{|name| self.class.expand(name, @patterns)} self.freeze end def freeze @names.freeze @patterns.freeze super end def each(&block) return to_enum unless block_given? @names.each(&block) end attr :names attr :patterns # Returns the intersection of others retaining order. def & languages languages.collect{|language_range| @patterns[language_range.locale]}.compact end def include? locale_name @patterns.include? locale_name end def join(*args) @names.join(*args) end def + others self.class.new(@names + others.to_a) end def to_a @names end end LanguageRange = Struct.new(:locale, :q) do def quality_factor (q || 1.0).to_f end def self.parse(scanner) return to_enum(:parse, scanner) unless block_given? while scanner.scan(LANGUAGE_RANGE) yield self.new(scanner[:locale], scanner[:q]) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new("Could not parse entire string!") unless scanner.eos? end end def self.parse(text) scanner = StringScanner.new(text) languages = LanguageRange.parse(scanner) return Sort.by_quality_factor(languages) end end end end http-accept-2.2.1/lib/http/accept/parse_error.rb0000644000004100000410000000027314560735316021630 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. module HTTP module Accept class ParseError < ArgumentError end end end http-accept-2.2.1/lib/http/accept/content_type.rb0000644000004100000410000000115314560735316022016 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. require_relative 'media_types' require_relative 'quoted_string' module HTTP module Accept # A content type is different from a media range, in that a content type should not have any wild cards. class ContentType < MediaTypes::MediaRange def initialize(type, subtype, parameters = {}) # We do some basic validation here: raise ArgumentError.new("#{self.class} can not have wildcards: #{type}", "#{subtype}") if type.include?('*') || subtype.include?('*') super end end end end http-accept-2.2.1/lib/http/accept/encodings.rb0000644000004100000410000000457414560735316021266 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016, by Matthew Kerwin. # Copyright, 2017-2024, by Samuel Williams. require 'strscan' require_relative 'parse_error' require_relative 'quoted_string' require_relative 'sort' module HTTP module Accept module Encodings # https://tools.ietf.org/html/rfc7231#section-5.3.4 CONTENT_CODING = TOKEN # https://tools.ietf.org/html/rfc7231#section-5.3.1 QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/ CODINGS = /(?#{CONTENT_CODING})(;q=(?#{QVALUE}))?/ ContentCoding = Struct.new(:encoding, :q) do def quality_factor (q || 1.0).to_f end def self.parse(scanner) return to_enum(:parse, scanner) unless block_given? while scanner.scan(CODINGS) yield self.new(scanner[:encoding], scanner[:q]) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new('Could not parse entire string!') unless scanner.eos? end end def self.parse(text) scanner = StringScanner.new(text) encodings = ContentCoding.parse(scanner) return Sort.by_quality_factor(encodings) end HTTP_ACCEPT_ENCODING = 'HTTP_ACCEPT_ENCODING'.freeze WILDCARD_CONTENT_CODING = ContentCoding.new('*', nil).freeze IDENTITY_CONTENT_CODING = ContentCoding.new('identity', nil).freeze # Parse the list of browser preferred content codings and return ordered by priority. If no # `Accept-Encoding:` header is specified, the behaviour is the same as if # `Accept-Encoding: *` was provided, and if a blank `Accept-Encoding:` header value is # specified, the behaviour is the same as if `Accept-Encoding: identity` was provided # (according to RFC). def self.browser_preferred_content_codings(env) if accept_content_codings = env[HTTP_ACCEPT_ENCODING]&.strip if accept_content_codings.empty? # "An Accept-Encoding header field with a combined field-value that is # empty implies that the user agent does not want any content-coding in # response." return [IDENTITY_CONTENT_CODING] else return HTTP::Accept::Encodings.parse(accept_content_codings) end end # "If no Accept-Encoding field is in the request, any content-coding # is considered acceptable by the user agent." return [WILDCARD_CONTENT_CODING] end end end end http-accept-2.2.1/lib/http/accept/quoted_string.rb0000644000004100000410000000174414560735316022200 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. module HTTP module Accept # According to https://tools.ietf.org/html/rfc7231#appendix-C TOKEN = /[!#$%&'*+\-.^_`|~0-9A-Z]+/i QUOTED_STRING = /"(?:.(?!(?#{TOKEN})(;q=(?#{QVALUE}))?/ Charset = Struct.new(:charset, :q) do def quality_factor (q || 1.0).to_f end def self.parse(scanner) return to_enum(:parse, scanner) unless block_given? while scanner.scan(CHARSETS) yield self.new(scanner[:charset], scanner[:q]) # Are there more? break unless scanner.scan(/\s*,\s*/) end raise ParseError.new('Could not parse entire string!') unless scanner.eos? end end def self.parse(text) scanner = StringScanner.new(text) charsets = Charset.parse(scanner) return Sort.by_quality_factor(charsets) end HTTP_ACCEPT_CHARSET = 'HTTP_ACCEPT_CHARSET'.freeze WILDCARD_CHARSET = Charset.new('*', nil).freeze # Parse the list of browser preferred charsets and return ordered by priority. def self.browser_preferred_charsets(env) if accept_charsets = env[HTTP_ACCEPT_CHARSET]&.strip if accept_charsets.empty? # https://tools.ietf.org/html/rfc7231#section-5.3.3 : # # Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) # # Because of the `1#` rule, an empty header value is not considered valid. raise ParseError.new('Could not parse entire string!') else return HTTP::Accept::Charsets.parse(accept_charsets) end end # "A request without any Accept-Charset header field implies that the # user agent will accept any charset in response." return [WILDCARD_CHARSET] end end end end http-accept-2.2.1/lib/http/accept/media_types/0000755000004100000410000000000014560735316021261 5ustar www-datawww-datahttp-accept-2.2.1/lib/http/accept/media_types/map.rb0000644000004100000410000000340314560735316022363 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. module HTTP module Accept module MediaTypes # Map a set of mime types to objects. class Map WILDCARD = "*/*".freeze def initialize @media_types = {} end def freeze unless frozen? @media_types.freeze @media_types.each{|key,value| value.freeze} super end end # Given a list of content types (e.g. from browser_preferred_content_types), return the best converter. Media types can be an array of MediaRange or String values. def for(media_types) media_types.each do |media_range| mime_type = case media_range when String then media_range else media_range.mime_type end if object = @media_types[mime_type] return object, media_range end end return nil end def []= media_range, object @media_types[media_range] = object end def [] media_range @media_types[media_range] end # Add a converter to the collection. A converter can be anything that responds to #content_type. Objects will be considered in the order they are added, subsequent objects cannot override previously defined media types. `object` must respond to #split('/', 2) which should give the type and subtype. def << object type, subtype = object.split('/', 2) # We set the default if not specified already: @media_types[WILDCARD] = object if @media_types.empty? if type != '*' @media_types["#{type}/*"] ||= object if subtype != '*' @media_types["#{type}/#{subtype}"] ||= object end end return self end end end end end http-accept-2.2.1/lib/http/accept/version.rb0000644000004100000410000000024614560735316020772 0ustar www-datawww-data# frozen_string_literal: true # Released under the MIT License. # Copyright, 2016-2024, by Samuel Williams. module HTTP module Accept VERSION = "2.2.1" end end http-accept-2.2.1/checksums.yaml.gz.sig0000444000004100000410000000060014560735316020035 0ustar www-datawww-datarկ1Ar%h.s<^̽k_J'+|ܥ۳>}5-3’zZ|Bb/|uX'IYVmP_\y;ZͿux DyF#΢]Jw|v+i K-*k/'lgsr.EKE@V?yktK= 0".freeze) if s.respond_to? :required_rubygems_version= s.metadata = { "funding_uri" => "https://github.com/sponsors/ioquatix/" } if s.respond_to? :metadata= s.require_paths = ["lib".freeze] s.authors = ["Samuel Williams".freeze, "Matthew Kerwin".freeze, "Alif Rachmawadi".freeze, "Andy Brody".freeze, "Ian Oxley".freeze, "Khaled Hassan Hussein".freeze, "Olle Jonsson".freeze, "Robert Pritzkow".freeze] s.cert_chain = ["-----BEGIN CERTIFICATE-----\nMIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11\nZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK\nCZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz\nMjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd\nMBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj\nbzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB\nigKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2\n9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW\nsGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE\ne5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN\nXibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss\nRZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn\ntUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM\nzp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW\nxm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O\nBBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs\naWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE\ncBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl\nxCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/\nc1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp\n8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws\nJkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP\neX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt\nQ2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8\nvoD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=\n-----END CERTIFICATE-----\n".freeze] s.date = "2024-02-05" s.files = ["lib/.DS_Store".freeze, "lib/http/accept.rb".freeze, "lib/http/accept/charsets.rb".freeze, "lib/http/accept/content_type.rb".freeze, "lib/http/accept/encodings.rb".freeze, "lib/http/accept/languages.rb".freeze, "lib/http/accept/media_types.rb".freeze, "lib/http/accept/media_types/map.rb".freeze, "lib/http/accept/parse_error.rb".freeze, "lib/http/accept/quoted_string.rb".freeze, "lib/http/accept/sort.rb".freeze, "lib/http/accept/version.rb".freeze, "license.md".freeze, "readme.md".freeze] s.homepage = "https://github.com/ioquatix/http-accept".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 3.0".freeze) s.rubygems_version = "3.3.15".freeze s.summary = "Parse Accept and Accept-Language HTTP headers.".freeze end http-accept-2.2.1/readme.md0000644000004100000410000001054214560735316015553 0ustar www-datawww-data# HTTP::Accept Provides a robust set of parsers for dealing with HTTP `Accept`, `Accept-Language`, `Accept-Encoding`, `Accept-Charset` headers. [![Development Status](https://github.com/socketry/http-accept/workflows/Test/badge.svg)](https://github.com/socketry/http-accept/actions?workflow=Test) ## Motivation I've been [developing some tools for building RESTful endpoints](https://github.com/ioquatix/utopia/blob/master/lib/utopia/controller/respond.rb) and part of that involved versioning. After reviewing the options, I settled on using the `Accept: application/json;version=1` method [as outlined here](http://labs.qandidate.com/blog/2014/10/16/using-the-accept-header-to-version-your-api/). The `version=1` part of the `media-type` is a `parameter` as defined by [RFC7231 Section 3.1.1.1](https://tools.ietf.org/html/rfc7231#section-3.1.1.1). After reviewing several existing different options for parsing the `Accept:` header, I noticed a disturbing trend: `header.split(',')`. Because parameters may contain quoted strings which contain commas, this is clearly not an appropriate way to parse the header. I am concerned about correctness, security and performance. As such, I implemented this gem to provide a simple high level interface for both parsing and correctly interpreting these headers. ## Installation Add this line to your application's Gemfile: ``` ruby gem 'http-accept' ``` And then execute: $ bundle Or install it yourself as: $ gem install http-accept You can then require it in your code like so: ``` ruby require 'http/accept' ``` ## Usage Here are some examples of how to parse various headers. ### Parsing Accept: headers You can parse the incoming `Accept:` header: ``` ruby media_types = HTTP::Accept::MediaTypes.parse("text/html;q=0.5, application/json; version=1") expect(media_types[0].mime_type).to be == "application/json" expect(media_types[0].parameters).to be == {'version' => '1'} expect(media_types[1].mime_type).to be == "text/html" expect(media_types[1].parameters).to be == {'q' => '0.5'} ``` Normally, you'd want to match the media types against some set of available mime types: ``` ruby module ToJSON def content_type HTTP::Accept::ContentType.new("application", "json", charset: 'utf-8') end # Used for inserting into map. def split(*args) content_type.split(*args) end def convert(object, options) object.to_json end end module ToXML # Are you kidding? end map = HTTP::Accept::MediaTypes::Map.new map << ToJSON map << ToXML object, media_range = map.for(media_types) content = object.convert(model, media_range.parameters) response = [200, {'Content-Type' => object.content_type}, [content]] ``` ### Parsing Accept-Language: headers You can parse the incoming `Accept-Language:` header: ``` ruby languages = HTTP::Accept::Languages.parse("da, en-gb;q=0.8, en;q=0.7") expect(languages[0].locale).to be == "da" expect(languages[1].locale).to be == "en-gb" expect(languages[2].locale).to be == "en" ``` Normally, you'd want to match the languages against some set of available localizations: ``` ruby available_localizations = HTTP::Accept::Languages::Locales.new(["en-nz", "en-us"]) # Given the languages that the user wants, and the localizations available, compute the set of desired localizations. desired_localizations = available_localizations & languages ``` The `desired_localizations` in the example above is a subset of `available_localizations`. `HTTP::Accept::Languages::Locales` provides an efficient data-structure for matching the Accept-Languages header to set of available localizations according to and ## Contributing We welcome contributions to this project. 1. Fork it. 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 new Pull Request. ### Developer Certificate of Origin This project uses the [Developer Certificate of Origin](https://developercertificate.org/). All contributors to this project must agree to this document to have their contributions accepted. ### Contributor Covenant This project is governed by the [Contributor Covenant](https://www.contributor-covenant.org/). All contributors and participants agree to abide by its terms. http-accept-2.2.1/data.tar.gz.sig0000444000004100000410000000060014560735316016605 0ustar www-datawww-dataA"]P#y+>>ÏqEd'Gub.e*4)Y`مH|.*.ko&rP6Bz(YHѾVfvgN*]lt9uTnwڼ>_vUkk\ \CBRh{=ֻC׉ӛ r<"|Q/tHhpF-IMOCG67.28UE2Zv.qcg& K'VQv%Q`mҗ6TXbG`iYpXnNp^wu?k6k)`ݓBoy^Jn9W 9 http-accept-2.2.1/license.md0000644000004100000410000000250314560735316015736 0ustar www-datawww-data# MIT License Copyright, 2016-2024, by Samuel Williams. Copyright, 2016, by Matthew Kerwin. Copyright, 2016, by Alif Rachmawadi. Copyright, 2017, by Andy Brody. Copyright, 2019, by Robert Pritzkow. Copyright, 2020, by Olle Jonsson. Copyright, 2021, by Khaled Hassan Hussein. Copyright, 2022, by Ian Oxley. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. http-accept-2.2.1/metadata.gz.sig0000444000004100000410000000060014560735316016667 0ustar www-datawww-datap) 9Ǖ\ /0ʯz_2Wvt)\(cP؉U(S OQquX9( fA%4~7s`;ԈB_\rkN^avlg gؠdo#R}gTVڤlʰo,¡7i{N'*Z[r 36JnbŶ1]flxX|MI& @ya \${b~*ܮt:[}9#w'êæ dAt mݞZ>K͉Dpr[^ X ?5t3wh6^ɐge $(uE h/;C$E)