strict_request_uri 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/.travis.yml +8 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/README.md +56 -0
- data/Rakefile +69 -0
- data/images/icon.png +0 -0
- data/images/strict_uri.png +0 -0
- data/lib/strict_request_uri.rb +103 -0
- data/spec/spec_helper.rb +9 -0
- data/spec/strict_request_uri_spec.rb +205 -0
- data/strict_request_uri.gemspec +71 -0
- metadata +156 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 738571a4631e7084c03451d4fd855a6c3cc0ed70
|
4
|
+
data.tar.gz: ac07e480bb801f3ff018354ea1589243415b66a3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 582c0060f97ccfb5dba596ab0c9d8a4f5c23acf364a57d7b21077ff66c20cd42a020f4e2d0380dff439c876ee88a00ff9b349099e4b576f19cc92e3a35e14835
|
7
|
+
data.tar.gz: 36eb35790951f82a32eba98f78feed081d5b9558010089950ab28cae62f5ebe37230415cbc83a7f658ee63223a0bc83881260e2b71ea8df8e7b13cb1d92382e5
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2016 WeTransfer
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# strict_request_uri
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/WeTransfer/strict_request_uri.svg?branch=master)](https://travis-ci.org/WeTransfer/strict_request_uri)
|
4
|
+
|
5
|
+
![Nasty URL](images/strict_uri.png)
|
6
|
+
|
7
|
+
Reject requests with an invalid REQUEST_URI at the gate.
|
8
|
+
Some HTTP clients will happily append raw junk bytes to your URL before doing a request. Others
|
9
|
+
will first append junk, and then URL-encode it.
|
10
|
+
|
11
|
+
What you want for a valid URL is something that is
|
12
|
+
|
13
|
+
* properly URL-encoded
|
14
|
+
* is valid UTF-8 once URL-decoded
|
15
|
+
|
16
|
+
This gem provides a Rack middleware that is going to try to decode REQUEST_URI, and if it
|
17
|
+
cannot be decoded, an error page will be rendered instead.
|
18
|
+
|
19
|
+
use StrictRequestUri do |env|
|
20
|
+
# You can use the preserved invalid path+qs to do additional checks/logging
|
21
|
+
logger.warn "Invalid URL received"
|
22
|
+
logger.warn env['strict_uri.original_invalid_url']
|
23
|
+
|
24
|
+
# You can also render a suggestion or redirect based on the suggested fixed URL.
|
25
|
+
# The fixed URL will have all junk at the end removed until the string becomes a valid URL.
|
26
|
+
logger.warn "Suggested instead:"
|
27
|
+
logger.warn env['strict_uri.proposed_fixed_url']
|
28
|
+
|
29
|
+
[400, {'Content-Type' => 'text/plain'}, ['This is a no go mate']]
|
30
|
+
end
|
31
|
+
|
32
|
+
Note that `PATH_INFO` and `QUERY_STRING` variables in Rack env are going to be replaced
|
33
|
+
with something harmless (because they get used to render self-URLs and so on).
|
34
|
+
|
35
|
+
You can also use it in your Rails middleware stack, and render a controller in return
|
36
|
+
|
37
|
+
Rails.application.config.middleware.insert_after 'Warden::Manager', StrictRequestUri do | env |
|
38
|
+
ErrorPagesController.action(:invalid_url).call(env)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
## Contributing to strict_request_uri
|
43
|
+
|
44
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
45
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
46
|
+
* Fork the project.
|
47
|
+
* Start a feature/bugfix branch.
|
48
|
+
* Commit and push until you are happy with your contribution.
|
49
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
50
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
51
|
+
|
52
|
+
## Copyright
|
53
|
+
|
54
|
+
Copyright (c) 2016 WeTransfer. See LICENSE.txt for
|
55
|
+
further details.
|
56
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require_relative 'lib/strict_request_uri'
|
15
|
+
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |gem|
|
18
|
+
gem.version = StrictRequestUri::VERSION
|
19
|
+
gem.name = "strict_request_uri"
|
20
|
+
gem.homepage = "https://github.com/WeTransfer/strict_request_uri"
|
21
|
+
gem.license = "MIT"
|
22
|
+
gem.description = %Q{Reject Rack requests with an invalid URL}
|
23
|
+
gem.summary = %Q{and show an error page instead}
|
24
|
+
gem.email = "me@julik.nl"
|
25
|
+
gem.authors = ["Julik Tarkhanov"]
|
26
|
+
# dependencies defined in Gemfile
|
27
|
+
end
|
28
|
+
# Jeweler::RubygemsDotOrgTasks.new
|
29
|
+
|
30
|
+
require 'rspec/core'
|
31
|
+
require 'rspec/core/rake_task'
|
32
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
33
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
34
|
+
end
|
35
|
+
|
36
|
+
desc "Code coverage detail"
|
37
|
+
task :simplecov do
|
38
|
+
ENV['COVERAGE'] = "true"
|
39
|
+
Rake::Task['spec'].execute
|
40
|
+
end
|
41
|
+
|
42
|
+
task :default => :spec
|
43
|
+
|
44
|
+
require 'rdoc/task'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
47
|
+
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "strict_request_uri #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
53
|
+
|
54
|
+
namespace :fury do
|
55
|
+
desc "Pick up the .gem file from pkg/ and push it to Gemfury"
|
56
|
+
task :release do
|
57
|
+
# IMPORTANT: You need to have the `fury` gem installed, and you need to be logged in.
|
58
|
+
# Please DO READ about "impersonation", which is how you push to your company account instead
|
59
|
+
# of your personal account!
|
60
|
+
# https://gemfury.com/help/collaboration#impersonation
|
61
|
+
paths = Dir.glob(__dir__ + '/pkg/*.gem')
|
62
|
+
if paths.length != 1
|
63
|
+
raise "Must have found only 1 .gem path, but found %s" % paths.inspect
|
64
|
+
end
|
65
|
+
escaped_gem_path = Shellwords.escape(paths.shift)
|
66
|
+
`fury push #{escaped_gem_path} --as=wetransfer`
|
67
|
+
end
|
68
|
+
end
|
69
|
+
task :release => [:clean, 'gemspec:generate', 'git:release', :build, 'fury:release']
|
data/images/icon.png
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
# Sometimes junk gets appended to the URLs clicked in e-mails.
|
4
|
+
# This junk then gets sent by browsers undecoded, and causes Unicode-related
|
5
|
+
# exceptions when the full request URI or path is rebuilt for ActionDispatch.
|
6
|
+
#
|
7
|
+
# We can fix this by iteratively removing bytes from the end of the URL until it becomes parseable.
|
8
|
+
#
|
9
|
+
# We do however answer to those URLs with a 400 to indicate clients that those requests are not
|
10
|
+
# welcome. This also allows us to tell the users that they are using a URL which is in fact
|
11
|
+
# not really valid.
|
12
|
+
class StrictRequestUri
|
13
|
+
VERSION = '1.0.2'
|
14
|
+
|
15
|
+
# Inits the middleware. The optional proc should be a Rack application that
|
16
|
+
# will render the error page. To make a controller render that page,
|
17
|
+
# use <ControllerClass>.action()
|
18
|
+
#
|
19
|
+
# use RequestUriCleanup do | env |
|
20
|
+
# ErrorsController.action(:invalid_request).call(env)
|
21
|
+
# end
|
22
|
+
def initialize(app, &error_page_rack_app)
|
23
|
+
@app = app
|
24
|
+
@error_page_app = if error_page_rack_app
|
25
|
+
error_page_rack_app
|
26
|
+
else
|
27
|
+
->(env) { [400, {'Content-Type' => 'text/plain'}, ['Invalid request URI']] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def call(env)
|
32
|
+
# Compose the original URL, taking care not to treat it as UTF8.
|
33
|
+
# Do not use Rack::Request since it is not really needed for this
|
34
|
+
# (and _might be doing something with strings that we do not necessarily want).
|
35
|
+
# For instance, Rack::Request does regexes when you ask it for the REQUEST_URI
|
36
|
+
tainted_url = reconstruct_original_url(env)
|
37
|
+
return @app.call(env) if string_parses_to_url?(tainted_url)
|
38
|
+
|
39
|
+
# At this point we know the URL is fishy.
|
40
|
+
referer = to_utf8(env['HTTP_REFERER'] || '(unknown)')
|
41
|
+
env['rack.errors'].puts("Invalid URL received from referer #{referer}") if env['rack.errors']
|
42
|
+
|
43
|
+
# Save the original URL so that the error page can use it
|
44
|
+
env['strict_uri.original_invalid_url'] = tainted_url
|
45
|
+
env['strict_uri.proposed_fixed_url'] =
|
46
|
+
truncate_bytes_at_end_until_parseable(tainted_url)
|
47
|
+
|
48
|
+
# Strictly speaking, the parts we are going to put into QUERY_STRING and PATH_INFO
|
49
|
+
# should _only_ be used for rendering the error page, and that's it.
|
50
|
+
#
|
51
|
+
# We can therefore wipe them clean.
|
52
|
+
env['PATH_INFO'] = '/invalid-url'
|
53
|
+
env['QUERY_STRING'] = ''
|
54
|
+
|
55
|
+
# And render the error page using the provided error app.
|
56
|
+
@error_page_app.call(env)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Reconstruct the original URL from the Rack env variables, converting them to
|
62
|
+
# binary encoding before joining them together. This ensures the "bad" bits stay
|
63
|
+
# broken and no errors are raised.
|
64
|
+
def reconstruct_original_url(env)
|
65
|
+
original_url_components = env.values_at('SCRIPT_NAME', 'PATH_INFO')
|
66
|
+
unless env['QUERY_STRING'].empty?
|
67
|
+
original_url_components << '?'
|
68
|
+
original_url_components << env['QUERY_STRING']
|
69
|
+
end
|
70
|
+
original_url_components.map{|e| e.unpack("C*").pack("C*") }.join
|
71
|
+
end
|
72
|
+
|
73
|
+
def string_parses_to_url?(string)
|
74
|
+
# We can have two sorts of possible damage.
|
75
|
+
# First sort is when raw garbage bytes just get added to the URL.
|
76
|
+
# This can be caught by attempting to parse the URL with URI().
|
77
|
+
parsed_uri = URI(string)
|
78
|
+
# The second kind of damage is when there _is_ in fact a normal URL-encoded
|
79
|
+
# character, which URI() will happily swallow - but this character is not valid
|
80
|
+
# UTF-8 and will make the Rails router crash. For our purposes it _also_ means
|
81
|
+
# the URL has been damaged bayound repair.
|
82
|
+
decoded_uri = Rack::Utils.unescape(string).unpack("U*")
|
83
|
+
true
|
84
|
+
rescue URI::InvalidURIError, ArgumentError
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_utf8(str, repl_char='?')
|
89
|
+
str.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: repl_char)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Chops off one byte from the given string iteratively, until the string can be parsed
|
93
|
+
# using URI() _and_ decoded using Rack::Utils.unescape.
|
94
|
+
def truncate_bytes_at_end_until_parseable(str)
|
95
|
+
cutoff = -1
|
96
|
+
until str.empty? do
|
97
|
+
str = str[0..cutoff]
|
98
|
+
return str if string_parses_to_url?(str)
|
99
|
+
cutoff -= 1
|
100
|
+
end
|
101
|
+
''
|
102
|
+
end
|
103
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe StrictRequestUri do
|
4
|
+
context 'with a good URL' do
|
5
|
+
it 'returns the downstream response for URLs without a query string' do
|
6
|
+
env_with_valid_chars = {
|
7
|
+
'SCRIPT_NAME' => 'myscript',
|
8
|
+
'PATH_INFO' => '/items/457',
|
9
|
+
'QUERY_STRING' => ''
|
10
|
+
}
|
11
|
+
app = ->(env) {
|
12
|
+
expect(env['QUERY_STRING']).to eq('')
|
13
|
+
expect(env['SCRIPT_NAME']).to eq('myscript')
|
14
|
+
expect(env['PATH_INFO']).to eq('/items/457')
|
15
|
+
:total_success
|
16
|
+
}
|
17
|
+
expect(described_class.new(app).call(env_with_valid_chars)).to eq(:total_success)
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'returns the downstream response for URLs with a query string' do
|
21
|
+
env_with_valid_chars = {
|
22
|
+
'SCRIPT_NAME' => 'myscript',
|
23
|
+
'PATH_INFO' => '/items/457',
|
24
|
+
'QUERY_STRING' => 'foo=bar'
|
25
|
+
}
|
26
|
+
app = ->(env) {
|
27
|
+
expect(env['QUERY_STRING']).to eq('foo=bar')
|
28
|
+
expect(env['SCRIPT_NAME']).to eq('myscript')
|
29
|
+
expect(env['PATH_INFO']).to eq('/items/457')
|
30
|
+
:total_success
|
31
|
+
}
|
32
|
+
expect(described_class.new(app).call(env_with_valid_chars)).to eq(:total_success)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'with a garbage URL' do
|
37
|
+
it 'calls the default error app if none was set' do
|
38
|
+
# All of those 3 components are required as per Rack spec
|
39
|
+
env_with_invalid_chars = {
|
40
|
+
'SCRIPT_NAME' => '',
|
41
|
+
'PATH_INFO' => [107, 17, 52, 140].pack("C*"),
|
42
|
+
'QUERY_STRING' => '',
|
43
|
+
}
|
44
|
+
|
45
|
+
middleware = described_class.new(nil) # will raise if the wrapped app is called
|
46
|
+
status, headers, body = middleware.call(env_with_invalid_chars)
|
47
|
+
expect(status).to eq(400)
|
48
|
+
expect(headers).to eq({'Content-Type' => 'text/plain'})
|
49
|
+
expect(body).to eq(['Invalid request URI'])
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'with junk after the path calls the error app instead' do
|
53
|
+
# The related bug ticket - https://www.assembla.com/spaces/wetransfer-2-0/tickets/1568
|
54
|
+
script_name = 'myscript'
|
55
|
+
valid_part = '/items/457'
|
56
|
+
invalid_part = [107, 17, 52, 140].pack("C*")
|
57
|
+
invalid_path_info = valid_part.encode(Encoding::BINARY) + invalid_part
|
58
|
+
|
59
|
+
expect {
|
60
|
+
invalid_path_info.encode(Encoding::UTF_8)
|
61
|
+
}.to raise_error(Encoding::UndefinedConversionError)
|
62
|
+
|
63
|
+
# All of those 3 components are required as per Rack spec
|
64
|
+
env_with_invalid_chars = {
|
65
|
+
'SCRIPT_NAME' => script_name,
|
66
|
+
'PATH_INFO' => invalid_path_info,
|
67
|
+
'QUERY_STRING' => '',
|
68
|
+
'rack.errors' => double('IO')
|
69
|
+
}
|
70
|
+
|
71
|
+
# Do not render from the controller since we do not have a complete Rack env hash initialized.
|
72
|
+
# Instead, sneak in our own testing Proc.
|
73
|
+
error_handling_app = ->(env) {
|
74
|
+
# Make sure those are now safe to concat with each other
|
75
|
+
expect(env['SCRIPT_NAME']).to eq("myscript")
|
76
|
+
expect(env['PATH_INFO']).to eq("/invalid-url")
|
77
|
+
expect(env['QUERY_STRING']).to eq('')
|
78
|
+
|
79
|
+
# Make sure the original broken URL is stashed somewhere for the error page to act on
|
80
|
+
expect(env['strict_uri.original_invalid_url']).to include(script_name)
|
81
|
+
expect(env['strict_uri.original_invalid_url']).to include(invalid_path_info)
|
82
|
+
expect(env['strict_uri.proposed_fixed_url']).to eq("myscript/items/457k")
|
83
|
+
|
84
|
+
# Ensure those are valid - if this call raises the spec will fail
|
85
|
+
env['PATH_INFO'].encode(Encoding::UTF_8)
|
86
|
+
|
87
|
+
[200, {'Content-Type' => 'text/plain'}, ['This is an error message']]
|
88
|
+
}
|
89
|
+
|
90
|
+
expect(env_with_invalid_chars['rack.errors']).to receive(:puts).
|
91
|
+
with("Invalid URL received from referer (unknown)")
|
92
|
+
|
93
|
+
middleware = described_class.new(nil, &error_handling_app) # will raise if the wrapped app is called
|
94
|
+
status, headers, body = middleware.call(env_with_invalid_chars)
|
95
|
+
expect(status).to eq(200)
|
96
|
+
expect(headers).to eq({'Content-Type' => 'text/plain'})
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'after the query string calls the error app instead' do
|
100
|
+
# The related bug ticket - https://www.assembla.com/spaces/wetransfer-2-0/tickets/1568
|
101
|
+
script_name = 'myscript'
|
102
|
+
valid_path_info = '/items/457'
|
103
|
+
query_string = 'foo=bar&baz=bad'
|
104
|
+
invalid_part = [107, 17, 52, 140].pack("C*")
|
105
|
+
invalid_qs = query_string.encode(Encoding::BINARY) + invalid_part
|
106
|
+
|
107
|
+
expect {
|
108
|
+
invalid_qs.encode(Encoding::UTF_8)
|
109
|
+
}.to raise_error(Encoding::UndefinedConversionError)
|
110
|
+
|
111
|
+
# All of those 3 components are required as per Rack spec
|
112
|
+
env_with_invalid_chars = {
|
113
|
+
'SCRIPT_NAME' => script_name,
|
114
|
+
'PATH_INFO' => valid_path_info,
|
115
|
+
'QUERY_STRING' => invalid_qs,
|
116
|
+
'HTTP_REFERER' => 'https://megacorp.co/webmail.asp',
|
117
|
+
'rack.errors' => double('IO')
|
118
|
+
}
|
119
|
+
|
120
|
+
error_handling_app = ->(env) {
|
121
|
+
# Make sure those are now safe to concat with each other
|
122
|
+
expect(env['SCRIPT_NAME']).to eq("myscript")
|
123
|
+
expect(env['PATH_INFO']).to eq("/invalid-url")
|
124
|
+
expect(env['QUERY_STRING']).to eq('')
|
125
|
+
|
126
|
+
expect(env['strict_uri.original_invalid_url']).to include(valid_path_info)
|
127
|
+
expect(env['strict_uri.original_invalid_url']).to include(invalid_qs)
|
128
|
+
|
129
|
+
# Ensure those are valid - if this call raises the spec will fail
|
130
|
+
env['QUERY_STRING'].encode(Encoding::UTF_8)
|
131
|
+
|
132
|
+
[200, {'Content-Type' => 'text/plain'}, ['This is an error message']]
|
133
|
+
}
|
134
|
+
expect(env_with_invalid_chars['rack.errors']).to receive(:puts).
|
135
|
+
with('Invalid URL received from referer https://megacorp.co/webmail.asp')
|
136
|
+
|
137
|
+
# nil will raise if the wrapped app is called
|
138
|
+
middleware = described_class.new(nil, &error_handling_app)
|
139
|
+
status, headers, body = middleware.call(env_with_invalid_chars)
|
140
|
+
expect(status).to eq(200)
|
141
|
+
expect(headers).to eq({'Content-Type' => 'text/plain'})
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context 'with production examples of garbled PATH_INFO' do
|
146
|
+
it 'triggers with URL-encoded bytes that are invalid UTF-8 when decoded' do
|
147
|
+
# Example from production - here at the end of the url you have
|
148
|
+
# \xC2 in percent-encoded form, which cannot be converted to UTF-8.
|
149
|
+
# If we let it through, it _can_ be rendered using request.original_url
|
150
|
+
# but _cannot_ be used by Journey when url_for is called.
|
151
|
+
#
|
152
|
+
# So we have to intercept this as well.
|
153
|
+
path = '/downloads/918ab1e20586c0b4e1875b3789b84ec720150615173920' +
|
154
|
+
'/a480d026f46b0f0533cec47545cd5e2820150615173920/0130a0%C2'
|
155
|
+
fake_action = ->(env) {
|
156
|
+
# Make sure those are now safe to concat with each other
|
157
|
+
expect(env['SCRIPT_NAME']).to eq('')
|
158
|
+
expect(env['PATH_INFO']).to eq('/invalid-url')
|
159
|
+
expect(env['QUERY_STRING']).to eq('')
|
160
|
+
|
161
|
+
expect(env['strict_uri.original_invalid_url']).not_to be_nil
|
162
|
+
expect(env['strict_uri.proposed_fixed_url']).to match(/\/0130a0$/)
|
163
|
+
expect(env['strict_uri.proposed_fixed_url']).to match(/^\/downloads\//)
|
164
|
+
[200, :h, :b]
|
165
|
+
}
|
166
|
+
invalid_env = {
|
167
|
+
'SCRIPT_NAME' => '',
|
168
|
+
'PATH_INFO' => path,
|
169
|
+
'QUERY_STRING' => '',
|
170
|
+
}
|
171
|
+
# nil will raise if the wrapped app is called
|
172
|
+
middleware = described_class.new(nil, &fake_action)
|
173
|
+
status, headers, body = middleware.call(invalid_env)
|
174
|
+
expect(status).to eq(200)
|
175
|
+
expect(headers).to eq(:h)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'triggers with raw bytes that cannot be URL-decoded' do
|
179
|
+
# Example from production - just random gunk appended to the end of the URL
|
180
|
+
path = '/downloads/918ab1e20586c0b4e1875b3789b84ec720150615173920' +
|
181
|
+
'/a480d026f46b0f0533cec47545cd5e2820150615173920/0130a' + '���'
|
182
|
+
|
183
|
+
fake_action = ->(env) {
|
184
|
+
# Make sure those are now safe to concat with each other
|
185
|
+
expect(env['SCRIPT_NAME']).to eq('')
|
186
|
+
expect(env['PATH_INFO']).to eq('/invalid-url')
|
187
|
+
expect(env['QUERY_STRING']).to eq('')
|
188
|
+
|
189
|
+
expect(env['request_uri_cleanup.original_invalid_url']).not_to be_nil
|
190
|
+
expect(env['request_uri_cleanup.proposed_fixed_url']).to match(/^\/downloads\//)
|
191
|
+
expect(env['request_uri_cleanup.proposed_fixed_url']).to match(/0130$/)
|
192
|
+
|
193
|
+
[200, :h, :b]
|
194
|
+
}
|
195
|
+
invalid_env = {
|
196
|
+
'SCRIPT_NAME' => '',
|
197
|
+
'PATH_INFO' => path,
|
198
|
+
'QUERY_STRING' => '',
|
199
|
+
}
|
200
|
+
middleware = described_class.new(nil)
|
201
|
+
status, headers, body = middleware.call(invalid_env)
|
202
|
+
expect(status).to eq(400)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: strict_request_uri 1.0.2 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "strict_request_uri"
|
9
|
+
s.version = "1.0.2"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Julik Tarkhanov"]
|
14
|
+
s.date = "2016-11-25"
|
15
|
+
s.description = "Reject Rack requests with an invalid URL"
|
16
|
+
s.email = "me@julik.nl"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.md"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".rspec",
|
24
|
+
".travis.yml",
|
25
|
+
"Gemfile",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.md",
|
28
|
+
"Rakefile",
|
29
|
+
"images/icon.png",
|
30
|
+
"images/strict_uri.png",
|
31
|
+
"lib/strict_request_uri.rb",
|
32
|
+
"spec/spec_helper.rb",
|
33
|
+
"spec/strict_request_uri_spec.rb",
|
34
|
+
"strict_request_uri.gemspec"
|
35
|
+
]
|
36
|
+
s.homepage = "https://github.com/WeTransfer/strict_request_uri"
|
37
|
+
s.licenses = ["MIT"]
|
38
|
+
s.rubygems_version = "2.4.5.1"
|
39
|
+
s.summary = "and show an error page instead"
|
40
|
+
|
41
|
+
if s.respond_to? :specification_version then
|
42
|
+
s.specification_version = 4
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
45
|
+
s.add_runtime_dependency(%q<rack>, ["~> 1"])
|
46
|
+
s.add_development_dependency(%q<gemfury>, [">= 0"])
|
47
|
+
s.add_development_dependency(%q<rspec>, ["~> 3.0"])
|
48
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
49
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0"])
|
50
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.2.1"])
|
51
|
+
s.add_development_dependency(%q<simplecov>, [">= 0"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<rack>, ["~> 1"])
|
54
|
+
s.add_dependency(%q<gemfury>, [">= 0"])
|
55
|
+
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
56
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
57
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
58
|
+
s.add_dependency(%q<jeweler>, ["~> 2.2.1"])
|
59
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
60
|
+
end
|
61
|
+
else
|
62
|
+
s.add_dependency(%q<rack>, ["~> 1"])
|
63
|
+
s.add_dependency(%q<gemfury>, [">= 0"])
|
64
|
+
s.add_dependency(%q<rspec>, ["~> 3.0"])
|
65
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
66
|
+
s.add_dependency(%q<bundler>, ["~> 1.0"])
|
67
|
+
s.add_dependency(%q<jeweler>, ["~> 2.2.1"])
|
68
|
+
s.add_dependency(%q<simplecov>, [">= 0"])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
metadata
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: strict_request_uri
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Julik Tarkhanov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-11-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rack
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: gemfury
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rdoc
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.12'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.12'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: bundler
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '1.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '1.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: jeweler
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 2.2.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 2.2.1
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Reject Rack requests with an invalid URL
|
112
|
+
email: me@julik.nl
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files:
|
116
|
+
- LICENSE.txt
|
117
|
+
- README.md
|
118
|
+
files:
|
119
|
+
- ".document"
|
120
|
+
- ".rspec"
|
121
|
+
- ".travis.yml"
|
122
|
+
- Gemfile
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- images/icon.png
|
127
|
+
- images/strict_uri.png
|
128
|
+
- lib/strict_request_uri.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/strict_request_uri_spec.rb
|
131
|
+
- strict_request_uri.gemspec
|
132
|
+
homepage: https://github.com/WeTransfer/strict_request_uri
|
133
|
+
licenses:
|
134
|
+
- MIT
|
135
|
+
metadata: {}
|
136
|
+
post_install_message:
|
137
|
+
rdoc_options: []
|
138
|
+
require_paths:
|
139
|
+
- lib
|
140
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '0'
|
150
|
+
requirements: []
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 2.4.5.1
|
153
|
+
signing_key:
|
154
|
+
specification_version: 4
|
155
|
+
summary: and show an error page instead
|
156
|
+
test_files: []
|