├── lib ├── open_uri_redirections │ └── version.rb ├── open_uri_redirections.rb └── open-uri │ └── redirections_patch.rb ├── Gemfile ├── Rakefile ├── .travis.yml ├── spec ├── samples │ ├── http_safe.response │ ├── https_unsafe.response │ ├── http_safe2.response │ ├── https_safe.response │ └── http_unsafe.response ├── spec_helper.rb └── redirections_spec.rb ├── .gitignore ├── open_uri_redirections.gemspec ├── LICENSE.txt └── README.md /lib/open_uri_redirections/version.rb: -------------------------------------------------------------------------------- 1 | module OpenUriRedirections 2 | VERSION = '0.2.1' 3 | end 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in open_uri_redirects.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler' 2 | require 'rspec/core/rake_task' 3 | 4 | Bundler::GemHelper.install_tasks 5 | RSpec::Core::RakeTask.new :spec 6 | 7 | task :default => :spec 8 | -------------------------------------------------------------------------------- /lib/open_uri_redirections.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require 'open-uri' 4 | require File.expand_path(File.join(File.dirname(__FILE__), 'open-uri/redirections_patch')) 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 1.8.7 4 | - 1.9.2 5 | - 1.9.3 6 | - 2.0.0 7 | - 2.1.0 8 | - 2.1.1 9 | - 2.1.2 10 | - 2.1.3 11 | - 2.1.4 12 | - 2.1.5 13 | - ruby-head 14 | - rbx-2 15 | - jruby 16 | -------------------------------------------------------------------------------- /spec/samples/http_safe.response: -------------------------------------------------------------------------------- 1 | HTTP/1.1 301 Moved Permanently 2 | Server: nginx 3 | Date: Mon, 03 Dec 2012 11:36:17 GMT 4 | Content-Type: text/html 5 | Content-Length: 178 6 | Connection: close 7 | Location: https://safe.com/ 8 | 9 | Redirecting to https://safe.com/ -------------------------------------------------------------------------------- /spec/samples/https_unsafe.response: -------------------------------------------------------------------------------- 1 | HTTP/1.1 301 Moved Permanently 2 | Server: nginx 3 | Date: Mon, 03 Dec 2012 11:36:17 GMT 4 | Content-Type: text/html 5 | Content-Length: 178 6 | Connection: close 7 | Location: http://unsafe.com/ 8 | 9 | Redirecting to http://unsafe.com. -------------------------------------------------------------------------------- /spec/samples/http_safe2.response: -------------------------------------------------------------------------------- 1 | HTTP/1.1 301 Moved Permanently 2 | Server: nginx 3 | Date: Mon, 03 Dec 2012 11:36:17 GMT 4 | Content-Type: text/html 5 | Content-Length: 178 6 | Connection: close 7 | Location: http://safe.com/ 8 | 9 | Redirecting to http://safe.com/ 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | .rvmrc 7 | .ruby-gemset 8 | .ruby-version 9 | .rubocop.yml 10 | Gemfile.lock 11 | InstalledFiles 12 | _yardoc 13 | coverage 14 | doc/ 15 | lib/bundler/man 16 | pkg 17 | rdoc 18 | spec/reports 19 | test/tmp 20 | test/version_tmp 21 | tmp 22 | -------------------------------------------------------------------------------- /spec/samples/https_safe.response: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx 3 | Date: Mon, 03 Dec 2012 11:37:51 GMT 4 | Content-Type: text/html; charset=utf-8 5 | Connection: keep-alive 6 | Status: 200 OK 7 | X-Runtime: 10 8 | ETag: "a9e1dd587bb233eb670ec06f7d553dbc" 9 | X-Frame-Options: deny 10 | Set-Cookie: _gh_sess=BAh7BzoPc2Vzc2lvbl9pZCIlZTc4ZDNlOGEwM2NlZDQ3Y2VhMDdlMTQyOTA4NWVmYzA6EF9jc3JmX3Rva2VuIjE0U2xsYUoybFFNSWxhWEtudHNvalJCVjZtVnJZcFVlRVk4WlpMbEZKWktRPQ%3D%3D--8ea4a235fd3c5e727d462e24b992fabd8f50050d; path=/; expires=Sat, 01-Jan-2022 00:00:00 GMT; secure; HttpOnly 11 | Content-Length: 21814 12 | Cache-Control: private, max-age=0, must-revalidate 13 | Strict-Transport-Security: max-age=2592000 14 | 15 | Hello, this is Safe. -------------------------------------------------------------------------------- /spec/samples/http_unsafe.response: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx 3 | Date: Mon, 03 Dec 2012 11:37:51 GMT 4 | Content-Type: text/html; charset=utf-8 5 | Connection: keep-alive 6 | Status: 200 OK 7 | X-Runtime: 10 8 | ETag: "a9e1dd587bb233eb670ec06f7d553dbc" 9 | X-Frame-Options: deny 10 | Set-Cookie: _gh_sess=BAh7BzoPc2Vzc2lvbl9pZCIlZTc4ZDNlOGEwM2NlZDQ3Y2VhMDdlMTQyOTA4NWVmYzA6EF9jc3JmX3Rva2VuIjE0U2xsYUoybFFNSWxhWEtudHNvalJCVjZtVnJZcFVlRVk4WlpMbEZKWktRPQ%3D%3D--8ea4a235fd3c5e727d462e24b992fabd8f50050d; path=/; expires=Sat, 01-Jan-2022 00:00:00 GMT; secure; HttpOnly 11 | Content-Length: 21814 12 | Cache-Control: private, max-age=0, must-revalidate 13 | Strict-Transport-Security: max-age=2592000 14 | 15 | Hello, this is Unsafe. -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | $: << File.join(File.dirname(__FILE__), "/../lib") 4 | require 'open_uri_redirections' 5 | require 'fakeweb' 6 | 7 | FakeWeb.allow_net_connect = false 8 | 9 | $samples_dir = File.dirname(__FILE__) + '/samples' 10 | 11 | ####################### 12 | # Faked web responses # 13 | ####################### 14 | 15 | FakeWeb.register_uri(:get, "http://safe.com/", :response => open("#{$samples_dir}/http_safe.response").read) 16 | FakeWeb.register_uri(:get, "https://safe.com/", :response => open("#{$samples_dir}/https_safe.response").read) 17 | 18 | FakeWeb.register_uri(:get, "http://safe2.com/", :response => open("#{$samples_dir}/http_safe2.response").read) 19 | 20 | FakeWeb.register_uri(:get, "https://unsafe.com/", :response => open("#{$samples_dir}/https_unsafe.response").read) 21 | FakeWeb.register_uri(:get, "http://unsafe.com/", :response => open("#{$samples_dir}/http_unsafe.response").read) 22 | -------------------------------------------------------------------------------- /open_uri_redirections.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'open_uri_redirections/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.name = 'open_uri_redirections' 8 | gem.version = OpenUriRedirections::VERSION 9 | gem.authors = ['Jaime Iniesta', 'Gabriel Cebrian', 'Felix C. Stegerman'] 10 | gem.email = %w(jaimeiniesta@gmail.com gabceb@gmail.com flx@obfusk.net) 11 | gem.description = 'OpenURI patch to allow HTTP <==> HTTPS redirections' 12 | gem.summary = 'OpenURI patch to allow HTTP <==> HTTPS redirections' 13 | gem.homepage = 'https://github.com/open-uri-redirections/open_uri_redirections' 14 | gem.license = 'MIT' 15 | 16 | gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) 17 | gem.test_files = `git ls-files -- spec`.split($INPUT_RECORD_SEPARATOR) 18 | gem.require_paths = ['lib'] 19 | 20 | gem.add_development_dependency 'rspec', '~> 3.1.0' 21 | gem.add_development_dependency 'fakeweb', '~> 1.3.0' 22 | gem.add_development_dependency 'rake', '~> 10.4.0' 23 | end 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2014 Jaime Iniesta 2 | Copyright (c) 2014 Felix C. Stegerman 3 | 4 | MIT License 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 21 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 22 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 23 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /lib/open-uri/redirections_patch.rb: -------------------------------------------------------------------------------- 1 | ##### 2 | # Patch to allow open-uri to follow safe (http to https) 3 | # and unsafe redirections (https to http). 4 | # 5 | # Original gist URL: 6 | # https://gist.github.com/1271420 7 | # 8 | # Relevant issue: 9 | # http://redmine.ruby-lang.org/issues/3719 10 | # 11 | # Source here: 12 | # https://github.com/ruby/ruby/blob/trunk/lib/open-uri.rb 13 | # 14 | # Thread-safe implementation adapted from: 15 | # https://github.com/obfusk/open_uri_w_redirect_to_https 16 | # 17 | module OpenURI 18 | class < HTTPS redirections. 47 | # * :all will allow HTTP => HTTPS and HTTPS => HTTP redirections. 48 | # 49 | # OpenURI::open can receive different kinds of arguments, like a string for 50 | # the mode or an integer for the permissions, and then a hash with options 51 | # like UserAgent, etc. 52 | # 53 | # To find the :allow_redirections option, we look for this options hash. 54 | # 55 | def self.open_uri(name, *rest, &block) 56 | Thread.current[:__open_uri_redirections__] = allow_redirections(rest) 57 | 58 | block2 = lambda do |io| 59 | Thread.current[:__open_uri_redirections__] = nil 60 | block[io] 61 | end 62 | 63 | begin 64 | open_uri_original name, *rest, &(block ? block2 : nil) 65 | ensure 66 | Thread.current[:__open_uri_redirections__] = nil 67 | end 68 | end 69 | 70 | private 71 | 72 | def self.allow_redirections(args) 73 | options = first_hash_argument(args) 74 | options.delete :allow_redirections if options 75 | end 76 | 77 | def self.first_hash_argument(arguments) 78 | arguments.select { |arg| arg.is_a? Hash }.first 79 | end 80 | 81 | def self.http_to_https?(uri1, uri2) 82 | schemes_from([uri1, uri2]) == %w(http https) 83 | end 84 | 85 | def self.https_to_http?(uri1, uri2) 86 | schemes_from([uri1, uri2]) == %w(https http) 87 | end 88 | 89 | def self.schemes_from(uris) 90 | uris.map { |u| u.scheme.downcase } 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenUriRedirections [![Build Status](https://secure.travis-ci.org/open-uri-redirections/open_uri_redirections.png)](http://travis-ci.org/open-uri-redirections/open_uri_redirections) [![Dependency Status](https://gemnasium.com/open-uri-redirections/open_uri_redirections.png)](https://gemnasium.com/open-uri-redirections/open_uri_redirections) [![Code Climate](https://codeclimate.com/github/open-uri-redirections/open_uri_redirections/badges/gpa.svg)](https://codeclimate.com/github/open-uri-redirections/open_uri_redirections) 2 | 3 | This gem applies a patch to OpenURI to optionally allow redirections from HTTP to HTTPS, or from HTTPS to HTTP. 4 | 5 | **UPDATE 2019: HTTP to HTTPS is supported by native `open-uri` by default from Ruby 2.4**. 6 | 7 | This is based on [this patch](http://bugs.ruby-lang.org/issues/859) and [this gem](https://github.com/obfusk/open_uri_w_redirect_to_https), and modified to allow redirections in both directions. 8 | 9 | Here is the problem it tries to solve: 10 | 11 | ```sh 12 | $ irb 13 | 1.9.2p320 :001 > require 'open-uri' 14 | => true 15 | 1.9.2p320 :002 > open('http://github.com') 16 | RuntimeError: redirection forbidden: http://github.com -> https://github.com/ 17 | ``` 18 | 19 | And here is how you can use this patch to follow the redirections: 20 | 21 | ```sh 22 | $ irb 23 | 1.9.2p320 :001 > require 'open-uri' 24 | => true 25 | > require 'open_uri_redirections' 26 | => true 27 | 1.9.2p320 :002 > open('http://github.com', :allow_redirections => :safe) 28 | => # 29 | ``` 30 | 31 | The patch contained in this gem adds the :allow_redirections option to `OpenURI#open`: 32 | 33 | * `:allow_redirections => :safe` will allow HTTP => HTTPS redirections. 34 | * `:allow_redirections => :all` will allow HTTP => HTTPS redirections and HTTPS => HTTP redirections. 35 | 36 | ## Understand what you're doing 37 | 38 | Before using this gem, read this: 39 | 40 | ### Original gist URL: 41 | [https://gist.github.com/1271420](https://gist.github.com/1271420) 42 | 43 | ### Relevant issue: 44 | [https://bugs.ruby-lang.org/issues/3719](https://bugs.ruby-lang.org/issues/3719) 45 | 46 | ### Source here: 47 | [https://github.com/ruby/ruby/blob/trunk/lib/open-uri.rb](https://github.com/ruby/ruby/blob/trunk/lib/open-uri.rb) 48 | 49 | ### Thread-safe implementation adapted from this gem: 50 | [https://github.com/obfusk/open_uri_w_redirect_to_https](https://github.com/obfusk/open_uri_w_redirect_to_https) 51 | 52 | Use it at your own risk! 53 | 54 | ## Installation 55 | 56 | Add this line to your application's Gemfile: 57 | 58 | gem 'open_uri_redirections' 59 | 60 | And then execute: 61 | 62 | ```sh 63 | $ bundle install 64 | ``` 65 | 66 | Or install it yourself as: 67 | 68 | ```sh 69 | $ gem install open_uri_redirections 70 | ``` 71 | 72 | ## Contributing 73 | 74 | 1. Fork it 75 | 2. Create your feature branch (`git checkout -b my-new-feature`) 76 | 3. Commit your changes (`git commit -am 'Add some feature'`) 77 | 4. Push to the branch (`git push origin my-new-feature`) 78 | 5. Create new Pull Request 79 | -------------------------------------------------------------------------------- /spec/redirections_spec.rb: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | require File.join(File.dirname(__FILE__), '/spec_helper') 4 | 5 | class << OpenURI 6 | alias_method :open_uri_original__, :open_uri_original 7 | end 8 | 9 | describe 'OpenURI' do 10 | describe '#open' do 11 | describe 'Default settings' do 12 | it 'should disallow HTTP => HTTPS redirections' do 13 | expect { 14 | open('http://safe.com') 15 | }.to raise_error(RuntimeError, safe_forbidden_msg) 16 | end 17 | 18 | it 'should disallow HTTPS => HTTP redirections' do 19 | expect { 20 | open('https://unsafe.com') 21 | }.to raise_error(RuntimeError, unsafe_forbidden_msg) 22 | end 23 | end 24 | 25 | describe ':allow_redirections => :safe' do 26 | it 'should allow HTTP => HTTPS redirections' do 27 | expect { 28 | open('http://safe.com', :allow_redirections => :safe) 29 | }.to_not raise_error 30 | end 31 | 32 | it 'should disallow HTTPS => HTTP redirections' do 33 | expect { 34 | open('https://unsafe.com', :allow_redirections => :safe) 35 | }.to raise_error(RuntimeError, unsafe_forbidden_msg) 36 | end 37 | 38 | it 'should follow safe redirections' do 39 | expect( 40 | open('http://safe.com', :allow_redirections => :safe).read 41 | ).to eq('Hello, this is Safe.') 42 | end 43 | 44 | it 'should follow safe double redirections' do 45 | expect( 46 | open('http://safe2.com', :allow_redirections => :safe).read 47 | ).to eq('Hello, this is Safe.') 48 | end 49 | 50 | it 'should follow safe redirections with block' do 51 | expect { |b| 52 | open('http://safe.com', :allow_redirections => :safe, &b) 53 | }.to yield_control 54 | end 55 | end 56 | 57 | describe ':allow_redirections => :all' do 58 | it 'should allow HTTP => HTTPS redirections' do 59 | expect { 60 | open('http://safe.com', :allow_redirections => :all) 61 | }.to_not raise_error 62 | end 63 | 64 | it 'should allow HTTPS => HTTP redirections' do 65 | expect { 66 | open('https://unsafe.com', :allow_redirections => :all) 67 | }.to_not raise_error 68 | end 69 | 70 | it 'should follow safe redirections' do 71 | expect( 72 | open('http://safe.com', :allow_redirections => :all).read 73 | ).to eq('Hello, this is Safe.') 74 | end 75 | 76 | it 'should follow unsafe redirections' do 77 | expect( 78 | open('https://unsafe.com', :allow_redirections => :all).read 79 | ).to eq('Hello, this is Unsafe.') 80 | end 81 | 82 | it 'should follow safe redirections with block' do 83 | expect { |b| 84 | open('http://safe.com', :allow_redirections => :all, &b) 85 | }.to yield_control 86 | end 87 | 88 | it 'should follow unsafe redirections with block' do 89 | expect { |b| 90 | open('https://unsafe.com', :allow_redirections => :all, &b) 91 | }.to yield_control 92 | end 93 | end 94 | 95 | describe 'passing arguments down the stack' do 96 | it 'should disallow HTTP => HTTPS redirections' do 97 | expect { 98 | open('http://safe.com', 'r', 0444, 'User-Agent' => 'Mozilla/5.0') 99 | }.to raise_error(RuntimeError, safe_forbidden_msg) 100 | end 101 | 102 | it 'should allow HTTP => HTTPS redirections' do 103 | expect { 104 | open('http://safe.com', 'r', 0444, 'User-Agent' => 'Mozilla/5.0', :allow_redirections => :safe) 105 | }.to_not raise_error 106 | end 107 | 108 | it 'should pass the arguments down the stack' do 109 | expect(OpenURI).to receive(:open_uri_original).with(an_instance_of(URI::HTTP), 'r', 0444, { 'User-Agent' => 'Mozilla/5.0' }) 110 | 111 | open('http://safe.com', 'r', 0444, 'User-Agent' => 'Mozilla/5.0', :allow_redirections => :safe) 112 | end 113 | end 114 | 115 | describe 'threads' do 116 | it 'seems to work (could be false positive)' do 117 | allow(OpenURI).to receive(:open_uri_original) { |*a, &b| sleep rand; OpenURI.open_uri_original__ *a, &b } 118 | ts = [] 119 | Thread.abort_on_exception = true 120 | 121 | begin 122 | 100.times { 123 | ts << Thread.new { 124 | expect { 125 | open('http://safe.com') 126 | }.to raise_error(RuntimeError, safe_forbidden_msg) 127 | } 128 | ts << Thread.new { 129 | expect { 130 | open('http://safe.com', :allow_redirections => :safe) 131 | }.to_not raise_error 132 | } 133 | ts << Thread.new { 134 | expect { 135 | open('https://unsafe.com') 136 | }.to raise_error(RuntimeError, unsafe_forbidden_msg) 137 | } 138 | ts << Thread.new { 139 | expect { 140 | open('https://unsafe.com', :allow_redirections => :safe) 141 | }.to raise_error(RuntimeError, unsafe_forbidden_msg) 142 | } 143 | } 144 | ensure 145 | ts.each(&:join) 146 | end 147 | end 148 | end 149 | end 150 | 151 | private 152 | 153 | def safe_forbidden_msg 154 | 'redirection forbidden: http://safe.com -> https://safe.com/' 155 | end 156 | 157 | def unsafe_forbidden_msg 158 | 'redirection forbidden: https://unsafe.com -> http://unsafe.com/' 159 | end 160 | end 161 | --------------------------------------------------------------------------------