http-security 0.1.0

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.
Files changed (73) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +21 -0
  5. data/.yardopts +1 -0
  6. data/ChangeLog.md +17 -0
  7. data/Gemfile +17 -0
  8. data/LICENSE.txt +20 -0
  9. data/README.md +90 -0
  10. data/Rakefile +34 -0
  11. data/http-security.gemspec +23 -0
  12. data/lib/http/security.rb +2 -0
  13. data/lib/http/security/exceptions.rb +8 -0
  14. data/lib/http/security/headers.rb +12 -0
  15. data/lib/http/security/headers/cache_control.rb +36 -0
  16. data/lib/http/security/headers/content_security_policy.rb +71 -0
  17. data/lib/http/security/headers/content_security_policy_report_only.rb +10 -0
  18. data/lib/http/security/headers/pragma.rb +24 -0
  19. data/lib/http/security/headers/public_key_pins.rb +60 -0
  20. data/lib/http/security/headers/public_key_pins_report_only.rb +10 -0
  21. data/lib/http/security/headers/set_cookie.rb +75 -0
  22. data/lib/http/security/headers/strict_transport_security.rb +29 -0
  23. data/lib/http/security/headers/x_content_type_options.rb +24 -0
  24. data/lib/http/security/headers/x_frame_options.rb +39 -0
  25. data/lib/http/security/headers/x_permitted_cross_domain_policies.rb +47 -0
  26. data/lib/http/security/headers/x_xss_protection.rb +34 -0
  27. data/lib/http/security/http_date.rb +13 -0
  28. data/lib/http/security/malformed_header.rb +33 -0
  29. data/lib/http/security/parsers.rb +14 -0
  30. data/lib/http/security/parsers/cache_control.rb +62 -0
  31. data/lib/http/security/parsers/content_security_policy.rb +128 -0
  32. data/lib/http/security/parsers/content_security_policy_report_only.rb +10 -0
  33. data/lib/http/security/parsers/expires.rb +19 -0
  34. data/lib/http/security/parsers/parser.rb +408 -0
  35. data/lib/http/security/parsers/pragma.rb +25 -0
  36. data/lib/http/security/parsers/public_key_pins.rb +43 -0
  37. data/lib/http/security/parsers/public_key_pins_report_only.rb +10 -0
  38. data/lib/http/security/parsers/set_cookie.rb +62 -0
  39. data/lib/http/security/parsers/strict_transport_security.rb +42 -0
  40. data/lib/http/security/parsers/x_content_type_options.rb +19 -0
  41. data/lib/http/security/parsers/x_frame_options.rb +47 -0
  42. data/lib/http/security/parsers/x_permitted_cross_domain_policies.rb +33 -0
  43. data/lib/http/security/parsers/x_xss_protection.rb +27 -0
  44. data/lib/http/security/response.rb +323 -0
  45. data/lib/http/security/version.rb +5 -0
  46. data/spec/data/alexa.csv +100 -0
  47. data/spec/headers/cache_control_spec.rb +40 -0
  48. data/spec/headers/content_security_policy_spec.rb +46 -0
  49. data/spec/headers/pragma_spec.rb +26 -0
  50. data/spec/headers/public_key_pins_spec.rb +68 -0
  51. data/spec/headers/set_cookie_spec.rb +122 -0
  52. data/spec/headers/strict_transport_security_spec.rb +39 -0
  53. data/spec/headers/x_content_type_options_spec.rb +26 -0
  54. data/spec/headers/x_frame_options_spec.rb +86 -0
  55. data/spec/headers/x_permitted_cross_domain_policies_spec.rb +108 -0
  56. data/spec/headers/x_xss_protection_spec.rb +59 -0
  57. data/spec/parsers/cache_control_spec.rb +26 -0
  58. data/spec/parsers/content_security_policy_report_only_spec.rb +48 -0
  59. data/spec/parsers/content_security_policy_spec.rb +74 -0
  60. data/spec/parsers/expires_spec.rb +71 -0
  61. data/spec/parsers/parser_spec.rb +317 -0
  62. data/spec/parsers/pragma_spec.rb +10 -0
  63. data/spec/parsers/public_key_pins_spec.rb +81 -0
  64. data/spec/parsers/set_cookie_spec.rb +55 -0
  65. data/spec/parsers/strict_transport_security_spec.rb +62 -0
  66. data/spec/parsers/x_content_type_options_spec.rb +10 -0
  67. data/spec/parsers/x_frame_options_spec.rb +24 -0
  68. data/spec/parsers/x_permitted_cross_domain_policies_spec.rb +34 -0
  69. data/spec/parsers/x_xss_protection_spec.rb +39 -0
  70. data/spec/response_spec.rb +262 -0
  71. data/spec/spec_helper.rb +13 -0
  72. data/tasks/alexa.rb +40 -0
  73. metadata +171 -0
@@ -0,0 +1,10 @@
1
+ require 'http/security/parsers/content_security_policy'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class ContentSecurityPolicyReportOnly < ContentSecurityPolicy
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ require 'http/security/parsers/parser'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class Expires < Parser
7
+ # Expires
8
+ # Syntax:
9
+ # Expires = "Expires" ":" HTTP-date
10
+ # HTTP/1.1 clients and caches MUST treat other invalid date formats,
11
+ # especially including the value "0", as in the past (i.e., "already expired").
12
+ rule :expires do
13
+ http_date | (str('-').maybe >> digits).as(:numeric)
14
+ end
15
+ root :expires
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,408 @@
1
+ require 'http/security/http_date'
2
+
3
+ require "parslet"
4
+ require 'uri'
5
+
6
+ module HTTP
7
+ module Security
8
+ module Parsers
9
+ class Parser < Parslet::Parser
10
+ def self.character_match_rule(name, character)
11
+ rule(name) do
12
+ wsp? >> str(character) >> wsp?
13
+ end
14
+ end
15
+
16
+ def self.directive_rule(name,string=nil)
17
+ string ||= name.to_s.tr('_','-')
18
+
19
+ rule(name) do
20
+ stri(string).as(:key)
21
+ end
22
+ end
23
+
24
+ def self.field_directive_rule(name,directive)
25
+ rule(name) do
26
+ stri(directive).as(:key) >> (equals >> field_name.as(:value)).maybe
27
+ end
28
+ end
29
+
30
+ def self.numeric_directive_rule(name,directive)
31
+ rule(name) do
32
+ stri(directive).as(:key) >> equals >> (
33
+ digits.as(:numeric) |
34
+ (s_quote >> digits.as(:numeric) >> s_quote) |
35
+ (d_quote >> digits.as(:numeric) >> d_quote)
36
+ ).as(:value)
37
+ end
38
+ end
39
+
40
+ def stri(str)
41
+ #str.gsub!(/-/,"\-")
42
+ key_chars = str.split(//)
43
+
44
+ key_chars.collect! do |char|
45
+ if char.eql?("-")
46
+ match["#{Regexp.escape("\x2d")}"]
47
+ else
48
+ match["#{char.upcase}#{char.downcase}"]
49
+ end
50
+ end
51
+
52
+ key_chars.reduce(:>>)
53
+ end
54
+
55
+ #
56
+ # Directive Helpers
57
+ #
58
+ numeric_directive_rule :max_age, "max-age"
59
+ numeric_directive_rule :max_stale, "max-stale"
60
+ numeric_directive_rule :min_fresh, "min-fresh"
61
+ numeric_directive_rule :s_maxage, "s-maxage"
62
+ character_match_rule :equals, "="
63
+ character_match_rule :s_quote, "'"
64
+ character_match_rule :d_quote, '"'
65
+ character_match_rule :semicolon, ";"
66
+ character_match_rule :comma, ","
67
+
68
+ # HTTP-date = rfc1123-date | rfc850-date | asctime-date
69
+ # rfc1123-date = wkday "," SP date1 SP time SP "GMT"
70
+ # rfc850-date = weekday "," SP date2 SP time SP "GMT"
71
+ # asctime-date = wkday SP date3 SP time SP 4DIGIT
72
+ # date1 = 2DIGIT SP month SP 4DIGIT
73
+ # ; day month year (e.g., 02 Jun 1982)
74
+ # date2 = 2DIGIT "-" month "-" 2DIGIT
75
+ # ; day-month-year (e.g., 02-Jun-82)
76
+ # date3 = month SP ( 2DIGIT | ( SP 1DIGIT ))
77
+ # ; month day (e.g., Jun 2)
78
+ # time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
79
+ # ; 00:00:00 - 23:59:59
80
+ # wkday = "Mon" | "Tue" | "Wed"
81
+ # | "Thu" | "Fri" | "Sat" | "Sun"
82
+ # weekday = "Monday" | "Tuesday" | "Wednesday"
83
+ # | "Thursday" | "Friday" | "Saturday" | "Sunday"
84
+ # month = "Jan" | "Feb" | "Mar" | "Apr"
85
+ # | "May" | "Jun" | "Jul" | "Aug"
86
+ # | "Sep" | "Oct" | "Nov" | "Dec"
87
+ rule(:http_date) do
88
+ (
89
+ rfc1123_date |
90
+ rfc850_date |
91
+ asctime_date
92
+ ).as(:date)
93
+ end
94
+
95
+ rule(:rfc1123_date) do
96
+ wkday >> str(",") >> wsp >> date1 >> wsp >> time >> wsp >> zone
97
+ end
98
+
99
+ rule(:rfc850_date) do
100
+ weekday >> str(",") >> wsp >> date2 >> wsp >> time >> wsp >> zone
101
+ end
102
+
103
+ rule(:asctime_date) do
104
+ wkday >> wsp >> date3 >> wsp >> time >> wsp >> four_digit
105
+ end
106
+
107
+ #day month year (e.g., 02 Jun 1982)
108
+ rule(:date1) do
109
+ two_digit >> wsp >> month >> wsp >> four_digit
110
+ end
111
+
112
+ #day-month-year (e.g., 02-Jun-82)
113
+ rule(:date2) do
114
+ two_digit >> str("-") >> month >> str("-") >> two_digit
115
+ end
116
+
117
+ #month day (e.g., Jun 2)
118
+ rule(:date3) do
119
+ month >> wsp >> (two_digit | (wsp >> one_digit))
120
+ end
121
+
122
+ #00:00:00 - 23:59:59
123
+ rule(:time) do
124
+ two_digit >> str(":") >> two_digit >> str(":") >> two_digit
125
+ end
126
+
127
+ rule(:zone) do
128
+ str('UT') | str('GMT') | # Universal Time
129
+ # North American : UT
130
+ str('EST') | str('EDT') | # Eastern: - 5/ - 4
131
+ str('CST') | str('CDT') | # Central: - 6/ - 5
132
+ str('MST') | str('MDT') | # Mountain: - 7/ - 6
133
+ str('PST') | str('PDT') | # Pacific: - 8/ - 7
134
+ alpha | # Military: Z = UT;
135
+ # A:-1; (J not used)
136
+ # M:-12; N:+1; Y:+12
137
+ match['+-'] >> four_digit # Local differential
138
+ # hours+min. (HHMM)
139
+ end
140
+
141
+ rule(:four_digit) do
142
+ digit.repeat(4,4)
143
+ end
144
+
145
+ rule(:two_digit) do
146
+ digit.repeat(2,2)
147
+ end
148
+
149
+ rule(:one_digit) do
150
+ digit.repeat(1,1)
151
+ end
152
+
153
+ rule(:wkday) do
154
+ stri("Mon") |
155
+ stri("Tue") |
156
+ stri("Wed") |
157
+ stri("Thu") |
158
+ stri("Fri") |
159
+ stri("Sat") |
160
+ stri("Sun")
161
+ end
162
+
163
+ rule(:weekday) do
164
+ stri("Monday") |
165
+ stri("Tuesday") |
166
+ stri("Wednesday") |
167
+ stri("Thursday") |
168
+ stri("Friday") |
169
+ stri("Saturday") |
170
+ stri("Sunday")
171
+ end
172
+
173
+ rule(:month) do
174
+ stri("Jan") |
175
+ stri("Feb") |
176
+ stri("Mar") |
177
+ stri("Apr") |
178
+ stri("May") |
179
+ stri("Jun") |
180
+ stri("Jul") |
181
+ stri("Aug") |
182
+ stri("Sep") |
183
+ stri("Oct") |
184
+ stri("Nov") |
185
+ stri("Dec")
186
+ end
187
+
188
+ rule(:uri) {
189
+ (
190
+ scheme_fragment.maybe >>
191
+ (user_info >> str("@")).maybe >>
192
+ host_name >>
193
+ (str(":") >> digits).maybe >>
194
+ uri_path
195
+ ).as(:uri)
196
+ }
197
+
198
+ rule(:user_info) {
199
+ (
200
+ unreserved | pct_encoded | sub_delims | str(":")
201
+ ).repeat(0)
202
+ }
203
+
204
+ rule(:unreserved) { alpha | digit | str("-") | str(".") | str("_") | str("~") }
205
+ rule(:pct_encoded) { str("%") >> hex_digit >> hex_digit }
206
+ rule(:sub_delims) { match[Regexp.escape("!$&'()*+,;=")] }
207
+ rule(:pchar) { unreserved | pct_encoded | sub_delims | str("@") | str(":") }
208
+
209
+
210
+ rule(:fragment) { (pchar | str("/") | str("?")).repeat(0) }
211
+ rule(:query) { fragment }
212
+ rule(:path) { pchar.repeat(1) >> (str('/') >> pchar.repeat).repeat }
213
+
214
+ rule(:paramchar) { str(";").absent? >> pchar }
215
+ rule(:param) { (paramchar).repeat }
216
+ rule(:params) { param >> (str(';') >> param).repeat }
217
+
218
+ rule(:uri_path) {
219
+ (str('/').maybe >> path.maybe) >>
220
+ (str(';') >> params).maybe >>
221
+ (str('?') >> query).maybe >>
222
+ (str('#') >> fragment).maybe
223
+ }
224
+
225
+ rule(:header_extension) do
226
+ token.as(:name) >> (
227
+ equals >> ( token | quoted_string).as(:value)
228
+ ).maybe
229
+ end
230
+
231
+ #
232
+ # Basic Rules
233
+ #
234
+ # RFC 2616, Section 2.2
235
+ #
236
+ rule(:digit) { match["0-9"] }
237
+ rule(:digits) { digit.repeat(1) }
238
+ rule(:hex_digit) { match['0-9a-fA-F'] }
239
+ rule(:upper) { match['A-Z'] }
240
+ rule(:lower) { match['a-z'] }
241
+ rule(:alpha) { match['a-zA-Z'] }
242
+ rule(:alnum) { match['a-zA-Z0-9'] }
243
+ rule(:cntrl) { match["\x00-\x1f"] }
244
+ rule(:ascii) { match["\x00-\x7f"] }
245
+ rule(:wsp) { match[" \t"] }
246
+ rule(:wsp?) { wsp.repeat }
247
+ rule(:crlf) { str("\r\n") }
248
+ rule(:lws) { crlf.maybe >> wsp.repeat(1) }
249
+
250
+ #1*<any (US-ASCII) CHAR except SPACE, CTLs, or separators>
251
+ rule(:token) do
252
+ match[
253
+ #
254
+ '^\x00-\x1f\x7f' + # no CTLs
255
+ '\x20' + # no SPACE
256
+ Regexp.escape("()<>@,;:\\\"/[]?={} \t") # no separators
257
+ ].repeat(1)
258
+ end
259
+
260
+ rule(:quoted_string) do
261
+ d_quote >> (qdtext | quoted_pair).repeat(0).as(:string) >> d_quote
262
+ end
263
+ rule(:qdtext) do
264
+ match['^\x00-\x1f\x22\x7f'] | lws
265
+ end
266
+
267
+ rule(:quoted_pair) do
268
+ str('\\') >> ascii.as(:escaped_char)
269
+ end
270
+
271
+ rule(:field_name) do
272
+ valid_field_name | ( d_quote >> valid_field_name >> d_quote )
273
+ end
274
+
275
+ rule(:valid_field_name) do
276
+ (
277
+ stri("Access-Control-Allow-Origin") |
278
+ stri("Accept-Ranges") |
279
+ stri("Age") |
280
+ stri("Allow") |
281
+ stri("Cache-Control") |
282
+ stri("Connection") |
283
+ stri("Content-Encoding") |
284
+ stri("Content-Language") |
285
+ stri("Content-Length") |
286
+ stri("Content-Location") |
287
+ stri("Content-MD5") |
288
+ stri("Content-Disposition") |
289
+ stri("Content-Range") |
290
+ stri("Content-Type") |
291
+ stri("ETag") |
292
+ stri("Expires") |
293
+ stri("Last-Modified") |
294
+ stri("Link") |
295
+ stri("Location") |
296
+ stri("P3P") |
297
+ stri("Pragma") |
298
+ stri("Proxy-Authenticate") |
299
+ stri("Refresh") |
300
+ stri("Retry-After") |
301
+ stri("Server") |
302
+ stri("Set-Cookie") |
303
+ stri("Status") |
304
+ stri("Strict-Transport-Security") |
305
+ stri("Trailer") |
306
+ stri("Transfer-Encoding") |
307
+ stri("Upgrade") |
308
+ stri("Vary") |
309
+ stri("Via") |
310
+ stri("Warning") |
311
+ stri("WWW-Authenticate") |
312
+ stri("X-Frame-Options")
313
+ ).as(:field)
314
+ end
315
+
316
+ #
317
+ # URI Elements
318
+ #
319
+ rule(:scheme_fragment) { (scheme >> str(":") >> str("//")).maybe }
320
+ rule(:scheme) { ( alpha | digit ).repeat }
321
+ rule(:host_name) { ( alnum | match("[-_.]") ).repeat(1) }
322
+
323
+ class Transform < Parslet::Transform
324
+
325
+ rule(boolean: simple(:bool)) do
326
+ case bool
327
+ when '0', 'no', 'false' then false
328
+ when '1', 'yes', 'true' then true
329
+ end
330
+ end
331
+ rule(numeric: simple(:numeric)) { Integer(numeric) }
332
+
333
+ ESCAPED_CHARS = {
334
+ '0' => "\0",
335
+ 'a' => "\a",
336
+ 'b' => "\b",
337
+ 't' => "\t",
338
+ 'n' => "\n",
339
+ 'v' => "\v",
340
+ 'f' => "\f",
341
+ 'r' => "\r"
342
+ }
343
+ ESCAPED_CHARS.default_proc = proc { |hash,key| key }
344
+
345
+ rule(escaped_char: simple(:char)) { ESCAPED_CHARS[char] }
346
+ rule(string: simple(:text)) { text.to_s }
347
+ rule(string: sequence(:strings)) { strings.join }
348
+
349
+ rule(date: simple(:date)) { HTTPDate.parse(date.to_s) }
350
+ rule(uri: simple(:uri)) { URI.parse(uri) }
351
+
352
+ rule(list: simple(:element)) { [element] }
353
+ rule(list: subtree(:elements)) do
354
+ case elements
355
+ when Array then elements
356
+ else [elements]
357
+ end
358
+ end
359
+
360
+ rule(name: simple(:name)) { {name.to_s => true} }
361
+ rule(name: simple(:name), value: simple(:value)) do
362
+ {name.to_s => value}
363
+ end
364
+ rule(name: simple(:name), values: subtree(:tree)) do
365
+ {name.to_s => tree}
366
+ end
367
+
368
+ rule(key: simple(:name)) do
369
+ {name.to_s.downcase.tr('-','_').to_sym => true}
370
+ end
371
+ rule(key: simple(:name), value: simple(:value)) do
372
+ {name.to_s.downcase.tr('-','_').to_sym => value}
373
+ end
374
+ rule(key: simple(:name), values: subtree(:tree)) do
375
+ {name.to_s.downcase.tr('-','_').to_sym => tree}
376
+ end
377
+
378
+ rule(directives: subtree(:hashes)) do
379
+ case hashes
380
+ when Array
381
+ hashes.reduce do |hash,sub_hash|
382
+ hash.merge!(sub_hash) do |key,old_value,new_value|
383
+ case old_value
384
+ when Array then old_value << new_value
385
+ when nil then new_value
386
+ else [old_value, new_value]
387
+ end
388
+ end
389
+ end
390
+ else
391
+ hashes
392
+ end
393
+ end
394
+
395
+ end
396
+
397
+ def parse(string)
398
+ Transform.new.apply(super(string))
399
+ end
400
+
401
+ def self.parse(string)
402
+ new.parse(string)
403
+ end
404
+
405
+ end
406
+ end
407
+ end
408
+ end
@@ -0,0 +1,25 @@
1
+ require 'http/security/parsers/parser'
2
+
3
+ module HTTP
4
+ module Security
5
+ module Parsers
6
+ class Pragma < Parser
7
+ # Pragma
8
+ # Syntax:
9
+ # Pragma = "Pragma" ":" 1#pragma-directive
10
+ # pragma-directive = "no-cache" | extension-pragma
11
+ # extension-pragma = token [ "=" ( token | quoted-string ) ]
12
+ rule(:pragma) do
13
+ (
14
+ no_cache | header_extension
15
+ ).as(:directives)
16
+ end
17
+ root :pragma
18
+
19
+ rule(:no_cache) do
20
+ stri('no-cache').as(:key)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end