acceptable 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|