http-security 0.1.0

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