acceptable 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +28 -0
- data/lib/rack/acceptable/const.rb +37 -0
- data/lib/rack/acceptable/data/mime.types +20 -0
- data/lib/rack/acceptable/language_tag.rb +405 -0
- data/lib/rack/acceptable/middleware/fake_accept.rb +28 -0
- data/lib/rack/acceptable/middleware/formats.rb +69 -0
- data/lib/rack/acceptable/middleware/provides.rb +97 -0
- data/lib/rack/acceptable/mimetypes.rb +293 -0
- data/lib/rack/acceptable/mixin/headers.rb +88 -0
- data/lib/rack/acceptable/mixin/media.rb +56 -0
- data/lib/rack/acceptable/request.rb +45 -0
- data/lib/rack/acceptable/utils.rb +231 -0
- data/lib/rack/acceptable/version.rb +7 -0
- data/lib/rack/acceptable.rb +27 -0
- metadata +88 -0
@@ -0,0 +1,231 @@
|
|
1
|
+
# encoding: binary
|
2
|
+
|
3
|
+
require 'rack/acceptable/const'
|
4
|
+
|
5
|
+
module Rack #:nodoc:
|
6
|
+
module Acceptable #:nodoc:
|
7
|
+
module Utils
|
8
|
+
|
9
|
+
#--
|
10
|
+
# http://tools.ietf.org/html/rfc2616#section-2.1
|
11
|
+
# http://tools.ietf.org/html/rfc2616#section-2.2
|
12
|
+
# http://tools.ietf.org/html/rfc2616#section-3.9
|
13
|
+
#++
|
14
|
+
|
15
|
+
QUALITY_PATTERN = '\s*(?:;\s*q=(0(?:\.\d{0,3})?|1(?:\.0{0,3})?))?'.freeze
|
16
|
+
QVALUE_REGEX = /\A(?:0(?:\.\d{0,3})?|1(?:\.0{0,3})?)\z/.freeze
|
17
|
+
QUALITY_REGEX = /\s*;\s*q\s*=([^;\s]*)/i.freeze
|
18
|
+
|
19
|
+
QVALUE_DEFAULT = 1.00
|
20
|
+
QVALUE = 'q'.freeze
|
21
|
+
|
22
|
+
# see benchmarks/simple/split_bench.rb
|
23
|
+
if RUBY_VERSION < '1.9.1'
|
24
|
+
|
25
|
+
PAIR_SPLITTER = /\=/.freeze
|
26
|
+
HYPHEN_SPLITTER = /-/
|
27
|
+
COMMA_SPLITTER = /,/
|
28
|
+
SEMICOLON_SPLITTER = /;/.freeze
|
29
|
+
|
30
|
+
else
|
31
|
+
|
32
|
+
PAIR_SPLITTER = '='.freeze
|
33
|
+
HYPHEN_SPLITTER = Const::HYPHEN
|
34
|
+
COMMA_SPLITTER = Const::COMMA
|
35
|
+
SEMICOLON_SPLITTER = Const::SEMICOLON
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
COMMA_WS_SPLITTER = /,\s*/.freeze
|
40
|
+
TOKEN_PATTERN = "[A-Za-z0-9#{Regexp.escape('!#$&%\'*+-.^_`|~')}]+".freeze
|
41
|
+
|
42
|
+
module_function
|
43
|
+
|
44
|
+
# ==== Parameters
|
45
|
+
# header<String>::
|
46
|
+
# The 'Accept' request-header, one of:
|
47
|
+
# * Accept
|
48
|
+
# * Accept-Charset
|
49
|
+
# * Accept-Encoding
|
50
|
+
# * Accept-Language
|
51
|
+
#
|
52
|
+
# ==== Returns
|
53
|
+
# Result of parsing. An Array with entries (as a +Strings+) and
|
54
|
+
# associated quality factors (qvalues). Default qvalue is 1.0.
|
55
|
+
#
|
56
|
+
# ==== Raises
|
57
|
+
# ArgumentError:: There's a malformed qvalue in header.
|
58
|
+
#
|
59
|
+
# ==== Notes
|
60
|
+
# * It checks *only* quality factors (full syntactical inspection of
|
61
|
+
# the HTTP header is *not* a task of simple qvalues extractor).
|
62
|
+
# * It does *not* perform additional operations (downcase etc),
|
63
|
+
# thereto a bunch of more specific parsers is provided.
|
64
|
+
#
|
65
|
+
def extract_qvalues(header)
|
66
|
+
header.split(COMMA_WS_SPLITTER).map! { |entry|
|
67
|
+
QUALITY_REGEX === entry
|
68
|
+
thing = $` || entry
|
69
|
+
if !(qvalue = $1)
|
70
|
+
[thing, QVALUE_DEFAULT]
|
71
|
+
elsif QVALUE_REGEX === qvalue
|
72
|
+
[thing, qvalue.to_f]
|
73
|
+
else
|
74
|
+
raise ArgumentError, "Malformed quality factor: #{qvalue.inspect}"
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
#:stopdoc:
|
80
|
+
|
81
|
+
def parse_header(header, regex)
|
82
|
+
header.split(COMMA_SPLITTER).map! do |entry|
|
83
|
+
raise unless regex === entry
|
84
|
+
[$1, ($2 || QVALUE_DEFAULT).to_f]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
#:startdoc:
|
89
|
+
|
90
|
+
HTTP_ACCEPT_TOKEN_REGEX = /^\s*(#{Utils::TOKEN_PATTERN})#{Utils::QUALITY_PATTERN}\s*$/o.freeze
|
91
|
+
|
92
|
+
module_function
|
93
|
+
|
94
|
+
# ==== Parameters
|
95
|
+
# provides<Array>:: The Array of available Content-Codings. Could be empty.
|
96
|
+
# accepts<Array>:: The Array of acceptable Content-Codings. Could be empty.
|
97
|
+
#
|
98
|
+
# ==== Returns
|
99
|
+
# The best one of available Content-Codings (as a +String+) or +nil+.
|
100
|
+
#
|
101
|
+
# ==== Notes
|
102
|
+
# Available and acceptable Content-Codings are supposed to be *downcased*
|
103
|
+
# According to section 3.5 of RFC 2616, Content-Codings are *case-insensitive*.
|
104
|
+
#
|
105
|
+
def detect_best_encoding(provides, accepts)
|
106
|
+
return nil if provides.empty?
|
107
|
+
|
108
|
+
identity = provides.include?(Const::IDENTITY) # presence of 'identity' in available content-codings
|
109
|
+
identity_or_wildcard = true # absence of explicit 'identity;q=0' or '*;q=0'
|
110
|
+
|
111
|
+
# RFC 2616, sec. 14.3:
|
112
|
+
# If no Accept-Encoding field is present in a request, the server
|
113
|
+
# MAY assume that the client will accept any content coding. In this
|
114
|
+
# case, if "identity" is one of the available content-codings, then
|
115
|
+
# the server SHOULD use the "identity" content-coding, unless it has
|
116
|
+
# additional information that a different content-coding is meaningful
|
117
|
+
# to the client.
|
118
|
+
|
119
|
+
return Const::IDENTITY if identity && accepts.empty?
|
120
|
+
#return (identity ? Const::IDENTITY : provides.first) if accepts.empty?
|
121
|
+
|
122
|
+
# RFC 2616, sec. 14.3:
|
123
|
+
# The "identity" content-coding is always acceptable, unless
|
124
|
+
# specifically refused because the Accept-Encoding field includes
|
125
|
+
# "identity;q=0", or because the field includes "*;q=0" and does
|
126
|
+
# not explicitly include the "identity" content-coding. If the
|
127
|
+
# Accept-Encoding field-value is empty, then only the "identity"
|
128
|
+
# encoding is acceptable.
|
129
|
+
|
130
|
+
candidates = []
|
131
|
+
expansion = nil
|
132
|
+
i = 0
|
133
|
+
|
134
|
+
accepts.sort_by { |_,q| [-q,i+=1] }.each do |c,q|
|
135
|
+
if q == 0
|
136
|
+
identity_or_wildcard = false if c == Const::IDENTITY || c == Const::WILDCARD
|
137
|
+
elsif c == Const::WILDCARD
|
138
|
+
expansion ||= provides - accepts.map { |c| c.first }
|
139
|
+
candidates.concat expansion
|
140
|
+
else
|
141
|
+
candidates << c
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
(candidates & provides).first ||
|
146
|
+
(Const::IDENTITY if identity && identity_or_wildcard) ||
|
147
|
+
nil
|
148
|
+
end
|
149
|
+
|
150
|
+
# ==== Parameters
|
151
|
+
# provides<Array>:: The Array of available Charsets. Could be empty.
|
152
|
+
# accepts<Array>:: The Array of acceptable Charsets. Could be empty.
|
153
|
+
#
|
154
|
+
# ==== Returns
|
155
|
+
# The best one of available Charsets (as a +String+) or +nil+.
|
156
|
+
#
|
157
|
+
# ==== Notes
|
158
|
+
# Available and acceptable Charsets are supposed to be *downcased*.
|
159
|
+
# According to section 3.4 of RFC 2616, Charsets are *case-insensitive*.
|
160
|
+
#
|
161
|
+
def detect_best_charset(provides, accepts)
|
162
|
+
return nil if provides.empty?
|
163
|
+
|
164
|
+
# RFC 2616, sec 14.2:
|
165
|
+
# If no Accept-Charset header is present, the default is that any
|
166
|
+
# character set is acceptable. If an Accept-Charset header is present,
|
167
|
+
# and if the server cannot send a response which is acceptable
|
168
|
+
# according to the Accept-Charset header, then the server SHOULD send
|
169
|
+
# an error response with the 406 (not acceptable) status code, though
|
170
|
+
# the sending of an unacceptable response is also allowed.
|
171
|
+
|
172
|
+
return provides.first if accepts.empty?
|
173
|
+
|
174
|
+
expansion = nil
|
175
|
+
candidates = []
|
176
|
+
i = 0
|
177
|
+
|
178
|
+
accepts << [Const::ISO_8859_1, 1.0] unless accepts.assoc(Const::ISO_8859_1) || accepts.assoc(Const::WILDCARD)
|
179
|
+
|
180
|
+
accepts.sort_by { |_,q| [-q,i+=1] }.each do |c,q|
|
181
|
+
|
182
|
+
next if q == 0
|
183
|
+
|
184
|
+
if c == Const::WILDCARD
|
185
|
+
|
186
|
+
# RFC 2616, sec 14.2:
|
187
|
+
# The special value "*", if present in the Accept-Charset field,
|
188
|
+
# matches every character set (including ISO-8859-1) which is not
|
189
|
+
# mentioned elsewhere in the Accept-Charset field. If no "*" is present
|
190
|
+
# in an Accept-Charset field, then all character sets not explicitly
|
191
|
+
# mentioned get a quality value of 0, except for ISO-8859-1, which gets
|
192
|
+
# a quality value of 1 if not explicitly mentioned.
|
193
|
+
|
194
|
+
expansion ||= provides - accepts.map { |c,_| c }
|
195
|
+
candidates.concat expansion
|
196
|
+
else
|
197
|
+
candidates << c
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
(candidates & provides).first
|
202
|
+
end
|
203
|
+
|
204
|
+
#--
|
205
|
+
# RFC 2616, sec. 3.10:
|
206
|
+
# White space is not allowed within the tag and all tags are case-
|
207
|
+
# insensitive.
|
208
|
+
#
|
209
|
+
# RFC 4647, sec. 2.1
|
210
|
+
# Note that the ABNF [RFC4234] in [RFC2616] is incorrect, since it disallows the
|
211
|
+
# use of digits anywhere in the 'language-range' (see [RFC2616errata]).
|
212
|
+
#++
|
213
|
+
|
214
|
+
HTTP_ACCEPT_LANGUAGE_REGEX = /^\s*(\*|[a-z]{1,8}(?:-[a-z\d]{1,8}|-\*)*)#{Utils::QUALITY_PATTERN}\s*$/io.freeze
|
215
|
+
|
216
|
+
def normalize_header(header)
|
217
|
+
ret = header.strip
|
218
|
+
ret.gsub!(/\s*(?:,\s*)+/, Const::COMMA)
|
219
|
+
ret.gsub!(/^,|,$/, Const::EMPTY_STRING)
|
220
|
+
ret
|
221
|
+
end
|
222
|
+
|
223
|
+
def blank?(s)
|
224
|
+
s.empty? || s.strip.empty?
|
225
|
+
end
|
226
|
+
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# EOF
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rack', '>=1.0.0'
|
3
|
+
require 'rack'
|
4
|
+
|
5
|
+
module Rack #:nodoc:
|
6
|
+
module Acceptable
|
7
|
+
|
8
|
+
# common
|
9
|
+
autoload :Const , 'rack/acceptable/const'
|
10
|
+
autoload :Utils , 'rack/acceptable/utils'
|
11
|
+
autoload :MIMETypes , 'rack/acceptable/mimetypes'
|
12
|
+
autoload :LanguageTag , 'rack/acceptable/language_tag'
|
13
|
+
|
14
|
+
# request and mixins
|
15
|
+
autoload :Headers , 'rack/acceptable/mixin/headers'
|
16
|
+
autoload :Media , 'rack/acceptable/mixin/media'
|
17
|
+
autoload :Request , 'rack/acceptable/request'
|
18
|
+
|
19
|
+
# middleware
|
20
|
+
autoload :Formats , 'rack/acceptable/middleware/formats'
|
21
|
+
autoload :Provides , 'rack/acceptable/middleware/provides'
|
22
|
+
autoload :FakeAccept , 'rack/acceptable/middleware/fake_accept'
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# EOF
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acceptable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 1
|
9
|
+
version: 0.2.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- SSDany
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-04-16 00:00:00 +04:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rack
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 0
|
30
|
+
- 0
|
31
|
+
version: 1.0.0
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description: HTTP Accept parsers for Rack.
|
35
|
+
email: inadsence@gmail.com
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files:
|
41
|
+
- README.rdoc
|
42
|
+
files:
|
43
|
+
- README.rdoc
|
44
|
+
- lib/rack/acceptable/const.rb
|
45
|
+
- lib/rack/acceptable/language_tag.rb
|
46
|
+
- lib/rack/acceptable/middleware/formats.rb
|
47
|
+
- lib/rack/acceptable/middleware/provides.rb
|
48
|
+
- lib/rack/acceptable/middleware/fake_accept.rb
|
49
|
+
- lib/rack/acceptable/mimetypes.rb
|
50
|
+
- lib/rack/acceptable/mixin/headers.rb
|
51
|
+
- lib/rack/acceptable/mixin/media.rb
|
52
|
+
- lib/rack/acceptable/request.rb
|
53
|
+
- lib/rack/acceptable/utils.rb
|
54
|
+
- lib/rack/acceptable/version.rb
|
55
|
+
- lib/rack/acceptable.rb
|
56
|
+
- lib/rack/acceptable/data/mime.types
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/SSDany/rack-acceptable
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
version: "0"
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project: acceptable
|
83
|
+
rubygems_version: 1.3.6
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: HTTP Accept parsers for Rack.
|
87
|
+
test_files: []
|
88
|
+
|