The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .gitignore
├── .mailmap
├── .rspec
├── .rubocop
├── .rubocop-disables.yml
├── .rubocop.yml
├── .travis.yml
├── AUTHORS
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── bin
    └── restclient
├── history.md
├── lib
    ├── rest-client.rb
    ├── rest_client.rb
    ├── restclient.rb
    └── restclient
    │   ├── abstract_response.rb
    │   ├── exceptions.rb
    │   ├── params_array.rb
    │   ├── payload.rb
    │   ├── platform.rb
    │   ├── raw_response.rb
    │   ├── request.rb
    │   ├── resource.rb
    │   ├── response.rb
    │   ├── utils.rb
    │   ├── version.rb
    │   ├── windows.rb
    │   └── windows
    │       └── root_certs.rb
├── rest-client.gemspec
├── rest-client.windows.gemspec
└── spec
    ├── ISS.jpg
    ├── helpers.rb
    ├── integration
        ├── _lib.rb
        ├── capath_digicert
        │   ├── 3513523f.0
        │   ├── 399e7759.0
        │   ├── README
        │   └── digicert.crt
        ├── capath_verisign
        │   ├── 415660c1.0
        │   ├── 7651b327.0
        │   ├── README
        │   └── verisign.crt
        ├── certs
        │   ├── digicert.crt
        │   └── verisign.crt
        ├── httpbin_spec.rb
        ├── integration_spec.rb
        └── request_spec.rb
    ├── spec_helper.rb
    └── unit
        ├── _lib.rb
        ├── abstract_response_spec.rb
        ├── exceptions_spec.rb
        ├── params_array_spec.rb
        ├── payload_spec.rb
        ├── raw_response_spec.rb
        ├── request2_spec.rb
        ├── request_spec.rb
        ├── resource_spec.rb
        ├── response_spec.rb
        ├── restclient_spec.rb
        ├── utils_spec.rb
        └── windows
            └── root_certs_spec.rb


/.gitignore:
--------------------------------------------------------------------------------
 1 | *.gem
 2 | /Gemfile.lock
 3 | /.bundle
 4 | /vendor
 5 | /doc
 6 | /pkg
 7 | /rdoc
 8 | /.yardoc
 9 | /tmp
10 | 


--------------------------------------------------------------------------------
/.mailmap:
--------------------------------------------------------------------------------
 1 | Blake Mizerany <blake.mizerany@gmail.com>
 2 | Lawrence Leonard Gilbert <larry@l2g.to>
 3 | <larry@l2g.to> <larry@L2G.to>
 4 | Marc-André Cournoyer <macournoyer@gmail.com>
 5 | Matthew Manning <matt.manning@gmail.com>
 6 | Nicholas Wieland <nicholas.wieland@gmail.com>
 7 | Rafael Ssouza <rafael.ssouza@gmail.com>
 8 | Richard Schneeman <richard.schneeman@gmail.com>
 9 | Rick Olson <technoweenie@gmail.com>
10 | T. Watanabe <wtnabe@wt-srv.watanabe>
11 | 


--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --format progress
3 | 


--------------------------------------------------------------------------------
/.rubocop:
--------------------------------------------------------------------------------
1 | --display-cop-names
2 | --fail-level=W
3 | 


--------------------------------------------------------------------------------
/.rubocop-disables.yml:
--------------------------------------------------------------------------------
  1 | # This configuration was generated by `rubocop --auto-gen-config`
  2 | # on 2014-07-08 08:57:44 +0000 using RuboCop version 0.24.1.
  3 | # The point is for the user to remove these configuration records
  4 | # one by one as the offenses are removed from the code base.
  5 | # Note that changes in the inspected code, or installation of new
  6 | # versions of RuboCop, may require this file to be generated again.
  7 | 
  8 | # TODO
  9 | # Offense count: 1
 10 | # Cop supports --auto-correct.
 11 | Lint/StringConversionInInterpolation:
 12 |   Enabled: false
 13 | 
 14 | # Tests only
 15 | # Offense count: 16
 16 | # Cop supports --auto-correct.
 17 | Lint/UnusedBlockArgument:
 18 |   Enabled: false
 19 | 
 20 | Security/Eval:
 21 |   Exclude:
 22 |   - rest-client.windows.gemspec
 23 | 
 24 | Lint/HandleExceptions:
 25 |   Exclude:
 26 |   - lib/restclient/utils.rb
 27 | 
 28 | Lint/UselessAccessModifier:
 29 |   Exclude:
 30 |   - lib/restclient/windows/root_certs.rb
 31 | 
 32 | # Offense count: 4
 33 | # Cop supports --auto-correct.
 34 | Style/Alias:
 35 |   Enabled: false
 36 | 
 37 | # TODO
 38 | # Offense count: 3
 39 | # Cop supports --auto-correct.
 40 | Style/AndOr:
 41 |   Enabled: false
 42 | 
 43 | # TODO
 44 | # Offense count: 3
 45 | # Cop supports --auto-correct.
 46 | Style/BlockDelimiters:
 47 |   Enabled: false
 48 | 
 49 | # Offense count: 48
 50 | # Cop supports --auto-correct.
 51 | # Configuration parameters: EnforcedStyle, SupportedStyles.
 52 | Style/BracesAroundHashParameters:
 53 |   Enabled: false
 54 | 
 55 | # Offense count: 1
 56 | Naming/ClassAndModuleCamelCase:
 57 |   Exclude:
 58 |     - lib/restclient/windows/root_certs.rb
 59 | 
 60 | # Offense count: 2
 61 | # Configuration parameters: EnforcedStyle, SupportedStyles.
 62 | Style/ClassAndModuleChildren:
 63 |   Enabled: false
 64 | 
 65 | # TODO?
 66 | # Offense count: 14
 67 | Metrics/AbcSize:
 68 |   Max: 75
 69 | 
 70 | # TODO?
 71 | Metrics/MethodLength:
 72 |   Max: 66
 73 | 
 74 | # TODO?
 75 | # Offense count: 4
 76 | Metrics/PerceivedComplexity:
 77 |   Max: 24
 78 | 
 79 | # Offense count: 1
 80 | # Configuration parameters: CountComments.
 81 | Metrics/ClassLength:
 82 |   Max: 411
 83 | 
 84 | # TODO
 85 | # Offense count: 5
 86 | Style/ClassVars:
 87 |   Enabled: false
 88 | 
 89 | # TODO
 90 | # Offense count: 5
 91 | # Cop supports --auto-correct.
 92 | # Configuration parameters: PreferredMethods.
 93 | Style/CollectionMethods:
 94 |   Enabled: false
 95 | 
 96 | # TODO
 97 | # Offense count: 4
 98 | # Cop supports --auto-correct.
 99 | Style/ColonMethodCall:
100 |   Enabled: false
101 | 
102 | Style/ConditionalAssignment:
103 |   EnforcedStyle: assign_inside_condition
104 | 
105 | # Offense count: 2
106 | Naming/ConstantName:
107 |   Enabled: false
108 | 
109 | # TODO: eh?
110 | # Offense count: 4
111 | Metrics/CyclomaticComplexity:
112 |   Max: 22
113 | 
114 | Style/PreferredHashMethods:
115 |   EnforcedStyle: verbose
116 | 
117 | # TODO: docs
118 | # Offense count: 17
119 | Style/Documentation:
120 |   Enabled: false
121 | 
122 | # Offense count: 9
123 | # Configuration parameters: EnforcedStyle, SupportedStyles.
124 | Layout/DotPosition:
125 |   Enabled: false
126 | 
127 | # Offense count: 1
128 | Style/DoubleNegation:
129 |   Enabled: false
130 | 
131 | # TODO
132 | # Offense count: 2
133 | Style/EachWithObject:
134 |   Enabled: false
135 | 
136 | # Offense count: 5
137 | # Cop supports --auto-correct.
138 | Layout/EmptyLines:
139 |   Enabled: false
140 | 
141 | # Offense count: 11
142 | # Cop supports --auto-correct.
143 | # Configuration parameters: EnforcedStyle, SupportedStyles.
144 | Layout/EmptyLinesAroundClassBody:
145 |   Enabled: false
146 | 
147 | # Offense count: 1
148 | # Cop supports --auto-correct.
149 | Layout/EmptyLinesAroundMethodBody:
150 |   Enabled: false
151 | 
152 | # Offense count: 9
153 | # Cop supports --auto-correct.
154 | # Configuration parameters: EnforcedStyle, SupportedStyles.
155 | Layout/EmptyLinesAroundModuleBody:
156 |   Enabled: false
157 | 
158 | Layout/EmptyLinesAroundExceptionHandlingKeywords:
159 |   Enabled: false
160 | 
161 | # Offense count: 31
162 | # Configuration parameters: EnforcedStyle, SupportedStyles.
163 | Style/Encoding:
164 |   Enabled: false
165 | 
166 | Naming/FileName:
167 |   Exclude:
168 |     - lib/rest-client.rb
169 | 
170 | # Offense count: 3
171 | # Configuration parameters: EnforcedStyle, SupportedStyles.
172 | Style/FormatString:
173 |   Enabled: false
174 | 
175 | # TODO: enable
176 | # Cop supports --auto-correct.
177 | # Configuration parameters: SupportedStyles.
178 | Style/HashSyntax:
179 |   Enabled: false
180 | 
181 | # NOTABUG
182 | # Offense count: 8
183 | # Configuration parameters: MaxLineLength.
184 | Style/IfUnlessModifier:
185 |   Enabled: false
186 | 
187 | Layout/IndentFirstHashElement:
188 |   Exclude:
189 |     - 'spec/**/*.rb'
190 | 
191 | # NOTABUG
192 | # Offense count: 19
193 | Style/Lambda:
194 |   Enabled: false
195 | 
196 | # TODO
197 | # Offense count: 14
198 | # Cop supports --auto-correct.
199 | Layout/LeadingCommentSpace:
200 |   Enabled: false
201 | 
202 | Metrics/LineLength:
203 |   Exclude:
204 |     - 'spec/**/*.rb'
205 |     - 'Rakefile'
206 | 
207 | # TODO
208 | # Offense count: 28
209 | # Cop supports --auto-correct.
210 | # Configuration parameters: EnforcedStyle, SupportedStyles.
211 | Style/MethodDefParentheses:
212 |   Enabled: false
213 | 
214 | # TODO
215 | # Offense count: 1
216 | Style/ModuleFunction:
217 |   Enabled: false
218 | 
219 | # Offense count: 4
220 | # Configuration parameters: EnforcedStyle, SupportedStyles.
221 | Style/Next:
222 |   Enabled: false
223 | 
224 | # Offense count: 1
225 | # Cop supports --auto-correct.
226 | # Configuration parameters: IncludeSemanticChanges.
227 | Style/NonNilCheck:
228 |   Enabled: false
229 | 
230 | # TODO: exclude
231 | # Offense count: 1
232 | # Cop supports --auto-correct.
233 | Style/Not:
234 |   Enabled: false
235 | 
236 | # Offense count: 2
237 | # Cop supports --auto-correct.
238 | Style/NumericLiterals:
239 |   MinDigits: 11
240 | 
241 | # TODO?
242 | # Offense count: 1
243 | # Cop supports --auto-correct.
244 | # Configuration parameters: AllowSafeAssignment.
245 | Style/ParenthesesAroundCondition:
246 |   Enabled: false
247 | 
248 | # Offense count: 8
249 | # Cop supports --auto-correct.
250 | # Configuration parameters: PreferredDelimiters.
251 | Style/PercentLiteralDelimiters:
252 |   PreferredDelimiters:
253 |     '%w': '{}'
254 |     '%W': '{}'
255 |     '%Q': '{}'
256 |   Exclude:
257 |     - 'bin/restclient'
258 | 
259 | # Offense count: 3
260 | # Configuration parameters: NamePrefixBlacklist.
261 | Naming/PredicateName:
262 |   Enabled: false
263 | 
264 | # TODO: configure
265 | # Offense count: 3
266 | # Configuration parameters: EnforcedStyle, SupportedStyles.
267 | Style/RaiseArgs:
268 |   Enabled: false
269 | 
270 | # TODO
271 | # Offense count: 1
272 | # Cop supports --auto-correct.
273 | Style/RedundantBegin:
274 |   Enabled: false
275 | 
276 | # Offense count: 2
277 | # Cop supports --auto-correct.
278 | Style/RedundantSelf:
279 |   Enabled: false
280 | 
281 | # Offense count: 1
282 | Style/RescueModifier:
283 |   Enabled: false
284 |   Exclude:
285 |     - 'bin/restclient'
286 | 
287 | # TODO: configure
288 | # Offense count: 12
289 | # Cop supports --auto-correct.
290 | # Configuration parameters: EnforcedStyle, SupportedStyles.
291 | Style/SignalException:
292 |   Enabled: false
293 | 
294 | # TODO
295 | # Offense count: 2
296 | # Cop supports --auto-correct.
297 | Layout/SpaceAfterNot:
298 |   Enabled: false
299 | 
300 | # Offense count: 19
301 | # Cop supports --auto-correct.
302 | # Configuration parameters: EnforcedStyle, SupportedStyles.
303 | Layout/SpaceAroundEqualsInParameterDefault:
304 |   Enabled: false
305 | 
306 | # Offense count: 20
307 | # Cop supports --auto-correct.
308 | Layout/SpaceAroundOperators:
309 |   Enabled: false
310 | 
311 | # Offense count: 9
312 | # Cop supports --auto-correct.
313 | # Configuration parameters: EnforcedStyle, SupportedStyles.
314 | Layout/SpaceBeforeBlockBraces:
315 |   Enabled: false
316 | 
317 | Layout/EmptyLinesAroundBlockBody:
318 |   Enabled: false
319 | 
320 | # Offense count: 37
321 | # Cop supports --auto-correct.
322 | # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters.
323 | Layout/SpaceInsideBlockBraces:
324 |   Enabled: false
325 | 
326 | # Offense count: 181
327 | # Cop supports --auto-correct.
328 | # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles.
329 | Layout/SpaceInsideHashLiteralBraces:
330 |   Enabled: false
331 | 
332 | # TODO
333 | # Offense count: 9
334 | # Cop supports --auto-correct.
335 | Layout/SpaceInsideParens:
336 |   Enabled: false
337 | 
338 | # Offense count: 414
339 | # Cop supports --auto-correct.
340 | # Configuration parameters: EnforcedStyle, SupportedStyles.
341 | Style/StringLiterals:
342 |   Enabled: false
343 | 
344 | Style/TrailingCommaInArrayLiteral:
345 |   EnforcedStyleForMultiline: consistent_comma
346 | Style/TrailingCommaInHashLiteral:
347 |   EnforcedStyleForMultiline: consistent_comma
348 | Style/TrailingCommaInArguments:
349 |   Enabled: false
350 | 
351 | # TODO: configure
352 | # Offense count: 1
353 | # Cop supports --auto-correct.
354 | # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, Whitelist.
355 | Style/TrivialAccessors:
356 |   Enabled: false
357 |   Exclude: ['lib/restclient/payload.rb']
358 | 
359 | # TODO?
360 | # Offense count: 3
361 | Style/UnlessElse:
362 |   Enabled: false
363 | 
364 | # TODO?
365 | # Offense count: 6
366 | # Cop supports --auto-correct.
367 | Style/UnneededPercentQ:
368 |   Enabled: false
369 | 
370 | # Offense count: 5
371 | # Cop supports --auto-correct.
372 | Style/WordArray:
373 |   MinSize: 4
374 | 
375 | # TODO?
376 | # Offense count: 5
377 | # Cop supports --auto-correct.
378 | # Configuration parameters: EnforcedStyle, SupportedStyles.
379 | Style/BarePercentLiterals:
380 |   Enabled: false
381 | 
382 | 
383 | Style/RescueStandardError:
384 |   Exclude:
385 |     - 'bin/restclient'
386 |     - 'lib/restclient/windows/root_certs.rb'
387 | 


--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | ---
2 | inherit_from:
3 | - .rubocop-disables.yml
4 | 
5 | AllCops:
6 |   Exclude:
7 |     - 'tmp/*.rb'
8 |     - 'vendor/**/*'
9 | 


--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
 1 | # Available ruby versions: http://rubies.travis-ci.org/
 2 | 
 3 | language: ruby
 4 | sudo: false
 5 | 
 6 | os:
 7 |   - linux
 8 | #  - osx
 9 | 
10 | rvm:
11 |   - "2.1" # latest 2.1.x
12 |   - "2.2.10"
13 |   - "2.3.8"
14 |   - "2.4.6"
15 |   - "2.5.5"
16 |   - "2.6.3"
17 |   - "ruby-head"
18 |   - "jruby-9.1.9.0"
19 |   - "jruby-head"
20 | 
21 | cache: bundler
22 | 
23 | script:
24 |   - bundle exec rake test
25 |   - bundle exec rake rubocop
26 | 
27 | branches:
28 |   except:
29 |     - "readme-edits"
30 | 
31 | before_install:
32 |   # Install rubygems < 3.0 so that we can support ruby < 2.3
33 |   # https://github.com/rubygems/rubygems/issues/2534
34 |   - gem install rubygems-update -v '<3' && update_rubygems
35 |   # bundler installation needed for jruby-head
36 |   # https://github.com/travis-ci/travis-ci/issues/5861
37 |   # stick to bundler 1.x in order to support ruby < 2.3
38 |   - gem install bundler -v '~> 1.17'
39 | 
40 | # Travis macOS support is pretty janky. These are some hacks to include tests
41 | # only on versions that actually work. We test on macOS because Apple monkey
42 | # patches OpenSSL to have different behavior, and we want to ensure that SSL
43 | # verification at least is broken in the expected ways on macOS.
44 | # (last tested: 2019-08)
45 | matrix:
46 |   # exclude: {}
47 |   include:
48 |     # test only a few versions on mac
49 |     - os: osx
50 |       rvm: 2.6.3
51 |     - os: osx
52 |       rvm: ruby-head
53 |     - os: osx
54 |       rvm: jruby-9.1.9.0
55 |     - os: osx
56 |       rvm: jruby-head
57 | 
58 |   allow_failures:
59 |     - rvm: 'ruby-head'
60 | 
61 |   # return results as soon as mandatory versions are done
62 |   fast_finish: true
63 | 


--------------------------------------------------------------------------------
/AUTHORS:
--------------------------------------------------------------------------------
  1 | The Ruby REST Client would not be what it is today without the help of
  2 | the following kind souls:
  3 | 
  4 | Adam Jacob
  5 | Adam Wiggins
  6 | Adrian Rangel
  7 | Alex Tomlins
  8 | Aman Gupta
  9 | Andy Brody
 10 | Avi Deitcher
 11 | Blake Mizerany
 12 | Brad Ediger
 13 | Braintree
 14 | Brian Donovan
 15 | Caleb Land
 16 | Chris Dinn
 17 | Chris Frohoff
 18 | Chris Green
 19 | Coda Hale
 20 | Crawford
 21 | Cyril Rohr
 22 | Dan Mayer
 23 | Dario Hamidi
 24 | Darren Coxall
 25 | David Backeus
 26 | David Perkowski
 27 | Dmitri Dolguikh
 28 | Dusty Doris
 29 | Dylan Egan
 30 | El Draper
 31 | Evan Broder
 32 | Evan Smith
 33 | François Beausoleil
 34 | Gabriele Cirulli
 35 | Garry Shutler
 36 | Giovanni Cappellotto
 37 | Greg Borenstein
 38 | Harm Aarts
 39 | Hiro Asari
 40 | Hugh McGowan
 41 | Ian Warshak
 42 | Igor Zubkov
 43 | Ivan Makfinsky
 44 | JH. Chabran
 45 | James Edward Gray II
 46 | Jari Bakken
 47 | Jeff Pereira
 48 | Jeff Remer
 49 | Jeffrey Hardy
 50 | Jeremy Kemper
 51 | Joe Rafaniello
 52 | John Barnette
 53 | Jon Rowe
 54 | Jordi Massaguer Pla
 55 | Joshua J. Campoverde
 56 | Juan Alvarez
 57 | Julien Kirch
 58 | Jun Aruga
 59 | Justin Coyne
 60 | Justin Lambert
 61 | Keith Rarick
 62 | Kenichi Kamiya
 63 | Kevin Read
 64 | Kosuke Asami
 65 | Kyle Meyer
 66 | Kyle VanderBeek
 67 | Lars Gierth
 68 | Lawrence Leonard Gilbert
 69 | Lee Jarvis
 70 | Lennon Day-Reynolds
 71 | Lin Jen-Shin
 72 | Magne Matre Gåsland
 73 | Marc-André Cournoyer
 74 | Marius Butuc
 75 | Matthew Manning
 76 | Michael Klett
 77 | Michael Rykov
 78 | Michael Westbom
 79 | Mike Fletcher
 80 | Nelson Elhage
 81 | Nicholas Wieland
 82 | Nick Hammond
 83 | Nick Plante
 84 | Niko Dittmann
 85 | Nikolay Shebanov
 86 | Oscar Del Ben
 87 | Pablo Astigarraga
 88 | Paul Dlug
 89 | Pedro Belo
 90 | Pedro Chambino
 91 | Philip Corliss
 92 | Pierre-Louis Gottfrois
 93 | Rafael Ssouza
 94 | Richard Schneeman
 95 | Rick Olson
 96 | Robert Eanes
 97 | Rodrigo Panachi
 98 | Sam Norbury
 99 | Samuel Cochran
100 | Syl Turner
101 | T. Watanabe
102 | Tekin
103 | W. Andrew Loe III
104 | Waynn Lue
105 | Xavier Shay
106 | tpresa
107 | 


--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
 1 | source "https://rubygems.org"
 2 | 
 3 | if !!File::ALT_SEPARATOR
 4 |   gemspec :name => 'rest-client.windows'
 5 | else
 6 |   gemspec :name => 'rest-client'
 7 | end
 8 | 
 9 | group :test do
10 |   gem 'rake'
11 | end
12 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | The MIT License (MIT)
 2 | 
 3 | Copyright (c) 2008-2014 Rest Client Authors
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 


--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
  1 | # load `rake build/install/release tasks'
  2 | require 'bundler/setup'
  3 | require_relative './lib/restclient/version'
  4 | 
  5 | namespace :ruby do
  6 |   Bundler::GemHelper.install_tasks(:name => 'rest-client')
  7 | end
  8 | 
  9 | require "rspec/core/rake_task"
 10 | 
 11 | desc "Run all specs"
 12 | RSpec::Core::RakeTask.new('spec')
 13 | 
 14 | desc "Run unit specs"
 15 | RSpec::Core::RakeTask.new('spec:unit') do |t|
 16 |   t.pattern = 'spec/unit/*_spec.rb'
 17 | end
 18 | 
 19 | desc "Run integration specs"
 20 | RSpec::Core::RakeTask.new('spec:integration') do |t|
 21 |   t.pattern = 'spec/integration/*_spec.rb'
 22 | end
 23 | 
 24 | desc "Print specdocs"
 25 | RSpec::Core::RakeTask.new(:doc) do |t|
 26 |   t.rspec_opts = ["--format", "specdoc", "--dry-run"]
 27 |   t.pattern = 'spec/**/*_spec.rb'
 28 | end
 29 | 
 30 | desc "Run all examples with RCov"
 31 | RSpec::Core::RakeTask.new('rcov') do |t|
 32 |   t.pattern = 'spec/*_spec.rb'
 33 |   t.rcov = true
 34 |   t.rcov_opts = ['--exclude', 'examples']
 35 | end
 36 | 
 37 | desc 'Regenerate authors file'
 38 | task :authors do
 39 |   Dir.chdir(File.dirname(__FILE__)) do
 40 |     File.open('AUTHORS', 'w') do |f|
 41 |       f.write <<-EOM
 42 | The Ruby REST Client would not be what it is today without the help of
 43 | the following kind souls:
 44 | 
 45 |       EOM
 46 |     end
 47 | 
 48 |     sh 'git shortlog -s | cut -f 2 >> AUTHORS'
 49 |   end
 50 | end
 51 | 
 52 | task :default do
 53 |   sh 'rake -T'
 54 | end
 55 | 
 56 | def alias_task(alias_task, original)
 57 |   desc "Alias for rake #{original}"
 58 |   task alias_task, Rake.application[original].arg_names => original
 59 | end
 60 | alias_task(:test, :spec)
 61 | 
 62 | ############################
 63 | 
 64 | WindowsPlatforms = %w{x86-mingw32 x64-mingw32 x86-mswin32}
 65 | 
 66 | namespace :all do
 67 | 
 68 |   desc "Build rest-client #{RestClient::VERSION} for all platforms"
 69 |   task :build => ['ruby:build'] + \
 70 |     WindowsPlatforms.map {|p| "windows:#{p}:build"}
 71 | 
 72 |   desc "Create tag v#{RestClient::VERSION} and for all platforms build and " \
 73 |     "push rest-client #{RestClient::VERSION} to Rubygems"
 74 |   task :release => ['build', 'ruby:release'] + \
 75 |     WindowsPlatforms.map {|p| "windows:#{p}:push"}
 76 | 
 77 | end
 78 | 
 79 | namespace :windows do
 80 |   spec_path = File.join(File.dirname(__FILE__), 'rest-client.windows.gemspec')
 81 | 
 82 |   WindowsPlatforms.each do |platform|
 83 |     namespace platform do
 84 |       gem_filename = "rest-client-#{RestClient::VERSION}-#{platform}.gem"
 85 |       base = File.dirname(__FILE__)
 86 |       pkg_dir = File.join(base, 'pkg')
 87 |       gem_file_path = File.join(pkg_dir, gem_filename)
 88 | 
 89 |       desc "Build #{gem_filename} into the pkg directory"
 90 |       task 'build' do
 91 |         orig_platform = ENV['BUILD_PLATFORM']
 92 |         begin
 93 |           ENV['BUILD_PLATFORM'] = platform
 94 | 
 95 |           sh("gem build -V #{spec_path}") do |ok, res|
 96 |             if ok
 97 |               FileUtils.mkdir_p(pkg_dir)
 98 |               FileUtils.mv(File.join(base, gem_filename), pkg_dir)
 99 |               Bundler.ui.confirm("rest-client #{RestClient::VERSION} " \
100 |                                  "built to pkg/#{gem_filename}")
101 |             else
102 |               abort "Command `gem build` failed: #{res}"
103 |             end
104 |           end
105 | 
106 |         ensure
107 |           ENV['BUILD_PLATFORM'] = orig_platform
108 |         end
109 |       end
110 | 
111 |       desc "Push #{gem_filename} to Rubygems"
112 |       task 'push' do
113 |         sh("gem push #{gem_file_path}")
114 |       end
115 |     end
116 |   end
117 | 
118 | end
119 | 
120 | ############################
121 | 
122 | require 'rdoc/task'
123 | 
124 | Rake::RDocTask.new do |t|
125 |   t.rdoc_dir = 'rdoc'
126 |   t.title    = "rest-client, fetch RESTful resources effortlessly"
127 |   t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
128 |   t.options << '--charset' << 'utf-8'
129 |   t.rdoc_files.include('README.md')
130 |   t.rdoc_files.include('lib/*.rb')
131 | end
132 | 
133 | ############################
134 | 
135 | require 'rubocop/rake_task'
136 | 
137 | RuboCop::RakeTask.new(:rubocop) do |t|
138 |   t.options = ['--display-cop-names']
139 | end
140 | alias_task(:lint, :rubocop)
141 | 


--------------------------------------------------------------------------------
/bin/restclient:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env ruby
 2 | 
 3 | $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib"
 4 | 
 5 | require 'rubygems'
 6 | require 'restclient'
 7 | require 'yaml'
 8 | 
 9 | def usage(why = nil)
10 |   puts "failed for reason: #{why}" if why
11 |   puts "usage: restclient [get|put|post|delete] url|name [username] [password]"
12 |   puts "  The verb is optional, if you leave it off you'll get an interactive shell."
13 |   puts "  put and post both take the input body on stdin."
14 |   exit(1)
15 | end
16 | 
17 | POSSIBLE_VERBS = ['get', 'put', 'post', 'delete']
18 | 
19 | if POSSIBLE_VERBS.include? ARGV.first
20 |   @verb = ARGV.shift
21 | else
22 |   @verb = nil
23 | end
24 | 
25 | @url = ARGV.shift || 'http://localhost:4567'
26 | 
27 | config = YAML.load(File.read(ENV['HOME'] + "/.restclient")) rescue {}
28 | 
29 | if (c = config[@url])
30 |   @url, @username, @password = [c['url'], c['username'], c['password']]
31 | else
32 |   @url, @username, @password = [@url, * ARGV]
33 | end
34 | 
35 | usage("invalid url '#{@url}") unless @url =~ /^https?/
36 | usage("too few args") unless ARGV.size < 3
37 | 
38 | def r
39 |   @r ||= RestClient::Resource.new(@url, @username, @password)
40 | end
41 | 
42 | r # force rc to load
43 | 
44 | if @verb
45 |   begin
46 |     if %w( put post ).include? @verb
47 |       puts r.send(@verb, STDIN.read)
48 |     else
49 |       puts r.send(@verb)
50 |     end
51 |     exit 0
52 |   rescue RestClient::Exception => e
53 |     puts e.response.body if e.respond_to?(:response) && e.response
54 |     raise
55 |   end
56 | end
57 | 
58 | POSSIBLE_VERBS.each do |m|
59 |   define_method(m.to_sym) do |path, *args, &b|
60 |     r[path].public_send(m.to_sym, *args, &b)
61 |   end
62 | end
63 | 
64 | def method_missing(s, * args, & b)
65 |   if POSSIBLE_VERBS.include? s
66 |     begin
67 |       r.send(s, *args, & b)
68 |     rescue RestClient::RequestFailed => e
69 |       print STDERR, e.response.body
70 |       raise e
71 |     end
72 |   else
73 |     super
74 |   end
75 | end
76 | 
77 | require 'irb'
78 | require 'irb/completion'
79 | 
80 | if File.exist? ".irbrc"
81 |   ENV['IRBRC'] = ".irbrc"
82 | end
83 | 
84 | rcfile = File.expand_path("~/.restclientrc")
85 | if File.exist?(rcfile)
86 |   load(rcfile)
87 | end
88 | 
89 | ARGV.clear
90 | 
91 | IRB.start
92 | exit!
93 | 


--------------------------------------------------------------------------------
/history.md:
--------------------------------------------------------------------------------
  1 | # 2.1.0
  2 | 
  3 | - Add a dependency on http-accept for parsing Content-Type charset headers.
  4 |   This works around a bad memory leak introduced in MRI Ruby 2.4.0 and fixed in
  5 |   Ruby 2.4.2. (#615)
  6 | - Use mime/types/columnar from mime-types 2.6.1+, which is leaner in memory
  7 |   usage than the older storage model of mime-types. (#393)
  8 | - Add `:log` option to individual requests. This allows users to set a log on a
  9 |   per-request / per-resource basis instead of the kludgy global log. (#538)
 10 | - Log request duration by tracking request start and end times. Make
 11 |   `log_response` a method on the Response object, and ensure the `size` method
 12 |   works on RawResponse objects. (#126)
 13 |   - `# => 200 OK | text/html 1270 bytes, 0.08s`
 14 |   - Also add a new `:stream_log_percent` parameter, which is applicable only
 15 |     when `:raw_response => true` is set. This causes progress logs to be
 16 |     emitted only on every N% (default 10%) of the total download size rather
 17 |     than on every chunk.
 18 | - Drop custom handling of compression and use built-in Net::HTTP support for
 19 |   supported Content-Encodings like gzip and deflate. Don't set any explicit
 20 |   `Accept-Encoding` header, rely instead on Net::HTTP defaults. (#597)
 21 |   - Note: this changes behavior for compressed responses when using
 22 |     `:raw_response => true`. Previously the raw response would not have been
 23 |     uncompressed by rest-client, but now Net::HTTP will uncompress it.
 24 | - The previous fix to avoid having Netrc username/password override an
 25 |   Authorization header was case-sensitive and incomplete. Fix this by
 26 |   respecting existing Authorization headers, regardless of letter case. (#550)
 27 | - Handle ParamsArray payloads. Previously, rest-client would silently drop a
 28 |   ParamsArray passed as the payload. Instead, automatically use
 29 |   Payload::Multipart if the ParamsArray contains a file handle, or use
 30 |   Payload::UrlEncoded if it doesn't. (#508)
 31 | - Gracefully handle Payload objects (Payload::Base or subclasses) that are
 32 |   passed as a payload argument. Previously, `Payload.generate` would wrap a
 33 |   Payload object in Payload::Streamed, creating a pointlessly nested payload.
 34 |   Also add a `closed?` method to Payload objects, and don't error in
 35 |   `short_inspect` if `size` returns nil. (#603)
 36 | - Test with an image in the public domain to avoid licensing complexity. (#607)
 37 | 
 38 | # 2.0.2
 39 | 
 40 | - Suppress the header override warning introduced in 2.0.1 if the value is the
 41 |   same. There's no conflict if the value is unchanged. (#578)
 42 | 
 43 | # 2.0.1
 44 | 
 45 | - Warn if auto-generated headers from the payload, such as Content-Type,
 46 |   override headers set by the user. This is usually not what the user wants to
 47 |   happen, and can be surprising. (#554)
 48 | - Drop the old check for weak default TLS ciphers, and use the built-in Ruby
 49 |   defaults. Ruby versions from Oct. 2014 onward use sane defaults, so this is
 50 |   no longer needed. (#573)
 51 | 
 52 | # 2.0.0
 53 | 
 54 | This release is largely API compatible, but makes several breaking changes.
 55 | 
 56 | - Drop support for Ruby 1.9
 57 | - Allow mime-types as new as 3.x (requires ruby 2.0)
 58 | - Respect Content-Type charset header provided by server. Previously,
 59 |   rest-client would not override the string encoding chosen by Net::HTTP. Now
 60 |   responses that specify a charset will yield a body string in that encoding.
 61 |   For example, `Content-Type: text/plain; charset=EUC-JP` will return a String
 62 |   encoded with `Encoding::EUC_JP`. (#361)
 63 | - Change exceptions raised on request timeout. Instead of
 64 |   `RestClient::RequestTimeout` (which is still used for HTTP 408), network
 65 |   timeouts will now raise either `RestClient::Exceptions::ReadTimeout` or
 66 |   `RestClient::Exceptions::OpenTimeout`, both of which inherit from
 67 |   `RestClient::Exceptions::Timeout`. For backwards compatibility, this still
 68 |   inherits from `RestClient::RequestTimeout` so existing uses will still work.
 69 |   This may change in a future major release. These new timeout classes also
 70 |   make the original wrapped exception available as `#original_exception`.
 71 | - Unify request exceptions under `RestClient::RequestFailed`, which still
 72 |   inherits from `ExceptionWithResponse`. Previously, HTTP 304, 401, and 404
 73 |   inherited directly from `ExceptionWithResponse` rather than from
 74 |   `RequestFailed`. Now _all_ HTTP status code exceptions inherit from both.
 75 | - Rename the `:timeout` request option to `:read_timeout`. When `:timeout` is
 76 |   passed, now set both `:read_timeout` and `:open_timeout`.
 77 | - Change default HTTP Accept header to `*/*`
 78 | - Use a more descriptive User-Agent header by default
 79 | - Drop RC4-MD5 from default cipher list
 80 | - Only prepend http:// to URIs without a scheme
 81 | - Fix some support for using IPv6 addresses in URLs (still affected by Ruby
 82 |   2.0+ bug https://bugs.ruby-lang.org/issues/9129, with the fix expected to be
 83 |   backported to 2.0 and 2.1)
 84 | - `Response` objects are now a subclass of `String` rather than a `String` that
 85 |   mixes in the response functionality. Most of the methods remain unchanged,
 86 |   but this makes it much easier to understand what is happening when you look
 87 |   at a RestClient response object. There are a few additional changes:
 88 |   - Response objects now implement `.inspect` to make this distinction clearer.
 89 |   - `Response#to_i` will now behave like `String#to_i` instead of returning the
 90 |     HTTP response code, which was very surprising behavior.
 91 |   - `Response#body` and `#to_s` will now return a true `String` object rather
 92 |     than self. Previously there was no easy way to get the true `String`
 93 |     response instead of the Frankenstein response string object with
 94 |     AbstractResponse mixed in.
 95 |   - Response objects no longer accept an extra request args hash, but instead
 96 |     access request args directly from the request object, which reduces
 97 |     confusion and duplication.
 98 | - Handle multiple HTTP response headers with the same name (except for
 99 |   Set-Cookie, which is special) by joining the values with a comma space,
100 |   compliant with RFC 7230
101 | - Rewrite cookie support to be much smarter and to use cookie jars consistently
102 |   for requests, responses, and redirection in order to resolve long-standing
103 |   complaints about the previously broken behavior: (#498)
104 |   - The `:cookies` option may now be a Hash of Strings, an Array of
105 |     HTTP::Cookie objects, or a full HTTP::CookieJar.
106 |   - Add `RestClient::Request#cookie_jar` and reimplement `Request#cookies` to
107 |     be a wrapper around the cookie jar.
108 |   - Still support passing the `:cookies` option in the headers hash, but now
109 |     raise ArgumentError if that option is also passed to `Request#initialize`.
110 |   - Warn if both `:cookies` and a `Cookie` header are supplied.
111 |   - Use the `Request#cookie_jar` as the basis for `Response#cookie_jar`,
112 |     creating a copy of the jar and adding any newly received cookies.
113 |   - When following redirection, also use this same strategy so that cookies
114 |     from the original request are carried through in a standards-compliant way
115 |     by the cookie jar.
116 | - Don't set basic auth header if explicit `Authorization` header is specified
117 | - Add `:proxy` option to requests, which can be used for thread-safe
118 |   per-request proxy configuration, overriding `RestClient.proxy`
119 | - Allow overriding `ENV['http_proxy']` to disable proxies by setting
120 |   `RestClient.proxy` to a falsey value. Previously there was no way in Ruby 2.x
121 |   to turn off a proxy specified in the environment without changing `ENV`.
122 | - Add actual support for streaming request payloads. Previously rest-client
123 |   would call `.to_s` even on RestClient::Payload::Streamed objects. Instead,
124 |   treat any object that responds to `.read` as a streaming payload and pass it
125 |   through to `.body_stream=` on the Net:HTTP object. This massively reduces the
126 |   memory required for large file uploads.
127 | - Changes to redirection behavior: (#381, #484)
128 |   - Remove `RestClient::MaxRedirectsReached` in favor of the normal
129 |     `ExceptionWithResponse` subclasses. This makes the response accessible on
130 |     the exception object as `.response`, making it possible for callers to tell
131 |     what has actually happened when the redirect limit is reached.
132 |   - When following HTTP redirection, store a list of each previous response on
133 |     the response object as `.history`. This makes it possible to access the
134 |     original response headers and body before the redirection was followed.
135 |   - Follow redirection consistently, regardless of whether the HTTP method was
136 |     passed as a symbol or string. Under the hood rest-client now normalizes the
137 |     HTTP request method to a lowercase string.
138 | - Add `:before_execution_proc` option to `RestClient::Request`. This makes it
139 |   possible to add procs like `RestClient.add_before_execution_proc` to a single
140 |   request without global state.
141 | - Run tests on Travis's beta OS X support.
142 | - Make `Request#transmit` a private method, along with a few others.
143 | - Refactor URI parsing to happen earlier, in Request initialization.
144 | - Improve consistency and functionality of complex URL parameter handling:
145 |   - When adding URL params, handle URLs that already contain params.
146 |   - Add new convention for handling URL params containing deeply nested arrays
147 |     and hashes, unify handling of null/empty values, and use the same code for
148 |     GET and POST params. (#437)
149 |   - Add the RestClient::ParamsArray class, a simple array-like container that
150 |     can be used to pass multiple keys with same name or keys where the ordering
151 |     is significant.
152 | - Add a few more exception classes for obscure HTTP status codes.
153 | - Multipart: use a much more robust multipart boundary with greater entropy.
154 | - Make `RestClient::Payload::Base#inspect` stop pretending to be a String.
155 | - Add `Request#redacted_uri` and `Request#redacted_url` to display the URI
156 |   with any password redacted.
157 | 
158 | # 2.0.0.rc1
159 | 
160 | Changes in the release candidate that did not persist through the final 2.0.0
161 | release:
162 | - RestClient::Exceptions::Timeout was originally going to be a direct subclass
163 |   of RestClient::Exception in the release candidate. This exception tree was
164 |   made a subclass of RestClient::RequestTimeout prior to the final release.
165 | 
166 | # 1.8.0
167 | 
168 | - Security: implement standards compliant cookie handling by adding a
169 |   dependency on http-cookie. This breaks compatibility, but was necessary to
170 |   address a session fixation / cookie disclosure vulnerability.
171 |   (#369 / CVE-2015-1820)
172 | 
173 |   Previously, any Set-Cookie headers found in an HTTP 30x response would be
174 |   sent to the redirection target, regardless of domain. Responses now expose a
175 |   cookie jar and respect standards compliant domain / path flags in Set-Cookie
176 |   headers.
177 | 
178 | # 1.7.3
179 | 
180 | - Security: redact password in URI from logs (#349 / OSVDB-117461)
181 | - Drop monkey patch on MIME::Types (added `type_for_extension` method, use
182 |   the public interface instead.
183 | 
184 | # 1.7.2
185 | 
186 | - Ignore duplicate certificates in CA store on Windows
187 | 
188 | # 1.7.1
189 | 
190 | - Relax mime-types dependency to continue supporting mime-types 1.x series.
191 |   There seem to be a large number of popular gems that have depended on
192 |   mime-types '~> 1.16' until very recently.
193 | - Improve urlencode performance
194 | - Clean up a number of style points
195 | 
196 | # 1.7.0
197 | 
198 | - This release drops support for Ruby 1.8.7 and breaks compatibility in a few
199 |   other relatively minor ways
200 | - Upgrade to mime-types ~> 2.0
201 | - Don't CGI.unescape cookie values sent to the server (issue #89)
202 | - Add support for reading credentials from netrc
203 | - Lots of SSL changes and enhancements: (#268)
204 |   - Enable peer verification by default (setting `VERIFY_PEER` with OpenSSL)
205 |   - By default, use the system default certificate store for SSL verification,
206 |     even on Windows (this uses a separate Windows build that pulls in ffi)
207 |   - Add support for SSL `ca_path`
208 |   - Add support for SSL `cert_store`
209 |   - Add support for SSL `verify_callback` (with some caveats for jruby, OS X, #277)
210 |   - Add support for SSL ciphers, and choose secure ones by default
211 | - Run tests under travis
212 | - Several other bugfixes and test improvements
213 |   - Convert Errno::ETIMEDOUT to RestClient::RequestTimeout
214 |   - Handle more HTTP response codes from recent standards
215 |   - Save raw responses to binary mode tempfile (#110)
216 |   - Disable timeouts with :timeout => nil rather than :timeout => -1
217 |   - Drop all Net::HTTP monkey patches
218 | 
219 | # 1.6.14
220 | 
221 | - This release is unchanged from 1.6.9. It was published in order to supersede
222 |   the malicious 1.6.10-13 versions, even for users who are still pinning to the
223 |   legacy 1.6.x series. All users are encouraged to upgrade to rest-client 2.x.
224 | 
225 | # 1.6.10, 1.6.11, 1.6.12, 1.6.13 (CVE-2019-15224)
226 | 
227 | - These versions were pushed by a malicious actor and included a backdoor permitting
228 |   remote code execution in Rails environments. (#713)
229 | - They were live for about five days before being yanked.
230 | 
231 | # 1.6.9
232 | 
233 | - Move rdoc to a development dependency
234 | 
235 | # 1.6.8
236 | 
237 | - The 1.6.x series will be the last to support Ruby 1.8.7
238 | - Pin mime-types to < 2.0 to maintain Ruby 1.8.7 support
239 | - Add Gemfile, AUTHORS, add license to gemspec
240 | - Point homepage at https://github.com/rest-client/rest-client
241 | - Clean up and fix various tests and ruby warnings
242 | - Backport `ssl_verify_callback` functionality from 1.7.0
243 | 
244 | # 1.6.7
245 | 
246 | - rebuild with 1.8.7 to avoid https://github.com/rubygems/rubygems/pull/57
247 | 
248 | # 1.6.6
249 | 
250 | - 1.6.5 was yanked
251 | 
252 | # 1.6.5
253 | 
254 | - RFC6265 requires single SP after ';' for separating parameters pairs in the 'Cookie:' header (patch provided by Hiroshi Nakamura)
255 | - enable url parameters for all actions
256 | - detect file parameters in arrays
257 | - allow disabling the timeouts by passing -1 (patch provided by Sven Böhm)
258 | 
259 | # 1.6.4
260 | 
261 | - fix restclient script compatibility with 1.9.2
262 | - fix unlinking temp file (patch provided by Evan Smith)
263 | - monkeypatching ruby for http patch method (patch provided by Syl Turner)
264 | 
265 | # 1.6.3
266 | 
267 | - 1.6.2 was yanked
268 | 
269 | # 1.6.2
270 | 
271 | - add support for HEAD in resources (patch provided by tpresa)
272 | - fix shell for 1.9.2
273 | - workaround when some gem monkeypatch net/http (patch provided by Ian Warshak)
274 | - DELETE requests should process parameters just like GET and HEAD
275 | - adding :block_response parameter for manual processing
276 | - limit number of redirections (patch provided by Chris Dinn)
277 | - close and unlink the temp file created by playload (patch provided by Chris Green)
278 | - make gemspec Rubygems 1.8 compatible (patch provided by David Backeus)
279 | - added RestClient.reset_before_execution_procs (patch provided by Cloudify)
280 | - added PATCH method (patch provided by Jeff Remer)
281 | - hack for HTTP servers that use raw DEFLATE compression, see http://www.ruby-forum.com/topic/136825 (path provided by James Reeves)
282 | 
283 | # 1.6.1
284 | 
285 | - add response body in Exception#inspect
286 | - add support for RestClient.options
287 | - fix tests for 1.9.2 (patch provided by Niko Dittmann)
288 | - block passing in Resource#[] (patch provided by Niko Dittmann)
289 | - cookies set in a response should be kept in a redirect
290 | - HEAD requests should process parameters just like GET (patch provided by Rob Eanes)
291 | - exception message should never be nil (patch provided by Michael Klett)
292 | 
293 | # 1.6.0
294 | 
295 | - forgot to include rest-client.rb in the gem
296 | - user, password and user-defined headers should survive a redirect
297 | - added all missing status codes
298 | - added parameter passing for get request using the :param key in header
299 | - the warning about the logger when using a string was a bad idea
300 | - multipart parameters names should not be escaped
301 | - remove the cookie escaping introduced by migrating to CGI cookie parsing in 1.5.1
302 | - add a streamed payload type (patch provided by Caleb Land)
303 | - Exception#http_body works even when no response
304 | 
305 | # 1.5.1
306 | 
307 | - only converts headers keys which are Symbols
308 | - use CGI for cookie parsing instead of custom code
309 | - unescape user and password before using them (patch provided by Lars Gierth)
310 | - expand ~ in ~/.restclientrc (patch provided by Mike Fletcher)
311 | - ssl verification raise an exception when the ca certificate is incorrect (patch provided by Braintree)
312 | 
313 | # 1.5.0
314 | 
315 | - the response is now a String with the Response module a.k.a. the change in 1.4.0 was a mistake (Response.body is returning self for compatability)
316 | - added AbstractResponse.to_i to improve semantic
317 | - multipart Payloads ignores the name attribute if it's not set (patch provided by Tekin Suleyman)
318 | - correctly takes into account user headers whose keys are strings (path provided by Cyril Rohr)
319 | - use binary mode for payload temp file
320 | - concatenate cookies with ';'
321 | - fixed deeper parameter handling
322 | - do not quote the boundary in the Content-Type header (patch provided by W. Andrew Loe III)
323 | 
324 | # 1.4.2
325 | 
326 | - fixed RestClient.add_before_execution_proc (patch provided by Nicholas Wieland)
327 | - fixed error when an exception is raised without a response (patch provided by Caleb Land)
328 | 
329 | # 1.4.1
330 | 
331 | - fixed parameters managment when using hash
332 | 
333 | # 1.4.0
334 | 
335 | - Response is no more a String, and the mixin is replaced by an abstract_response, existing calls are redirected to response body with a warning.
336 | - enable repeated parameters  RestClient.post 'http://example.com/resource', :param1 => ['one', 'two', 'three'], => :param2 => 'foo' (patch provided by Rodrigo Panachi)
337 | - fixed the redirect code concerning relative path and query string combination (patch provided by Kevin Read)
338 | - redirection code moved to Response so redirection can be customized using the block syntax
339 | - only get and head redirections are now followed by default, as stated in the specification
340 | - added RestClient.add_before_execution_proc to hack the http request, like for oauth
341 | 
342 | The response change may be breaking in rare cases.
343 | 
344 | # 1.3.1
345 | 
346 | - added compatibility to enable responses in exception to act like Net::HTTPResponse
347 | 
348 | # 1.3.0
349 | 
350 | - a block can be used to process a request's result, this enable to handle custom error codes or paththrought (design by Cyril Rohr)
351 | - cleaner log API, add a warning for some cases but should be compatible
352 | - accept multiple "Set-Cookie" headers, see http://www.ietf.org/rfc/rfc2109.txt (patch provided by Cyril Rohr)
353 | - remove "Content-Length" and "Content-Type" headers when following a redirection (patch provided by haarts)
354 | - all http error codes have now a corresponding exception class and all of them contain the Reponse -> this means that the raised exception can be different
355 | - changed "Content-Disposition: multipart/form-data" to "Content-Disposition: form-data" per RFC 2388 (patch provided by Kyle Crawford)
356 | 
357 | The only breaking change should be the exception classes, but as the new classes inherits from the existing ones, the breaking cases should be rare.
358 | 
359 | # 1.2.0
360 | 
361 | - formatting changed from tabs to spaces
362 | - logged requests now include generated headers
363 | - accept and content-type headers can now be specified using extentions: RestClient.post "http://example.com/resource", { 'x' => 1 }.to_json, :content_type => :json, :accept => :json
364 | - should be 1.1.1 but renamed to 1.2.0 because 1.1.X versions has already been packaged on Debian
365 | 
366 | # 1.1.0
367 | 
368 | - new maintainer: Archiloque, the working repo is now at http://github.com/archiloque/rest-client
369 | - a mailing list has been created at rest.client@librelist.com and an freenode irc channel #rest-client
370 | - François Beausoleil' multipart code from http://github.com/francois/rest-client has been merged
371 | - ability to use hash in hash as payload
372 | - the mime-type code now rely on the mime-types gem http://mime-types.rubyforge.org/ instead of an internal partial list
373 | - 204 response returns a Response instead of nil (patch provided by Elliott Draper)
374 | 
375 | All changes exept the last one should be fully compatible with the previous version.
376 | 
377 | NOTE: due to a dependency problem and to the last change, heroku users should update their heroku gem to >= 1.5.3 to be able to use this version.
378 | 


--------------------------------------------------------------------------------
/lib/rest-client.rb:
--------------------------------------------------------------------------------
1 | # More logical way to require 'rest-client'
2 | require File.dirname(__FILE__) + '/restclient'
3 | 


--------------------------------------------------------------------------------
/lib/rest_client.rb:
--------------------------------------------------------------------------------
1 | # This file exists for backward compatbility with require 'rest_client'
2 | require File.dirname(__FILE__) + '/restclient'
3 | 


--------------------------------------------------------------------------------
/lib/restclient.rb:
--------------------------------------------------------------------------------
  1 | require 'net/http'
  2 | require 'openssl'
  3 | require 'stringio'
  4 | require 'uri'
  5 | 
  6 | require File.dirname(__FILE__) + '/restclient/version'
  7 | require File.dirname(__FILE__) + '/restclient/platform'
  8 | require File.dirname(__FILE__) + '/restclient/exceptions'
  9 | require File.dirname(__FILE__) + '/restclient/utils'
 10 | require File.dirname(__FILE__) + '/restclient/request'
 11 | require File.dirname(__FILE__) + '/restclient/abstract_response'
 12 | require File.dirname(__FILE__) + '/restclient/response'
 13 | require File.dirname(__FILE__) + '/restclient/raw_response'
 14 | require File.dirname(__FILE__) + '/restclient/resource'
 15 | require File.dirname(__FILE__) + '/restclient/params_array'
 16 | require File.dirname(__FILE__) + '/restclient/payload'
 17 | require File.dirname(__FILE__) + '/restclient/windows'
 18 | 
 19 | # This module's static methods are the entry point for using the REST client.
 20 | #
 21 | #   # GET
 22 | #   xml = RestClient.get 'http://example.com/resource'
 23 | #   jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg'
 24 | #
 25 | #   # authentication and SSL
 26 | #   RestClient.get 'https://user:password@example.com/private/resource'
 27 | #
 28 | #   # POST or PUT with a hash sends parameters as a urlencoded form body
 29 | #   RestClient.post 'http://example.com/resource', :param1 => 'one'
 30 | #
 31 | #   # nest hash parameters
 32 | #   RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' }
 33 | #
 34 | #   # POST and PUT with raw payloads
 35 | #   RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain'
 36 | #   RestClient.post 'http://example.com/resource.xml', xml_doc
 37 | #   RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf'
 38 | #
 39 | #   # DELETE
 40 | #   RestClient.delete 'http://example.com/resource'
 41 | #
 42 | #   # retrieve the response http code and headers
 43 | #   res = RestClient.get 'http://example.com/some.jpg'
 44 | #   res.code                    # => 200
 45 | #   res.headers[:content_type]  # => 'image/jpg'
 46 | #
 47 | #   # HEAD
 48 | #   RestClient.head('http://example.com').headers
 49 | #
 50 | # To use with a proxy, just set RestClient.proxy to the proper http proxy:
 51 | #
 52 | #   RestClient.proxy = "http://proxy.example.com/"
 53 | #
 54 | # Or inherit the proxy from the environment:
 55 | #
 56 | #   RestClient.proxy = ENV['http_proxy']
 57 | #
 58 | # For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call:
 59 | #
 60 | #   >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz'
 61 | #   => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}"
 62 | #
 63 | module RestClient
 64 | 
 65 |   def self.get(url, headers={}, &block)
 66 |     Request.execute(:method => :get, :url => url, :headers => headers, &block)
 67 |   end
 68 | 
 69 |   def self.post(url, payload, headers={}, &block)
 70 |     Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers, &block)
 71 |   end
 72 | 
 73 |   def self.patch(url, payload, headers={}, &block)
 74 |     Request.execute(:method => :patch, :url => url, :payload => payload, :headers => headers, &block)
 75 |   end
 76 | 
 77 |   def self.put(url, payload, headers={}, &block)
 78 |     Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers, &block)
 79 |   end
 80 | 
 81 |   def self.delete(url, headers={}, &block)
 82 |     Request.execute(:method => :delete, :url => url, :headers => headers, &block)
 83 |   end
 84 | 
 85 |   def self.head(url, headers={}, &block)
 86 |     Request.execute(:method => :head, :url => url, :headers => headers, &block)
 87 |   end
 88 | 
 89 |   def self.options(url, headers={}, &block)
 90 |     Request.execute(:method => :options, :url => url, :headers => headers, &block)
 91 |   end
 92 | 
 93 |   # A global proxy URL to use for all requests. This can be overridden on a
 94 |   # per-request basis by passing `:proxy` to RestClient::Request.
 95 |   def self.proxy
 96 |     @proxy ||= nil
 97 |   end
 98 | 
 99 |   def self.proxy=(value)
100 |     @proxy = value
101 |     @proxy_set = true
102 |   end
103 | 
104 |   # Return whether RestClient.proxy was set explicitly. We use this to
105 |   # differentiate between no value being set and a value explicitly set to nil.
106 |   #
107 |   # @return [Boolean]
108 |   #
109 |   def self.proxy_set?
110 |     @proxy_set ||= false
111 |   end
112 | 
113 |   # Setup the log for RestClient calls.
114 |   # Value should be a logger but can can be stdout, stderr, or a filename.
115 |   # You can also configure logging by the environment variable RESTCLIENT_LOG.
116 |   def self.log= log
117 |     @@log = create_log log
118 |   end
119 | 
120 |   # Create a log that respond to << like a logger
121 |   # param can be 'stdout', 'stderr', a string (then we will log to that file) or a logger (then we return it)
122 |   def self.create_log param
123 |     if param
124 |       if param.is_a? String
125 |         if param == 'stdout'
126 |           stdout_logger = Class.new do
127 |             def << obj
128 |               STDOUT.puts obj
129 |             end
130 |           end
131 |           stdout_logger.new
132 |         elsif param == 'stderr'
133 |           stderr_logger = Class.new do
134 |             def << obj
135 |               STDERR.puts obj
136 |             end
137 |           end
138 |           stderr_logger.new
139 |         else
140 |           file_logger = Class.new do
141 |             attr_writer :target_file
142 | 
143 |             def << obj
144 |               File.open(@target_file, 'a') { |f| f.puts obj }
145 |             end
146 |           end
147 |           logger = file_logger.new
148 |           logger.target_file = param
149 |           logger
150 |         end
151 |       else
152 |         param
153 |       end
154 |     end
155 |   end
156 | 
157 |   @@env_log = create_log ENV['RESTCLIENT_LOG']
158 | 
159 |   @@log = nil
160 | 
161 |   def self.log # :nodoc:
162 |     @@env_log || @@log
163 |   end
164 | 
165 |   @@before_execution_procs = []
166 | 
167 |   # Add a Proc to be called before each request in executed.
168 |   # The proc parameters will be the http request and the request params.
169 |   def self.add_before_execution_proc &proc
170 |     raise ArgumentError.new('block is required') unless proc
171 |     @@before_execution_procs << proc
172 |   end
173 | 
174 |   # Reset the procs to be called before each request is executed.
175 |   def self.reset_before_execution_procs
176 |     @@before_execution_procs = []
177 |   end
178 | 
179 |   def self.before_execution_procs # :nodoc:
180 |     @@before_execution_procs
181 |   end
182 | 
183 | end
184 | 


--------------------------------------------------------------------------------
/lib/restclient/abstract_response.rb:
--------------------------------------------------------------------------------
  1 | require 'cgi'
  2 | require 'http-cookie'
  3 | 
  4 | module RestClient
  5 | 
  6 |   module AbstractResponse
  7 | 
  8 |     attr_reader :net_http_res, :request, :start_time, :end_time, :duration
  9 | 
 10 |     def inspect
 11 |       raise NotImplementedError.new('must override in subclass')
 12 |     end
 13 | 
 14 |     # Logger from the request, potentially nil.
 15 |     def log
 16 |       request.log
 17 |     end
 18 | 
 19 |     def log_response
 20 |       return unless log
 21 | 
 22 |       code = net_http_res.code
 23 |       res_name = net_http_res.class.to_s.gsub(/\ANet::HTTP/, '')
 24 |       content_type = (net_http_res['Content-type'] || '').gsub(/;.*\z/, '')
 25 | 
 26 |       log << "# => #{code} #{res_name} | #{content_type} #{size} bytes, #{sprintf('%.2f', duration)}s\n"
 27 |     end
 28 | 
 29 |     # HTTP status code
 30 |     def code
 31 |       @code ||= @net_http_res.code.to_i
 32 |     end
 33 | 
 34 |     def history
 35 |       @history ||= request.redirection_history || []
 36 |     end
 37 | 
 38 |     # A hash of the headers, beautified with symbols and underscores.
 39 |     # e.g. "Content-type" will become :content_type.
 40 |     def headers
 41 |       @headers ||= AbstractResponse.beautify_headers(@net_http_res.to_hash)
 42 |     end
 43 | 
 44 |     # The raw headers.
 45 |     def raw_headers
 46 |       @raw_headers ||= @net_http_res.to_hash
 47 |     end
 48 | 
 49 |     # @param [Net::HTTPResponse] net_http_res
 50 |     # @param [RestClient::Request] request
 51 |     # @param [Time] start_time
 52 |     def response_set_vars(net_http_res, request, start_time)
 53 |       @net_http_res = net_http_res
 54 |       @request = request
 55 |       @start_time = start_time
 56 |       @end_time = Time.now
 57 | 
 58 |       if @start_time
 59 |         @duration = @end_time - @start_time
 60 |       else
 61 |         @duration = nil
 62 |       end
 63 | 
 64 |       # prime redirection history
 65 |       history
 66 |     end
 67 | 
 68 |     # Hash of cookies extracted from response headers.
 69 |     #
 70 |     # NB: This will return only cookies whose domain matches this request, and
 71 |     # may not even return all of those cookies if there are duplicate names.
 72 |     # Use the full cookie_jar for more nuanced access.
 73 |     #
 74 |     # @see #cookie_jar
 75 |     #
 76 |     # @return [Hash]
 77 |     #
 78 |     def cookies
 79 |       hash = {}
 80 | 
 81 |       cookie_jar.cookies(@request.uri).each do |cookie|
 82 |         hash[cookie.name] = cookie.value
 83 |       end
 84 | 
 85 |       hash
 86 |     end
 87 | 
 88 |     # Cookie jar extracted from response headers.
 89 |     #
 90 |     # @return [HTTP::CookieJar]
 91 |     #
 92 |     def cookie_jar
 93 |       return @cookie_jar if defined?(@cookie_jar) && @cookie_jar
 94 | 
 95 |       jar = @request.cookie_jar.dup
 96 |       headers.fetch(:set_cookie, []).each do |cookie|
 97 |         jar.parse(cookie, @request.uri)
 98 |       end
 99 | 
100 |       @cookie_jar = jar
101 |     end
102 | 
103 |     # Return the default behavior corresponding to the response code:
104 |     #
105 |     # For 20x status codes: return the response itself
106 |     #
107 |     # For 30x status codes:
108 |     #   301, 302, 307: redirect GET / HEAD if there is a Location header
109 |     #   303: redirect, changing method to GET, if there is a Location header
110 |     #
111 |     # For all other responses, raise a response exception
112 |     #
113 |     def return!(&block)
114 |       case code
115 |       when 200..207
116 |         self
117 |       when 301, 302, 307
118 |         case request.method
119 |         when 'get', 'head'
120 |           check_max_redirects
121 |           follow_redirection(&block)
122 |         else
123 |           raise exception_with_response
124 |         end
125 |       when 303
126 |         check_max_redirects
127 |         follow_get_redirection(&block)
128 |       else
129 |         raise exception_with_response
130 |       end
131 |     end
132 | 
133 |     def to_i
134 |       warn('warning: calling Response#to_i is not recommended')
135 |       super
136 |     end
137 | 
138 |     def description
139 |       "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n"
140 |     end
141 | 
142 |     # Follow a redirection response by making a new HTTP request to the
143 |     # redirection target.
144 |     def follow_redirection(&block)
145 |       _follow_redirection(request.args.dup, &block)
146 |     end
147 | 
148 |     # Follow a redirection response, but change the HTTP method to GET and drop
149 |     # the payload from the original request.
150 |     def follow_get_redirection(&block)
151 |       new_args = request.args.dup
152 |       new_args[:method] = :get
153 |       new_args.delete(:payload)
154 | 
155 |       _follow_redirection(new_args, &block)
156 |     end
157 | 
158 |     # Convert headers hash into canonical form.
159 |     #
160 |     # Header names will be converted to lowercase symbols with underscores
161 |     # instead of hyphens.
162 |     #
163 |     # Headers specified multiple times will be joined by comma and space,
164 |     # except for Set-Cookie, which will always be an array.
165 |     #
166 |     # Per RFC 2616, if a server sends multiple headers with the same key, they
167 |     # MUST be able to be joined into a single header by a comma. However,
168 |     # Set-Cookie (RFC 6265) cannot because commas are valid within cookie
169 |     # definitions. The newer RFC 7230 notes (3.2.2) that Set-Cookie should be
170 |     # handled as a special case.
171 |     #
172 |     # http://tools.ietf.org/html/rfc2616#section-4.2
173 |     # http://tools.ietf.org/html/rfc7230#section-3.2.2
174 |     # http://tools.ietf.org/html/rfc6265
175 |     #
176 |     # @param headers [Hash]
177 |     # @return [Hash]
178 |     #
179 |     def self.beautify_headers(headers)
180 |       headers.inject({}) do |out, (key, value)|
181 |         key_sym = key.tr('-', '_').downcase.to_sym
182 | 
183 |         # Handle Set-Cookie specially since it cannot be joined by comma.
184 |         if key.downcase == 'set-cookie'
185 |           out[key_sym] = value
186 |         else
187 |           out[key_sym] = value.join(', ')
188 |         end
189 | 
190 |         out
191 |       end
192 |     end
193 | 
194 |     private
195 | 
196 |     # Follow a redirection
197 |     #
198 |     # @param new_args [Hash] Start with this hash of arguments for the
199 |     #   redirection request. The hash will be mutated, so be sure to dup any
200 |     #   existing hash that should not be modified.
201 |     #
202 |     def _follow_redirection(new_args, &block)
203 | 
204 |       # parse location header and merge into existing URL
205 |       url = headers[:location]
206 | 
207 |       # cannot follow redirection if there is no location header
208 |       unless url
209 |         raise exception_with_response
210 |       end
211 | 
212 |       # handle relative redirects
213 |       unless url.start_with?('http')
214 |         url = URI.parse(request.url).merge(url).to_s
215 |       end
216 |       new_args[:url] = url
217 | 
218 |       new_args[:password] = request.password
219 |       new_args[:user] = request.user
220 |       new_args[:headers] = request.headers
221 |       new_args[:max_redirects] = request.max_redirects - 1
222 | 
223 |       # pass through our new cookie jar
224 |       new_args[:cookies] = cookie_jar
225 | 
226 |       # prepare new request
227 |       new_req = Request.new(new_args)
228 | 
229 |       # append self to redirection history
230 |       new_req.redirection_history = history + [self]
231 | 
232 |       # execute redirected request
233 |       new_req.execute(&block)
234 |     end
235 | 
236 |     def check_max_redirects
237 |       if request.max_redirects <= 0
238 |         raise exception_with_response
239 |       end
240 |     end
241 | 
242 |     def exception_with_response
243 |       begin
244 |         klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
245 |       rescue KeyError
246 |         raise RequestFailed.new(self, code)
247 |       end
248 | 
249 |       raise klass.new(self, code)
250 |     end
251 |   end
252 | end
253 | 


--------------------------------------------------------------------------------
/lib/restclient/exceptions.rb:
--------------------------------------------------------------------------------
  1 | module RestClient
  2 | 
  3 |   # Hash of HTTP status code => message.
  4 |   #
  5 |   # 1xx: Informational - Request received, continuing process
  6 |   # 2xx: Success - The action was successfully received, understood, and
  7 |   #      accepted
  8 |   # 3xx: Redirection - Further action must be taken in order to complete the
  9 |   #      request
 10 |   # 4xx: Client Error - The request contains bad syntax or cannot be fulfilled
 11 |   # 5xx: Server Error - The server failed to fulfill an apparently valid
 12 |   #      request
 13 |   #
 14 |   # @see
 15 |   #   http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
 16 |   #
 17 |   STATUSES = {100 => 'Continue',
 18 |               101 => 'Switching Protocols',
 19 |               102 => 'Processing', #WebDAV
 20 | 
 21 |               200 => 'OK',
 22 |               201 => 'Created',
 23 |               202 => 'Accepted',
 24 |               203 => 'Non-Authoritative Information', # http/1.1
 25 |               204 => 'No Content',
 26 |               205 => 'Reset Content',
 27 |               206 => 'Partial Content',
 28 |               207 => 'Multi-Status', #WebDAV
 29 |               208 => 'Already Reported', # RFC5842
 30 |               226 => 'IM Used', # RFC3229
 31 | 
 32 |               300 => 'Multiple Choices',
 33 |               301 => 'Moved Permanently',
 34 |               302 => 'Found',
 35 |               303 => 'See Other', # http/1.1
 36 |               304 => 'Not Modified',
 37 |               305 => 'Use Proxy', # http/1.1
 38 |               306 => 'Switch Proxy', # no longer used
 39 |               307 => 'Temporary Redirect', # http/1.1
 40 |               308 => 'Permanent Redirect', # RFC7538
 41 | 
 42 |               400 => 'Bad Request',
 43 |               401 => 'Unauthorized',
 44 |               402 => 'Payment Required',
 45 |               403 => 'Forbidden',
 46 |               404 => 'Not Found',
 47 |               405 => 'Method Not Allowed',
 48 |               406 => 'Not Acceptable',
 49 |               407 => 'Proxy Authentication Required',
 50 |               408 => 'Request Timeout',
 51 |               409 => 'Conflict',
 52 |               410 => 'Gone',
 53 |               411 => 'Length Required',
 54 |               412 => 'Precondition Failed',
 55 |               413 => 'Payload Too Large', # RFC7231 (renamed, see below)
 56 |               414 => 'URI Too Long', # RFC7231 (renamed, see below)
 57 |               415 => 'Unsupported Media Type',
 58 |               416 => 'Range Not Satisfiable', # RFC7233 (renamed, see below)
 59 |               417 => 'Expectation Failed',
 60 |               418 => 'I\'m A Teapot', #RFC2324
 61 |               421 => 'Too Many Connections From This IP',
 62 |               422 => 'Unprocessable Entity', #WebDAV
 63 |               423 => 'Locked', #WebDAV
 64 |               424 => 'Failed Dependency', #WebDAV
 65 |               425 => 'Unordered Collection', #WebDAV
 66 |               426 => 'Upgrade Required',
 67 |               428 => 'Precondition Required', #RFC6585
 68 |               429 => 'Too Many Requests', #RFC6585
 69 |               431 => 'Request Header Fields Too Large', #RFC6585
 70 |               449 => 'Retry With', #Microsoft
 71 |               450 => 'Blocked By Windows Parental Controls', #Microsoft
 72 | 
 73 |               500 => 'Internal Server Error',
 74 |               501 => 'Not Implemented',
 75 |               502 => 'Bad Gateway',
 76 |               503 => 'Service Unavailable',
 77 |               504 => 'Gateway Timeout',
 78 |               505 => 'HTTP Version Not Supported',
 79 |               506 => 'Variant Also Negotiates',
 80 |               507 => 'Insufficient Storage', #WebDAV
 81 |               508 => 'Loop Detected', # RFC5842
 82 |               509 => 'Bandwidth Limit Exceeded', #Apache
 83 |               510 => 'Not Extended',
 84 |               511 => 'Network Authentication Required', # RFC6585
 85 |   }
 86 | 
 87 |   STATUSES_COMPATIBILITY = {
 88 |     # The RFCs all specify "Not Found", but "Resource Not Found" was used in
 89 |     # earlier RestClient releases.
 90 |     404 => ['ResourceNotFound'],
 91 | 
 92 |     # HTTP 413 was renamed to "Payload Too Large" in RFC7231.
 93 |     413 => ['RequestEntityTooLarge'],
 94 | 
 95 |     # HTTP 414 was renamed to "URI Too Long" in RFC7231.
 96 |     414 => ['RequestURITooLong'],
 97 | 
 98 |     # HTTP 416 was renamed to "Range Not Satisfiable" in RFC7233.
 99 |     416 => ['RequestedRangeNotSatisfiable'],
100 |   }
101 | 
102 | 
103 |   # This is the base RestClient exception class. Rescue it if you want to
104 |   # catch any exception that your request might raise
105 |   # You can get the status code by e.http_code, or see anything about the
106 |   # response via e.response.
107 |   # For example, the entire result body (which is
108 |   # probably an HTML error page) is e.response.
109 |   class Exception < RuntimeError
110 |     attr_accessor :response
111 |     attr_accessor :original_exception
112 |     attr_writer :message
113 | 
114 |     def initialize response = nil, initial_response_code = nil
115 |       @response = response
116 |       @message = nil
117 |       @initial_response_code = initial_response_code
118 |     end
119 | 
120 |     def http_code
121 |       # return integer for compatibility
122 |       if @response
123 |         @response.code.to_i
124 |       else
125 |         @initial_response_code
126 |       end
127 |     end
128 | 
129 |     def http_headers
130 |       @response.headers if @response
131 |     end
132 | 
133 |     def http_body
134 |       @response.body if @response
135 |     end
136 | 
137 |     def to_s
138 |       message
139 |     end
140 | 
141 |     def message
142 |       @message || default_message
143 |     end
144 | 
145 |     def default_message
146 |       self.class.name
147 |     end
148 |   end
149 | 
150 |   # Compatibility
151 |   class ExceptionWithResponse < RestClient::Exception
152 |   end
153 | 
154 |   # The request failed with an error code not managed by the code
155 |   class RequestFailed < ExceptionWithResponse
156 | 
157 |     def default_message
158 |       "HTTP status code #{http_code}"
159 |     end
160 | 
161 |     def to_s
162 |       message
163 |     end
164 |   end
165 | 
166 |   # RestClient exception classes. TODO: move all exceptions into this module.
167 |   #
168 |   # We will a create an exception for each status code, see
169 |   # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
170 |   #
171 |   module Exceptions
172 |     # Map http status codes to the corresponding exception class
173 |     EXCEPTIONS_MAP = {}
174 |   end
175 | 
176 |   # Create HTTP status exception classes
177 |   STATUSES.each_pair do |code, message|
178 |     klass = Class.new(RequestFailed) do
179 |       send(:define_method, :default_message) {"#{http_code ? "#{http_code} " : ''}#{message}"}
180 |     end
181 |     klass_constant = const_set(message.delete(' \-\''), klass)
182 |     Exceptions::EXCEPTIONS_MAP[code] = klass_constant
183 |   end
184 | 
185 |   # Create HTTP status exception classes used for backwards compatibility
186 |   STATUSES_COMPATIBILITY.each_pair do |code, compat_list|
187 |     klass = Exceptions::EXCEPTIONS_MAP.fetch(code)
188 |     compat_list.each do |old_name|
189 |       const_set(old_name, klass)
190 |     end
191 |   end
192 | 
193 |   module Exceptions
194 |     # We have to split the Exceptions module like we do here because the
195 |     # EXCEPTIONS_MAP is under Exceptions, but we depend on
196 |     # RestClient::RequestTimeout below.
197 | 
198 |     # Base class for request timeouts.
199 |     #
200 |     # NB: Previous releases of rest-client would raise RequestTimeout both for
201 |     # HTTP 408 responses and for actual connection timeouts.
202 |     class Timeout < RestClient::RequestTimeout
203 |       def initialize(message=nil, original_exception=nil)
204 |         super(nil, nil)
205 |         self.message = message if message
206 |         self.original_exception = original_exception if original_exception
207 |       end
208 |     end
209 | 
210 |     # Timeout when connecting to a server. Typically wraps Net::OpenTimeout (in
211 |     # ruby 2.0 or greater).
212 |     class OpenTimeout < Timeout
213 |       def default_message
214 |         'Timed out connecting to server'
215 |       end
216 |     end
217 | 
218 |     # Timeout when reading from a server. Typically wraps Net::ReadTimeout (in
219 |     # ruby 2.0 or greater).
220 |     class ReadTimeout < Timeout
221 |       def default_message
222 |         'Timed out reading data from server'
223 |       end
224 |     end
225 |   end
226 | 
227 | 
228 |   # The server broke the connection prior to the request completing.  Usually
229 |   # this means it crashed, or sometimes that your network connection was
230 |   # severed before it could complete.
231 |   class ServerBrokeConnection < RestClient::Exception
232 |     def initialize(message = 'Server broke connection')
233 |       super nil, nil
234 |       self.message = message
235 |     end
236 |   end
237 | 
238 |   class SSLCertificateNotVerified < RestClient::Exception
239 |     def initialize(message = 'SSL certificate not verified')
240 |       super nil, nil
241 |       self.message = message
242 |     end
243 |   end
244 | end
245 | 


--------------------------------------------------------------------------------
/lib/restclient/params_array.rb:
--------------------------------------------------------------------------------
 1 | module RestClient
 2 | 
 3 |   # The ParamsArray class is used to represent an ordered list of [key, value]
 4 |   # pairs. Use this when you need to include a key multiple times or want
 5 |   # explicit control over parameter ordering.
 6 |   #
 7 |   # Most of the request payload & parameter functions normally accept a Hash of
 8 |   # keys => values, which does not allow for duplicated keys.
 9 |   #
10 |   # @see RestClient::Utils.encode_query_string
11 |   # @see RestClient::Utils.flatten_params
12 |   #
13 |   class ParamsArray
14 |     include Enumerable
15 | 
16 |     # @param array [Array<Array>] An array of parameter key,value pairs. These
17 |     #   pairs may be 2 element arrays [key, value] or single element hashes
18 |     #   {key => value}. They may also be single element arrays to represent a
19 |     #   key with no value.
20 |     #
21 |     # @example
22 |     #   >> ParamsArray.new([[:foo, 123], [:foo, 456], [:bar, 789]])
23 |     #   This will be encoded as "foo=123&foo=456&bar=789"
24 |     #
25 |     # @example
26 |     #   >> ParamsArray.new({foo: 123, bar: 456})
27 |     #   This is valid, but there's no reason not to just use the Hash directly
28 |     #   instead of a ParamsArray.
29 |     #
30 |     #
31 |     def initialize(array)
32 |       @array = process_input(array)
33 |     end
34 | 
35 |     def each(*args, &blk)
36 |       @array.each(*args, &blk)
37 |     end
38 | 
39 |     def empty?
40 |       @array.empty?
41 |     end
42 | 
43 |     private
44 | 
45 |     def process_input(array)
46 |       array.map {|v| process_pair(v) }
47 |     end
48 | 
49 |     # A pair may be:
50 |     # - A single element hash, e.g. {foo: 'bar'}
51 |     # - A two element array, e.g. ['foo', 'bar']
52 |     # - A one element array, e.g. ['foo']
53 |     #
54 |     def process_pair(pair)
55 |       case pair
56 |       when Hash
57 |         if pair.length != 1
58 |           raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
59 |         end
60 |         pair.to_a.fetch(0)
61 |       when Array
62 |         if pair.length > 2
63 |           raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}")
64 |         end
65 |         [pair.fetch(0), pair[1]]
66 |       else
67 |         # recurse, converting any non-array to an array
68 |         process_pair(pair.to_a)
69 |       end
70 |     end
71 |   end
72 | end
73 | 


--------------------------------------------------------------------------------
/lib/restclient/payload.rb:
--------------------------------------------------------------------------------
  1 | require 'tempfile'
  2 | require 'securerandom'
  3 | require 'stringio'
  4 | 
  5 | begin
  6 |   # Use mime/types/columnar if available, for reduced memory usage
  7 |   require 'mime/types/columnar'
  8 | rescue LoadError
  9 |   require 'mime/types'
 10 | end
 11 | 
 12 | module RestClient
 13 |   module Payload
 14 |     extend self
 15 | 
 16 |     def generate(params)
 17 |       if params.is_a?(RestClient::Payload::Base)
 18 |         # pass through Payload objects unchanged
 19 |         params
 20 |       elsif params.is_a?(String)
 21 |         Base.new(params)
 22 |       elsif params.is_a?(Hash)
 23 |         if params.delete(:multipart) == true || has_file?(params)
 24 |           Multipart.new(params)
 25 |         else
 26 |           UrlEncoded.new(params)
 27 |         end
 28 |       elsif params.is_a?(ParamsArray)
 29 |         if _has_file?(params)
 30 |           Multipart.new(params)
 31 |         else
 32 |           UrlEncoded.new(params)
 33 |         end
 34 |       elsif params.respond_to?(:read)
 35 |         Streamed.new(params)
 36 |       else
 37 |         nil
 38 |       end
 39 |     end
 40 | 
 41 |     def has_file?(params)
 42 |       unless params.is_a?(Hash)
 43 |         raise ArgumentError.new("Must pass Hash, not #{params.inspect}")
 44 |       end
 45 |       _has_file?(params)
 46 |     end
 47 | 
 48 |     def _has_file?(obj)
 49 |       case obj
 50 |       when Hash, ParamsArray
 51 |         obj.any? {|_, v| _has_file?(v) }
 52 |       when Array
 53 |         obj.any? {|v| _has_file?(v) }
 54 |       else
 55 |         obj.respond_to?(:path) && obj.respond_to?(:read)
 56 |       end
 57 |     end
 58 | 
 59 |     class Base
 60 |       def initialize(params)
 61 |         build_stream(params)
 62 |       end
 63 | 
 64 |       def build_stream(params)
 65 |         @stream = StringIO.new(params)
 66 |         @stream.seek(0)
 67 |       end
 68 | 
 69 |       def read(*args)
 70 |         @stream.read(*args)
 71 |       end
 72 | 
 73 |       def to_s
 74 |         result = read
 75 |         @stream.seek(0)
 76 |         result
 77 |       end
 78 | 
 79 |       def headers
 80 |         {'Content-Length' => size.to_s}
 81 |       end
 82 | 
 83 |       def size
 84 |         @stream.size
 85 |       end
 86 | 
 87 |       alias :length :size
 88 | 
 89 |       def close
 90 |         @stream.close unless @stream.closed?
 91 |       end
 92 | 
 93 |       def closed?
 94 |         @stream.closed?
 95 |       end
 96 | 
 97 |       def to_s_inspect
 98 |         to_s.inspect
 99 |       end
100 | 
101 |       def short_inspect
102 |         if size && size > 500
103 |           "#{size} byte(s) length"
104 |         else
105 |           to_s_inspect
106 |         end
107 |       end
108 | 
109 |     end
110 | 
111 |     class Streamed < Base
112 |       def build_stream(params = nil)
113 |         @stream = params
114 |       end
115 | 
116 |       def size
117 |         if @stream.respond_to?(:size)
118 |           @stream.size
119 |         elsif @stream.is_a?(IO)
120 |           @stream.stat.size
121 |         end
122 |       end
123 | 
124 |       # TODO (breaks compatibility): ought to use mime_for() to autodetect the
125 |       # Content-Type for stream objects that have a filename.
126 | 
127 |       alias :length :size
128 |     end
129 | 
130 |     class UrlEncoded < Base
131 |       def build_stream(params = nil)
132 |         @stream = StringIO.new(Utils.encode_query_string(params))
133 |         @stream.seek(0)
134 |       end
135 | 
136 |       def headers
137 |         super.merge({'Content-Type' => 'application/x-www-form-urlencoded'})
138 |       end
139 |     end
140 | 
141 |     class Multipart < Base
142 |       EOL = "\r\n"
143 | 
144 |       def build_stream(params)
145 |         b = '--' + boundary
146 | 
147 |         @stream = Tempfile.new('rest-client.multipart.')
148 |         @stream.binmode
149 |         @stream.write(b + EOL)
150 | 
151 |         case params
152 |         when Hash, ParamsArray
153 |           x = Utils.flatten_params(params)
154 |         else
155 |           x = params
156 |         end
157 | 
158 |         last_index = x.length - 1
159 |         x.each_with_index do |a, index|
160 |           k, v = * a
161 |           if v.respond_to?(:read) && v.respond_to?(:path)
162 |             create_file_field(@stream, k, v)
163 |           else
164 |             create_regular_field(@stream, k, v)
165 |           end
166 |           @stream.write(EOL + b)
167 |           @stream.write(EOL) unless last_index == index
168 |         end
169 |         @stream.write('--')
170 |         @stream.write(EOL)
171 |         @stream.seek(0)
172 |       end
173 | 
174 |       def create_regular_field(s, k, v)
175 |         s.write("Content-Disposition: form-data; name=\"#{k}\"")
176 |         s.write(EOL)
177 |         s.write(EOL)
178 |         s.write(v)
179 |       end
180 | 
181 |       def create_file_field(s, k, v)
182 |         begin
183 |           s.write("Content-Disposition: form-data;")
184 |           s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
185 |           s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
186 |           s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
187 |           s.write(EOL)
188 |           while (data = v.read(8124))
189 |             s.write(data)
190 |           end
191 |         ensure
192 |           v.close if v.respond_to?(:close)
193 |         end
194 |       end
195 | 
196 |       def mime_for(path)
197 |         mime = MIME::Types.type_for path
198 |         mime.empty? ? 'text/plain' : mime[0].content_type
199 |       end
200 | 
201 |       def boundary
202 |         return @boundary if defined?(@boundary) && @boundary
203 | 
204 |         # Use the same algorithm used by WebKit: generate 16 random
205 |         # alphanumeric characters, replacing `+` `/` with `A` `B` (included in
206 |         # the list twice) to round out the set of 64.
207 |         s = SecureRandom.base64(12)
208 |         s.tr!('+/', 'AB')
209 | 
210 |         @boundary = '----RubyFormBoundary' + s
211 |       end
212 | 
213 |       # for Multipart do not escape the keys
214 |       #
215 |       # Ostensibly multipart keys MAY be percent encoded per RFC 7578, but in
216 |       # practice no major browser that I'm aware of uses percent encoding.
217 |       #
218 |       # Further discussion of multipart encoding:
219 |       # https://github.com/rest-client/rest-client/pull/403#issuecomment-156976930
220 |       #
221 |       def handle_key key
222 |         key
223 |       end
224 | 
225 |       def headers
226 |         super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}})
227 |       end
228 | 
229 |       def close
230 |         @stream.close!
231 |       end
232 |     end
233 |   end
234 | end
235 | 


--------------------------------------------------------------------------------
/lib/restclient/platform.rb:
--------------------------------------------------------------------------------
 1 | require 'rbconfig'
 2 | 
 3 | module RestClient
 4 |   module Platform
 5 |     # Return true if we are running on a darwin-based Ruby platform. This will
 6 |     # be false for jruby even on OS X.
 7 |     #
 8 |     # @return [Boolean]
 9 |     def self.mac_mri?
10 |       RUBY_PLATFORM.include?('darwin')
11 |     end
12 | 
13 |     # Return true if we are running on Windows.
14 |     #
15 |     # @return [Boolean]
16 |     #
17 |     def self.windows?
18 |       # Ruby only sets File::ALT_SEPARATOR on Windows, and the Ruby standard
19 |       # library uses that to test what platform it's on.
20 |       !!File::ALT_SEPARATOR
21 |     end
22 | 
23 |     # Return true if we are running on jruby.
24 |     #
25 |     # @return [Boolean]
26 |     #
27 |     def self.jruby?
28 |       # defined on mri >= 1.9
29 |       RUBY_ENGINE == 'jruby'
30 |     end
31 | 
32 |     def self.architecture
33 |       "#{RbConfig::CONFIG['host_os']} #{RbConfig::CONFIG['host_cpu']}"
34 |     end
35 | 
36 |     def self.ruby_agent_version
37 |       case RUBY_ENGINE
38 |       when 'jruby'
39 |         "jruby/#{JRUBY_VERSION} (#{RUBY_VERSION}p#{RUBY_PATCHLEVEL})"
40 |       else
41 |         "#{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}"
42 |       end
43 |     end
44 | 
45 |     def self.default_user_agent
46 |       "rest-client/#{VERSION} (#{architecture}) #{ruby_agent_version}"
47 |     end
48 |   end
49 | end
50 | 


--------------------------------------------------------------------------------
/lib/restclient/raw_response.rb:
--------------------------------------------------------------------------------
 1 | module RestClient
 2 |   # The response from RestClient on a raw request looks like a string, but is
 3 |   # actually one of these.  99% of the time you're making a rest call all you
 4 |   # care about is the body, but on the occasion you want to fetch the
 5 |   # headers you can:
 6 |   #
 7 |   #   RestClient.get('http://example.com').headers[:content_type]
 8 |   #
 9 |   # In addition, if you do not use the response as a string, you can access
10 |   # a Tempfile object at res.file, which contains the path to the raw
11 |   # downloaded request body.
12 |   class RawResponse
13 | 
14 |     include AbstractResponse
15 | 
16 |     attr_reader :file, :request, :start_time, :end_time
17 | 
18 |     def inspect
19 |       "<RestClient::RawResponse @code=#{code.inspect}, @file=#{file.inspect}, @request=#{request.inspect}>"
20 |     end
21 | 
22 |     # @param [Tempfile] tempfile The temporary file containing the body
23 |     # @param [Net::HTTPResponse] net_http_res
24 |     # @param [RestClient::Request] request
25 |     # @param [Time] start_time
26 |     def initialize(tempfile, net_http_res, request, start_time=nil)
27 |       @file = tempfile
28 | 
29 |       # reopen the tempfile so we can read it
30 |       @file.open
31 | 
32 |       response_set_vars(net_http_res, request, start_time)
33 |     end
34 | 
35 |     def to_s
36 |       body
37 |     end
38 | 
39 |     def body
40 |       @file.rewind
41 |       @file.read
42 |     end
43 | 
44 |     def size
45 |       file.size
46 |     end
47 | 
48 |   end
49 | end
50 | 


--------------------------------------------------------------------------------
/lib/restclient/request.rb:
--------------------------------------------------------------------------------
  1 | require 'tempfile'
  2 | require 'cgi'
  3 | require 'netrc'
  4 | require 'set'
  5 | 
  6 | begin
  7 |   # Use mime/types/columnar if available, for reduced memory usage
  8 |   require 'mime/types/columnar'
  9 | rescue LoadError
 10 |   require 'mime/types'
 11 | end
 12 | 
 13 | module RestClient
 14 |   # This class is used internally by RestClient to send the request, but you can also
 15 |   # call it directly if you'd like to use a method not supported by the
 16 |   # main API.  For example:
 17 |   #
 18 |   #   RestClient::Request.execute(:method => :head, :url => 'http://example.com')
 19 |   #
 20 |   # Mandatory parameters:
 21 |   # * :method
 22 |   # * :url
 23 |   # Optional parameters (have a look at ssl and/or uri for some explanations):
 24 |   # * :headers a hash containing the request headers
 25 |   # * :cookies may be a Hash{String/Symbol => String} of cookie values, an
 26 |   #     Array<HTTP::Cookie>, or an HTTP::CookieJar containing cookies. These
 27 |   #     will be added to a cookie jar before the request is sent.
 28 |   # * :user and :password for basic auth, will be replaced by a user/password available in the :url
 29 |   # * :block_response call the provided block with the HTTPResponse as parameter
 30 |   # * :raw_response return a low-level RawResponse instead of a Response
 31 |   # * :log Set the log for this request only, overriding RestClient.log, if
 32 |   #      any.
 33 |   # * :stream_log_percent (Only relevant with :raw_response => true) Customize
 34 |   #     the interval at which download progress is logged. Defaults to every
 35 |   #     10% complete.
 36 |   # * :max_redirects maximum number of redirections (default to 10)
 37 |   # * :proxy An HTTP proxy URI to use for this request. Any value here
 38 |   #   (including nil) will override RestClient.proxy.
 39 |   # * :verify_ssl enable ssl verification, possible values are constants from
 40 |   #     OpenSSL::SSL::VERIFY_*, defaults to OpenSSL::SSL::VERIFY_PEER
 41 |   # * :read_timeout and :open_timeout are how long to wait for a response and
 42 |   #     to open a connection, in seconds. Pass nil to disable the timeout.
 43 |   # * :timeout can be used to set both timeouts
 44 |   # * :ssl_client_cert, :ssl_client_key, :ssl_ca_file, :ssl_ca_path,
 45 |   #     :ssl_cert_store, :ssl_verify_callback, :ssl_verify_callback_warnings
 46 |   # * :ssl_version specifies the SSL version for the underlying Net::HTTP connection
 47 |   # * :ssl_ciphers sets SSL ciphers for the connection. See
 48 |   #     OpenSSL::SSL::SSLContext#ciphers=
 49 |   # * :before_execution_proc a Proc to call before executing the request. This
 50 |   #      proc, like procs from RestClient.before_execution_procs, will be
 51 |   #      called with the HTTP request and request params.
 52 |   class Request
 53 | 
 54 |     attr_reader :method, :uri, :url, :headers, :payload, :proxy,
 55 |                 :user, :password, :read_timeout, :max_redirects,
 56 |                 :open_timeout, :raw_response, :processed_headers, :args,
 57 |                 :ssl_opts
 58 | 
 59 |     # An array of previous redirection responses
 60 |     attr_accessor :redirection_history
 61 | 
 62 |     def self.execute(args, & block)
 63 |       new(args).execute(& block)
 64 |     end
 65 | 
 66 |     SSLOptionList = %w{client_cert client_key ca_file ca_path cert_store
 67 |                        version ciphers verify_callback verify_callback_warnings}
 68 | 
 69 |     def inspect
 70 |       "<RestClient::Request @method=#{@method.inspect}, @url=#{@url.inspect}>"
 71 |     end
 72 | 
 73 |     def initialize args
 74 |       @method = normalize_method(args[:method])
 75 |       @headers = (args[:headers] || {}).dup
 76 |       if args[:url]
 77 |         @url = process_url_params(normalize_url(args[:url]), headers)
 78 |       else
 79 |         raise ArgumentError, "must pass :url"
 80 |       end
 81 | 
 82 |       @user = @password = nil
 83 |       parse_url_with_auth!(url)
 84 | 
 85 |       # process cookie arguments found in headers or args
 86 |       @cookie_jar = process_cookie_args!(@uri, @headers, args)
 87 | 
 88 |       @payload = Payload.generate(args[:payload])
 89 | 
 90 |       @user = args[:user] if args.include?(:user)
 91 |       @password = args[:password] if args.include?(:password)
 92 | 
 93 |       if args.include?(:timeout)
 94 |         @read_timeout = args[:timeout]
 95 |         @open_timeout = args[:timeout]
 96 |       end
 97 |       if args.include?(:read_timeout)
 98 |         @read_timeout = args[:read_timeout]
 99 |       end
100 |       if args.include?(:open_timeout)
101 |         @open_timeout = args[:open_timeout]
102 |       end
103 |       @block_response = args[:block_response]
104 |       @raw_response = args[:raw_response] || false
105 | 
106 |       @stream_log_percent = args[:stream_log_percent] || 10
107 |       if @stream_log_percent <= 0 || @stream_log_percent > 100
108 |         raise ArgumentError.new(
109 |           "Invalid :stream_log_percent #{@stream_log_percent.inspect}")
110 |       end
111 | 
112 |       @proxy = args.fetch(:proxy) if args.include?(:proxy)
113 | 
114 |       @ssl_opts = {}
115 | 
116 |       if args.include?(:verify_ssl)
117 |         v_ssl = args.fetch(:verify_ssl)
118 |         if v_ssl
119 |           if v_ssl == true
120 |             # interpret :verify_ssl => true as VERIFY_PEER
121 |             @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
122 |           else
123 |             # otherwise pass through any truthy values
124 |             @ssl_opts[:verify_ssl] = v_ssl
125 |           end
126 |         else
127 |           # interpret all falsy :verify_ssl values as VERIFY_NONE
128 |           @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
129 |         end
130 |       else
131 |         # if :verify_ssl was not passed, default to VERIFY_PEER
132 |         @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER
133 |       end
134 | 
135 |       SSLOptionList.each do |key|
136 |         source_key = ('ssl_' + key).to_sym
137 |         if args.has_key?(source_key)
138 |           @ssl_opts[key.to_sym] = args.fetch(source_key)
139 |         end
140 |       end
141 | 
142 |       # Set some other default SSL options, but only if we have an HTTPS URI.
143 |       if use_ssl?
144 | 
145 |         # If there's no CA file, CA path, or cert store provided, use default
146 |         if !ssl_ca_file && !ssl_ca_path && !@ssl_opts.include?(:cert_store)
147 |           @ssl_opts[:cert_store] = self.class.default_ssl_cert_store
148 |         end
149 |       end
150 | 
151 |       @log = args[:log]
152 |       @max_redirects = args[:max_redirects] || 10
153 |       @processed_headers = make_headers headers
154 |       @processed_headers_lowercase = Hash[@processed_headers.map {|k, v| [k.downcase, v]}]
155 |       @args = args
156 | 
157 |       @before_execution_proc = args[:before_execution_proc]
158 |     end
159 | 
160 |     def execute & block
161 |       # With 2.0.0+, net/http accepts URI objects in requests and handles wrapping
162 |       # IPv6 addresses in [] for use in the Host request header.
163 |       transmit uri, net_http_request_class(method).new(uri, processed_headers), payload, & block
164 |     ensure
165 |       payload.close if payload
166 |     end
167 | 
168 |     # SSL-related options
169 |     def verify_ssl
170 |       @ssl_opts.fetch(:verify_ssl)
171 |     end
172 |     SSLOptionList.each do |key|
173 |       define_method('ssl_' + key) do
174 |         @ssl_opts[key.to_sym]
175 |       end
176 |     end
177 | 
178 |     # Return true if the request URI will use HTTPS.
179 |     #
180 |     # @return [Boolean]
181 |     #
182 |     def use_ssl?
183 |       uri.is_a?(URI::HTTPS)
184 |     end
185 | 
186 |     # Extract the query parameters and append them to the url
187 |     #
188 |     # Look through the headers hash for a :params option (case-insensitive,
189 |     # may be string or symbol). If present and the value is a Hash or
190 |     # RestClient::ParamsArray, *delete* the key/value pair from the headers
191 |     # hash and encode the value into a query string. Append this query string
192 |     # to the URL and return the resulting URL.
193 |     #
194 |     # @param [String] url
195 |     # @param [Hash] headers An options/headers hash to process. Mutation
196 |     #   warning: the params key may be removed if present!
197 |     #
198 |     # @return [String] resulting url with query string
199 |     #
200 |     def process_url_params(url, headers)
201 |       url_params = nil
202 | 
203 |       # find and extract/remove "params" key if the value is a Hash/ParamsArray
204 |       headers.delete_if do |key, value|
205 |         if key.to_s.downcase == 'params' &&
206 |             (value.is_a?(Hash) || value.is_a?(RestClient::ParamsArray))
207 |           if url_params
208 |             raise ArgumentError.new("Multiple 'params' options passed")
209 |           end
210 |           url_params = value
211 |           true
212 |         else
213 |           false
214 |         end
215 |       end
216 | 
217 |       # build resulting URL with query string
218 |       if url_params && !url_params.empty?
219 |         query_string = RestClient::Utils.encode_query_string(url_params)
220 | 
221 |         if url.include?('?')
222 |           url + '&' + query_string
223 |         else
224 |           url + '?' + query_string
225 |         end
226 |       else
227 |         url
228 |       end
229 |     end
230 | 
231 |     # Render a hash of key => value pairs for cookies in the Request#cookie_jar
232 |     # that are valid for the Request#uri. This will not necessarily include all
233 |     # cookies if there are duplicate keys. It's safer to use the cookie_jar
234 |     # directly if that's a concern.
235 |     #
236 |     # @see Request#cookie_jar
237 |     #
238 |     # @return [Hash]
239 |     #
240 |     def cookies
241 |       hash = {}
242 | 
243 |       @cookie_jar.cookies(uri).each do |c|
244 |         hash[c.name] = c.value
245 |       end
246 | 
247 |       hash
248 |     end
249 | 
250 |     # @return [HTTP::CookieJar]
251 |     def cookie_jar
252 |       @cookie_jar
253 |     end
254 | 
255 |     # Render a Cookie HTTP request header from the contents of the @cookie_jar,
256 |     # or nil if the jar is empty.
257 |     #
258 |     # @see Request#cookie_jar
259 |     #
260 |     # @return [String, nil]
261 |     #
262 |     def make_cookie_header
263 |       return nil if cookie_jar.nil?
264 | 
265 |       arr = cookie_jar.cookies(url)
266 |       return nil if arr.empty?
267 | 
268 |       return HTTP::Cookie.cookie_value(arr)
269 |     end
270 | 
271 |     # Process cookies passed as hash or as HTTP::CookieJar. For backwards
272 |     # compatibility, these may be passed as a :cookies option masquerading
273 |     # inside the headers hash. To avoid confusion, if :cookies is passed in
274 |     # both headers and Request#initialize, raise an error.
275 |     #
276 |     # :cookies may be a:
277 |     # - Hash{String/Symbol => String}
278 |     # - Array<HTTP::Cookie>
279 |     # - HTTP::CookieJar
280 |     #
281 |     # Passing as a hash:
282 |     #   Keys may be symbols or strings. Values must be strings.
283 |     #   Infer the domain name from the request URI and allow subdomains (as
284 |     #   though '.example.com' had been set in a Set-Cookie header). Assume a
285 |     #   path of '/'.
286 |     #
287 |     #     RestClient::Request.new(url: 'http://example.com', method: :get,
288 |     #       :cookies => {:foo => 'Value', 'bar' => '123'}
289 |     #     )
290 |     #
291 |     # results in cookies as though set from the server by:
292 |     #     Set-Cookie: foo=Value; Domain=.example.com; Path=/
293 |     #     Set-Cookie: bar=123; Domain=.example.com; Path=/
294 |     #
295 |     # which yields a client cookie header of:
296 |     #     Cookie: foo=Value; bar=123
297 |     #
298 |     # Passing as HTTP::CookieJar, which will be passed through directly:
299 |     #
300 |     #     jar = HTTP::CookieJar.new
301 |     #     jar.add(HTTP::Cookie.new('foo', 'Value', domain: 'example.com',
302 |     #                              path: '/', for_domain: false))
303 |     #
304 |     #     RestClient::Request.new(..., :cookies => jar)
305 |     #
306 |     # @param [URI::HTTP] uri The URI for the request. This will be used to
307 |     # infer the domain name for cookies passed as strings in a hash. To avoid
308 |     # this implicit behavior, pass a full cookie jar or use HTTP::Cookie hash
309 |     # values.
310 |     # @param [Hash] headers The headers hash from which to pull the :cookies
311 |     #   option. MUTATION NOTE: This key will be deleted from the hash if
312 |     #   present.
313 |     # @param [Hash] args The options passed to Request#initialize. This hash
314 |     #   will be used as another potential source for the :cookies key.
315 |     #   These args will not be mutated.
316 |     #
317 |     # @return [HTTP::CookieJar] A cookie jar containing the parsed cookies.
318 |     #
319 |     def process_cookie_args!(uri, headers, args)
320 | 
321 |       # Avoid ambiguity in whether options from headers or options from
322 |       # Request#initialize should take precedence by raising ArgumentError when
323 |       # both are present. Prior versions of rest-client claimed to give
324 |       # precedence to init options, but actually gave precedence to headers.
325 |       # Avoid that mess by erroring out instead.
326 |       if headers[:cookies] && args[:cookies]
327 |         raise ArgumentError.new(
328 |           "Cannot pass :cookies in Request.new() and in headers hash")
329 |       end
330 | 
331 |       cookies_data = headers.delete(:cookies) || args[:cookies]
332 | 
333 |       # return copy of cookie jar as is
334 |       if cookies_data.is_a?(HTTP::CookieJar)
335 |         return cookies_data.dup
336 |       end
337 | 
338 |       # convert cookies hash into a CookieJar
339 |       jar = HTTP::CookieJar.new
340 | 
341 |       (cookies_data || []).each do |key, val|
342 | 
343 |         # Support for Array<HTTP::Cookie> mode:
344 |         # If key is a cookie object, add it to the jar directly and assert that
345 |         # there is no separate val.
346 |         if key.is_a?(HTTP::Cookie)
347 |           if val
348 |             raise ArgumentError.new("extra cookie val: #{val.inspect}")
349 |           end
350 | 
351 |           jar.add(key)
352 |           next
353 |         end
354 | 
355 |         if key.is_a?(Symbol)
356 |           key = key.to_s
357 |         end
358 | 
359 |         # assume implicit domain from the request URI, and set for_domain to
360 |         # permit subdomains
361 |         jar.add(HTTP::Cookie.new(key, val, domain: uri.hostname.downcase,
362 |                                  path: '/', for_domain: true))
363 |       end
364 | 
365 |       jar
366 |     end
367 | 
368 |     # Generate headers for use by a request. Header keys will be stringified
369 |     # using `#stringify_headers` to normalize them as capitalized strings.
370 |     #
371 |     # The final headers consist of:
372 |     #   - default headers from #default_headers
373 |     #   - user_headers provided here
374 |     #   - headers from the payload object (e.g. Content-Type, Content-Lenth)
375 |     #   - cookie headers from #make_cookie_header
376 |     #
377 |     # BUG: stringify_headers does not alter the capitalization of headers that
378 |     # are passed as strings, it only normalizes those passed as symbols. This
379 |     # behavior will probably remain for a while for compatibility, but it means
380 |     # that the warnings that attempt to detect accidental header overrides may
381 |     # not always work.
382 |     # https://github.com/rest-client/rest-client/issues/599
383 |     #
384 |     # @param [Hash] user_headers User-provided headers to include
385 |     #
386 |     # @return [Hash<String, String>] A hash of HTTP headers => values
387 |     #
388 |     def make_headers(user_headers)
389 |       headers = stringify_headers(default_headers).merge(stringify_headers(user_headers))
390 | 
391 |       # override headers from the payload (e.g. Content-Type, Content-Length)
392 |       if @payload
393 |         payload_headers = @payload.headers
394 | 
395 |         # Warn the user if we override any headers that were previously
396 |         # present. This usually indicates that rest-client was passed
397 |         # conflicting information, e.g. if it was asked to render a payload as
398 |         # x-www-form-urlencoded but a Content-Type application/json was
399 |         # also supplied by the user.
400 |         payload_headers.each_pair do |key, val|
401 |           if headers.include?(key) && headers[key] != val
402 |             warn("warning: Overriding #{key.inspect} header " +
403 |                  "#{headers.fetch(key).inspect} with #{val.inspect} " +
404 |                  "due to payload")
405 |           end
406 |         end
407 | 
408 |         headers.merge!(payload_headers)
409 |       end
410 | 
411 |       # merge in cookies
412 |       cookies = make_cookie_header
413 |       if cookies && !cookies.empty?
414 |         if headers['Cookie']
415 |           warn('warning: overriding "Cookie" header with :cookies option')
416 |         end
417 |         headers['Cookie'] = cookies
418 |       end
419 | 
420 |       headers
421 |     end
422 | 
423 |     # The proxy URI for this request. If `:proxy` was provided on this request,
424 |     # use it over `RestClient.proxy`.
425 |     #
426 |     # Return false if a proxy was explicitly set and is falsy.
427 |     #
428 |     # @return [URI, false, nil]
429 |     #
430 |     def proxy_uri
431 |       if defined?(@proxy)
432 |         if @proxy
433 |           URI.parse(@proxy)
434 |         else
435 |           false
436 |         end
437 |       elsif RestClient.proxy_set?
438 |         if RestClient.proxy
439 |           URI.parse(RestClient.proxy)
440 |         else
441 |           false
442 |         end
443 |       else
444 |         nil
445 |       end
446 |     end
447 | 
448 |     def net_http_object(hostname, port)
449 |       p_uri = proxy_uri
450 | 
451 |       if p_uri.nil?
452 |         # no proxy set
453 |         Net::HTTP.new(hostname, port)
454 |       elsif !p_uri
455 |         # proxy explicitly set to none
456 |         Net::HTTP.new(hostname, port, nil, nil, nil, nil)
457 |       else
458 |         Net::HTTP.new(hostname, port,
459 |                       p_uri.hostname, p_uri.port, p_uri.user, p_uri.password)
460 | 
461 |       end
462 |     end
463 | 
464 |     def net_http_request_class(method)
465 |       Net::HTTP.const_get(method.capitalize, false)
466 |     end
467 | 
468 |     def net_http_do_request(http, req, body=nil, &block)
469 |       if body && body.respond_to?(:read)
470 |         req.body_stream = body
471 |         return http.request(req, nil, &block)
472 |       else
473 |         return http.request(req, body, &block)
474 |       end
475 |     end
476 | 
477 |     # Normalize a URL by adding a protocol if none is present.
478 |     #
479 |     # If the string has no HTTP-like scheme (i.e. scheme followed by '//'), a
480 |     # scheme of 'http' will be added. This mimics the behavior of browsers and
481 |     # user agents like cURL.
482 |     #
483 |     # @param [String] url A URL string.
484 |     #
485 |     # @return [String]
486 |     #
487 |     def normalize_url(url)
488 |       url = 'http://' + url unless url.match(%r{\A[a-z][a-z0-9+.-]*://}i)
489 |       url
490 |     end
491 | 
492 |     # Return a certificate store that can be used to validate certificates with
493 |     # the system certificate authorities. This will probably not do anything on
494 |     # OS X, which monkey patches OpenSSL in terrible ways to insert its own
495 |     # validation. On most *nix platforms, this will add the system certifcates
496 |     # using OpenSSL::X509::Store#set_default_paths. On Windows, this will use
497 |     # RestClient::Windows::RootCerts to look up the CAs trusted by the system.
498 |     #
499 |     # @return [OpenSSL::X509::Store]
500 |     #
501 |     def self.default_ssl_cert_store
502 |       cert_store = OpenSSL::X509::Store.new
503 |       cert_store.set_default_paths
504 | 
505 |       # set_default_paths() doesn't do anything on Windows, so look up
506 |       # certificates using the win32 API.
507 |       if RestClient::Platform.windows?
508 |         RestClient::Windows::RootCerts.instance.to_a.uniq.each do |cert|
509 |           begin
510 |             cert_store.add_cert(cert)
511 |           rescue OpenSSL::X509::StoreError => err
512 |             # ignore duplicate certs
513 |             raise unless err.message == 'cert already in hash table'
514 |           end
515 |         end
516 |       end
517 | 
518 |       cert_store
519 |     end
520 | 
521 |     def redacted_uri
522 |       if uri.password
523 |         sanitized_uri = uri.dup
524 |         sanitized_uri.password = 'REDACTED'
525 |         sanitized_uri
526 |       else
527 |         uri
528 |       end
529 |     end
530 | 
531 |     def redacted_url
532 |       redacted_uri.to_s
533 |     end
534 | 
535 |     # Default to the global logger if there's not a request-specific one
536 |     def log
537 |       @log || RestClient.log
538 |     end
539 | 
540 |     def log_request
541 |       return unless log
542 | 
543 |       out = []
544 | 
545 |       out << "RestClient.#{method} #{redacted_url.inspect}"
546 |       out << payload.short_inspect if payload
547 |       out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ")
548 |       log << out.join(', ') + "\n"
549 |     end
550 | 
551 |     # Return a hash of headers whose keys are capitalized strings
552 |     #
553 |     # BUG: stringify_headers does not fix the capitalization of headers that
554 |     # are already Strings. Leaving this behavior as is for now for
555 |     # backwards compatibility.
556 |     # https://github.com/rest-client/rest-client/issues/599
557 |     #
558 |     def stringify_headers headers
559 |       headers.inject({}) do |result, (key, value)|
560 |         if key.is_a? Symbol
561 |           key = key.to_s.split(/_/).map(&:capitalize).join('-')
562 |         end
563 |         if 'CONTENT-TYPE' == key.upcase
564 |           result[key] = maybe_convert_extension(value.to_s)
565 |         elsif 'ACCEPT' == key.upcase
566 |           # Accept can be composed of several comma-separated values
567 |           if value.is_a? Array
568 |             target_values = value
569 |           else
570 |             target_values = value.to_s.split ','
571 |           end
572 |           result[key] = target_values.map { |ext|
573 |             maybe_convert_extension(ext.to_s.strip)
574 |           }.join(', ')
575 |         else
576 |           result[key] = value.to_s
577 |         end
578 |         result
579 |       end
580 |     end
581 | 
582 |     # Default headers set by RestClient. In addition to these headers, servers
583 |     # will receive headers set by Net::HTTP, such as Accept-Encoding and Host.
584 |     #
585 |     # @return [Hash<Symbol, String>]
586 |     def default_headers
587 |       {
588 |         :accept => '*/*',
589 |         :user_agent => RestClient::Platform.default_user_agent,
590 |       }
591 |     end
592 | 
593 |     private
594 | 
595 |     # Parse the `@url` string into a URI object and save it as
596 |     # `@uri`. Also save any basic auth user or password as @user and @password.
597 |     # If no auth info was passed, check for credentials in a Netrc file.
598 |     #
599 |     # @param [String] url A URL string.
600 |     #
601 |     # @return [URI]
602 |     #
603 |     # @raise URI::InvalidURIError on invalid URIs
604 |     #
605 |     def parse_url_with_auth!(url)
606 |       uri = URI.parse(url)
607 | 
608 |       if uri.hostname.nil?
609 |         raise URI::InvalidURIError.new("bad URI(no host provided): #{url}")
610 |       end
611 | 
612 |       @user = CGI.unescape(uri.user) if uri.user
613 |       @password = CGI.unescape(uri.password) if uri.password
614 |       if !@user && !@password
615 |         @user, @password = Netrc.read[uri.hostname]
616 |       end
617 | 
618 |       @uri = uri
619 |     end
620 | 
621 |     def print_verify_callback_warnings
622 |       warned = false
623 |       if RestClient::Platform.mac_mri?
624 |         warn('warning: ssl_verify_callback return code is ignored on OS X')
625 |         warned = true
626 |       end
627 |       if RestClient::Platform.jruby?
628 |         warn('warning: SSL verify_callback may not work correctly in jruby')
629 |         warn('see https://github.com/jruby/jruby/issues/597')
630 |         warned = true
631 |       end
632 |       warned
633 |     end
634 | 
635 |     # Parse a method and return a normalized string version.
636 |     #
637 |     # Raise ArgumentError if the method is falsy, but otherwise do no
638 |     # validation.
639 |     #
640 |     # @param method [String, Symbol]
641 |     #
642 |     # @return [String]
643 |     #
644 |     # @see net_http_request_class
645 |     #
646 |     def normalize_method(method)
647 |       raise ArgumentError.new('must pass :method') unless method
648 |       method.to_s.downcase
649 |     end
650 | 
651 |     def transmit uri, req, payload, & block
652 | 
653 |       # We set this to true in the net/http block so that we can distinguish
654 |       # read_timeout from open_timeout. Now that we only support Ruby 2.0+,
655 |       # this is only needed for Timeout exceptions thrown outside of Net::HTTP.
656 |       established_connection = false
657 | 
658 |       setup_credentials req
659 | 
660 |       net = net_http_object(uri.hostname, uri.port)
661 |       net.use_ssl = uri.is_a?(URI::HTTPS)
662 |       net.ssl_version = ssl_version if ssl_version
663 |       net.ciphers = ssl_ciphers if ssl_ciphers
664 | 
665 |       net.verify_mode = verify_ssl
666 | 
667 |       net.cert = ssl_client_cert if ssl_client_cert
668 |       net.key = ssl_client_key if ssl_client_key
669 |       net.ca_file = ssl_ca_file if ssl_ca_file
670 |       net.ca_path = ssl_ca_path if ssl_ca_path
671 |       net.cert_store = ssl_cert_store if ssl_cert_store
672 | 
673 |       # We no longer rely on net.verify_callback for the main SSL verification
674 |       # because it's not well supported on all platforms (see comments below).
675 |       # But do allow users to set one if they want.
676 |       if ssl_verify_callback
677 |         net.verify_callback = ssl_verify_callback
678 | 
679 |         # Hilariously, jruby only calls the callback when cert_store is set to
680 |         # something, so make sure to set one.
681 |         # https://github.com/jruby/jruby/issues/597
682 |         if RestClient::Platform.jruby?
683 |           net.cert_store ||= OpenSSL::X509::Store.new
684 |         end
685 | 
686 |         if ssl_verify_callback_warnings != false
687 |           if print_verify_callback_warnings
688 |             warn('pass :ssl_verify_callback_warnings => false to silence this')
689 |           end
690 |         end
691 |       end
692 | 
693 |       if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE
694 |         warn('WARNING: OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE')
695 |         warn('This dangerous monkey patch leaves you open to MITM attacks!')
696 |         warn('Try passing :verify_ssl => false instead.')
697 |       end
698 | 
699 |       if defined? @read_timeout
700 |         if @read_timeout == -1
701 |           warn 'Deprecated: to disable timeouts, please use nil instead of -1'
702 |           @read_timeout = nil
703 |         end
704 |         net.read_timeout = @read_timeout
705 |       end
706 |       if defined? @open_timeout
707 |         if @open_timeout == -1
708 |           warn 'Deprecated: to disable timeouts, please use nil instead of -1'
709 |           @open_timeout = nil
710 |         end
711 |         net.open_timeout = @open_timeout
712 |       end
713 | 
714 |       RestClient.before_execution_procs.each do |before_proc|
715 |         before_proc.call(req, args)
716 |       end
717 | 
718 |       if @before_execution_proc
719 |         @before_execution_proc.call(req, args)
720 |       end
721 | 
722 |       log_request
723 | 
724 |       start_time = Time.now
725 |       tempfile = nil
726 | 
727 |       net.start do |http|
728 |         established_connection = true
729 | 
730 |         if @block_response
731 |           net_http_do_request(http, req, payload, &@block_response)
732 |         else
733 |           res = net_http_do_request(http, req, payload) { |http_response|
734 |             if @raw_response
735 |               # fetch body into tempfile
736 |               tempfile = fetch_body_to_tempfile(http_response)
737 |             else
738 |               # fetch body
739 |               http_response.read_body
740 |             end
741 |             http_response
742 |           }
743 |           process_result(res, start_time, tempfile, &block)
744 |         end
745 |       end
746 |     rescue EOFError
747 |       raise RestClient::ServerBrokeConnection
748 |     rescue Net::OpenTimeout => err
749 |       raise RestClient::Exceptions::OpenTimeout.new(nil, err)
750 |     rescue Net::ReadTimeout => err
751 |       raise RestClient::Exceptions::ReadTimeout.new(nil, err)
752 |     rescue Timeout::Error, Errno::ETIMEDOUT => err
753 |       # handling for non-Net::HTTP timeouts
754 |       if established_connection
755 |         raise RestClient::Exceptions::ReadTimeout.new(nil, err)
756 |       else
757 |         raise RestClient::Exceptions::OpenTimeout.new(nil, err)
758 |       end
759 | 
760 |     rescue OpenSSL::SSL::SSLError => error
761 |       # TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just
762 |       # pass through OpenSSL::SSL::SSLError directly.
763 |       #
764 |       # Exceptions in verify_callback are ignored [1], and jruby doesn't support
765 |       # it at all [2]. RestClient has to catch OpenSSL::SSL::SSLError and either
766 |       # re-throw it as is, or throw SSLCertificateNotVerified based on the
767 |       # contents of the message field of the original exception.
768 |       #
769 |       # The client has to handle OpenSSL::SSL::SSLError exceptions anyway, so
770 |       # we shouldn't make them handle both OpenSSL and RestClient exceptions.
771 |       #
772 |       # [1] https://github.com/ruby/ruby/blob/89e70fe8e7/ext/openssl/ossl.c#L238
773 |       # [2] https://github.com/jruby/jruby/issues/597
774 | 
775 |       if error.message.include?("certificate verify failed")
776 |         raise SSLCertificateNotVerified.new(error.message)
777 |       else
778 |         raise error
779 |       end
780 |     end
781 | 
782 |     def setup_credentials(req)
783 |       if user && !@processed_headers_lowercase.include?('authorization')
784 |         req.basic_auth(user, password)
785 |       end
786 |     end
787 | 
788 |     def fetch_body_to_tempfile(http_response)
789 |       # Taken from Chef, which as in turn...
790 |       # Stolen from http://www.ruby-forum.com/topic/166423
791 |       # Kudos to _why!
792 |       tf = Tempfile.new('rest-client.')
793 |       tf.binmode
794 | 
795 |       size = 0
796 |       total = http_response['Content-Length'].to_i
797 |       stream_log_bucket = nil
798 | 
799 |       http_response.read_body do |chunk|
800 |         tf.write chunk
801 |         size += chunk.size
802 |         if log
803 |           if total == 0
804 |             log << "streaming %s %s (%d of unknown) [0 Content-Length]\n" % [@method.upcase, @url, size]
805 |           else
806 |             percent = (size * 100) / total
807 |             current_log_bucket, _ = percent.divmod(@stream_log_percent)
808 |             if current_log_bucket != stream_log_bucket
809 |               stream_log_bucket = current_log_bucket
810 |               log << "streaming %s %s %d%% done (%d of %d)\n" % [@method.upcase, @url, (size * 100) / total, size, total]
811 |             end
812 |           end
813 |         end
814 |       end
815 |       tf.close
816 |       tf
817 |     end
818 | 
819 |     # @param res The Net::HTTP response object
820 |     # @param start_time [Time] Time of request start
821 |     def process_result(res, start_time, tempfile=nil, &block)
822 |       if @raw_response
823 |         unless tempfile
824 |           raise ArgumentError.new('tempfile is required')
825 |         end
826 |         response = RawResponse.new(tempfile, res, self, start_time)
827 |       else
828 |         response = Response.create(res.body, res, self, start_time)
829 |       end
830 | 
831 |       response.log_response
832 | 
833 |       if block_given?
834 |         block.call(response, self, res, & block)
835 |       else
836 |         response.return!(&block)
837 |       end
838 | 
839 |     end
840 | 
841 |     def parser
842 |       URI.const_defined?(:Parser) ? URI::Parser.new : URI
843 |     end
844 | 
845 |     # Given a MIME type or file extension, return either a MIME type or, if
846 |     # none is found, the input unchanged.
847 |     #
848 |     #     >> maybe_convert_extension('json')
849 |     #     => 'application/json'
850 |     #
851 |     #     >> maybe_convert_extension('unknown')
852 |     #     => 'unknown'
853 |     #
854 |     #     >> maybe_convert_extension('application/xml')
855 |     #     => 'application/xml'
856 |     #
857 |     # @param ext [String]
858 |     #
859 |     # @return [String]
860 |     #
861 |     def maybe_convert_extension(ext)
862 |       unless ext =~ /\A[a-zA-Z0-9_@-]+\z/
863 |         # Don't look up strings unless they look like they could be a file
864 |         # extension known to mime-types.
865 |         #
866 |         # There currently isn't any API public way to look up extensions
867 |         # directly out of MIME::Types, but the type_for() method only strips
868 |         # off after a period anyway.
869 |         return ext
870 |       end
871 | 
872 |       types = MIME::Types.type_for(ext)
873 |       if types.empty?
874 |         ext
875 |       else
876 |         types.first.content_type
877 |       end
878 |     end
879 |   end
880 | end
881 | 


--------------------------------------------------------------------------------
/lib/restclient/resource.rb:
--------------------------------------------------------------------------------
  1 | module RestClient
  2 |   # A class that can be instantiated for access to a RESTful resource,
  3 |   # including authentication.
  4 |   #
  5 |   # Example:
  6 |   #
  7 |   #   resource = RestClient::Resource.new('http://some/resource')
  8 |   #   jpg = resource.get(:accept => 'image/jpg')
  9 |   #
 10 |   # With HTTP basic authentication:
 11 |   #
 12 |   #   resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
 13 |   #   resource.delete
 14 |   #
 15 |   # With a timeout (seconds):
 16 |   #
 17 |   #   RestClient::Resource.new('http://slow', :read_timeout => 10)
 18 |   #
 19 |   # With an open timeout (seconds):
 20 |   #
 21 |   #   RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
 22 |   #
 23 |   # You can also use resources to share common headers. For headers keys,
 24 |   # symbols are converted to strings. Example:
 25 |   #
 26 |   #   resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
 27 |   #
 28 |   # This header will be transported as X-Client-Version (notice the X prefix,
 29 |   # capitalization and hyphens)
 30 |   #
 31 |   # Use the [] syntax to allocate subresources:
 32 |   #
 33 |   #   site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
 34 |   #   site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
 35 |   #
 36 |   class Resource
 37 |     attr_reader :url, :options, :block
 38 | 
 39 |     def initialize(url, options={}, backwards_compatibility=nil, &block)
 40 |       @url = url
 41 |       @block = block
 42 |       if options.class == Hash
 43 |         @options = options
 44 |       else # compatibility with previous versions
 45 |         @options = { :user => options, :password => backwards_compatibility }
 46 |       end
 47 |     end
 48 | 
 49 |     def get(additional_headers={}, &block)
 50 |       headers = (options[:headers] || {}).merge(additional_headers)
 51 |       Request.execute(options.merge(
 52 |               :method => :get,
 53 |               :url => url,
 54 |               :headers => headers,
 55 |               :log => log), &(block || @block))
 56 |     end
 57 | 
 58 |     def head(additional_headers={}, &block)
 59 |       headers = (options[:headers] || {}).merge(additional_headers)
 60 |       Request.execute(options.merge(
 61 |               :method => :head,
 62 |               :url => url,
 63 |               :headers => headers,
 64 |               :log => log), &(block || @block))
 65 |     end
 66 | 
 67 |     def post(payload, additional_headers={}, &block)
 68 |       headers = (options[:headers] || {}).merge(additional_headers)
 69 |       Request.execute(options.merge(
 70 |               :method => :post,
 71 |               :url => url,
 72 |               :payload => payload,
 73 |               :headers => headers,
 74 |               :log => log), &(block || @block))
 75 |     end
 76 | 
 77 |     def put(payload, additional_headers={}, &block)
 78 |       headers = (options[:headers] || {}).merge(additional_headers)
 79 |       Request.execute(options.merge(
 80 |               :method => :put,
 81 |               :url => url,
 82 |               :payload => payload,
 83 |               :headers => headers,
 84 |               :log => log), &(block || @block))
 85 |     end
 86 | 
 87 |     def patch(payload, additional_headers={}, &block)
 88 |       headers = (options[:headers] || {}).merge(additional_headers)
 89 |       Request.execute(options.merge(
 90 |               :method => :patch,
 91 |               :url => url,
 92 |               :payload => payload,
 93 |               :headers => headers,
 94 |               :log => log), &(block || @block))
 95 |     end
 96 | 
 97 |     def delete(additional_headers={}, &block)
 98 |       headers = (options[:headers] || {}).merge(additional_headers)
 99 |       Request.execute(options.merge(
100 |               :method => :delete,
101 |               :url => url,
102 |               :headers => headers,
103 |               :log => log), &(block || @block))
104 |     end
105 | 
106 |     def to_s
107 |       url
108 |     end
109 | 
110 |     def user
111 |       options[:user]
112 |     end
113 | 
114 |     def password
115 |       options[:password]
116 |     end
117 | 
118 |     def headers
119 |       options[:headers] || {}
120 |     end
121 | 
122 |     def read_timeout
123 |       options[:read_timeout]
124 |     end
125 | 
126 |     def open_timeout
127 |       options[:open_timeout]
128 |     end
129 | 
130 |     def log
131 |       options[:log] || RestClient.log
132 |     end
133 | 
134 |     # Construct a subresource, preserving authentication.
135 |     #
136 |     # Example:
137 |     #
138 |     #   site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
139 |     #   site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
140 |     #
141 |     # This is especially useful if you wish to define your site in one place and
142 |     # call it in multiple locations:
143 |     #
144 |     #   def orders
145 |     #     RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
146 |     #   end
147 |     #
148 |     #   orders.get                     # GET http://example.com/orders
149 |     #   orders['1'].get                # GET http://example.com/orders/1
150 |     #   orders['1/items'].delete       # DELETE http://example.com/orders/1/items
151 |     #
152 |     # Nest resources as far as you want:
153 |     #
154 |     #   site = RestClient::Resource.new('http://example.com')
155 |     #   posts = site['posts']
156 |     #   first_post = posts['1']
157 |     #   comments = first_post['comments']
158 |     #   comments.post 'Hello', :content_type => 'text/plain'
159 |     #
160 |     def [](suburl, &new_block)
161 |       case
162 |       when block_given? then self.class.new(concat_urls(url, suburl), options, &new_block)
163 |       when block        then self.class.new(concat_urls(url, suburl), options, &block)
164 |       else                   self.class.new(concat_urls(url, suburl), options)
165 |       end
166 |     end
167 | 
168 |     def concat_urls(url, suburl) # :nodoc:
169 |       url = url.to_s
170 |       suburl = suburl.to_s
171 |       if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
172 |         url + suburl
173 |       else
174 |         "#{url}/#{suburl}"
175 |       end
176 |     end
177 |   end
178 | end
179 | 


--------------------------------------------------------------------------------
/lib/restclient/response.rb:
--------------------------------------------------------------------------------
 1 | module RestClient
 2 | 
 3 |   # A Response from RestClient, you can access the response body, the code or the headers.
 4 |   #
 5 |   class Response < String
 6 | 
 7 |     include AbstractResponse
 8 | 
 9 |     # Return the HTTP response body.
10 |     #
11 |     # Future versions of RestClient will deprecate treating response objects
12 |     # directly as strings, so it will be necessary to call `.body`.
13 |     #
14 |     # @return [String]
15 |     #
16 |     def body
17 |       # Benchmarking suggests that "#{self}" is fastest, and that caching the
18 |       # body string in an instance variable doesn't make it enough faster to be
19 |       # worth the extra memory storage.
20 |       String.new(self)
21 |     end
22 | 
23 |     # Convert the HTTP response body to a pure String object.
24 |     #
25 |     # @return [String]
26 |     def to_s
27 |       body
28 |     end
29 | 
30 |     # Convert the HTTP response body to a pure String object.
31 |     #
32 |     # @return [String]
33 |     def to_str
34 |       body
35 |     end
36 | 
37 |     def inspect
38 |       "<RestClient::Response #{code.inspect} #{body_truncated(10).inspect}>"
39 |     end
40 | 
41 |     # Initialize a Response object. Because RestClient::Response is
42 |     # (unfortunately) a subclass of String for historical reasons,
43 |     # Response.create is the preferred initializer.
44 |     #
45 |     # @param [String, nil] body The response body from the Net::HTTPResponse
46 |     # @param [Net::HTTPResponse] net_http_res
47 |     # @param [RestClient::Request] request
48 |     # @param [Time] start_time
49 |     def self.create(body, net_http_res, request, start_time=nil)
50 |       result = self.new(body || '')
51 | 
52 |       result.response_set_vars(net_http_res, request, start_time)
53 |       fix_encoding(result)
54 | 
55 |       result
56 |     end
57 | 
58 |     # Set the String encoding according to the 'Content-Type: charset' header,
59 |     # if possible.
60 |     def self.fix_encoding(response)
61 |       charset = RestClient::Utils.get_encoding_from_headers(response.headers)
62 |       encoding = nil
63 | 
64 |       begin
65 |         encoding = Encoding.find(charset) if charset
66 |       rescue ArgumentError
67 |         if response.log
68 |           response.log << "No such encoding: #{charset.inspect}"
69 |         end
70 |       end
71 | 
72 |       return unless encoding
73 | 
74 |       response.force_encoding(encoding)
75 | 
76 |       response
77 |     end
78 | 
79 |     private
80 | 
81 |     def body_truncated(length)
82 |       b = body
83 |       if b.length > length
84 |         b[0..length] + '...'
85 |       else
86 |         b
87 |       end
88 |     end
89 |   end
90 | end
91 | 


--------------------------------------------------------------------------------
/lib/restclient/utils.rb:
--------------------------------------------------------------------------------
  1 | require 'http/accept'
  2 | 
  3 | module RestClient
  4 |   # Various utility methods
  5 |   module Utils
  6 | 
  7 |     # Return encoding from an HTTP header hash.
  8 |     #
  9 |     # We use the RFC 7231 specification and do not impose a default encoding on
 10 |     # text. This differs from the older RFC 2616 behavior, which specifies
 11 |     # using ISO-8859-1 for text/* content types without a charset.
 12 |     #
 13 |     # Strings will use the default encoding when this method returns nil. This
 14 |     # default is likely to be UTF-8 for Ruby >= 2.0
 15 |     #
 16 |     # @param headers [Hash<Symbol,String>]
 17 |     #
 18 |     # @return [String, nil] Return the string encoding or nil if no header is
 19 |     #   found.
 20 |     #
 21 |     # @example
 22 |     #   >> get_encoding_from_headers({:content_type => 'text/plain; charset=UTF-8'})
 23 |     #   => "UTF-8"
 24 |     #
 25 |     def self.get_encoding_from_headers(headers)
 26 |       type_header = headers[:content_type]
 27 |       return nil unless type_header
 28 | 
 29 |       # TODO: remove this hack once we drop support for Ruby 2.0
 30 |       if RUBY_VERSION.start_with?('2.0')
 31 |         _content_type, params = deprecated_cgi_parse_header(type_header)
 32 | 
 33 |         if params.include?('charset')
 34 |           return params.fetch('charset').gsub(/(\A["']*)|(["']*\z)/, '')
 35 |         end
 36 | 
 37 |       else
 38 | 
 39 |         begin
 40 |           _content_type, params = cgi_parse_header(type_header)
 41 |         rescue HTTP::Accept::ParseError
 42 |           return nil
 43 |         else
 44 |           params['charset']
 45 |         end
 46 |       end
 47 |     end
 48 | 
 49 |     # Parse a Content-Type like header.
 50 |     #
 51 |     # Return the main content-type and a hash of params.
 52 |     #
 53 |     # @param [String] line
 54 |     # @return [Array(String, Hash)]
 55 |     #
 56 |     def self.cgi_parse_header(line)
 57 |       types = HTTP::Accept::MediaTypes.parse(line)
 58 | 
 59 |       if types.empty?
 60 |         raise HTTP::Accept::ParseError.new("Found no types in header line")
 61 |       end
 62 | 
 63 |       [types.first.mime_type, types.first.parameters]
 64 |     end
 65 | 
 66 |     # Parse semi-colon separated, potentially quoted header string iteratively.
 67 |     #
 68 |     # @private
 69 |     #
 70 |     # @deprecated This method is deprecated and only exists to support Ruby
 71 |     #   2.0, which is not supported by HTTP::Accept.
 72 |     #
 73 |     # @todo remove this method when dropping support for Ruby 2.0
 74 |     #
 75 |     def self._cgi_parseparam(s)
 76 |       return enum_for(__method__, s) unless block_given?
 77 | 
 78 |       while s[0] == ';'
 79 |         s = s[1..-1]
 80 |         ends = s.index(';')
 81 |         while ends && ends > 0 \
 82 |               && (s[0...ends].count('"') -
 83 |                   s[0...ends].scan('\"').count) % 2 != 0
 84 |           ends = s.index(';', ends + 1)
 85 |         end
 86 |         if ends.nil?
 87 |           ends = s.length
 88 |         end
 89 |         f = s[0...ends]
 90 |         yield f.strip
 91 |         s = s[ends..-1]
 92 |       end
 93 |       nil
 94 |     end
 95 | 
 96 |     # Parse a Content-Type like header.
 97 |     #
 98 |     # Return the main content-type and a hash of options.
 99 |     #
100 |     # This method was ported directly from Python's cgi.parse_header(). It
101 |     # probably doesn't read or perform particularly well in ruby.
102 |     # https://github.com/python/cpython/blob/3.4/Lib/cgi.py#L301-L331
103 |     #
104 |     # @param [String] line
105 |     # @return [Array(String, Hash)]
106 |     #
107 |     # @deprecated This method is deprecated and only exists to support Ruby
108 |     #   2.0, which is not supported by HTTP::Accept.
109 |     #
110 |     # @todo remove this method when dropping support for Ruby 2.0
111 |     #
112 |     def self.deprecated_cgi_parse_header(line)
113 |       parts = _cgi_parseparam(';' + line)
114 |       key = parts.next
115 |       pdict = {}
116 | 
117 |       begin
118 |         while (p = parts.next)
119 |           i = p.index('=')
120 |           if i
121 |             name = p[0...i].strip.downcase
122 |             value = p[i+1..-1].strip
123 |             if value.length >= 2 && value[0] == '"' && value[-1] == '"'
124 |               value = value[1...-1]
125 |               value = value.gsub('\\\\', '\\').gsub('\\"', '"')
126 |             end
127 |             pdict[name] = value
128 |           end
129 |         end
130 |       rescue StopIteration
131 |       end
132 | 
133 |       [key, pdict]
134 |     end
135 | 
136 |     # Serialize a ruby object into HTTP query string parameters.
137 |     #
138 |     # There is no standard for doing this, so we choose our own slightly
139 |     # idiosyncratic format. The output closely matches the format understood by
140 |     # Rails, Rack, and PHP.
141 |     #
142 |     # If you don't want handling of complex objects and only want to handle
143 |     # simple flat hashes, you may want to use `URI.encode_www_form` instead,
144 |     # which implements HTML5-compliant URL encoded form data.
145 |     #
146 |     # @param [Hash,ParamsArray] object The object to serialize
147 |     #
148 |     # @return [String] A string appropriate for use as an HTTP query string
149 |     #
150 |     # @see {flatten_params}
151 |     #
152 |     # @see URI.encode_www_form
153 |     #
154 |     # @see See also Object#to_query in ActiveSupport
155 |     # @see http://php.net/manual/en/function.http-build-query.php
156 |     #   http_build_query in PHP
157 |     # @see See also Rack::Utils.build_nested_query in Rack
158 |     #
159 |     # Notable differences from the ActiveSupport implementation:
160 |     #
161 |     # - Empty hash and empty array are treated the same as nil instead of being
162 |     #   omitted entirely from the output. Rather than disappearing, they will
163 |     #   appear to be nil instead.
164 |     #
165 |     # It's most common to pass a Hash as the object to serialize, but you can
166 |     # also use a ParamsArray if you want to be able to pass the same key with
167 |     # multiple values and not use the rack/rails array convention.
168 |     #
169 |     # @since 2.0.0
170 |     #
171 |     # @example Simple hashes
172 |     #   >> encode_query_string({foo: 123, bar: 456})
173 |     #   => 'foo=123&bar=456'
174 |     #
175 |     # @example Simple arrays
176 |     #   >> encode_query_string({foo: [1,2,3]})
177 |     #   => 'foo[]=1&foo[]=2&foo[]=3'
178 |     #
179 |     # @example Nested hashes
180 |     #   >> encode_query_string({outer: {foo: 123, bar: 456}})
181 |     #   => 'outer[foo]=123&outer[bar]=456'
182 |     #
183 |     # @example Deeply nesting
184 |     #   >> encode_query_string({coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]})
185 |     #   => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3'
186 |     #
187 |     # @example Null and empty values
188 |     #   >> encode_query_string({string: '', empty: nil, list: [], hash: {}})
189 |     #   => 'string=&empty&list&hash'
190 |     #
191 |     # @example Nested nulls
192 |     #   >> encode_query_string({foo: {string: '', empty: nil}})
193 |     #   => 'foo[string]=&foo[empty]'
194 |     #
195 |     # @example Multiple fields with the same name using ParamsArray
196 |     #   >> encode_query_string(RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]]))
197 |     #   => 'foo=1&foo=2&foo=3'
198 |     #
199 |     # @example Nested ParamsArray
200 |     #   >> encode_query_string({foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])})
201 |     #   => 'foo[a]=1&foo[a]=2'
202 |     #
203 |     #   >> encode_query_string(RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]]))
204 |     #   => 'foo[a]=1&foo[a]=2'
205 |     #
206 |     def self.encode_query_string(object)
207 |       flatten_params(object, true).map {|k, v| v.nil? ? k : "#{k}=#{v}" }.join('&')
208 |     end
209 | 
210 |     # Transform deeply nested param containers into a flat array of [key,
211 |     # value] pairs.
212 |     #
213 |     # @example
214 |     #   >> flatten_params({key1: {key2: 123}})
215 |     #   => [["key1[key2]", 123]]
216 |     #
217 |     # @example
218 |     #   >> flatten_params({key1: {key2: 123, arr: [1,2,3]}})
219 |     #   => [["key1[key2]", 123], ["key1[arr][]", 1], ["key1[arr][]", 2], ["key1[arr][]", 3]]
220 |     #
221 |     # @param object [Hash, ParamsArray] The container to flatten
222 |     # @param uri_escape [Boolean] Whether to URI escape keys and values
223 |     # @param parent_key [String] Should not be passed (used for recursion)
224 |     #
225 |     def self.flatten_params(object, uri_escape=false, parent_key=nil)
226 |       unless object.is_a?(Hash) || object.is_a?(ParamsArray) ||
227 |              (parent_key && object.is_a?(Array))
228 |         raise ArgumentError.new('expected Hash or ParamsArray, got: ' + object.inspect)
229 |       end
230 | 
231 |       # transform empty collections into nil, where possible
232 |       if object.empty? && parent_key
233 |         return [[parent_key, nil]]
234 |       end
235 | 
236 |       # This is essentially .map(), but we need to do += for nested containers
237 |       object.reduce([]) { |result, item|
238 |         if object.is_a?(Array)
239 |           # item is already the value
240 |           k = nil
241 |           v = item
242 |         else
243 |           # item is a key, value pair
244 |           k, v = item
245 |           k = escape(k.to_s) if uri_escape
246 |         end
247 | 
248 |         processed_key = parent_key ? "#{parent_key}[#{k}]" : k
249 | 
250 |         case v
251 |         when Array, Hash, ParamsArray
252 |           result.concat flatten_params(v, uri_escape, processed_key)
253 |         else
254 |           v = escape(v.to_s) if uri_escape && v
255 |           result << [processed_key, v]
256 |         end
257 |       }
258 |     end
259 | 
260 |     # Encode string for safe transport by URI or form encoding. This uses a CGI
261 |     # style escape, which transforms ` ` into `+` and various special
262 |     # characters into percent encoded forms.
263 |     #
264 |     # This calls URI.encode_www_form_component for the implementation. The only
265 |     # difference between this and CGI.escape is that it does not escape `*`.
266 |     # http://stackoverflow.com/questions/25085992/
267 |     #
268 |     # @see URI.encode_www_form_component
269 |     #
270 |     def self.escape(string)
271 |       URI.encode_www_form_component(string)
272 |     end
273 |   end
274 | end
275 | 


--------------------------------------------------------------------------------
/lib/restclient/version.rb:
--------------------------------------------------------------------------------
1 | module RestClient
2 |   VERSION_INFO = [2, 1, 0].freeze
3 |   VERSION = VERSION_INFO.map(&:to_s).join('.').freeze
4 | 
5 |   def self.version
6 |     VERSION
7 |   end
8 | end
9 | 


--------------------------------------------------------------------------------
/lib/restclient/windows.rb:
--------------------------------------------------------------------------------
1 | module RestClient
2 |   module Windows
3 |   end
4 | end
5 | 
6 | if RestClient::Platform.windows?
7 |   require_relative './windows/root_certs'
8 | end
9 | 


--------------------------------------------------------------------------------
/lib/restclient/windows/root_certs.rb:
--------------------------------------------------------------------------------
  1 | require 'openssl'
  2 | require 'ffi'
  3 | 
  4 | # Adapted from Puppet, Copyright (c) Puppet Labs Inc,
  5 | # licensed under the Apache License, Version 2.0.
  6 | #
  7 | # https://github.com/puppetlabs/puppet/blob/bbe30e0a/lib/puppet/util/windows/root_certs.rb
  8 | 
  9 | # Represents a collection of trusted root certificates.
 10 | #
 11 | # @api public
 12 | class RestClient::Windows::RootCerts
 13 |   include Enumerable
 14 |   extend FFI::Library
 15 | 
 16 |   typedef :ulong, :dword
 17 |   typedef :uintptr_t, :handle
 18 | 
 19 |   def initialize(roots)
 20 |     @roots = roots
 21 |   end
 22 | 
 23 |   # Enumerates each root certificate.
 24 |   # @yieldparam cert [OpenSSL::X509::Certificate] each root certificate
 25 |   # @api public
 26 |   def each
 27 |     @roots.each {|cert| yield cert}
 28 |   end
 29 | 
 30 |   # Returns a new instance.
 31 |   # @return [RestClient::Windows::RootCerts] object constructed from current root certificates
 32 |   def self.instance
 33 |     new(self.load_certs)
 34 |   end
 35 | 
 36 |   # Returns an array of root certificates.
 37 |   #
 38 |   # @return [Array<[OpenSSL::X509::Certificate]>] an array of root certificates
 39 |   # @api private
 40 |   def self.load_certs
 41 |     certs = []
 42 | 
 43 |     # This is based on a patch submitted to openssl:
 44 |     # http://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html
 45 |     ptr = FFI::Pointer::NULL
 46 |     store = CertOpenSystemStoreA(nil, "ROOT")
 47 |     begin
 48 |       while (ptr = CertEnumCertificatesInStore(store, ptr)) and not ptr.null?
 49 |         context = CERT_CONTEXT.new(ptr)
 50 |         cert_buf = context[:pbCertEncoded].read_bytes(context[:cbCertEncoded])
 51 |         begin
 52 |           certs << OpenSSL::X509::Certificate.new(cert_buf)
 53 |         rescue => detail
 54 |           warn("Failed to import root certificate: #{detail.inspect}")
 55 |         end
 56 |       end
 57 |     ensure
 58 |       CertCloseStore(store, 0)
 59 |     end
 60 | 
 61 |     certs
 62 |   end
 63 | 
 64 |   private
 65 | 
 66 |   # typedef ULONG_PTR HCRYPTPROV_LEGACY;
 67 |   # typedef void *HCERTSTORE;
 68 | 
 69 |   class CERT_CONTEXT < FFI::Struct
 70 |     layout(
 71 |       :dwCertEncodingType, :dword,
 72 |       :pbCertEncoded,      :pointer,
 73 |       :cbCertEncoded,      :dword,
 74 |       :pCertInfo,          :pointer,
 75 |       :hCertStore,         :handle
 76 |     )
 77 |   end
 78 | 
 79 |   # HCERTSTORE
 80 |   # WINAPI
 81 |   # CertOpenSystemStoreA(
 82 |   #   __in_opt HCRYPTPROV_LEGACY hProv,
 83 |   #   __in LPCSTR szSubsystemProtocol
 84 |   #   );
 85 |   ffi_lib :crypt32
 86 |   attach_function :CertOpenSystemStoreA, [:pointer, :string], :handle
 87 | 
 88 |   # PCCERT_CONTEXT
 89 |   # WINAPI
 90 |   # CertEnumCertificatesInStore(
 91 |   #   __in HCERTSTORE hCertStore,
 92 |   #   __in_opt PCCERT_CONTEXT pPrevCertContext
 93 |   #   );
 94 |   ffi_lib :crypt32
 95 |   attach_function :CertEnumCertificatesInStore, [:handle, :pointer], :pointer
 96 | 
 97 |   # BOOL
 98 |   # WINAPI
 99 |   # CertCloseStore(
100 |   #   __in_opt HCERTSTORE hCertStore,
101 |   #   __in DWORD dwFlags
102 |   #   );
103 |   ffi_lib :crypt32
104 |   attach_function :CertCloseStore, [:handle, :dword], :bool
105 | end
106 | 


--------------------------------------------------------------------------------
/rest-client.gemspec:
--------------------------------------------------------------------------------
 1 | # -*- encoding: utf-8 -*-
 2 | 
 3 | require File.expand_path('../lib/restclient/version', __FILE__)
 4 | 
 5 | Gem::Specification.new do |s|
 6 |   s.name = 'rest-client'
 7 |   s.version = RestClient::VERSION
 8 |   s.authors = ['REST Client Team']
 9 |   s.description = 'A simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete.'
10 |   s.license = 'MIT'
11 |   s.email = 'discuss@rest-client.groups.io'
12 |   s.executables = ['restclient']
13 |   s.extra_rdoc_files = ['README.md', 'history.md']
14 |   s.files = `git ls-files -z`.split("\0")
15 |   s.test_files = `git ls-files -z spec/`.split("\0")
16 |   s.homepage = 'https://github.com/rest-client/rest-client'
17 |   s.summary = 'Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.'
18 | 
19 |   s.add_development_dependency('webmock', '~> 2.0')
20 |   s.add_development_dependency('rspec', '~> 3.0')
21 |   s.add_development_dependency('pry', '~> 0')
22 |   s.add_development_dependency('pry-doc', '~> 0')
23 |   s.add_development_dependency('rdoc', '>= 2.4.2', '< 6.0')
24 |   s.add_development_dependency('rubocop', '~> 0.49')
25 | 
26 |   s.add_dependency('http-accept', '>= 1.7.0', '< 2.0')
27 |   s.add_dependency('http-cookie', '>= 1.0.2', '< 2.0')
28 |   s.add_dependency('mime-types', '>= 1.16', '< 4.0')
29 |   s.add_dependency('netrc', '~> 0.8')
30 | 
31 |   s.required_ruby_version = '>= 2.0.0'
32 | end
33 | 


--------------------------------------------------------------------------------
/rest-client.windows.gemspec:
--------------------------------------------------------------------------------
 1 | #
 2 | # Gemspec for Windows platforms. We can't put these in the main gemspec because
 3 | # it results in bundler platform hell when trying to build the gem.
 4 | #
 5 | # Set $BUILD_PLATFORM when calling gem build with this gemspec to build for
 6 | # Windows platforms like x86-mingw32.
 7 | #
 8 | s = eval(File.read(File.join(File.dirname(__FILE__), 'rest-client.gemspec')))
 9 | 
10 | platform = ENV['BUILD_PLATFORM'] || RUBY_PLATFORM
11 | 
12 | case platform
13 | when /(mingw32|mswin32)/
14 |   # ffi is needed for RestClient::Windows::RootCerts
15 |   s.add_dependency('ffi', '~> 1.9')
16 |   s.platform = platform
17 | end
18 | 
19 | s
20 | 


--------------------------------------------------------------------------------
/spec/ISS.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rest-client/rest-client/2c72a2e77e2e87d25ff38feba0cf048d51bd5eca/spec/ISS.jpg


--------------------------------------------------------------------------------
/spec/helpers.rb:
--------------------------------------------------------------------------------
 1 | require 'uri'
 2 | 
 3 | module Helpers
 4 | 
 5 |   # @param [Hash] opts A hash of methods, passed directly to the double
 6 |   #   definition. Use this to stub other required methods.
 7 |   #
 8 |   # @return double for Net::HTTPResponse
 9 |   def res_double(opts={})
10 |     instance_double('Net::HTTPResponse', {to_hash: {}, body: 'response body'}.merge(opts))
11 |   end
12 | 
13 |   # Given a Net::HTTPResponse or double and a Request or double, create a
14 |   # RestClient::Response object.
15 |   #
16 |   # @param net_http_res_double an rspec double for Net::HTTPResponse
17 |   # @param request A RestClient::Request or rspec double
18 |   #
19 |   # @return [RestClient::Response]
20 |   #
21 |   def response_from_res_double(net_http_res_double, request=nil, duration: 1)
22 |     request ||= request_double()
23 |     start_time = Time.now - duration
24 | 
25 |     response = RestClient::Response.create(net_http_res_double.body, net_http_res_double, request, start_time)
26 | 
27 |     # mock duration to ensure it gets the value we expect
28 |     allow(response).to receive(:duration).and_return(duration)
29 | 
30 |     response
31 |   end
32 | 
33 |   # Redirect stderr to a string for the duration of the passed block.
34 |   def fake_stderr
35 |     original_stderr = $stderr
36 |     $stderr = StringIO.new
37 |     yield
38 |     $stderr.string
39 |   ensure
40 |     $stderr = original_stderr
41 |   end
42 | 
43 |   # Create a double for RestClient::Request
44 |   def request_double(url: 'http://example.com', method: 'get')
45 |     instance_double('RestClient::Request',
46 |       url: url, uri: URI.parse(url), method: method, user: nil, password: nil,
47 |       cookie_jar: HTTP::CookieJar.new, redirection_history: nil,
48 |       args: {url: url, method: method})
49 |   end
50 | 
51 |   def test_image_path
52 |     File.dirname(__FILE__) + "/ISS.jpg"
53 |   end
54 | end
55 | 


--------------------------------------------------------------------------------
/spec/integration/_lib.rb:
--------------------------------------------------------------------------------
1 | require_relative '../spec_helper'
2 | 


--------------------------------------------------------------------------------
/spec/integration/capath_digicert/3513523f.0:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
 5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
 9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22 | -----END CERTIFICATE-----
23 | 


--------------------------------------------------------------------------------
/spec/integration/capath_digicert/399e7759.0:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
 5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
 9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22 | -----END CERTIFICATE-----
23 | 


--------------------------------------------------------------------------------
/spec/integration/capath_digicert/README:
--------------------------------------------------------------------------------
1 | The CA path symlinks can be created by c_rehash(1ssl).
2 | 
3 | But in order for the tests to work on Windows, they have to be regular files.
4 | You can turn them all into regular files by running this on a GNU system:
5 | 
6 |     for file in $(find . -type l); do
7 |         cp -iv --remove-destination $(readlink -e $file) $file
8 |     done
9 | 


--------------------------------------------------------------------------------
/spec/integration/capath_digicert/digicert.crt:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
 5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
 9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22 | -----END CERTIFICATE-----
23 | 


--------------------------------------------------------------------------------
/spec/integration/capath_verisign/415660c1.0:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
 3 | A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
 4 | cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
 5 | MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
 6 | BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
 7 | YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
 8 | ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
 9 | BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
10 | I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
11 | CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
12 | lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
13 | AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
14 | -----END CERTIFICATE-----
15 | 


--------------------------------------------------------------------------------
/spec/integration/capath_verisign/7651b327.0:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
 3 | A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
 4 | cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
 5 | MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
 6 | BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
 7 | YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
 8 | ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
 9 | BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
10 | I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
11 | CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
12 | lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
13 | AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
14 | -----END CERTIFICATE-----
15 | 


--------------------------------------------------------------------------------
/spec/integration/capath_verisign/README:
--------------------------------------------------------------------------------
1 | The CA path symlinks can be created by c_rehash(1ssl).
2 | 
3 | But in order for the tests to work on Windows, they have to be regular files.
4 | You can turn them all into regular files by running this on a GNU system:
5 | 
6 |     for file in $(find . -type l); do
7 |         cp -iv --remove-destination $(readlink -e $file) $file
8 |     done
9 | 


--------------------------------------------------------------------------------
/spec/integration/capath_verisign/verisign.crt:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
 3 | A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
 4 | cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
 5 | MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
 6 | BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
 7 | YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
 8 | ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
 9 | BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
10 | I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
11 | CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
12 | lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
13 | AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
14 | -----END CERTIFICATE-----
15 | 


--------------------------------------------------------------------------------
/spec/integration/certs/digicert.crt:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
 4 | d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
 5 | QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
 6 | MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
 7 | b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
 8 | 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
 9 | CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
10 | nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
11 | 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
12 | T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
13 | gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
14 | BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
15 | TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
16 | DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
17 | hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
18 | 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
19 | PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
20 | YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
21 | CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
22 | -----END CERTIFICATE-----
23 | 


--------------------------------------------------------------------------------
/spec/integration/certs/verisign.crt:
--------------------------------------------------------------------------------
 1 | -----BEGIN CERTIFICATE-----
 2 | MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG
 3 | A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz
 4 | cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2
 5 | MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV
 6 | BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt
 7 | YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN
 8 | ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE
 9 | BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is
10 | I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G
11 | CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do
12 | lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc
13 | AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k
14 | -----END CERTIFICATE-----
15 | 


--------------------------------------------------------------------------------
/spec/integration/httpbin_spec.rb:
--------------------------------------------------------------------------------
  1 | require_relative '_lib'
  2 | require 'json'
  3 | 
  4 | require 'zlib'
  5 | 
  6 | describe RestClient::Request do
  7 |   before(:all) do
  8 |     WebMock.disable!
  9 |   end
 10 | 
 11 |   after(:all) do
 12 |     WebMock.enable!
 13 |   end
 14 | 
 15 |   def default_httpbin_url
 16 |     # add a hack to work around java/jruby bug
 17 |     # java.lang.RuntimeException: Could not generate DH keypair with backtrace
 18 |     # Also (2017-04-09) Travis Jruby versions have a broken CA keystore
 19 |     if ENV['TRAVIS_RUBY_VERSION'] =~ /\Ajruby-/
 20 |       'http://httpbin.org/'
 21 |     else
 22 |       'https://httpbin.org/'
 23 |     end
 24 |   end
 25 | 
 26 |   def httpbin(suffix='')
 27 |     url = ENV.fetch('HTTPBIN_URL', default_httpbin_url)
 28 |     unless url.end_with?('/')
 29 |       url += '/'
 30 |     end
 31 | 
 32 |     url + suffix
 33 |   end
 34 | 
 35 |   def execute_httpbin(suffix, opts={})
 36 |     opts = {url: httpbin(suffix)}.merge(opts)
 37 |     RestClient::Request.execute(opts)
 38 |   end
 39 | 
 40 |   def execute_httpbin_json(suffix, opts={})
 41 |     JSON.parse(execute_httpbin(suffix, opts))
 42 |   end
 43 | 
 44 |   describe '.execute' do
 45 |     it 'sends a user agent' do
 46 |       data = execute_httpbin_json('user-agent', method: :get)
 47 |       expect(data['user-agent']).to match(/rest-client/)
 48 |     end
 49 | 
 50 |     it 'receives cookies on 302' do
 51 |       expect {
 52 |         execute_httpbin('cookies/set?foo=bar', method: :get, max_redirects: 0)
 53 |       }.to raise_error(RestClient::Found) { |ex|
 54 |         expect(ex.http_code).to eq 302
 55 |         expect(ex.response.cookies['foo']).to eq 'bar'
 56 |       }
 57 |     end
 58 | 
 59 |     it 'passes along cookies through 302' do
 60 |       data = execute_httpbin_json('cookies/set?foo=bar', method: :get)
 61 |       expect(data).to have_key('cookies')
 62 |       expect(data['cookies']['foo']).to eq 'bar'
 63 |     end
 64 | 
 65 |     it 'handles quote wrapped cookies' do
 66 |       expect {
 67 |         execute_httpbin('cookies/set?foo=' + CGI.escape('"bar:baz"'),
 68 |                         method: :get, max_redirects: 0)
 69 |       }.to raise_error(RestClient::Found) { |ex|
 70 |         expect(ex.http_code).to eq 302
 71 |         expect(ex.response.cookies['foo']).to eq '"bar:baz"'
 72 |       }
 73 |     end
 74 | 
 75 |     it 'sends basic auth' do
 76 |       user = 'user'
 77 |       pass = 'pass'
 78 | 
 79 |       data = execute_httpbin_json("basic-auth/#{user}/#{pass}", method: :get, user: user, password: pass)
 80 |       expect(data).to eq({'authenticated' => true, 'user' => user})
 81 | 
 82 |       expect {
 83 |         execute_httpbin_json("basic-auth/#{user}/#{pass}", method: :get, user: user, password: 'badpass')
 84 |       }.to raise_error(RestClient::Unauthorized) { |ex|
 85 |         expect(ex.http_code).to eq 401
 86 |       }
 87 |     end
 88 | 
 89 |     it 'handles gzipped/deflated responses' do
 90 |       [['gzip', 'gzipped'], ['deflate', 'deflated']].each do |encoding, var|
 91 |         raw = execute_httpbin(encoding, method: :get)
 92 | 
 93 |         begin
 94 |           data = JSON.parse(raw)
 95 |         rescue StandardError
 96 |           puts "Failed to parse: " + raw.inspect
 97 |           raise
 98 |         end
 99 | 
100 |         expect(data['method']).to eq 'GET'
101 |         expect(data.fetch(var)).to be true
102 |       end
103 |     end
104 | 
105 |     it 'does not uncompress response when accept-encoding is set' do
106 |       # == gzip ==
107 |       raw = execute_httpbin('gzip', method: :get, headers: {accept_encoding: 'gzip, deflate'})
108 | 
109 |       # check for gzip magic number
110 |       expect(raw.body).to start_with("\x1F\x8B".b)
111 | 
112 |       decoded = Zlib::GzipReader.new(StringIO.new(raw.body)).read
113 |       parsed = JSON.parse(decoded)
114 | 
115 |       expect(parsed['method']).to eq 'GET'
116 |       expect(parsed.fetch('gzipped')).to be true
117 | 
118 |       # == delate ==
119 |       raw = execute_httpbin('deflate', method: :get, headers: {accept_encoding: 'gzip, deflate'})
120 | 
121 |       decoded = Zlib::Inflate.new.inflate(raw.body)
122 |       parsed = JSON.parse(decoded)
123 | 
124 |       expect(parsed['method']).to eq 'GET'
125 |       expect(parsed.fetch('deflated')).to be true
126 |     end
127 |   end
128 | end
129 | 


--------------------------------------------------------------------------------
/spec/integration/integration_spec.rb:
--------------------------------------------------------------------------------
  1 | # -*- coding: utf-8 -*-
  2 | require_relative '_lib'
  3 | require 'base64'
  4 | 
  5 | describe RestClient do
  6 | 
  7 |   it "a simple request" do
  8 |     body = 'abc'
  9 |     stub_request(:get, "www.example.com").to_return(:body => body, :status => 200)
 10 |     response = RestClient.get "www.example.com"
 11 |     expect(response.code).to eq 200
 12 |     expect(response.body).to eq body
 13 |   end
 14 | 
 15 |   it "a 404" do
 16 |     body = "Ho hai ! I'm not here !"
 17 |     stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
 18 |     begin
 19 |       RestClient.get "www.example.com"
 20 |       raise
 21 |     rescue RestClient::ResourceNotFound => e
 22 |       expect(e.http_code).to eq 404
 23 |       expect(e.response.code).to eq 404
 24 |       expect(e.response.body).to eq body
 25 |       expect(e.http_body).to eq body
 26 |     end
 27 |   end
 28 | 
 29 |   describe 'charset parsing' do
 30 |     it 'handles utf-8' do
 31 |       body = "λ".force_encoding('ASCII-8BIT')
 32 |       stub_request(:get, "www.example.com").to_return(
 33 |         :body => body, :status => 200, :headers => {
 34 |           'Content-Type' => 'text/plain; charset=UTF-8'
 35 |       })
 36 |       response = RestClient.get "www.example.com"
 37 |       expect(response.encoding).to eq Encoding::UTF_8
 38 |       expect(response.valid_encoding?).to eq true
 39 |     end
 40 | 
 41 |     it 'handles windows-1252' do
 42 |       body = "\xff".force_encoding('ASCII-8BIT')
 43 |       stub_request(:get, "www.example.com").to_return(
 44 |         :body => body, :status => 200, :headers => {
 45 |           'Content-Type' => 'text/plain; charset=windows-1252'
 46 |       })
 47 |       response = RestClient.get "www.example.com"
 48 |       expect(response.encoding).to eq Encoding::WINDOWS_1252
 49 |       expect(response.encode('utf-8')).to eq "ÿ"
 50 |       expect(response.valid_encoding?).to eq true
 51 |     end
 52 | 
 53 |     it 'handles binary' do
 54 |       body = "\xfe".force_encoding('ASCII-8BIT')
 55 |       stub_request(:get, "www.example.com").to_return(
 56 |         :body => body, :status => 200, :headers => {
 57 |           'Content-Type' => 'application/octet-stream; charset=binary'
 58 |       })
 59 |       response = RestClient.get "www.example.com"
 60 |       expect(response.encoding).to eq Encoding::BINARY
 61 |       expect {
 62 |         response.encode('utf-8')
 63 |       }.to raise_error(Encoding::UndefinedConversionError)
 64 |       expect(response.valid_encoding?).to eq true
 65 |     end
 66 | 
 67 |     it 'handles euc-jp' do
 68 |       body = "\xA4\xA2\xA4\xA4\xA4\xA6\xA4\xA8\xA4\xAA".
 69 |         force_encoding(Encoding::BINARY)
 70 |       body_utf8 = 'あいうえお'
 71 |       expect(body_utf8.encoding).to eq Encoding::UTF_8
 72 | 
 73 |       stub_request(:get, 'www.example.com').to_return(
 74 |         :body => body, :status => 200, :headers => {
 75 |           'Content-Type' => 'text/plain; charset=EUC-JP'
 76 |       })
 77 |       response = RestClient.get 'www.example.com'
 78 |       expect(response.encoding).to eq Encoding::EUC_JP
 79 |       expect(response.valid_encoding?).to eq true
 80 |       expect(response.length).to eq 5
 81 |       expect(response.encode('utf-8')).to eq body_utf8
 82 |     end
 83 | 
 84 |     it 'defaults to the default encoding' do
 85 |       stub_request(:get, 'www.example.com').to_return(
 86 |         body: 'abc', status: 200, headers: {
 87 |           'Content-Type' => 'text/plain'
 88 |         })
 89 | 
 90 |       response = RestClient.get 'www.example.com'
 91 |       # expect(response.encoding).to eq Encoding.default_external
 92 |       expect(response.encoding).to eq Encoding::UTF_8
 93 |     end
 94 | 
 95 |     it 'handles invalid encoding' do
 96 |       stub_request(:get, 'www.example.com').to_return(
 97 |         body: 'abc', status: 200, headers: {
 98 |           'Content-Type' => 'text; charset=plain'
 99 |         })
100 | 
101 |       response = RestClient.get 'www.example.com'
102 |       # expect(response.encoding).to eq Encoding.default_external
103 |       expect(response.encoding).to eq Encoding::UTF_8
104 |     end
105 | 
106 |     it 'leaves images as binary' do
107 |       gif = Base64.strict_decode64('R0lGODlhAQABAAAAADs=')
108 | 
109 |       stub_request(:get, 'www.example.com').to_return(
110 |         body: gif, status: 200, headers: {
111 |           'Content-Type' => 'image/gif'
112 |         })
113 | 
114 |       response = RestClient.get 'www.example.com'
115 |       expect(response.encoding).to eq Encoding::BINARY
116 |     end
117 |   end
118 | end
119 | 


--------------------------------------------------------------------------------
/spec/integration/request_spec.rb:
--------------------------------------------------------------------------------
  1 | require_relative '_lib'
  2 | 
  3 | describe RestClient::Request do
  4 |   before(:all) do
  5 |     WebMock.disable!
  6 |   end
  7 | 
  8 |   after(:all) do
  9 |     WebMock.enable!
 10 |   end
 11 | 
 12 |   describe "ssl verification" do
 13 |     it "is successful with the correct ca_file" do
 14 |       request = RestClient::Request.new(
 15 |         :method => :get,
 16 |         :url => 'https://www.mozilla.org',
 17 |         :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "digicert.crt")
 18 |       )
 19 |       expect { request.execute }.to_not raise_error
 20 |     end
 21 | 
 22 |     it "is successful with the correct ca_path" do
 23 |       request = RestClient::Request.new(
 24 |         :method => :get,
 25 |         :url => 'https://www.mozilla.org',
 26 |         :ssl_ca_path => File.join(File.dirname(__FILE__), "capath_digicert")
 27 |       )
 28 |       expect { request.execute }.to_not raise_error
 29 |     end
 30 | 
 31 |     # TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just
 32 |     # pass through OpenSSL::SSL::SSLError directly. See note in
 33 |     # lib/restclient/request.rb.
 34 |     #
 35 |     # On OS X, this test fails since Apple has patched OpenSSL to always fall
 36 |     # back on the system CA store.
 37 |     it "is unsuccessful with an incorrect ca_file", :unless => RestClient::Platform.mac_mri? do
 38 |       request = RestClient::Request.new(
 39 |         :method => :get,
 40 |         :url => 'https://www.mozilla.org',
 41 |         :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt")
 42 |       )
 43 |       expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified)
 44 |     end
 45 | 
 46 |     # On OS X, this test fails since Apple has patched OpenSSL to always fall
 47 |     # back on the system CA store.
 48 |     it "is unsuccessful with an incorrect ca_path", :unless => RestClient::Platform.mac_mri? do
 49 |       request = RestClient::Request.new(
 50 |         :method => :get,
 51 |         :url => 'https://www.mozilla.org',
 52 |         :ssl_ca_path => File.join(File.dirname(__FILE__), "capath_verisign")
 53 |       )
 54 |       expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified)
 55 |     end
 56 | 
 57 |     it "is successful using the default system cert store" do
 58 |       request = RestClient::Request.new(
 59 |         :method => :get,
 60 |         :url => 'https://www.mozilla.org',
 61 |         :verify_ssl => true,
 62 |       )
 63 |       expect {request.execute }.to_not raise_error
 64 |     end
 65 | 
 66 |     it "executes the verify_callback" do
 67 |       ran_callback = false
 68 |       request = RestClient::Request.new(
 69 |         :method => :get,
 70 |         :url => 'https://www.mozilla.org',
 71 |         :verify_ssl => true,
 72 |         :ssl_verify_callback => lambda { |preverify_ok, store_ctx|
 73 |           ran_callback = true
 74 |           preverify_ok
 75 |         },
 76 |       )
 77 |       expect {request.execute }.to_not raise_error
 78 |       expect(ran_callback).to eq(true)
 79 |     end
 80 | 
 81 |     it "fails verification when the callback returns false",
 82 |        :unless => RestClient::Platform.mac_mri? do
 83 |       request = RestClient::Request.new(
 84 |         :method => :get,
 85 |         :url => 'https://www.mozilla.org',
 86 |         :verify_ssl => true,
 87 |         :ssl_verify_callback => lambda { |preverify_ok, store_ctx| false },
 88 |       )
 89 |       expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified)
 90 |     end
 91 | 
 92 |     it "succeeds verification when the callback returns true",
 93 |        :unless => RestClient::Platform.mac_mri? do
 94 |       request = RestClient::Request.new(
 95 |         :method => :get,
 96 |         :url => 'https://www.mozilla.org',
 97 |         :verify_ssl => true,
 98 |         :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt"),
 99 |         :ssl_verify_callback => lambda { |preverify_ok, store_ctx| true },
100 |       )
101 |       expect { request.execute }.to_not raise_error
102 |     end
103 |   end
104 | 
105 |   describe "timeouts" do
106 |     it "raises OpenTimeout when it hits an open timeout" do
107 |       request = RestClient::Request.new(
108 |         :method => :get,
109 |         :url => 'http://www.mozilla.org',
110 |         :open_timeout => 1e-10,
111 |       )
112 |       expect { request.execute }.to(
113 |         raise_error(RestClient::Exceptions::OpenTimeout))
114 |     end
115 | 
116 |     it "raises ReadTimeout when it hits a read timeout via :read_timeout" do
117 |       request = RestClient::Request.new(
118 |         :method => :get,
119 |         :url => 'https://www.mozilla.org',
120 |         :read_timeout => 1e-10,
121 |       )
122 |       expect { request.execute }.to(
123 |         raise_error(RestClient::Exceptions::ReadTimeout))
124 |     end
125 |   end
126 | 
127 | end
128 | 


--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
 1 | require 'webmock/rspec'
 2 | require 'rest-client'
 3 | 
 4 | require_relative './helpers'
 5 | 
 6 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
 7 | RSpec.configure do |config|
 8 |   config.raise_errors_for_deprecations!
 9 | 
10 |   # Run specs in random order to surface order dependencies. If you find an
11 |   # order dependency and want to debug it, you can fix the order by providing
12 |   # the seed, which is printed after each run.
13 |   #     --seed 1234
14 |   config.order = 'random'
15 | 
16 |   # always run with ruby warnings enabled
17 |   # TODO: figure out why this is so obscenely noisy (rspec bug?)
18 |   # config.warnings = true
19 | 
20 |   # add helpers
21 |   config.include Helpers, :include_helpers
22 | 
23 |   config.mock_with :rspec do |mocks|
24 |     mocks.yield_receiver_to_any_instance_implementation_blocks = true
25 |   end
26 | end
27 | 
28 | # always run with ruby warnings enabled (see above)
29 | $VERBOSE = true
30 | 


--------------------------------------------------------------------------------
/spec/unit/_lib.rb:
--------------------------------------------------------------------------------
1 | require_relative '../spec_helper'
2 | 


--------------------------------------------------------------------------------
/spec/unit/abstract_response_spec.rb:
--------------------------------------------------------------------------------
  1 | require_relative '_lib'
  2 | 
  3 | describe RestClient::AbstractResponse, :include_helpers do
  4 | 
  5 |   # Sample class implementing AbstractResponse used for testing.
  6 |   class MyAbstractResponse
  7 | 
  8 |     include RestClient::AbstractResponse
  9 | 
 10 |     attr_accessor :size
 11 | 
 12 |     def initialize(net_http_res, request)
 13 |       response_set_vars(net_http_res, request, Time.now - 1)
 14 |     end
 15 | 
 16 |   end
 17 | 
 18 |   before do
 19 |     @net_http_res = res_double()
 20 |     @request = request_double(url: 'http://example.com', method: 'get')
 21 |     @response = MyAbstractResponse.new(@net_http_res, @request)
 22 |   end
 23 | 
 24 |   it "fetches the numeric response code" do
 25 |     expect(@net_http_res).to receive(:code).and_return('200')
 26 |     expect(@response.code).to eq 200
 27 |   end
 28 | 
 29 |   it "has a nice description" do
 30 |     expect(@net_http_res).to receive(:to_hash).and_return({'Content-Type' => ['application/pdf']})
 31 |     expect(@net_http_res).to receive(:code).and_return('200')
 32 |     expect(@response.description).to eq "200 OK | application/pdf  bytes\n"
 33 |   end
 34 | 
 35 |   describe '.beautify_headers' do
 36 |     it "beautifies the headers by turning the keys to symbols" do
 37 |       h = RestClient::AbstractResponse.beautify_headers('content-type' => [ 'x' ])
 38 |       expect(h.keys.first).to eq :content_type
 39 |     end
 40 | 
 41 |     it "beautifies the headers by turning the values to strings instead of one-element arrays" do
 42 |       h = RestClient::AbstractResponse.beautify_headers('x' => [ 'text/html' ] )
 43 |       expect(h.values.first).to eq 'text/html'
 44 |     end
 45 | 
 46 |     it 'joins multiple header values by comma' do
 47 |       expect(RestClient::AbstractResponse.beautify_headers(
 48 |         {'My-Header' => ['one', 'two']}
 49 |       )).to eq({:my_header => 'one, two'})
 50 |     end
 51 | 
 52 |     it 'leaves set-cookie headers as array' do
 53 |       expect(RestClient::AbstractResponse.beautify_headers(
 54 |         {'Set-Cookie' => ['cookie1=foo', 'cookie2=bar']}
 55 |       )).to eq({:set_cookie => ['cookie1=foo', 'cookie2=bar']})
 56 |     end
 57 |   end
 58 | 
 59 |   it "fetches the headers" do
 60 |     expect(@net_http_res).to receive(:to_hash).and_return('content-type' => [ 'text/html' ])
 61 |     expect(@response.headers).to eq({ :content_type => 'text/html' })
 62 |   end
 63 | 
 64 |   it "extracts cookies from response headers" do
 65 |     expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
 66 |     expect(@response.cookies).to eq({ 'session_id' => '1' })
 67 |   end
 68 | 
 69 |   it "extract strange cookies" do
 70 |     expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/'])
 71 |     expect(@response.headers).to eq({:set_cookie => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/']})
 72 |     expect(@response.cookies).to eq({ 'session_id' => 'ZJ/HQVH6YE+rVkTpn0zvTQ==' })
 73 |   end
 74 | 
 75 |   it "doesn't escape cookies" do
 76 |     expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca; path=/'])
 77 |     expect(@response.cookies).to eq({ 'session_id' => 'BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca' })
 78 |   end
 79 | 
 80 |   describe '.cookie_jar' do
 81 |     it 'extracts cookies into cookie jar' do
 82 |       expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
 83 |       expect(@response.cookie_jar).to be_a HTTP::CookieJar
 84 | 
 85 |       cookie = @response.cookie_jar.cookies.first
 86 |       expect(cookie.domain).to eq 'example.com'
 87 |       expect(cookie.name).to eq 'session_id'
 88 |       expect(cookie.value).to eq '1'
 89 |       expect(cookie.path).to eq '/'
 90 |     end
 91 | 
 92 |     it 'handles cookies when URI scheme is implicit' do
 93 |       net_http_res = double('net http response')
 94 |       expect(net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/'])
 95 |       request = double('request', url: 'example.com', uri: URI.parse('http://example.com'),
 96 |                        method: 'get', cookie_jar: HTTP::CookieJar.new, redirection_history: nil)
 97 |       response = MyAbstractResponse.new(net_http_res, request)
 98 |       expect(response.cookie_jar).to be_a HTTP::CookieJar
 99 | 
100 |       cookie = response.cookie_jar.cookies.first
101 |       expect(cookie.domain).to eq 'example.com'
102 |       expect(cookie.name).to eq 'session_id'
103 |       expect(cookie.value).to eq '1'
104 |       expect(cookie.path).to eq '/'
105 |     end
106 |   end
107 | 
108 |   it "can access the net http result directly" do
109 |     expect(@response.net_http_res).to eq @net_http_res
110 |   end
111 | 
112 |   describe "#return!" do
113 |     it "should return the response itself on 200-codes" do
114 |       expect(@net_http_res).to receive(:code).and_return('200')
115 |       expect(@response.return!).to be_equal(@response)
116 |     end
117 | 
118 |     it "should raise RequestFailed on unknown codes" do
119 |       expect(@net_http_res).to receive(:code).and_return('1000')
120 |       expect { @response.return! }.to raise_error RestClient::RequestFailed
121 |     end
122 | 
123 |     it "should raise an error on a redirection after non-GET/HEAD requests" do
124 |       expect(@net_http_res).to receive(:code).and_return('301')
125 |       expect(@request).to receive(:method).and_return('put')
126 |       expect(@response).not_to receive(:follow_redirection)
127 |       expect { @response.return! }.to raise_error RestClient::RequestFailed
128 |     end
129 | 
130 |     it "should follow 302 redirect" do
131 |       expect(@net_http_res).to receive(:code).and_return('302')
132 |       expect(@response).to receive(:check_max_redirects).and_return('fake-check')
133 |       expect(@response).to receive(:follow_redirection).and_return('fake-redirection')
134 |       expect(@response.return!).to eq 'fake-redirection'
135 |     end
136 | 
137 |     it "should gracefully handle 302 redirect with no location header" do
138 |       @net_http_res = res_double(code: 302)
139 |       @request = request_double()
140 |       @response = MyAbstractResponse.new(@net_http_res, @request)
141 |       expect(@response).to receive(:check_max_redirects).and_return('fake-check')
142 |       expect { @response.return! }.to raise_error RestClient::Found
143 |     end
144 |   end
145 | end
146 | 


--------------------------------------------------------------------------------
/spec/unit/exceptions_spec.rb:
--------------------------------------------------------------------------------
  1 | require_relative '_lib'
  2 | 
  3 | describe RestClient::Exception do
  4 |   it "returns a 'message' equal to the class name if the message is not set, because 'message' should not be nil" do
  5 |     e = RestClient::Exception.new
  6 |     expect(e.message).to eq "RestClient::Exception"
  7 |   end
  8 | 
  9 |   it "returns the 'message' that was set" do
 10 |     e = RestClient::Exception.new
 11 |     message = "An explicitly set message"
 12 |     e.message = message
 13 |     expect(e.message).to eq message
 14 |   end
 15 | 
 16 |   it "sets the exception message to ErrorMessage" do
 17 |     expect(RestClient::ResourceNotFound.new.message).to eq 'Not Found'
 18 |   end
 19 | 
 20 |   it "contains exceptions in RestClient" do
 21 |     expect(RestClient::Unauthorized.new).to be_a_kind_of(RestClient::Exception)
 22 |     expect(RestClient::ServerBrokeConnection.new).to be_a_kind_of(RestClient::Exception)
 23 |   end
 24 | end
 25 | 
 26 | describe RestClient::ServerBrokeConnection do
 27 |   it "should have a default message of 'Server broke connection'" do
 28 |     e = RestClient::ServerBrokeConnection.new
 29 |     expect(e.message).to eq 'Server broke connection'
 30 |   end
 31 | end
 32 | 
 33 | describe RestClient::RequestFailed do
 34 |   before do
 35 |     @response = double('HTTP Response', :code => '502')
 36 |   end
 37 | 
 38 |   it "stores the http response on the exception" do
 39 |     response = "response"
 40 |     begin
 41 |       raise RestClient::RequestFailed, response
 42 |     rescue RestClient::RequestFailed => e
 43 |       expect(e.response).to eq response
 44 |     end
 45 |   end
 46 | 
 47 |   it "http_code convenience method for fetching the code as an integer" do
 48 |     expect(RestClient::RequestFailed.new(@response).http_code).to eq 502
 49 |   end
 50 | 
 51 |   it "http_body convenience method for fetching the body (decoding when necessary)" do
 52 |     expect(RestClient::RequestFailed.new(@response).http_code).to eq 502
 53 |     expect(RestClient::RequestFailed.new(@response).message).to eq 'HTTP status code 502'
 54 |   end
 55 | 
 56 |   it "shows the status code in the message" do
 57 |     expect(RestClient::RequestFailed.new(@response).to_s).to match(/502/)
 58 |   end
 59 | end
 60 | 
 61 | describe RestClient::ResourceNotFound do
 62 |   it "also has the http response attached" do
 63 |     response = "response"
 64 |     begin
 65 |       raise RestClient::ResourceNotFound, response
 66 |     rescue RestClient::ResourceNotFound => e
 67 |       expect(e.response).to eq response
 68 |     end
 69 |   end
 70 | 
 71 |   it 'stores the body on the response of the exception' do
 72 |     body = "body"
 73 |     stub_request(:get, "www.example.com").to_return(:body => body, :status => 404)
 74 |     begin
 75 |       RestClient.get "www.example.com"
 76 |       raise
 77 |     rescue RestClient::ResourceNotFound => e
 78 |       expect(e.response.body).to eq body
 79 |     end
 80 |   end
 81 | end
 82 | 
 83 | describe "backwards compatibility" do
 84 |   it 'aliases RestClient::NotFound as ResourceNotFound' do
 85 |     expect(RestClient::ResourceNotFound).to eq RestClient::NotFound
 86 |   end
 87 | 
 88 |   it 'aliases old names for HTTP 413, 414, 416' do
 89 |     expect(RestClient::RequestEntityTooLarge).to eq RestClient::PayloadTooLarge
 90 |     expect(RestClient::RequestURITooLong).to eq RestClient::URITooLong
 91 |     expect(RestClient::RequestedRangeNotSatisfiable).to eq RestClient::RangeNotSatisfiable
 92 |   end
 93 | 
 94 |   it 'subclasses NotFound from RequestFailed, ExceptionWithResponse' do
 95 |     expect(RestClient::NotFound).to be < RestClient::RequestFailed
 96 |     expect(RestClient::NotFound).to be < RestClient::ExceptionWithResponse
 97 |   end
 98 | 
 99 |   it 'subclasses timeout from RestClient::RequestTimeout, RequestFailed, EWR' do
100 |     expect(RestClient::Exceptions::OpenTimeout).to be < RestClient::Exceptions::Timeout
101 |     expect(RestClient::Exceptions::ReadTimeout).to be < RestClient::Exceptions::Timeout
102 | 
103 |     expect(RestClient::Exceptions::Timeout).to be < RestClient::RequestTimeout
104 |     expect(RestClient::Exceptions::Timeout).to be < RestClient::RequestFailed
105 |     expect(RestClient::Exceptions::Timeout).to be < RestClient::ExceptionWithResponse
106 |   end
107 | 
108 | end
109 | 


--------------------------------------------------------------------------------
/spec/unit/params_array_spec.rb:
--------------------------------------------------------------------------------
 1 | require_relative '_lib'
 2 | 
 3 | describe RestClient::ParamsArray do
 4 | 
 5 |   describe '.new' do
 6 |     it 'accepts various types of containers' do
 7 |       as_array = [[:foo, 123], [:foo, 456], [:bar, 789], [:empty, nil]]
 8 |       [
 9 |         [[:foo, 123], [:foo, 456], [:bar, 789], [:empty, nil]],
10 |         [{foo: 123}, {foo: 456}, {bar: 789}, {empty: nil}],
11 |         [{foo: 123}, {foo: 456}, {bar: 789}, {empty: nil}],
12 |         [{foo: 123}, [:foo, 456], {bar: 789}, {empty: nil}],
13 |         [{foo: 123}, [:foo, 456], {bar: 789}, [:empty]],
14 |       ].each do |input|
15 |         expect(RestClient::ParamsArray.new(input).to_a).to eq as_array
16 |       end
17 | 
18 |       expect(RestClient::ParamsArray.new([]).to_a).to eq []
19 |       expect(RestClient::ParamsArray.new([]).empty?).to eq true
20 |     end
21 | 
22 |     it 'rejects various invalid input' do
23 |       expect {
24 |         RestClient::ParamsArray.new([[]])
25 |       }.to raise_error(IndexError)
26 | 
27 |       expect {
28 |         RestClient::ParamsArray.new([[1,2,3]])
29 |       }.to raise_error(ArgumentError)
30 | 
31 |       expect {
32 |         RestClient::ParamsArray.new([1,2,3])
33 |       }.to raise_error(NoMethodError)
34 |     end
35 |   end
36 | end
37 | 


--------------------------------------------------------------------------------
/spec/unit/payload_spec.rb:
--------------------------------------------------------------------------------
  1 | # encoding: binary
  2 | 
  3 | require_relative '_lib'
  4 | 
  5 | describe RestClient::Payload, :include_helpers do
  6 |   context "Base Payload" do
  7 |     it "should reset stream after to_s" do
  8 |       payload = RestClient::Payload::Base.new('foobar')
  9 |       expect(payload.to_s).to eq 'foobar'
 10 |       expect(payload.to_s).to eq 'foobar'
 11 |     end
 12 |   end
 13 | 
 14 |   context "A regular Payload" do
 15 |     it "should use standard enctype as default content-type" do
 16 |       expect(RestClient::Payload::UrlEncoded.new({}).headers['Content-Type']).
 17 |         to eq 'application/x-www-form-urlencoded'
 18 |     end
 19 | 
 20 |     it "should form properly encoded params" do
 21 |       expect(RestClient::Payload::UrlEncoded.new({:foo => 'bar'}).to_s).
 22 |         to eq "foo=bar"
 23 |       expect(["foo=bar&baz=qux", "baz=qux&foo=bar"]).to include(
 24 |                                                         RestClient::Payload::UrlEncoded.new({:foo => 'bar', :baz => 'qux'}).to_s)
 25 |     end
 26 | 
 27 |     it "should escape parameters" do
 28 |       expect(RestClient::Payload::UrlEncoded.new({'foo + bar' => 'baz'}).to_s).
 29 |         to eq "foo+%2B+bar=baz"
 30 |     end
 31 | 
 32 |     it "should properly handle hashes as parameter" do
 33 |       expect(RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz'}}).to_s).
 34 |         to eq "foo[bar]=baz"
 35 |       expect(RestClient::Payload::UrlEncoded.new({:foo => {:bar => {:baz => 'qux'}}}).to_s).
 36 |         to eq "foo[bar][baz]=qux"
 37 |     end
 38 | 
 39 |     it "should handle many attributes inside a hash" do
 40 |       parameters = RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz', :baz => 'qux'}}).to_s
 41 |       expect(parameters).to eq 'foo[bar]=baz&foo[baz]=qux'
 42 |     end
 43 | 
 44 |     it "should handle attributes inside an array inside an hash" do
 45 |       parameters = RestClient::Payload::UrlEncoded.new({"foo" => [{"bar" => 'baz'}, {"bar" => 'qux'}]}).to_s
 46 |       expect(parameters).to eq 'foo[][bar]=baz&foo[][bar]=qux'
 47 |     end
 48 | 
 49 |     it "should handle arrays inside a hash inside a hash" do
 50 |       parameters = RestClient::Payload::UrlEncoded.new({"foo" => {'even' => [0, 2], 'odd' => [1, 3]}}).to_s
 51 |       expect(parameters).to eq 'foo[even][]=0&foo[even][]=2&foo[odd][]=1&foo[odd][]=3'
 52 |     end
 53 | 
 54 |     it "should form properly use symbols as parameters" do
 55 |       expect(RestClient::Payload::UrlEncoded.new({:foo => :bar}).to_s).
 56 |         to eq "foo=bar"
 57 |       expect(RestClient::Payload::UrlEncoded.new({:foo => {:bar => :baz}}).to_s).
 58 |         to eq "foo[bar]=baz"
 59 |     end
 60 | 
 61 |     it "should properly handle arrays as repeated parameters" do
 62 |       expect(RestClient::Payload::UrlEncoded.new({:foo => ['bar']}).to_s).
 63 |         to eq "foo[]=bar"
 64 |       expect(RestClient::Payload::UrlEncoded.new({:foo => ['bar', 'baz']}).to_s).
 65 |         to eq "foo[]=bar&foo[]=baz"
 66 |     end
 67 | 
 68 |     it 'should not close if stream already closed' do
 69 |       p = RestClient::Payload::UrlEncoded.new({'foo ' => 'bar'})
 70 |       3.times {p.close}
 71 |     end
 72 | 
 73 |   end
 74 | 
 75 |   context "A multipart Payload" do
 76 |     it "should use standard enctype as default content-type" do
 77 |       m = RestClient::Payload::Multipart.new({})
 78 |       allow(m).to receive(:boundary).and_return(123)
 79 |       expect(m.headers['Content-Type']).to eq 'multipart/form-data; boundary=123'
 80 |     end
 81 | 
 82 |     it 'should not error on close if stream already closed' do
 83 |       m = RestClient::Payload::Multipart.new(:file => File.new(test_image_path))
 84 |       3.times {m.close}
 85 |     end
 86 | 
 87 |     it "should form properly separated multipart data" do
 88 |       m = RestClient::Payload::Multipart.new([[:bar, "baz"], [:foo, "bar"]])
 89 |       expect(m.to_s).to eq <<-EOS
 90 | --#{m.boundary}\r
 91 | Content-Disposition: form-data; name="bar"\r
 92 | \r
 93 | baz\r
 94 | --#{m.boundary}\r
 95 | Content-Disposition: form-data; name="foo"\r
 96 | \r
 97 | bar\r
 98 | --#{m.boundary}--\r
 99 |       EOS
100 |     end
101 | 
102 |     it "should not escape parameters names" do
103 |       m = RestClient::Payload::Multipart.new([["bar ", "baz"]])
104 |       expect(m.to_s).to eq <<-EOS
105 | --#{m.boundary}\r
106 | Content-Disposition: form-data; name="bar "\r
107 | \r
108 | baz\r
109 | --#{m.boundary}--\r
110 |       EOS
111 |     end
112 | 
113 |     it "should form properly separated multipart data" do
114 |       f = File.new(test_image_path)
115 |       m = RestClient::Payload::Multipart.new({:foo => f})
116 |       expect(m.to_s).to eq <<-EOS
117 | --#{m.boundary}\r
118 | Content-Disposition: form-data; name="foo"; filename="ISS.jpg"\r
119 | Content-Type: image/jpeg\r
120 | \r
121 | #{File.open(f.path, 'rb'){|bin| bin.read}}\r
122 | --#{m.boundary}--\r
123 |       EOS
124 |     end
125 | 
126 |     it "should ignore the name attribute when it's not set" do
127 |       f = File.new(test_image_path)
128 |       m = RestClient::Payload::Multipart.new({nil => f})
129 |       expect(m.to_s).to eq <<-EOS
130 | --#{m.boundary}\r
131 | Content-Disposition: form-data; filename="ISS.jpg"\r
132 | Content-Type: image/jpeg\r
133 | \r
134 | #{File.open(f.path, 'rb'){|bin| bin.read}}\r
135 | --#{m.boundary}--\r
136 |       EOS
137 |     end
138 | 
139 |     it "should detect optional (original) content type and filename" do
140 |       f = File.new(test_image_path)
141 |       expect(f).to receive(:content_type).and_return('text/plain')
142 |       expect(f).to receive(:original_filename).and_return('foo.txt')
143 |       m = RestClient::Payload::Multipart.new({:foo => f})
144 |       expect(m.to_s).to eq <<-EOS
145 | --#{m.boundary}\r
146 | Content-Disposition: form-data; name="foo"; filename="foo.txt"\r
147 | Content-Type: text/plain\r
148 | \r
149 | #{File.open(f.path, 'rb'){|bin| bin.read}}\r
150 | --#{m.boundary}--\r
151 |       EOS
152 |     end
153 | 
154 |     it "should handle hash in hash parameters" do
155 |       m = RestClient::Payload::Multipart.new({:bar => {:baz => "foo"}})
156 |       expect(m.to_s).to eq <<-EOS
157 | --#{m.boundary}\r
158 | Content-Disposition: form-data; name="bar[baz]"\r
159 | \r
160 | foo\r
161 | --#{m.boundary}--\r
162 |       EOS
163 | 
164 |       f = File.new(test_image_path)
165 |       f.instance_eval "def content_type; 'text/plain'; end"
166 |       f.instance_eval "def original_filename; 'foo.txt'; end"
167 |       m = RestClient::Payload::Multipart.new({:foo => {:bar => f}})
168 |       expect(m.to_s).to eq <<-EOS
169 | --#{m.boundary}\r
170 | Content-Disposition: form-data; name="foo[bar]"; filename="foo.txt"\r
171 | Content-Type: text/plain\r
172 | \r
173 | #{File.open(f.path, 'rb'){|bin| bin.read}}\r
174 | --#{m.boundary}--\r
175 |       EOS
176 |     end
177 | 
178 |     it 'should correctly format hex boundary' do
179 |       allow(SecureRandom).to receive(:base64).with(12).and_return('TGs89+ttw/xna6TV')
180 |       f = File.new(test_image_path)
181 |       m = RestClient::Payload::Multipart.new({:foo => f})
182 |       expect(m.boundary).to eq('-' * 4 + 'RubyFormBoundary' + 'TGs89AttwBxna6TV')
183 |     end
184 | 
185 |   end
186 | 
187 |   context "streamed payloads" do
188 |     it "should properly determine the size of file payloads" do
189 |       f = File.new(test_image_path)
190 |       payload = RestClient::Payload.generate(f)
191 |       expect(payload.size).to eq 72_463
192 |       expect(payload.length).to eq 72_463
193 |     end
194 | 
195 |     it "should properly determine the size of other kinds of streaming payloads" do
196 |       s = StringIO.new 'foo'
197 |       payload = RestClient::Payload.generate(s)
198 |       expect(payload.size).to eq 3
199 |       expect(payload.length).to eq 3
200 | 
201 |       begin
202 |         f = Tempfile.new "rest-client"
203 |         f.write 'foo bar'
204 | 
205 |         payload = RestClient::Payload.generate(f)
206 |         expect(payload.size).to eq 7
207 |         expect(payload.length).to eq 7
208 |       ensure
209 |         f.close
210 |       end
211 |     end
212 | 
213 |     it "should have a closed? method" do
214 |       f = File.new(test_image_path)
215 |       payload = RestClient::Payload.generate(f)
216 |       expect(payload.closed?).to be_falsey
217 |       payload.close
218 |       expect(payload.closed?).to be_truthy
219 |     end
220 |   end
221 | 
222 |   context "Payload generation" do
223 |     it "should recognize standard urlencoded params" do
224 |       expect(RestClient::Payload.generate({"foo" => 'bar'})).to be_kind_of(RestClient::Payload::UrlEncoded)
225 |     end
226 | 
227 |     it "should recognize multipart params" do
228 |       f = File.new(test_image_path)
229 |       expect(RestClient::Payload.generate({"foo" => f})).to be_kind_of(RestClient::Payload::Multipart)
230 |     end
231 | 
232 |     it "should be multipart if forced" do
233 |       expect(RestClient::Payload.generate({"foo" => "bar", :multipart => true})).to be_kind_of(RestClient::Payload::Multipart)
234 |     end
235 | 
236 |     it "should handle deeply nested multipart" do
237 |       f = File.new(test_image_path)
238 |       params = {foo: RestClient::ParamsArray.new({nested: f})}
239 |       expect(RestClient::Payload.generate(params)).to be_kind_of(RestClient::Payload::Multipart)
240 |     end
241 | 
242 | 
243 |     it "should return data if no of the above" do
244 |       expect(RestClient::Payload.generate("data")).to be_kind_of(RestClient::Payload::Base)
245 |     end
246 | 
247 |     it "should recognize nested multipart payloads in hashes" do
248 |       f = File.new(test_image_path)
249 |       expect(RestClient::Payload.generate({"foo" => {"file" => f}})).to be_kind_of(RestClient::Payload::Multipart)
250 |     end
251 | 
252 |     it "should recognize nested multipart payloads in arrays" do
253 |       f = File.new(test_image_path)
254 |       expect(RestClient::Payload.generate({"foo" => [f]})).to be_kind_of(RestClient::Payload::Multipart)
255 |     end
256 | 
257 |     it "should recognize file payloads that can be streamed" do
258 |       f = File.new(test_image_path)
259 |       expect(RestClient::Payload.generate(f)).to be_kind_of(RestClient::Payload::Streamed)
260 |     end
261 | 
262 |     it "should recognize other payloads that can be streamed" do
263 |       expect(RestClient::Payload.generate(StringIO.new('foo'))).to be_kind_of(RestClient::Payload::Streamed)
264 |     end
265 | 
266 |     # hashery gem introduces Hash#read convenience method. Existence of #read method used to determine of content is streameable :/
267 |     it "shouldn't treat hashes as streameable" do
268 |       expect(RestClient::Payload.generate({"foo" => 'bar'})).to be_kind_of(RestClient::Payload::UrlEncoded)
269 |     end
270 | 
271 |     it "should recognize multipart payload wrapped in ParamsArray" do
272 |       f = File.new(test_image_path)
273 |       params = RestClient::ParamsArray.new([[:image, f]])
274 |       expect(RestClient::Payload.generate(params)).to be_kind_of(RestClient::Payload::Multipart)
275 |     end
276 | 
277 |     it "should handle non-multipart payload wrapped in ParamsArray" do
278 |       params = RestClient::ParamsArray.new([[:arg, 'value1'], [:arg, 'value2']])
279 |       expect(RestClient::Payload.generate(params)).to be_kind_of(RestClient::Payload::UrlEncoded)
280 |     end
281 | 
282 |     it "should pass through Payload::Base and subclasses unchanged" do
283 |       payloads = [
284 |         RestClient::Payload::Base.new('foobar'),
285 |         RestClient::Payload::UrlEncoded.new({:foo => 'bar'}),
286 |         RestClient::Payload::Streamed.new(File.new(test_image_path)),
287 |         RestClient::Payload::Multipart.new({myfile: File.new(test_image_path)}),
288 |       ]
289 | 
290 |       payloads.each do |payload|
291 |         expect(RestClient::Payload.generate(payload)).to equal(payload)
292 |       end
293 |     end
294 |   end
295 | end
296 | 


--------------------------------------------------------------------------------
/spec/unit/raw_response_spec.rb:
--------------------------------------------------------------------------------
 1 | require_relative '_lib'
 2 | 
 3 | describe RestClient::RawResponse do
 4 |   before do
 5 |     @tf = double("Tempfile", :read => "the answer is 42", :open => true, :rewind => true)
 6 |     @net_http_res = double('net http response')
 7 |     @request = double('restclient request', :redirection_history => nil)
 8 |     @response = RestClient::RawResponse.new(@tf, @net_http_res, @request)
 9 |   end
10 | 
11 |   it "behaves like string" do
12 |     expect(@response.to_s).to eq 'the answer is 42'
13 |   end
14 | 
15 |   it "exposes a Tempfile" do
16 |     expect(@response.file).to eq @tf
17 |   end
18 | 
19 |   it "includes AbstractResponse" do
20 |     expect(RestClient::RawResponse.ancestors).to include(RestClient::AbstractResponse)
21 |   end
22 | end
23 | 


--------------------------------------------------------------------------------
/spec/unit/request2_spec.rb:
--------------------------------------------------------------------------------
 1 | require_relative '_lib'
 2 | 
 3 | describe RestClient::Request, :include_helpers do
 4 | 
 5 |   context 'params for GET requests' do
 6 |     it "manage params for get requests" do
 7 |       stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200)
 8 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => {:a => :b, 'c' => 'd'}}).body).to eq 'foo'
 9 | 
10 |       stub_request(:get, 'http://some/resource').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200)
11 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => :a}).body).to eq 'foo'
12 |     end
13 | 
14 |     it 'adds GET params when params are present in URL' do
15 |       stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200)
16 |       expect(RestClient::Request.execute(:url => 'http://some/resource?a=b', :method => :get, :headers => {:foo => :bar, :params => {:c => 'd'}}).body).to eq 'foo'
17 |     end
18 | 
19 |     it 'encodes nested GET params' do
20 |       stub_request(:get, 'http://some/resource?a[foo][]=1&a[foo][]=2&a[bar]&b=foo+bar&math=2+%2B+2+%3D%3D+4').with(:headers => {'Accept'=>'*/*',}).to_return(:body => 'foo', :status => 200)
21 |       expect(RestClient::Request.execute(url: 'http://some/resource', method: :get, headers: {
22 |         params: {
23 |           a: {
24 |             foo: [1,2],
25 |             bar: nil,
26 |           },
27 |           b: 'foo bar',
28 |           math: '2 + 2 == 4',
29 |         }
30 |       }).body).to eq 'foo'
31 |     end
32 | 
33 |   end
34 | 
35 |   it "can use a block to process response" do
36 |     response_value = nil
37 |     block = proc do |http_response|
38 |       response_value = http_response.body
39 |     end
40 |     stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200)
41 |     RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => {:a => :b, 'c' => 'd'}}, :block_response => block)
42 |     expect(response_value).to eq "foo"
43 |   end
44 | 
45 |   it 'closes payload if not nil' do
46 |     test_file = File.new(test_image_path)
47 | 
48 |     stub_request(:post, 'http://some/resource').with(:headers => {'Accept'=>'*/*'}).to_return(:body => 'foo', :status => 200)
49 |     RestClient::Request.execute(:url => 'http://some/resource', :method => :post, :payload => {:file => test_file})
50 | 
51 |     expect(test_file.closed?).to be true
52 |   end
53 | 
54 | end
55 | 


--------------------------------------------------------------------------------
/spec/unit/resource_spec.rb:
--------------------------------------------------------------------------------
  1 | require_relative '_lib'
  2 | 
  3 | describe RestClient::Resource do
  4 |   before do
  5 |     @resource = RestClient::Resource.new('http://some/resource', :user => 'jane', :password => 'mypass', :headers => {'X-Something' => '1'})
  6 |   end
  7 | 
  8 |   context "Resource delegation" do
  9 |     it "GET" do
 10 |       expect(RestClient::Request).to receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil)
 11 |       @resource.get
 12 |     end
 13 | 
 14 |     it "HEAD" do
 15 |       expect(RestClient::Request).to receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil)
 16 |       @resource.head
 17 |     end
 18 | 
 19 |     it "POST" do
 20 |       expect(RestClient::Request).to receive(:execute).with(:method => :post, :url => 'http://some/resource', :payload => 'abc', :headers => {:content_type => 'image/jpg', 'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil)
 21 |       @resource.post 'abc', :content_type => 'image/jpg'
 22 |     end
 23 | 
 24 |     it "PUT" do
 25 |       expect(RestClient::Request).to receive(:execute).with(:method => :put, :url => 'http://some/resource', :payload => 'abc', :headers => {:content_type => 'image/jpg', 'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil)
 26 |       @resource.put 'abc', :content_type => 'image/jpg'
 27 |     end
 28 | 
 29 |     it "PATCH" do
 30 |       expect(RestClient::Request).to receive(:execute).with(:method => :patch, :url => 'http://some/resource', :payload => 'abc', :headers => {:content_type => 'image/jpg', 'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil)
 31 |       @resource.patch 'abc', :content_type => 'image/jpg'
 32 |     end
 33 | 
 34 |     it "DELETE" do
 35 |       expect(RestClient::Request).to receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil)
 36 |       @resource.delete
 37 |     end
 38 | 
 39 |     it "overrides resource headers" do
 40 |       expect(RestClient::Request).to receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {'X-Something' => '2'}, :user => 'jane', :password => 'mypass', :log => nil)
 41 |       @resource.get 'X-Something' => '2'
 42 |     end
 43 |   end
 44 | 
 45 |   it "can instantiate with no user/password" do
 46 |     @resource = RestClient::Resource.new('http://some/resource')
 47 |   end
 48 | 
 49 |   it "is backwards compatible with previous constructor" do
 50 |     @resource = RestClient::Resource.new('http://some/resource', 'user', 'pass')
 51 |     expect(@resource.user).to eq 'user'
 52 |     expect(@resource.password).to eq 'pass'
 53 |   end
 54 | 
 55 |   it "concatenates urls, inserting a slash when it needs one" do
 56 |     expect(@resource.concat_urls('http://example.com', 'resource')).to eq 'http://example.com/resource'
 57 |   end
 58 | 
 59 |   it "concatenates urls, using no slash if the first url ends with a slash" do
 60 |     expect(@resource.concat_urls('http://example.com/', 'resource')).to eq 'http://example.com/resource'
 61 |   end
 62 | 
 63 |   it "concatenates urls, using no slash if the second url starts with a slash" do
 64 |     expect(@resource.concat_urls('http://example.com', '/resource')).to eq 'http://example.com/resource'
 65 |   end
 66 | 
 67 |   it "concatenates even non-string urls, :posts + 1 => 'posts/1'" do
 68 |     expect(@resource.concat_urls(:posts, 1)).to eq 'posts/1'
 69 |   end
 70 | 
 71 |   it "offers subresources via []" do
 72 |     parent = RestClient::Resource.new('http://example.com')
 73 |     expect(parent['posts'].url).to eq 'http://example.com/posts'
 74 |   end
 75 | 
 76 |   it "transports options to subresources" do
 77 |     parent = RestClient::Resource.new('http://example.com', :user => 'user', :password => 'password')
 78 |     expect(parent['posts'].user).to eq 'user'
 79 |     expect(parent['posts'].password).to eq 'password'
 80 |   end
 81 | 
 82 |   it "passes a given block to subresources" do
 83 |     block = proc {|r| r}
 84 |     parent = RestClient::Resource.new('http://example.com', &block)
 85 |     expect(parent['posts'].block).to eq block
 86 |   end
 87 | 
 88 |   it "the block should be overrideable" do
 89 |     block1 = proc {|r| r}
 90 |     block2 = proc {|r| }
 91 |     parent = RestClient::Resource.new('http://example.com', &block1)
 92 |     # parent['posts', &block2].block.should eq block2 # ruby 1.9 syntax
 93 |     expect(parent.send(:[], 'posts', &block2).block).to eq block2
 94 |     expect(parent.send(:[], 'posts', &block2).block).not_to eq block1
 95 |   end
 96 | 
 97 |   # Test fails on jruby 9.1.[0-5].* due to
 98 |   # https://github.com/jruby/jruby/issues/4217
 99 |   it "the block should be overrideable in ruby 1.9 syntax",
100 |       :unless => (RUBY_ENGINE == 'jruby' && JRUBY_VERSION =~ /\A9\.1\.[0-5]\./) \
101 |   do
102 |     block1 = proc {|r| r}
103 |     block2 = ->(r) {}
104 | 
105 |     parent = RestClient::Resource.new('http://example.com', &block1)
106 |     expect(parent['posts', &block2].block).to eq block2
107 |     expect(parent['posts', &block2].block).not_to eq block1
108 |   end
109 | 
110 |   it "prints its url with to_s" do
111 |     expect(RestClient::Resource.new('x').to_s).to eq 'x'
112 |   end
113 | 
114 |   describe 'block' do
115 |     it 'can use block when creating the resource' do
116 |       stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404)
117 |       resource = RestClient::Resource.new('www.example.com') { |response, request| 'foo' }
118 |       expect(resource.get).to eq 'foo'
119 |     end
120 | 
121 |     it 'can use block when executing the resource' do
122 |       stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404)
123 |       resource = RestClient::Resource.new('www.example.com')
124 |       expect(resource.get { |response, request| 'foo' }).to eq 'foo'
125 |     end
126 | 
127 |     it 'execution block override resource block' do
128 |       stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404)
129 |       resource = RestClient::Resource.new('www.example.com') { |response, request| 'foo' }
130 |       expect(resource.get { |response, request| 'bar' }).to eq 'bar'
131 |     end
132 | 
133 |   end
134 | end
135 | 


--------------------------------------------------------------------------------
/spec/unit/response_spec.rb:
--------------------------------------------------------------------------------
  1 | require_relative '_lib'
  2 | 
  3 | describe RestClient::Response, :include_helpers do
  4 |   before do
  5 |     @net_http_res = res_double(to_hash: {'Status' => ['200 OK']}, code: '200', body: 'abc')
  6 |     @example_url = 'http://example.com'
  7 |     @request = request_double(url: @example_url, method: 'get')
  8 |     @response = response_from_res_double(@net_http_res, @request, duration: 1)
  9 |   end
 10 | 
 11 |   it "behaves like string" do
 12 |     expect(@response.to_s).to eq 'abc'
 13 |     expect(@response.to_str).to eq 'abc'
 14 | 
 15 |     expect(@response).to receive(:warn)
 16 |     expect(@response.to_i).to eq 0
 17 |   end
 18 | 
 19 |   it "accepts nil strings and sets it to empty for the case of HEAD" do
 20 |     # TODO
 21 |     expect(RestClient::Response.create(nil, @net_http_res, @request).to_s).to eq ""
 22 |   end
 23 | 
 24 |   describe 'header processing' do
 25 |     it "test headers and raw headers" do
 26 |       expect(@response.raw_headers["Status"][0]).to eq "200 OK"
 27 |       expect(@response.headers[:status]).to eq "200 OK"
 28 |     end
 29 | 
 30 |     it 'handles multiple headers by joining with comma' do
 31 |       net_http_res = res_double(to_hash: {'My-Header' => ['foo', 'bar']}, code: '200', body: nil)
 32 |       example_url = 'http://example.com'
 33 |       request = request_double(url: example_url, method: 'get')
 34 |       response = response_from_res_double(net_http_res, request)
 35 | 
 36 |       expect(response.raw_headers['My-Header']).to eq ['foo', 'bar']
 37 |       expect(response.headers[:my_header]).to eq 'foo, bar'
 38 |     end
 39 |   end
 40 | 
 41 |   describe "cookie processing" do
 42 |     it "should correctly deal with one Set-Cookie header with one cookie inside" do
 43 |       header_val = "main_page=main_page_no_rewrite; path=/; expires=Sat, 10-Jan-2037 15:03:14 GMT".freeze
 44 | 
 45 |       net_http_res = double('net http response', :to_hash => {"etag" => ["\"e1ac1a2df945942ef4cac8116366baad\""], "set-cookie" => [header_val]})
 46 |       response = RestClient::Response.create('abc', net_http_res, @request)
 47 |       expect(response.headers[:set_cookie]).to eq [header_val]
 48 |       expect(response.cookies).to eq({ "main_page" => "main_page_no_rewrite" })
 49 |     end
 50 | 
 51 |     it "should correctly deal with multiple cookies [multiple Set-Cookie headers]" do
 52 |       net_http_res = double('net http response', :to_hash => {"etag" => ["\"e1ac1a2df945942ef4cac8116366baad\""], "set-cookie" => ["main_page=main_page_no_rewrite; path=/; expires=Sat, 10-Jan-2037 15:03:14 GMT", "remember_me=; path=/; expires=Sat, 10-Jan-2037 00:00:00 GMT", "user=somebody; path=/; expires=Sat, 10-Jan-2037 00:00:00 GMT"]})
 53 |       response = RestClient::Response.create('abc', net_http_res, @request)
 54 |       expect(response.headers[:set_cookie]).to eq ["main_page=main_page_no_rewrite; path=/; expires=Sat, 10-Jan-2037 15:03:14 GMT", "remember_me=; path=/; expires=Sat, 10-Jan-2037 00:00:00 GMT", "user=somebody; path=/; expires=Sat, 10-Jan-2037 00:00:00 GMT"]
 55 |       expect(response.cookies).to eq({
 56 |         "main_page" => "main_page_no_rewrite",
 57 |         "remember_me" => "",
 58 |         "user" => "somebody"
 59 |       })
 60 |     end
 61 | 
 62 |     it "should correctly deal with multiple cookies [one Set-Cookie header with multiple cookies]" do
 63 |       net_http_res = double('net http response', :to_hash => {"etag" => ["\"e1ac1a2df945942ef4cac8116366baad\""], "set-cookie" => ["main_page=main_page_no_rewrite; path=/; expires=Sat, 10-Jan-2037 15:03:14 GMT, remember_me=; path=/; expires=Sat, 10-Jan-2037 00:00:00 GMT, user=somebody; path=/; expires=Sat, 10-Jan-2037 00:00:00 GMT"]})
 64 |       response = RestClient::Response.create('abc', net_http_res, @request)
 65 |       expect(response.cookies).to eq({
 66 |         "main_page" => "main_page_no_rewrite",
 67 |         "remember_me" => "",
 68 |         "user" => "somebody"
 69 |       })
 70 |     end
 71 |   end
 72 | 
 73 |   describe "exceptions processing" do
 74 |     it "should return itself for normal codes" do
 75 |       (200..206).each do |code|
 76 |         net_http_res = res_double(:code => '200')
 77 |         resp = RestClient::Response.create('abc', net_http_res, @request)
 78 |         resp.return!
 79 |       end
 80 |     end
 81 | 
 82 |     it "should throw an exception for other codes" do
 83 |       RestClient::Exceptions::EXCEPTIONS_MAP.each_pair do |code, exc|
 84 |         unless (200..207).include? code
 85 |           net_http_res = res_double(:code => code.to_i)
 86 |           resp = RestClient::Response.create('abc', net_http_res, @request)
 87 |           allow(@request).to receive(:max_redirects).and_return(5)
 88 |           expect { resp.return! }.to raise_error(exc)
 89 |         end
 90 |       end
 91 |     end
 92 | 
 93 |   end
 94 | 
 95 |   describe "redirection" do
 96 | 
 97 |     it "follows a redirection when the request is a get" do
 98 |       stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'})
 99 |       stub_request(:get, 'http://new/resource').to_return(:body => 'Foo')
100 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo'
101 |     end
102 | 
103 |     it "keeps redirection history" do
104 |       stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'})
105 |       stub_request(:get, 'http://new/resource').to_return(:body => 'Foo')
106 |       r = RestClient::Request.execute(url: 'http://some/resource', method: :get)
107 |       expect(r.body).to eq 'Foo'
108 |       expect(r.history.length).to eq 1
109 |       expect(r.history.fetch(0)).to be_a(RestClient::Response)
110 |       expect(r.history.fetch(0).code).to be 301
111 |     end
112 | 
113 |     it "follows a redirection and keep the parameters" do
114 |       stub_request(:get, 'http://some/resource').with(:headers => {'Accept' => 'application/json'}, :basic_auth => ['foo', 'bar']).to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'})
115 |       stub_request(:get, 'http://new/resource').with(:headers => {'Accept' => 'application/json'}, :basic_auth => ['foo', 'bar']).to_return(:body => 'Foo')
116 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :user => 'foo', :password => 'bar', :headers => {:accept => :json}).body).to eq 'Foo'
117 |     end
118 | 
119 |     it "follows a redirection and keep the cookies" do
120 |       stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Set-Cookie' => 'Foo=Bar', 'Location' => 'http://some/new_resource', })
121 |       stub_request(:get, 'http://some/new_resource').with(:headers => {'Cookie' => 'Foo=Bar'}).to_return(:body => 'Qux')
122 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Qux'
123 |     end
124 | 
125 |     it 'respects cookie domains on redirect' do
126 |       stub_request(:get, 'http://some.example.com/').to_return(:body => '', :status => 301,
127 |         :headers => {'Set-Cookie' => 'Foo=Bar', 'Location' => 'http://new.example.com/', })
128 |       stub_request(:get, 'http://new.example.com/').with(
129 |         :headers => {'Cookie' => 'passedthrough=1'}).to_return(:body => 'Qux')
130 | 
131 |       expect(RestClient::Request.execute(:url => 'http://some.example.com/', :method => :get, cookies: [HTTP::Cookie.new('passedthrough', '1', domain: 'new.example.com', path: '/')]).body).to eq 'Qux'
132 |     end
133 | 
134 |     it "doesn't follow a 301 when the request is a post" do
135 |       net_http_res = res_double(:code => 301)
136 |       response = response_from_res_double(net_http_res, request_double(method: 'post'))
137 | 
138 |       expect {
139 |         response.return!
140 |       }.to raise_error(RestClient::MovedPermanently)
141 |     end
142 | 
143 |     it "doesn't follow a 302 when the request is a post" do
144 |       net_http_res = res_double(:code => 302)
145 |       response = response_from_res_double(net_http_res, request_double(method: 'post'))
146 | 
147 |       expect {
148 |         response.return!
149 |       }.to raise_error(RestClient::Found)
150 |     end
151 | 
152 |     it "doesn't follow a 307 when the request is a post" do
153 |       net_http_res = res_double(:code => 307)
154 |       response = response_from_res_double(net_http_res, request_double(method: 'post'))
155 | 
156 |       expect(response).not_to receive(:follow_redirection)
157 |       expect {
158 |         response.return!
159 |       }.to raise_error(RestClient::TemporaryRedirect)
160 |     end
161 | 
162 |     it "doesn't follow a redirection when the request is a put" do
163 |       net_http_res = res_double(:code => 301)
164 |       response = response_from_res_double(net_http_res, request_double(method: 'put'))
165 |       expect {
166 |         response.return!
167 |       }.to raise_error(RestClient::MovedPermanently)
168 |     end
169 | 
170 |     it "follows a redirection when the request is a post and result is a 303" do
171 |       stub_request(:put, 'http://some/resource').to_return(:body => '', :status => 303, :headers => {'Location' => 'http://new/resource'})
172 |       stub_request(:get, 'http://new/resource').to_return(:body => 'Foo')
173 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :put).body).to eq 'Foo'
174 |     end
175 | 
176 |     it "follows a redirection when the request is a head" do
177 |       stub_request(:head, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'})
178 |       stub_request(:head, 'http://new/resource').to_return(:body => 'Foo')
179 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :head).body).to eq 'Foo'
180 |     end
181 | 
182 |     it "handles redirects with relative paths" do
183 |       stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'index'})
184 |       stub_request(:get, 'http://some/index').to_return(:body => 'Foo')
185 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo'
186 |     end
187 | 
188 |     it "handles redirects with relative path and query string" do
189 |       stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'index?q=1'})
190 |       stub_request(:get, 'http://some/index?q=1').to_return(:body => 'Foo')
191 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo'
192 |     end
193 | 
194 |     it "follow a redirection when the request is a get and the response is in the 30x range" do
195 |       stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'})
196 |       stub_request(:get, 'http://new/resource').to_return(:body => 'Foo')
197 |       expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo'
198 |     end
199 | 
200 |     it "follows no more than 10 redirections before raising error" do
201 |       stub_request(:get, 'http://some/redirect-1').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'})
202 |       stub_request(:get, 'http://some/redirect-2').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'})
203 |       expect {
204 |         RestClient::Request.execute(url: 'http://some/redirect-1', method: :get)
205 |       }.to raise_error(RestClient::MovedPermanently) { |ex|
206 |         ex.response.history.each {|r| expect(r).to be_a(RestClient::Response) }
207 |         expect(ex.response.history.length).to eq 10
208 |       }
209 |       expect(WebMock).to have_requested(:get, 'http://some/redirect-2').times(10)
210 |     end
211 | 
212 |     it "follows no more than max_redirects redirections, if specified" do
213 |       stub_request(:get, 'http://some/redirect-1').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'})
214 |       stub_request(:get, 'http://some/redirect-2').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'})
215 |       expect {
216 |         RestClient::Request.execute(url: 'http://some/redirect-1', method: :get, max_redirects: 5)
217 |       }.to raise_error(RestClient::MovedPermanently) { |ex|
218 |         expect(ex.response.history.length).to eq 5
219 |       }
220 |       expect(WebMock).to have_requested(:get, 'http://some/redirect-2').times(5)
221 |     end
222 | 
223 |     it "allows for manual following of redirects" do
224 |       stub_request(:get, 'http://some/redirect-1').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/resource'})
225 |       stub_request(:get, 'http://some/resource').to_return(:body => 'Qux', :status => 200)
226 | 
227 |       begin
228 |         RestClient::Request.execute(url: 'http://some/redirect-1', method: :get, max_redirects: 0)
229 |       rescue RestClient::MovedPermanently => err
230 |         resp = err.response.follow_redirection
231 |       else
232 |         raise 'notreached'
233 |       end
234 | 
235 |       expect(resp.code).to eq 200
236 |       expect(resp.body).to eq 'Qux'
237 |     end
238 |   end
239 | 
240 |   describe "logging" do
241 |     it "uses the request's logger" do
242 |       stub_request(:get, 'http://some/resource').to_return(body: 'potato', status: 200)
243 | 
244 |       logger = double('logger', '<<' => true)
245 |       request = RestClient::Request.new(url: 'http://some/resource', method: :get, log: logger)
246 | 
247 |       expect(logger).to receive(:<<)
248 | 
249 |       request.execute
250 |     end
251 |   end
252 | end
253 | 


--------------------------------------------------------------------------------
/spec/unit/restclient_spec.rb:
--------------------------------------------------------------------------------
 1 | require_relative '_lib'
 2 | 
 3 | describe RestClient do
 4 |   describe "API" do
 5 |     it "GET" do
 6 |       expect(RestClient::Request).to receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {})
 7 |       RestClient.get('http://some/resource')
 8 |     end
 9 | 
10 |     it "POST" do
11 |       expect(RestClient::Request).to receive(:execute).with(:method => :post, :url => 'http://some/resource', :payload => 'payload', :headers => {})
12 |       RestClient.post('http://some/resource', 'payload')
13 |     end
14 | 
15 |     it "PUT" do
16 |       expect(RestClient::Request).to receive(:execute).with(:method => :put, :url => 'http://some/resource', :payload => 'payload', :headers => {})
17 |       RestClient.put('http://some/resource', 'payload')
18 |     end
19 | 
20 |     it "PATCH" do
21 |       expect(RestClient::Request).to receive(:execute).with(:method => :patch, :url => 'http://some/resource', :payload => 'payload', :headers => {})
22 |       RestClient.patch('http://some/resource', 'payload')
23 |     end
24 | 
25 |     it "DELETE" do
26 |       expect(RestClient::Request).to receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {})
27 |       RestClient.delete('http://some/resource')
28 |     end
29 | 
30 |     it "HEAD" do
31 |       expect(RestClient::Request).to receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {})
32 |       RestClient.head('http://some/resource')
33 |     end
34 | 
35 |     it "OPTIONS" do
36 |       expect(RestClient::Request).to receive(:execute).with(:method => :options, :url => 'http://some/resource', :headers => {})
37 |       RestClient.options('http://some/resource')
38 |     end
39 |   end
40 | 
41 |   describe "logging" do
42 |     after do
43 |       RestClient.log = nil
44 |     end
45 | 
46 |     it "uses << if the log is not a string" do
47 |       log = RestClient.log = []
48 |       expect(log).to receive(:<<).with('xyz')
49 |       RestClient.log << 'xyz'
50 |     end
51 | 
52 |     it "displays the log to stdout" do
53 |       RestClient.log = 'stdout'
54 |       expect(STDOUT).to receive(:puts).with('xyz')
55 |       RestClient.log << 'xyz'
56 |     end
57 | 
58 |     it "displays the log to stderr" do
59 |       RestClient.log = 'stderr'
60 |       expect(STDERR).to receive(:puts).with('xyz')
61 |       RestClient.log << 'xyz'
62 |     end
63 | 
64 |     it "append the log to the requested filename" do
65 |       RestClient.log = '/tmp/restclient.log'
66 |       f = double('file handle')
67 |       expect(File).to receive(:open).with('/tmp/restclient.log', 'a').and_yield(f)
68 |       expect(f).to receive(:puts).with('xyz')
69 |       RestClient.log << 'xyz'
70 |     end
71 |   end
72 | 
73 |   describe 'version' do
74 |     # test that there is a sane version number to avoid accidental 0.0.0 again
75 |     it 'has a version > 2.0.0.alpha, < 3.0' do
76 |       ver = Gem::Version.new(RestClient.version)
77 |       expect(Gem::Requirement.new('> 2.0.0.alpha', '< 3.0')).to be_satisfied_by(ver)
78 |     end
79 |   end
80 | end
81 | 


--------------------------------------------------------------------------------
/spec/unit/utils_spec.rb:
--------------------------------------------------------------------------------
  1 | require_relative '_lib'
  2 | 
  3 | describe RestClient::Utils do
  4 |   describe '.get_encoding_from_headers' do
  5 |     it 'assumes no encoding by default for text' do
  6 |       headers = {:content_type => 'text/plain'}
  7 |       expect(RestClient::Utils.get_encoding_from_headers(headers)).
  8 |         to eq nil
  9 |     end
 10 | 
 11 |     it 'returns nil on failures' do
 12 |       expect(RestClient::Utils.get_encoding_from_headers(
 13 |         {:content_type => 'blah'})).to eq nil
 14 |       expect(RestClient::Utils.get_encoding_from_headers(
 15 |         {})).to eq nil
 16 |       expect(RestClient::Utils.get_encoding_from_headers(
 17 |         {:content_type => 'foo; bar=baz'})).to eq nil
 18 |     end
 19 | 
 20 |     it 'handles various charsets' do
 21 |       expect(RestClient::Utils.get_encoding_from_headers(
 22 |         {:content_type => 'text/plain; charset=UTF-8'})).to eq 'UTF-8'
 23 |       expect(RestClient::Utils.get_encoding_from_headers(
 24 |         {:content_type => 'application/json; charset=ISO-8859-1'})).
 25 |         to eq 'ISO-8859-1'
 26 |       expect(RestClient::Utils.get_encoding_from_headers(
 27 |         {:content_type => 'text/html; charset=windows-1251'})).
 28 |         to eq 'windows-1251'
 29 | 
 30 |       expect(RestClient::Utils.get_encoding_from_headers(
 31 |         {:content_type => 'text/html; charset="UTF-16"'})).
 32 |         to eq 'UTF-16'
 33 |     end
 34 |   end
 35 | 
 36 |   describe '.cgi_parse_header' do
 37 |     it 'parses headers', :unless => RUBY_VERSION.start_with?('2.0') do
 38 |       expect(RestClient::Utils.cgi_parse_header('text/plain')).
 39 |         to eq ['text/plain', {}]
 40 | 
 41 |       expect(RestClient::Utils.cgi_parse_header('text/vnd.just.made.this.up')).
 42 |         to eq ['text/vnd.just.made.this.up', {}]
 43 | 
 44 |       expect(RestClient::Utils.cgi_parse_header('text/plain;charset=us-ascii')).
 45 |         to eq ['text/plain', {'charset' => 'us-ascii'}]
 46 | 
 47 |       expect(RestClient::Utils.cgi_parse_header('text/plain ; charset="us-ascii"')).
 48 |         to eq ['text/plain', {'charset' => 'us-ascii'}]
 49 | 
 50 |       expect(RestClient::Utils.cgi_parse_header(
 51 |         'text/plain ; charset="us-ascii"; another=opt')).
 52 |         to eq ['text/plain', {'charset' => 'us-ascii', 'another' => 'opt'}]
 53 | 
 54 |       expect(RestClient::Utils.cgi_parse_header(
 55 |         'foo/bar; filename="silly.txt"')).
 56 |         to eq ['foo/bar', {'filename' => 'silly.txt'}]
 57 | 
 58 |       expect(RestClient::Utils.cgi_parse_header(
 59 |         'foo/bar; filename="strange;name"')).
 60 |         to eq ['foo/bar', {'filename' => 'strange;name'}]
 61 | 
 62 |       expect(RestClient::Utils.cgi_parse_header(
 63 |         'foo/bar; filename="strange;name";size=123')).to eq \
 64 |         ['foo/bar', {'filename' => 'strange;name', 'size' => '123'}]
 65 | 
 66 |       expect(RestClient::Utils.cgi_parse_header(
 67 |         'foo/bar; name="files"; filename="fo\\"o;bar"')).to eq \
 68 |         ['foo/bar', {'name' => 'files', 'filename' => 'fo"o;bar'}]
 69 |     end
 70 |   end
 71 | 
 72 |   describe '.encode_query_string' do
 73 |     it 'handles simple hashes' do
 74 |       {
 75 |         {foo: 123, bar: 456} => 'foo=123&bar=456',
 76 |         {'foo' => 123, 'bar' => 456} => 'foo=123&bar=456',
 77 |         {foo: 'abc', bar: 'one two'} => 'foo=abc&bar=one+two',
 78 |         {escaped: '1+2=3'} => 'escaped=1%2B2%3D3',
 79 |         {'escaped + key' => 'foo'} => 'escaped+%2B+key=foo',
 80 |       }.each_pair do |input, expected|
 81 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
 82 |       end
 83 |     end
 84 | 
 85 |     it 'handles simple arrays' do
 86 |       {
 87 |         {foo: [1, 2, 3]} => 'foo[]=1&foo[]=2&foo[]=3',
 88 |         {foo: %w{a b c}, bar: [1, 2, 3]} => 'foo[]=a&foo[]=b&foo[]=c&bar[]=1&bar[]=2&bar[]=3',
 89 |         {foo: ['one two', 3]} => 'foo[]=one+two&foo[]=3',
 90 |         {'a+b' => [1,2,3]} => 'a%2Bb[]=1&a%2Bb[]=2&a%2Bb[]=3',
 91 |       }.each_pair do |input, expected|
 92 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
 93 |       end
 94 |     end
 95 | 
 96 |     it 'handles nested hashes' do
 97 |       {
 98 |         {outer: {foo: 123, bar: 456}} => 'outer[foo]=123&outer[bar]=456',
 99 |         {outer: {foo: [1, 2, 3], bar: 'baz'}} => 'outer[foo][]=1&outer[foo][]=2&outer[foo][]=3&outer[bar]=baz',
100 |       }.each_pair do |input, expected|
101 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
102 |       end
103 |     end
104 | 
105 |     it 'handles null and empty values' do
106 |       {
107 |         {string: '', empty: nil, list: [], hash: {}, falsey: false } =>
108 |           'string=&empty&list&hash&falsey=false',
109 |       }.each_pair do |input, expected|
110 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
111 |       end
112 |     end
113 | 
114 |     it 'handles nested nulls' do
115 |       {
116 |         {foo: {string: '', empty: nil}} => 'foo[string]=&foo[empty]',
117 |       }.each_pair do |input, expected|
118 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
119 |       end
120 |     end
121 | 
122 |     it 'handles deep nesting' do
123 |       {
124 |         {coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]} => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3',
125 |       }.each_pair do |input, expected|
126 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
127 |       end
128 |     end
129 | 
130 |     it 'handles multiple fields with the same name using ParamsArray' do
131 |       {
132 |         RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]]) => 'foo=1&foo=2&foo=3',
133 |       }.each_pair do |input, expected|
134 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
135 |       end
136 |     end
137 | 
138 |     it 'handles nested ParamsArrays' do
139 |       {
140 |         {foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])} => 'foo[a]=1&foo[a]=2',
141 |         RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]]) => 'foo[a]=1&foo[a]=2',
142 |       }.each_pair do |input, expected|
143 |         expect(RestClient::Utils.encode_query_string(input)).to eq expected
144 |       end
145 |     end
146 |   end
147 | end
148 | 


--------------------------------------------------------------------------------
/spec/unit/windows/root_certs_spec.rb:
--------------------------------------------------------------------------------
 1 | require_relative '../_lib'
 2 | 
 3 | describe 'RestClient::Windows::RootCerts',
 4 |          :if => RestClient::Platform.windows? do
 5 |   let(:x509_store) { RestClient::Windows::RootCerts.instance.to_a }
 6 | 
 7 |   it 'should return at least one X509 certificate' do
 8 |     expect(x509_store.to_a.size).to be >= 1
 9 |   end
10 | 
11 |   it 'should return an X509 certificate with a subject' do
12 |     x509 = x509_store.first
13 | 
14 |     expect(x509.subject.to_s).to match(/CN=.*/)
15 |   end
16 | 
17 |   it 'should return X509 certificate objects' do
18 |     x509_store.each do |cert|
19 |       expect(cert).to be_a(OpenSSL::X509::Certificate)
20 |     end
21 |   end
22 | end
23 | 


--------------------------------------------------------------------------------