rest-client-2.1.0/0000755000004100000410000000000013531144232013771 5ustar www-datawww-datarest-client-2.1.0/.travis.yml0000644000004100000410000000261213531144232016103 0ustar www-datawww-data# Available ruby versions: http://rubies.travis-ci.org/ language: ruby sudo: false os: - linux # - osx rvm: - "2.1" # latest 2.1.x - "2.2.10" - "2.3.8" - "2.4.6" - "2.5.5" - "2.6.3" - "ruby-head" - "jruby-9.1.9.0" - "jruby-head" cache: bundler script: - bundle exec rake test - bundle exec rake rubocop branches: except: - "readme-edits" before_install: # Install rubygems < 3.0 so that we can support ruby < 2.3 # https://github.com/rubygems/rubygems/issues/2534 - gem install rubygems-update -v '<3' && update_rubygems # bundler installation needed for jruby-head # https://github.com/travis-ci/travis-ci/issues/5861 # stick to bundler 1.x in order to support ruby < 2.3 - gem install bundler -v '~> 1.17' # Travis macOS support is pretty janky. These are some hacks to include tests # only on versions that actually work. We test on macOS because Apple monkey # patches OpenSSL to have different behavior, and we want to ensure that SSL # verification at least is broken in the expected ways on macOS. # (last tested: 2019-08) matrix: # exclude: {} include: # test only a few versions on mac - os: osx rvm: 2.6.3 - os: osx rvm: ruby-head - os: osx rvm: jruby-9.1.9.0 - os: osx rvm: jruby-head allow_failures: - rvm: 'ruby-head' # return results as soon as mandatory versions are done fast_finish: true rest-client-2.1.0/.rspec0000644000004100000410000000003213531144232015101 0ustar www-datawww-data--color --format progress rest-client-2.1.0/README.md0000644000004100000410000007234313531144232015261 0ustar www-datawww-data# REST Client -- simple DSL for accessing HTTP and REST resources [![Gem Downloads](https://img.shields.io/gem/dt/rest-client.svg)](https://rubygems.org/gems/rest-client) [![Build Status](https://travis-ci.org/rest-client/rest-client.svg?branch=master)](https://travis-ci.org/rest-client/rest-client) [![Code Climate](https://codeclimate.com/github/rest-client/rest-client.svg)](https://codeclimate.com/github/rest-client/rest-client) [![Inline docs](http://inch-ci.org/github/rest-client/rest-client.svg?branch=master)](http://www.rubydoc.info/github/rest-client/rest-client/master) A simple HTTP and REST client for Ruby, inspired by the Sinatra's microframework style of specifying actions: get, put, post, delete. * Main page: https://github.com/rest-client/rest-client * Mailing list: https://groups.io/g/rest-client ### New mailing list We have a new email list for announcements, hosted by Groups.io. * Subscribe on the web: https://groups.io/g/rest-client * Subscribe by sending an email: mailto:rest-client+subscribe@groups.io * Open discussion subgroup: https://groups.io/g/rest-client+discuss The old Librelist mailing list is *defunct*, as Librelist appears to be broken and not accepting new mail. The old archives are still up, but have been imported into the new list archives as well. http://librelist.com/browser/rest.client ## Requirements MRI Ruby 2.0 and newer are supported. Alternative interpreters compatible with 2.0+ should work as well. Earlier Ruby versions such as 1.8.7, 1.9.2, and 1.9.3 are no longer supported. These versions no longer have any official support, and do not receive security updates. The rest-client gem depends on these other gems for usage at runtime: * [mime-types](http://rubygems.org/gems/mime-types) * [netrc](http://rubygems.org/gems/netrc) * [http-accept](https://rubygems.org/gems/http-accept) * [http-cookie](https://rubygems.org/gems/http-cookie) There are also several development dependencies. It's recommended to use [bundler](http://bundler.io/) to manage these dependencies for hacking on rest-client. ### Upgrading to rest-client 2.0 from 1.x Users are encouraged to upgrade to rest-client 2.0, which cleans up a number of API warts and wrinkles, making rest-client generally more useful. Usage is largely compatible, so many applications will be able to upgrade with no changes. Overview of significant changes: * requires Ruby >= 2.0 * `RestClient::Response` objects are a subclass of `String` rather than a Frankenstein monster. And `#body` or `#to_s` return a true `String` object. * cleanup of exception classes, including new `RestClient::Exceptions::Timeout` * improvements to handling of redirects: responses and history are properly exposed * major changes to cookie support: cookie jars are used for browser-like behavior throughout * encoding: Content-Type charset response headers are used to automatically set the encoding of the response string * HTTP params: handling of GET/POST params is more consistent and sophisticated for deeply nested hash objects, and `ParamsArray` can be used to pass ordered params * improved proxy support with per-request proxy configuration, plus the ability to disable proxies set by environment variables * default request headers: rest-client sets `Accept: */*` and `User-Agent: rest-client/...` See [history.md](./history.md) for a more complete description of changes. ## Usage: Raw URL Basic usage: ```ruby require 'rest-client' RestClient.get(url, headers={}) RestClient.post(url, payload, headers={}) ``` In the high level helpers, only POST, PATCH, and PUT take a payload argument. To pass a payload with other HTTP verbs or to pass more advanced options, use `RestClient::Request.execute` instead. More detailed examples: ```ruby require 'rest-client' RestClient.get 'http://example.com/resource' RestClient.get 'http://example.com/resource', {params: {id: 50, 'foo' => 'bar'}} RestClient.get 'https://user:password@example.com/private/resource', {accept: :json} RestClient.post 'http://example.com/resource', {param1: 'one', nested: {param2: 'two'}} RestClient.post "http://example.com/resource", {'x' => 1}.to_json, {content_type: :json, accept: :json} RestClient.delete 'http://example.com/resource' >> response = RestClient.get 'http://example.com/resource' => >> response.code => 200 >> response.cookies => {"Foo"=>"BAR", "QUUX"=>"QUUUUX"} >> response.headers => {:content_type=>"text/html; charset=utf-8", :cache_control=>"private" ... } >> response.body => "\n\n\n Example Domain\n\n ..." RestClient.post( url, { :transfer => { :path => '/foo/bar', :owner => 'that_guy', :group => 'those_guys' }, :upload => { :file => File.new(path, 'rb') } }) ``` ## Passing advanced options The top level helper methods like RestClient.get accept a headers hash as their last argument and don't allow passing more complex options. But these helpers are just thin wrappers around `RestClient::Request.execute`. ```ruby RestClient::Request.execute(method: :get, url: 'http://example.com/resource', timeout: 10) RestClient::Request.execute(method: :get, url: 'http://example.com/resource', ssl_ca_file: 'myca.pem', ssl_ciphers: 'AESGCM:!aNULL') ``` You can also use this to pass a payload for HTTP verbs like DELETE, where the `RestClient.delete` helper doesn't accept a payload. ```ruby RestClient::Request.execute(method: :delete, url: 'http://example.com/resource', payload: 'foo', headers: {myheader: 'bar'}) ``` Due to unfortunate choices in the original API, the params used to populate the query string are actually taken out of the headers hash. So if you want to pass both the params hash and more complex options, use the special key `:params` in the headers hash. This design may change in a future major release. ```ruby RestClient::Request.execute(method: :get, url: 'http://example.com/resource', timeout: 10, headers: {params: {foo: 'bar'}}) ➔ GET http://example.com/resource?foo=bar ``` ## Multipart Yeah, that's right! This does multipart sends for you! ```ruby RestClient.post '/data', :myfile => File.new("/path/to/image.jpg", 'rb') ``` This does two things for you: - Auto-detects that you have a File value sends it as multipart - Auto-detects the mime of the file and sets it in the HEAD of the payload for each entry If you are sending params that do not contain a File object but the payload needs to be multipart then: ```ruby RestClient.post '/data', {:foo => 'bar', :multipart => true} ``` ## Usage: ActiveResource-Style ```ruby resource = RestClient::Resource.new 'http://example.com/resource' resource.get private_resource = RestClient::Resource.new 'https://example.com/private/resource', 'user', 'pass' private_resource.put File.read('pic.jpg'), :content_type => 'image/jpg' ``` See RestClient::Resource module docs for details. ## Usage: Resource Nesting ```ruby site = RestClient::Resource.new('http://example.com') site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain' ``` See `RestClient::Resource` docs for details. ## Exceptions (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) - for result codes between `200` and `207`, a `RestClient::Response` will be returned - for result codes `301`, `302` or `307`, the redirection will be followed if the request is a `GET` or a `HEAD` - for result code `303`, the redirection will be followed and the request transformed into a `GET` - for other cases, a `RestClient::ExceptionWithResponse` holding the Response will be raised; a specific exception class will be thrown for known error codes - call `.response` on the exception to get the server's response ```ruby >> RestClient.get 'http://example.com/nonexistent' Exception: RestClient::NotFound: 404 Not Found >> begin RestClient.get 'http://example.com/nonexistent' rescue RestClient::ExceptionWithResponse => e e.response end => ``` ### Other exceptions While most exceptions have been collected under `RestClient::RequestFailed` aka `RestClient::ExceptionWithResponse`, there are a few quirky exceptions that have been kept for backwards compatibility. RestClient will propagate up exceptions like socket errors without modification: ```ruby >> RestClient.get 'http://localhost:12345' Exception: Errno::ECONNREFUSED: Connection refused - connect(2) for "localhost" port 12345 ``` RestClient handles a few specific error cases separately in order to give better error messages. These will hopefully be cleaned up in a future major release. `RestClient::ServerBrokeConnection` is translated from `EOFError` to give a better error message. `RestClient::SSLCertificateNotVerified` is raised when HTTPS validation fails. Other `OpenSSL::SSL::SSLError` errors are raised as is. ### Redirection By default, rest-client will follow HTTP 30x redirection requests. __New in 2.0:__ `RestClient::Response` exposes a `#history` method that returns a list of each response received in a redirection chain. ```ruby >> r = RestClient.get('http://httpbin.org/redirect/2') => # see each response in the redirect chain >> r.history => [, ] # see each requested URL >> r.request.url => "http://httpbin.org/get" >> r.history.map {|x| x.request.url} => ["http://httpbin.org/redirect/2", "http://httpbin.org/relative-redirect/1"] ``` #### Manually following redirection To disable automatic redirection, set `:max_redirects => 0`. __New in 2.0:__ Prior versions of rest-client would raise `RestClient::MaxRedirectsReached`, with no easy way to access the server's response. In 2.0, rest-client raises the normal `RestClient::ExceptionWithResponse` as it would with any other non-HTTP-20x response. ```ruby >> RestClient::Request.execute(method: :get, url: 'http://httpbin.org/redirect/1') => RestClient::Response 200 "{\n "args":..." >> RestClient::Request.execute(method: :get, url: 'http://httpbin.org/redirect/1', max_redirects: 0) RestClient::Found: 302 Found ``` To manually follow redirection, you can call `Response#follow_redirection`. Or you could of course inspect the result and choose custom behavior. ```ruby >> RestClient::Request.execute(method: :get, url: 'http://httpbin.org/redirect/1', max_redirects: 0) RestClient::Found: 302 Found >> begin RestClient::Request.execute(method: :get, url: 'http://httpbin.org/redirect/1', max_redirects: 0) rescue RestClient::ExceptionWithResponse => err end >> err => # >> err.response => RestClient::Response 302 "> err.response.headers[:location] => "/get" >> err.response.follow_redirection => RestClient::Response 200 "{\n "args":..." ``` ## Result handling The result of a `RestClient::Request` is a `RestClient::Response` object. __New in 2.0:__ `RestClient::Response` objects are now a subclass of `String`. Previously, they were a real String object with response functionality mixed in, which was very confusing to work with. Response objects have several useful methods. (See the class rdoc for more details.) - `Response#code`: The HTTP response code - `Response#body`: The response body as a string. (AKA .to_s) - `Response#headers`: A hash of HTTP response headers - `Response#raw_headers`: A hash of HTTP response headers as unprocessed arrays - `Response#cookies`: A hash of HTTP cookies set by the server - `Response#cookie_jar`: New in 1.8 An HTTP::CookieJar of cookies - `Response#request`: The RestClient::Request object used to make the request - `Response#history`: New in 2.0 If redirection was followed, a list of prior Response objects ```ruby RestClient.get('http://example.com') ➔ begin RestClient.get('http://example.com/notfound') rescue RestClient::ExceptionWithResponse => err err.response end ➔ ``` ### Response callbacks, error handling A block can be passed to the RestClient method. This block will then be called with the Response. Response.return! can be called to invoke the default response's behavior. ```ruby # Don't raise exceptions but return the response >> RestClient.get('http://example.com/nonexistent') {|response, request, result| response } => ``` ```ruby # Manage a specific error code RestClient.get('http://example.com/resource') { |response, request, result, &block| case response.code when 200 p "It worked !" response when 423 raise SomeCustomExceptionIfYouWant else response.return!(&block) end } ``` But note that it may be more straightforward to use exceptions to handle different HTTP error response cases: ```ruby begin resp = RestClient.get('http://example.com/resource') rescue RestClient::Unauthorized, RestClient::Forbidden => err puts 'Access denied' return err.response rescue RestClient::ImATeapot => err puts 'The server is a teapot! # RFC 2324' return err.response else puts 'It worked!' return resp end ``` For GET and HEAD requests, rest-client automatically follows redirection. For other HTTP verbs, call `.follow_redirection` on the response object (works both in block form and in exception form). ```ruby # Follow redirections for all request types and not only for get and head # RFC : "If the 301, 302 or 307 status code is received in response to a request other than GET or HEAD, # the user agent MUST NOT automatically redirect the request unless it can be confirmed by the user, # since this might change the conditions under which the request was issued." # block style RestClient.post('http://example.com/redirect', 'body') { |response, request, result| case response.code when 301, 302, 307 response.follow_redirection else response.return! end } # exception style by explicit classes begin RestClient.post('http://example.com/redirect', 'body') rescue RestClient::MovedPermanently, RestClient::Found, RestClient::TemporaryRedirect => err err.response.follow_redirection end # exception style by response code begin RestClient.post('http://example.com/redirect', 'body') rescue RestClient::ExceptionWithResponse => err case err.http_code when 301, 302, 307 err.response.follow_redirection else raise end end ``` ## Non-normalized URIs If you need to normalize URIs, e.g. to work with International Resource Identifiers (IRIs), use the addressable gem (http://addressable.rubyforge.org/api/) in your code: ```ruby require 'addressable/uri' RestClient.get(Addressable::URI.parse("http://www.詹姆斯.com/").normalize.to_str) ``` ## Lower-level access For cases not covered by the general API, you can use the `RestClient::Request` class, which provides a lower-level API. You can: - specify ssl parameters - override cookies - manually handle the response (e.g. to operate on it as a stream rather than reading it all into memory) See `RestClient::Request`'s documentation for more information. ### Streaming request payload RestClient will try to stream any file-like payload rather than reading it into memory. This happens through `RestClient::Payload::Streamed`, which is automatically called internally by `RestClient::Payload.generate` on anything with a `read` method. ```ruby >> r = RestClient.put('http://httpbin.org/put', File.open('/tmp/foo.txt', 'r'), content_type: 'text/plain') => ``` In Multipart requests, RestClient will also stream file handles passed as Hash (or __new in 2.1__ ParamsArray). ```ruby >> r = RestClient.put('http://httpbin.org/put', {file_a: File.open('a.txt', 'r'), file_b: File.open('b.txt', 'r')}) => # received by server as two file uploads with multipart/form-data >> JSON.parse(r)['files'].keys => ['file_a', 'file_b'] ``` ### Streaming responses Normally, when you use `RestClient.get` or the lower level `RestClient::Request.execute method: :get` to retrieve data, the entire response is buffered in memory and returned as the response to the call. However, if you are retrieving a large amount of data, for example a Docker image, an iso, or any other large file, you may want to stream the response directly to disk rather than loading it in memory. If you have a very large file, it may become *impossible* to load it into memory. There are two main ways to do this: #### `raw_response`, saves into Tempfile If you pass `raw_response: true` to `RestClient::Request.execute`, it will save the response body to a temporary file (using `Tempfile`) and return a `RestClient::RawResponse` object rather than a `RestClient::Response`. Note that the tempfile created by `Tempfile.new` will be in `Dir.tmpdir` (usually `/tmp/`), which you can override to store temporary files in a different location. This file will be unlinked when it is dereferenced. If logging is enabled, this will also print download progress. __New in 2.1:__ Customize the interval with `:stream_log_percent` (defaults to 10 for printing a message every 10% complete). For example: ```ruby >> raw = RestClient::Request.execute( method: :get, url: 'http://releases.ubuntu.com/16.04.2/ubuntu-16.04.2-desktop-amd64.iso', raw_response: true) => , @request=> >> raw.file.size => 1554186240 >> raw.file.path => "/tmp/rest-client.20170522-5346-1pptjm1" raw.file.path => "/tmp/rest-client.20170522-5346-1pptjm1" >> require 'digest/sha1' >> Digest::SHA1.file(raw.file.path).hexdigest => "4375b73e3a1aa305a36320ffd7484682922262b3" ``` #### `block_response`, receives raw Net::HTTPResponse If you want to stream the data from the response to a file as it comes, rather than entirely in memory, you can also pass `RestClient::Request.execute` a parameter `:block_response` to which you pass a block/proc. This block receives the raw unmodified Net::HTTPResponse object from Net::HTTP, which you can use to stream directly to a file as each chunk is received. Note that this bypasses all the usual HTTP status code handling, so you will want to do you own checking for HTTP 20x response codes, redirects, etc. The following is an example: ````ruby File.open('/some/output/file', 'w') {|f| block = proc { |response| response.read_body do |chunk| f.write chunk end } RestClient::Request.execute(method: :get, url: 'http://example.com/some/really/big/file.img', block_response: block) } ```` ## Shell The restclient shell command gives an IRB session with RestClient already loaded: ```ruby $ restclient >> RestClient.get 'http://example.com' ``` Specify a URL argument for get/post/put/delete on that resource: ```ruby $ restclient http://example.com >> put '/resource', 'data' ``` Add a user and password for authenticated resources: ```ruby $ restclient https://example.com user pass >> delete '/private/resource' ``` Create ~/.restclient for named sessions: ```ruby sinatra: url: http://localhost:4567 rack: url: http://localhost:9292 private_site: url: http://example.com username: user password: pass ``` Then invoke: ```ruby $ restclient private_site ``` Use as a one-off, curl-style: ```ruby $ restclient get http://example.com/resource > output_body $ restclient put http://example.com/resource < input_body ``` ## Logging To enable logging globally you can: - set RestClient.log with a Ruby Logger ```ruby RestClient.log = STDOUT ``` - or set an environment variable to avoid modifying the code (in this case you can use a file name, "stdout" or "stderr"): ```ruby $ RESTCLIENT_LOG=stdout path/to/my/program ``` You can also set individual loggers when instantiating a Resource or making an individual request: ```ruby resource = RestClient::Resource.new 'http://example.com/resource', log: Logger.new(STDOUT) ``` ```ruby RestClient::Request.execute(method: :get, url: 'http://example.com/foo', log: Logger.new(STDERR)) ``` All options produce logs like this: ```ruby RestClient.get "http://some/resource" # => 200 OK | text/html 250 bytes RestClient.put "http://some/resource", "payload" # => 401 Unauthorized | application/xml 340 bytes ``` Note that these logs are valid Ruby, so you can paste them into the `restclient` shell or a script to replay your sequence of rest calls. ## Proxy All calls to RestClient, including Resources, will use the proxy specified by `RestClient.proxy`: ```ruby RestClient.proxy = "http://proxy.example.com/" RestClient.get "http://some/resource" # => response from some/resource as proxied through proxy.example.com ``` Often the proxy URL is set in an environment variable, so you can do this to use whatever proxy the system is configured to use: ```ruby RestClient.proxy = ENV['http_proxy'] ``` __New in 2.0:__ Specify a per-request proxy by passing the :proxy option to RestClient::Request. This will override any proxies set by environment variable or by the global `RestClient.proxy` value. ```ruby RestClient::Request.execute(method: :get, url: 'http://example.com', proxy: 'http://proxy.example.com') # => single request proxied through the proxy ``` This can be used to disable the use of a proxy for a particular request. ```ruby RestClient.proxy = "http://proxy.example.com/" RestClient::Request.execute(method: :get, url: 'http://example.com', proxy: nil) # => single request sent without a proxy ``` ## Query parameters Rest-client can render a hash as HTTP query parameters for GET/HEAD/DELETE requests or as HTTP post data in `x-www-form-urlencoded` format for POST requests. __New in 2.0:__ Even though there is no standard specifying how this should work, rest-client follows a similar convention to the one used by Rack / Rails servers for handling arrays, nested hashes, and null values. The implementation in [./lib/rest-client/utils.rb](RestClient::Utils.encode_query_string) closely follows [Rack::Utils.build_nested_query](http://www.rubydoc.info/gems/rack/Rack/Utils#build_nested_query-class_method), but treats empty arrays and hashes as `nil`. (Rack drops them entirely, which is confusing behavior.) If you don't like this behavior and want more control, just serialize params yourself (e.g. with `URI.encode_www_form`) and add the query string to the URL directly for GET parameters or pass the payload as a string for POST requests. Basic GET params: ```ruby RestClient.get('https://httpbin.org/get', params: {foo: 'bar', baz: 'qux'}) # GET "https://httpbin.org/get?foo=bar&baz=qux" ``` Basic `x-www-form-urlencoded` POST params: ```ruby >> r = RestClient.post('https://httpbin.org/post', {foo: 'bar', baz: 'qux'}) # POST "https://httpbin.org/post", data: "foo=bar&baz=qux" => >> JSON.parse(r.body) => {"args"=>{}, "data"=>"", "files"=>{}, "form"=>{"baz"=>"qux", "foo"=>"bar"}, "headers"=> {"Accept"=>"*/*", "Accept-Encoding"=>"gzip, deflate", "Content-Length"=>"15", "Content-Type"=>"application/x-www-form-urlencoded", "Host"=>"httpbin.org"}, "json"=>nil, "url"=>"https://httpbin.org/post"} ``` JSON payload: rest-client does not speak JSON natively, so serialize your payload to a string before passing it to rest-client. ```ruby >> payload = {'name' => 'newrepo', 'description': 'A new repo'} >> RestClient.post('https://api.github.com/user/repos', payload.to_json, content_type: :json) => ``` Advanced GET params (arrays): ```ruby >> r = RestClient.get('https://http-params.herokuapp.com/get', params: {foo: [1,2,3]}) # GET "https://http-params.herokuapp.com/get?foo[]=1&foo[]=2&foo[]=3" => >> puts r.body query_string: "foo[]=1&foo[]=2&foo[]=3" decoded: "foo[]=1&foo[]=2&foo[]=3" GET: {"foo"=>["1", "2", "3"]} ``` Advanced GET params (nested hashes): ```ruby >> r = RestClient.get('https://http-params.herokuapp.com/get', params: {outer: {foo: 123, bar: 456}}) # GET "https://http-params.herokuapp.com/get?outer[foo]=123&outer[bar]=456" => >> puts r.body ... query_string: "outer[foo]=123&outer[bar]=456" decoded: "outer[foo]=123&outer[bar]=456" GET: {"outer"=>{"foo"=>"123", "bar"=>"456"}} ``` __New in 2.0:__ The new `RestClient::ParamsArray` class allows callers to provide ordering even to structured parameters. This is useful for unusual cases where the server treats the order of parameters as significant or you want to pass a particular key multiple times. Multiple fields with the same name using ParamsArray: ```ruby >> RestClient.get('https://httpbin.org/get', params: RestClient::ParamsArray.new([[:foo, 1], [:foo, 2]])) # GET "https://httpbin.org/get?foo=1&foo=2" ``` Nested ParamsArray: ```ruby >> RestClient.get('https://httpbin.org/get', params: {foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])}) # GET "https://httpbin.org/get?foo[a]=1&foo[a]=2" ``` ## Headers Request headers can be set by passing a ruby hash containing keys and values representing header names and values: ```ruby # GET request with modified headers RestClient.get 'http://example.com/resource', {:Authorization => 'Bearer cT0febFoD5lxAlNAXHo6g'} # POST request with modified headers RestClient.post 'http://example.com/resource', {:foo => 'bar', :baz => 'qux'}, {:Authorization => 'Bearer cT0febFoD5lxAlNAXHo6g'} # DELETE request with modified headers RestClient.delete 'http://example.com/resource', {:Authorization => 'Bearer cT0febFoD5lxAlNAXHo6g'} ``` ## Timeouts By default the timeout for a request is 60 seconds. Timeouts for your request can be adjusted by setting the `timeout:` to the number of seconds that you would like the request to wait. Setting `timeout:` will override both `read_timeout:` and `open_timeout:`. ```ruby RestClient::Request.execute(method: :get, url: 'http://example.com/resource', timeout: 120) ``` Additionally, you can set `read_timeout:` and `open_timeout:` separately. ```ruby RestClient::Request.execute(method: :get, url: 'http://example.com/resource', read_timeout: 120, open_timeout: 240) ``` ## Cookies Request and Response objects know about HTTP cookies, and will automatically extract and set headers for them as needed: ```ruby response = RestClient.get 'http://example.com/action_which_sets_session_id' response.cookies # => {"_applicatioN_session_id" => "1234"} response2 = RestClient.post( 'http://localhost:3000/', {:param1 => "foo"}, {:cookies => {:session_id => "1234"}} ) # ...response body ``` ### Full cookie jar support (new in 1.8) The original cookie implementation was very naive and ignored most of the cookie RFC standards. __New in 1.8__: An HTTP::CookieJar of cookies Response objects now carry a cookie_jar method that exposes an HTTP::CookieJar of cookies, which supports full standards compliant behavior. ## SSL/TLS support Various options are supported for configuring rest-client's TLS settings. By default, rest-client will verify certificates using the system's CA store on all platforms. (This is intended to be similar to how browsers behave.) You can specify an :ssl_ca_file, :ssl_ca_path, or :ssl_cert_store to customize the certificate authorities accepted. ### SSL Client Certificates ```ruby RestClient::Resource.new( 'https://example.com', :ssl_client_cert => OpenSSL::X509::Certificate.new(File.read("cert.pem")), :ssl_client_key => OpenSSL::PKey::RSA.new(File.read("key.pem"), "passphrase, if any"), :ssl_ca_file => "ca_certificate.pem", :verify_ssl => OpenSSL::SSL::VERIFY_PEER ).get ``` Self-signed certificates can be generated with the openssl command-line tool. ## Hook RestClient.add_before_execution_proc add a Proc to be called before each execution. It's handy if you need direct access to the HTTP request. Example: ```ruby # Add oauth support using the oauth gem require 'oauth' access_token = ... RestClient.add_before_execution_proc do |req, params| access_token.sign! req end RestClient.get 'http://example.com' ``` ## More Need caching, more advanced logging or any ability provided by Rack middleware? Have a look at rest-client-components: http://github.com/crohr/rest-client-components ## Credits | | | |-------------------------|---------------------------------------------------------| | **REST Client Team** | Andy Brody | | **Creator** | Adam Wiggins | | **Maintainers Emeriti** | Lawrence Leonard Gilbert, Matthew Manning, Julien Kirch | | **Major contributions** | Blake Mizerany, Julien Kirch | A great many generous folks have contributed features and patches. See AUTHORS for the full list. ## Legal Released under the MIT License: https://opensource.org/licenses/MIT Photo of the International Space Station was produced by NASA and is in the public domain. Code for reading Windows root certificate store derived from work by Puppet; used under terms of the Apache License, Version 2.0. rest-client-2.1.0/bin/0000755000004100000410000000000013531144232014541 5ustar www-datawww-datarest-client-2.1.0/bin/restclient0000755000004100000410000000351713531144232016651 0ustar www-datawww-data#!/usr/bin/env ruby $LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib" require 'rubygems' require 'restclient' require 'yaml' def usage(why = nil) puts "failed for reason: #{why}" if why puts "usage: restclient [get|put|post|delete] url|name [username] [password]" puts " The verb is optional, if you leave it off you'll get an interactive shell." puts " put and post both take the input body on stdin." exit(1) end POSSIBLE_VERBS = ['get', 'put', 'post', 'delete'] if POSSIBLE_VERBS.include? ARGV.first @verb = ARGV.shift else @verb = nil end @url = ARGV.shift || 'http://localhost:4567' config = YAML.load(File.read(ENV['HOME'] + "/.restclient")) rescue {} if (c = config[@url]) @url, @username, @password = [c['url'], c['username'], c['password']] else @url, @username, @password = [@url, * ARGV] end usage("invalid url '#{@url}") unless @url =~ /^https?/ usage("too few args") unless ARGV.size < 3 def r @r ||= RestClient::Resource.new(@url, @username, @password) end r # force rc to load if @verb begin if %w( put post ).include? @verb puts r.send(@verb, STDIN.read) else puts r.send(@verb) end exit 0 rescue RestClient::Exception => e puts e.response.body if e.respond_to?(:response) && e.response raise end end POSSIBLE_VERBS.each do |m| define_method(m.to_sym) do |path, *args, &b| r[path].public_send(m.to_sym, *args, &b) end end def method_missing(s, * args, & b) if POSSIBLE_VERBS.include? s begin r.send(s, *args, & b) rescue RestClient::RequestFailed => e print STDERR, e.response.body raise e end else super end end require 'irb' require 'irb/completion' if File.exist? ".irbrc" ENV['IRBRC'] = ".irbrc" end rcfile = File.expand_path("~/.restclientrc") if File.exist?(rcfile) load(rcfile) end ARGV.clear IRB.start exit! rest-client-2.1.0/.rubocop-disables.yml0000644000004100000410000001744613531144232020043 0ustar www-datawww-data# This configuration was generated by `rubocop --auto-gen-config` # on 2014-07-08 08:57:44 +0000 using RuboCop version 0.24.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. # TODO # Offense count: 1 # Cop supports --auto-correct. Lint/StringConversionInInterpolation: Enabled: false # Tests only # Offense count: 16 # Cop supports --auto-correct. Lint/UnusedBlockArgument: Enabled: false Security/Eval: Exclude: - rest-client.windows.gemspec Lint/HandleExceptions: Exclude: - lib/restclient/utils.rb Lint/UselessAccessModifier: Exclude: - lib/restclient/windows/root_certs.rb # Offense count: 4 # Cop supports --auto-correct. Style/Alias: Enabled: false # TODO # Offense count: 3 # Cop supports --auto-correct. Style/AndOr: Enabled: false # TODO # Offense count: 3 # Cop supports --auto-correct. Style/BlockDelimiters: Enabled: false # Offense count: 48 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/BracesAroundHashParameters: Enabled: false # Offense count: 1 Naming/ClassAndModuleCamelCase: Exclude: - lib/restclient/windows/root_certs.rb # Offense count: 2 # Configuration parameters: EnforcedStyle, SupportedStyles. Style/ClassAndModuleChildren: Enabled: false # TODO? # Offense count: 14 Metrics/AbcSize: Max: 75 # TODO? Metrics/MethodLength: Max: 66 # TODO? # Offense count: 4 Metrics/PerceivedComplexity: Max: 24 # Offense count: 1 # Configuration parameters: CountComments. Metrics/ClassLength: Max: 411 # TODO # Offense count: 5 Style/ClassVars: Enabled: false # TODO # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: PreferredMethods. Style/CollectionMethods: Enabled: false # TODO # Offense count: 4 # Cop supports --auto-correct. Style/ColonMethodCall: Enabled: false Style/ConditionalAssignment: EnforcedStyle: assign_inside_condition # Offense count: 2 Naming/ConstantName: Enabled: false # TODO: eh? # Offense count: 4 Metrics/CyclomaticComplexity: Max: 22 Style/PreferredHashMethods: EnforcedStyle: verbose # TODO: docs # Offense count: 17 Style/Documentation: Enabled: false # Offense count: 9 # Configuration parameters: EnforcedStyle, SupportedStyles. Layout/DotPosition: Enabled: false # Offense count: 1 Style/DoubleNegation: Enabled: false # TODO # Offense count: 2 Style/EachWithObject: Enabled: false # Offense count: 5 # Cop supports --auto-correct. Layout/EmptyLines: Enabled: false # Offense count: 11 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Layout/EmptyLinesAroundClassBody: Enabled: false # Offense count: 1 # Cop supports --auto-correct. Layout/EmptyLinesAroundMethodBody: Enabled: false # Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Layout/EmptyLinesAroundModuleBody: Enabled: false Layout/EmptyLinesAroundExceptionHandlingKeywords: Enabled: false # Offense count: 31 # Configuration parameters: EnforcedStyle, SupportedStyles. Style/Encoding: Enabled: false Naming/FileName: Exclude: - lib/rest-client.rb # Offense count: 3 # Configuration parameters: EnforcedStyle, SupportedStyles. Style/FormatString: Enabled: false # TODO: enable # Cop supports --auto-correct. # Configuration parameters: SupportedStyles. Style/HashSyntax: Enabled: false # NOTABUG # Offense count: 8 # Configuration parameters: MaxLineLength. Style/IfUnlessModifier: Enabled: false Layout/IndentFirstHashElement: Exclude: - 'spec/**/*.rb' # NOTABUG # Offense count: 19 Style/Lambda: Enabled: false # TODO # Offense count: 14 # Cop supports --auto-correct. Layout/LeadingCommentSpace: Enabled: false Metrics/LineLength: Exclude: - 'spec/**/*.rb' - 'Rakefile' # TODO # Offense count: 28 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/MethodDefParentheses: Enabled: false # TODO # Offense count: 1 Style/ModuleFunction: Enabled: false # Offense count: 4 # Configuration parameters: EnforcedStyle, SupportedStyles. Style/Next: Enabled: false # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: IncludeSemanticChanges. Style/NonNilCheck: Enabled: false # TODO: exclude # Offense count: 1 # Cop supports --auto-correct. Style/Not: Enabled: false # Offense count: 2 # Cop supports --auto-correct. Style/NumericLiterals: MinDigits: 11 # TODO? # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: AllowSafeAssignment. Style/ParenthesesAroundCondition: Enabled: false # Offense count: 8 # Cop supports --auto-correct. # Configuration parameters: PreferredDelimiters. Style/PercentLiteralDelimiters: PreferredDelimiters: '%w': '{}' '%W': '{}' '%Q': '{}' Exclude: - 'bin/restclient' # Offense count: 3 # Configuration parameters: NamePrefixBlacklist. Naming/PredicateName: Enabled: false # TODO: configure # Offense count: 3 # Configuration parameters: EnforcedStyle, SupportedStyles. Style/RaiseArgs: Enabled: false # TODO # Offense count: 1 # Cop supports --auto-correct. Style/RedundantBegin: Enabled: false # Offense count: 2 # Cop supports --auto-correct. Style/RedundantSelf: Enabled: false # Offense count: 1 Style/RescueModifier: Enabled: false Exclude: - 'bin/restclient' # TODO: configure # Offense count: 12 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/SignalException: Enabled: false # TODO # Offense count: 2 # Cop supports --auto-correct. Layout/SpaceAfterNot: Enabled: false # Offense count: 19 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Layout/SpaceAroundEqualsInParameterDefault: Enabled: false # Offense count: 20 # Cop supports --auto-correct. Layout/SpaceAroundOperators: Enabled: false # Offense count: 9 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Layout/SpaceBeforeBlockBraces: Enabled: false Layout/EmptyLinesAroundBlockBody: Enabled: false # Offense count: 37 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. Layout/SpaceInsideBlockBraces: Enabled: false # Offense count: 181 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SupportedStyles. Layout/SpaceInsideHashLiteralBraces: Enabled: false # TODO # Offense count: 9 # Cop supports --auto-correct. Layout/SpaceInsideParens: Enabled: false # Offense count: 414 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/StringLiterals: Enabled: false Style/TrailingCommaInArrayLiteral: EnforcedStyleForMultiline: consistent_comma Style/TrailingCommaInHashLiteral: EnforcedStyleForMultiline: consistent_comma Style/TrailingCommaInArguments: Enabled: false # TODO: configure # Offense count: 1 # Cop supports --auto-correct. # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, Whitelist. Style/TrivialAccessors: Enabled: false Exclude: ['lib/restclient/payload.rb'] # TODO? # Offense count: 3 Style/UnlessElse: Enabled: false # TODO? # Offense count: 6 # Cop supports --auto-correct. Style/UnneededPercentQ: Enabled: false # Offense count: 5 # Cop supports --auto-correct. Style/WordArray: MinSize: 4 # TODO? # Offense count: 5 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. Style/BarePercentLiterals: Enabled: false Style/RescueStandardError: Exclude: - 'bin/restclient' - 'lib/restclient/windows/root_certs.rb' rest-client-2.1.0/spec/0000755000004100000410000000000013531144232014723 5ustar www-datawww-datarest-client-2.1.0/spec/helpers.rb0000644000004100000410000000315713531144232016720 0ustar www-datawww-datarequire 'uri' module Helpers # @param [Hash] opts A hash of methods, passed directly to the double # definition. Use this to stub other required methods. # # @return double for Net::HTTPResponse def res_double(opts={}) instance_double('Net::HTTPResponse', {to_hash: {}, body: 'response body'}.merge(opts)) end # Given a Net::HTTPResponse or double and a Request or double, create a # RestClient::Response object. # # @param net_http_res_double an rspec double for Net::HTTPResponse # @param request A RestClient::Request or rspec double # # @return [RestClient::Response] # def response_from_res_double(net_http_res_double, request=nil, duration: 1) request ||= request_double() start_time = Time.now - duration response = RestClient::Response.create(net_http_res_double.body, net_http_res_double, request, start_time) # mock duration to ensure it gets the value we expect allow(response).to receive(:duration).and_return(duration) response end # Redirect stderr to a string for the duration of the passed block. def fake_stderr original_stderr = $stderr $stderr = StringIO.new yield $stderr.string ensure $stderr = original_stderr end # Create a double for RestClient::Request def request_double(url: 'http://example.com', method: 'get') instance_double('RestClient::Request', url: url, uri: URI.parse(url), method: method, user: nil, password: nil, cookie_jar: HTTP::CookieJar.new, redirection_history: nil, args: {url: url, method: method}) end def test_image_path File.dirname(__FILE__) + "/ISS.jpg" end end rest-client-2.1.0/spec/ISS.jpg0000644000004100000410000021541713531144232016075 0ustar www-datawww-dataJFIF,,BExifII* z (1 2iNIKON CORPORATIONNIKON D2Xs,,GIMP 2.8.162017:05:28 01:43:41'"'d0221     4949490100T*2    2010:02:20 01:34:262010:02:20 01:34:26Έ@B`e@B  ASCIINASA S/N 1147 87.8F http://ns.adobe.com/xap/1.0/ 3072 2098 8, 8, 8 Uncompressed RGB NIKON CORPORATION NIKON D2Xs Top-left 3 300.0000 300.0000 Planar format Inch Adobe Photoshop CS4 Windows 2010:02:21 02:06:14 1/500 sec. f/10.0 Manual 100 Exif Version 2.21 2010:02:20 01:34:26 2010:02:20 01:34:26 8.97 EV (1/499 sec.) 6.64 EV (f/10.0) 0.00 EV 3.00 EV (f/2.8) 4294967295.0 m Pattern Unknown 17.0 mm NASA S/N 1147 87.8F 49 49 49 FlashPix Version 1.0 sRGB 3072 2098 One-chip color area sensor DSC Directly photographed Normal process Manual exposure Auto white balance 1 25 Standard Normal Soft Normal Normal Unknown C      C  T k #b@@&`̉a63 jhq @ @H @L4F " 1K ` d  q0l$@ R@@f 0 h &0` c!&iH6@`@l 0#l 8&$) L6r cH B00$͠0 Fa `aq1`ABPLJh %&! h$@!@"LhD`H600i"@FB@4ɠ54 @h0  &l&A Ĉ&@C 1` FĦ@i @8LMLHd#l`l$$Fl`m%5m)aHX4l4I@a  C & 60`bD H060J`f 4 R 0р$A2h1(d%@h (Қ!a5@@0" 0A3lQD0f"6@!RI@ iM(D0 `L$``L%0 @j D3R I! тS 4#R!q!( DLI"IA0 $(&iLP9(i 0JH &`,C F$!,6 58f3 m dn)L4@A i(4@dD i@ͣ&$nI:qiA  0hm22[JbH!Ma iJda 0$dѠ "2SF4`$iH[ 3SA+8?8KdIlm&l<']W6acP!"DXFԒL4j@IMN)H 0TF q]rQ]OӞNmi~+'nddg*~2AP@!&E!N5LL Sj(WvǮhXM! A@Tҭae.fMzSdKME 6(sMc3I 4RZY#Lihetӟd\qf_.ܚQ|jX6rpzo6b˹ܣOcMLeb4j 4tIB,q6ҥ*rZ-F#J 8F2B%7#'(Re![Pk.VC"/U*߸ѱϢ5Ij\,COFR8ӄY*q6e*eT3Qў5$ԑ UVȖmR6Fm.q"-6db% cN.FR# &N&5KR(Ϣm4Zͮ. N&̢ܓRfQbIQk/45>a[]}B#)-(*Ȯe 'UךGBb)iq]Ꚉʅ+ofQDyqnŬ#)dJM-ZeiaMp:\9pHm-N+M$FTge mr2R#IyoKܺ:y>Ӧ$ܢYcєꬕ ӋIq]AZ/eFʮĵY'7u"nII((=ڮ.RP+Ӄ]FJ,\[gũ&%fkqgO$kx+nUvA,XUum UA'48J5&HHn0.'Dg2Q-mP۔Z@XmI8]}Ϊڢ"/\.5F޸q_rj]ȓ\Y\_u R반mpfq!Jބ# N[WRNzj8%NeVfUr&Nc)0(s2=|o/V~?o)oȮuf<r2Ʌ8>6ne@:gg W_S{ߌlɨ@e*v28=-I-\fW՗1U˪#~}Juv3(t9?Ի.A3^ENE;C/3Qu-IW[KrOW46 LKތd-}?4z(V;kQnkn8V՘\Y.ΪضVRRnE̪P)ٛۊ]s˦5DK+oH(6WY]Kލs<-mlN@^R[u6M=zD$g24ǔ`]W%3Pw|ܞWUNΛZ`[Rԛ_͉BΛW7`t]}.JMiˮ-:ڔ^S4K΃詪jϦ⛡_IXkˌG~ꞃrڃ~- kS~zv2U[hlʑ0m^=]u[8n| 3+M^7:9RМNDgϡiCI9͉űG;z/:k)5gU5U9=޻p?{=>7ӱsu4=&BF Zў%@G%Bl6TS;KN]wlo;ԣs6zMu(48 S#:+d+szz5_q-='cwx7WKѢqUe4'[ t]gAӳËTkƌr{S.n͵;):̢tVyLS}uL3};}nfkn8'qz8w-#+&_3ӏ8t2WWqʉHžR-*_;6n9}݃&_Aԛf鞛]EvevUuN.-2^Vg/>&Ԕk*]z8YveMMؙW/NyT2mhjK}[_ϯS <]}ՙtGIxVJ[fN~E;r"͞1^Wߠ;\^S{]?xkEJYBs#n{^'Wl;*jȱ81(tYg1]t++^mȄty6]}1&2h_8:˺Yy[]֫]tZrW=!os~"mWaXXy?Km9ȼu*ϦV9O뾻W]jVQK%r7uEyEkptUL9܃Kq8 &-:<{9WڦWkRgB3:qZ5/kʹ鼗k~g={Qqgf!4Y wEzz:r:{ظ?ĸ^걹6t]y9۽m(=.=UU<<Fm} fG6hu1uYR Jꭅm;wO[n[n}%Th3hfJ5r#(s\=-6 #J$*۪8(IJg4eLn3#7ƨ쭫*rQqIT뻯q;64iQv?O˻)>Lls-J= ӟ qL&USɶ}vV_Iɮ^RÛ[o=s[,4ubʪoa:&Z;O> 6R~ 6Iʄٜaʽ^M}eY:Fv+ssL4sYCsEMeaz\C[v/ϛ 49dيkFi[ml4olY]Mh̡+z:+8̄'63(TZ\gMW]Q(be~2pv[|\7C.6Ijlܞ,v.y;u\k2j >oO!,۱wd8҉s~g^bM-jͦ/W2NNr +WD *P'Uv]WmsIȌOl|V''iMƫv]Sn>ʮd{^y”%ŮfⲦs'̈ԐEWޖd;!yDO?=FFDr*T Yٟ?l9Ԝg'IӋ A t0&c< S&<"|Jc圳o%0|圣nY;''ARPLv?é3,x1cB o̯S"s}ldݐLJ(+t}C"t/lz `Nf2\Yys,Y1?9Fv̔``qΐ;|ċg!(2wdr?_ߜgfs_ <|ͷͧ8gTD@r LٍؕM ̧ 4ML8P9)ΦFo|tEUyy!sDC+f#.Q7#Ȝ8o9cӜ~A>?g){Lq6C>{>ȁ\O?0|!)B b`X`Jc; fgTץ',1!AAY\\__serAӸBݴ F&=&lur#RPғAC7p2]9Ig,9[fb _l B3b3w0l$& w=baĨY-(,8fmQ# OR?/1QdByοC̔9o|Y+YA ?Ȍ%X(Dg#3ov6nNdCDK/?|0C |Y#}S$:fcT樲 wt4yP-Q AV 3d=}8\.or-X&.;"1L̉xD3T6nbY;q(G]'U:> |I㺥ri>rUjq&mD=};G3 Hr\6MS9n,csw47sRJ][g&H~2%o>&-d RGtnVR7}h^NLG=44 pl9ۑAGѸ@%l8<)uF Ϗ>x 5O~ij ^e6<7^X?-f;1PY4ONmUӴӹp@,OruWʵ#^X";R-嚃WdЃr⤑]/:Udy5"V-q$S:d`xؓ\&Uҩg%Ť!U{ЕZ,'oQ&2; Plܕ" n.MX!oO^MKt)k*ڱ=ZoUٴPܾEmRnXꯊ赵oj7Vy'UO)ԼISkFwXo:$K?&sTT#sRq$ΨؠekSk>؞8CZ8Dmd=am;q2qRbWO;i:wxÕN\Bc,Se0=N3:CFOw]6igGh0[@Ԟ2<- qːԬɱ-l؈Q.ZkwiW}mIpi TDso ,Im8ͼ1i Uu4ޱ9am[JHVenGҚ,N\Ɂ,;=B:zĥzx]UGc ݕ@֒N1+,G$PW8_1 ͇˴8-) +gYfP "iwxmv4&f䑨;\YLŀl!ϸL[9-5%+>DJWcMYbkW+}Qɠ$ faf3βcO%$feA-MMp]x1Yj /\_xf.Ƀb J==`c,kuYҭյ5ڰ<\V3NqXJO8JkX+N bmuc$}G<NO 6 rJ3}[4aa]qGTaƞ6 \GF)AX~?K UaQLg?,50 EV "cJeC7gyʓ0a-a@YHdƜ.Ε,bG!Kjı cs6*9; !1A"Q #2aBq3$04@P`Rp?_i_ܿo5o_/M #8Hxf{v=}s<>>?B']H7HZ~^VR&H7ԈCC;vocR竑΍g̏-'̙WiZУtũcz}O=>ǧ{?cM:-G+I =JGu+y-}Bx L\lKWM_Ǜ/&zv:zr+'H򔻊Q6𱉉dlY7._.\rY'zjcٝ C]%# Ub̑q2$$o\#,r:ZK$f/fYF,ѱt9&zOE7.ř.&d_˗22E˗.\Ⱥ|'zz]/%UxZruτt˖yUŧI3371,b(J°*QvdJ7ܜc 7ݙ°lkܺ.d)Ix䌓rFFFE.~61^\D:\TFw[K3r/}9!M3QF6=$Fk/%T*dC}Xə]csO{wC&b91Zt^%o콋2?tFf1z (n"oKrRO4f9=#3OA_rKRn74SNR܂Dr rKf(1Rґ(%X'Q's($d!Lb22f7ŘH0,6``-1Gi^V72$du?OiS*Tcƒ7b?-hj/يR;7.XaCT1q.t,Ycv( 6.L2{tnFt{lH~;lHwl 0f- WSFjJWp4O*-w]ឯř{˂Y{Cb̋^HMwMBj%,4e!HV?a&b(di~EGkJ9p:F$܄ԩR]M.=eR\ݎ/slM5IIQ'knhr<ɽgMcq4u,#R62&-vX߁-[>,7آu)ɓP$ C# Ivi\e&k|KٚWhTsSu)lh|JMv-8Kp!/Qd>bcRRx}ʯȓr{x:)_Vn?F9g$M$x3nWcQ}l)aN1T!-zn-ӆec(sҌ&ѺL ;+n\sKlu555ܧ,k)#N78:z#f0V"W8w:[jL>#L+(qqH!&&mțI_qw%K{zGb;2pkq¯ Ԭ߸)CO 5FLˎq4Fm˄t)(bYiK50Γ4rsS-1I]-]TV1CHXTe'FkqPRh$.=idӿ'N0E7jѦ%5"՘,XX v)5ӧ.G Ģr{ #v,Z|U8Mtd9s߱[>XN̫&)<[GSR5WYQ2gJ\ќ^mdP'J8>yEIGR;Ŕ(L#%fAƜuRWV pY%͛FuYՍԅH ΓF-DPpZ7زi\ش55(](1 )TSmd V^v<+phj$>&l'ϊŸF)eq҃'~ߛM BpoxK2ʀp}'DRdj5F6ٖw8\mɉHn5Qh%*I]Iw#[:"Do*;A5nCU+R&Z?-4k̏ $|Gn,˫XM4GU93(*ZÔY&N\Rñ)(fZ2٢R߂IC/I"sXr%̉zQ-ꖽ6jaԢѠqpfQ5mVYh84PY/K^DU!fSIKapצՈFIzrD}Qoh|E}Ñ'!H[b-E]8J0ɗgaSy+&QA=[0ܓ]dDZV)BYd&EcN,^HUIK/ st) _RzU?b-J7E8yВ(V*|RNs'k21W=;l:#5RF'zK4rI3m >+f_VoJisaLRyz'ة~*B2 I]3dӒ:yJá&E.IORz|[S{J}ˍ4sʝl6S(O:ixO:5 FwW+ &70PÍT+;8Xѧ+nyNQD5f#݇ EP cG!Mjj#(TiU7/շ1wإ%NS؝,7chEإnF9n:yQ)+ɘ~JtE E)^9+7𷎚xT889ZN5uN}V3eGQN:u+e6Χ'"jD\uO%߱)⟨[2RmQI WZVuʒ-J~c.I%x^WTc#+.NG)Ѫؽ;hxؕJZ8 2,T*ln"^ IxkKQVcIch-pPp۱;dMTTWF"4kN*U3 N"0R9Y,+܎r#B;'N*6a*K܈\HuvHvBd~ZMJn8da!,!!;[Qv3'⒱r|]+\i gq#>=Eɧ/V_b:n]_m[\drQQ3;ٕvػV {n8!1A"Q2a #Bq03@$4PR`?,K/_Wo߭kҽ(_+ (hJ+WoYeeoYeY~Ye_W~YeYeYe~_YeeYe_ee_,uE(eY,R;3M?r?C]Ϡ #|? 2}OrO1ڑYL4ib;RbY~d24tkɫ^NP>)yOw?'s/ɫ's=%|\MS;~NU'f>1>y> s|Į#豾$?%d^hxieEM X$%}8;}$|Ζ<+> ̞~G#\,j,k}(J(Ez\E2B곯'}FK}?K>$>d K4<2GmJ(#~v:hs#_IK.+<%)(hIIe3srSMbLֿfY^QEz9R({͙yW5t/C_Ck±ƉuYerI5#Z5ߢqE8,i*b4TY "JU}m~F imoV8ikӒRѤP86Qeߥ3KQv(hHIa:kk&Z7M(:;ҷȟhC4M&{߂dbȢIRLhe2SoEi+icUlMLJV4{\MnвEmnk>^ۣ"tGxS⎢D#CeTMPG9xLqHbHpIlVTJ*CJ?' JbDh(KҢ4פkrqx]IlQ#vqjC<*ُّHN&n87q7^G!$IqPn)#Q[l~?kXcLTCLciu2L:2=TFS۵J3[3SˤY#~/۽~fzFw!bBSEH8kYQ)̗Ʌ3]"ǓseԏӦy.SȍKǦRD-&9$ka9?PA-J*ӌZfHhm˓|p4Shx"ǺfYxX3{~HEA>M B4C*ޛ(FU#:wD3A"dǏ:Č_-< Z:_~N&<$[,rOTESz%";B&R]vOKnHe=zLUwTC&e Ô}6L41Z*:1tr폫(dՍe#ydؔB#NV%FO/ghA]*"H䩣,c4S1%2;NNŅfi|N3tvٲCUQZr#ݒPX#}={fd8'0*/CؗWq$l7l"z1r2pV;Yk:8X:=>NKo\_'oݟI9Ff)'%;ٮHfyƸe,d/7iθ+cպfHΩNGGĽ#8i40rFSWFEIn\$%$&YE{)%rDsKɋ&:8ҋ|ҫ%.b,+XF1bd#$M1ǻ+6I8ʌ)<3`Yzi!temnE'Tp#cwQ;2":Etgu4toXF#LQ|ămnYLfI yue"J?ÖsC&G8Y%=\cR?&5{D(K_x9@M34tsӗOt{e9-d-2ЖB?*$$ӨV;fMO=юQI3{K["d$c oFX{ }lƶM*6RNƾdύGa$cы2fx0I"\ą%FDƪ~Ko`ITF8}E#TJ'y3D J8 GGtr8ǥQGnu/*oXk5$rZ4ԈM(HX#7vF1ıĞ=P_vlsc=?TBȼRR/r?h7v)6J~j؊Ti<㚱)o]L#ݝ\ώN;nN{3YbEfG{ޝ:5S_$DVZl{-`nBd2/K-_. _豳U7ɽՁi+H껰۪93g+"?%=׳^&_&_ }!pKo\3}W @׳BVU.@7 DгNVUVX?U c H=B.j з@ǚåk v`) `ŏ$-h[mMĮ9 Vu'%ku 26 \Ui 7¥BnQx´7>%wfafHd=XU)×ħZTCxBw0ݘr. `H CVj#Q`$s] 9ZJuO ,{vOPiQ!ekj6+W78 ,V{e13QpnῢUJBcc.[+G Y]}њ#LejpT12lR'v?Eݫg.1Dz`cs0PK3T p+9ZyxPPVK-0|잤#iㅩZa5\ {VLsfOfJs 0"׊9Iw!9jUK?$4 {\n+O[8RGPq ŒyPGdx-}-pdlԨ-0s>!?VǶZ94%&N"trpyAsԴM4G/e+\O5eC*(@~ۆyljW"UŢޡ\2\Nrn(~Pӟyk]WuE?ᒲ1CUOPD \9֛EF*D8Z*qe MutOZ\VjB.wLBiR{.o'EѩR`[-.J>* 9l!Eg )63]sMs_K 0_wD]}Uw}`PoD.%Z2IR^ю`SIvSʲߎ29s*7ah#h ISfN.Y^w4`˳.5qUFH~"jdKxdYDì<Zm{%O~T7,uJrU'sAü:uQ%[?0+ɬoDOo|S\e:@b b.kyْ߇woc<˙"@-9T˻ĵV<%n eê N2^Z,,>ʭ=Ζ~`X-o6j:jS/4rWXpC>n+Y=!sVZ`.A ُDD >E=ἥb5s(cajVx*8ښ pkd0p%{1LZψOy @O-jϺ. aIe״pKO3Nם2d\F=Je:{4e}J%:?[%=Rlu_y_V\z&y/`5[}z(^XC[Q7UlS?k\k2;׈Q>+2 w{#x(:%J\gPy#pt8gp4 ̐P t@$@Z |N:5Ɖ^ 6tjDdy\WE)ܡTŭ;TN >ǘPܭ\SGQneW6ziӈ*5}A6c20Tj,UcrWri.OksCFTՆ֋H 5IcvJmUAlɧERH6j_L[i~i7Hpr!:e"EBݞ9G2k|"TI5#\B>)@h6TKP]܁ab=֝ M4x5z&Πh?i>h G.n=2n7ÒmԎk-WQ6;FN5isT8v؄ON4-%Lj R\QaMJ>O ˓M0J#؎B-hS*6\Clw?TS7 VFFF\9^eEЧ9hx[ t[^jaI@)ml@%Wuv.n [J. lu*>#>jӸ  Gt݇2>SO #f u9-b\^Ot]rc*[C=_Ci*w5>Q,hls.%JisYŸI ܮ,D]5ƈ tw#5M?+!J4vϷw7N;hU͉ysMwh?R]p(Qan"el?w9.JE~;kN̗\'P2c7lS`6{zxٸ ʭOmG@S&tVFm;=jTT[%FVn;Dm%bѥlsڪl P|;s7TM{.Q?b/4L/:-`=e?D8el<W;<&BJ:ܮt7rsM4]骀UkWP m:.aM/M{15E-mu[=&Suά;dիJj >^Ϣm'sr[5FwEPc[FOgMFB۱7ylQ>*Gv[kHd:q{nwwUEVgVΔUOUr6e I9Jn(XHHI)r^*7OՃu]0 ,$$4c)LsNC[%i`A0^8-/mruFÉNeSl]Dto%ShND]EU{ptu[vy)%#!LdrHp RzsUv}C'Nk`ߪ35L5iD:E%AA[ت؂aUoתjٙ[NNͫ\_xʭtS6ʭuJsr4 KEg'Sym-85GSàNmTn` 5U<dТ,Tf+jA.TY>Hgʅ1*\ef*Orݷ{Phk.Pr%kuJn𐁡+Vh]|Ju~^ ~MPe Y\u#U1*Cn8y&> jUV){4=n@72P.9ĜʍLJXl&8N%KrW0UJ&vUf|Xʏ֌9Fj059o"MEŻзmivpPf*?EvhwT\[F!KΞiixuF]%CjfDZP|J)=| lw={iZ GTUȼZSqcE'hC i> Vkz)܌L**~`iplc@ItZ{Rc k>?QyJo w-.JP̔t'xV୪-By;{j9k2Yy0khA'#c \cTßj6sqdwFC]j@>{\ɒ (QWԪj?%FEyߨ"hioI@\ PHWVq=- ,u* BfQ }Rgn鼸cZGf[൒=ܞj<z) Z.#j׳dz:*+|Î ;= {f.6;,S/4.*{ÄTyx#Yx\`QNF YG{)^ ( 6WӍѿkGu y ֋rm EC$d;4TSq9W. Qsq 4`ƈ3m\[-X֜+ؕ'-*18Vx^K snx:6~+CH:BZPԮ'-}V>eeIM|\%kbf%\잪maP5l mMi0qX~l-lm:mv*N[~4uЋh >?TD88Ee':w9Ðme7d)y$vZ1>HWUe"sE3%QHk'4sh=THm^}8c#.'qέ7v渻( -ʳ^'Vv7unh OrL1\uT貃 zZ_* 愼CBࠌdV1ْ uG*/nDp.n2S^9T.~о3(nSXqM4e&?æ9Mlk$[3ju:rs <!'K1wyBkcW=AӎR-RfT#mf_8a?5+s~ 4[ۃڥӃȭ[,o :6!V@Ө!0>R:{C1={01H=THT]iS;`>-.".;0[ , 9P3)|*AI8 t(UXAO5V~ "%6aaRxk- e H|!sB& d4i)[?A]!Q *wCც-3sl9ER7Zj-v)֦O} H腬P"E]H!b)Nq58T5xȔ9sIڵOT]!K,.I JQiOY_S9.^c[%T7U%qzvx=/5 7jm,5O&YB ։yaNU =ePT* gMup t(2sUƾ': 62V2I%;<ӐΨ7Z-dUK^Z@䍮yT̕#1VPy:2JsBss!]z."#97T.c0:v`ªچ{pDz@է:5Gڈn sLU´ O>'Ui9yi qtJ}:iΐz<.EBh9)!1AQaq 0@?!r,j_~̹=.\^g.\g_Lz\~.Y.,1PV}WWJ*T³SSrjW>^᚜C~s׏DsW9ʥN?.}jsN'N}1_z zq̥c_1̯Xsq lxgJqןN=*\Fs~W3Zϯ+ܯˉϧ8 ̿^#:_3N%~;ןė.*W3Ïßnq.\k1s\ׯ3r oes9 eq/ӟ^g3L߯}8jzRz8+ǧ3ӏDJz/o^=kϣ?8zs9k׏K.8V=9}sW̬8kz&efhӬ0z^a菷N`J-iVJG_ z? ꩤڃqYq@&N3,Z|Y?qD$aAoc;G3>Xkܧ9Nkjmߴ[J{_e 3|,?fTE6~)14Gy\l|b^'wϲX|fF_&9u/)*UC$Vez$u-jcg LGwsܡRA(!DZM<>%uʘAžҫwNQ2LWWNy-.H'9S<O|!/is4D246fPB k+szALp/xf%k/fɛNzTXЎz[.Y(FUA~:f+P-C+JT7;#k=!iZKꏬn,zy6Jx0n'^-{->Btw9}2;B{ J 0P(U fE*e Em GIٕu \=,]h/d gv꙱By)䇙#dA\05"=%+>PWڣiQuQ;ql bSxY@{x34pbg\̼tIhm Kg5. ,Z^$e(^Fmer1-G ff}EuLeFq.{6G!4 : ?C /κ@p] gMXsXW ycm˽G1P#}"dySP5ٸ>LZ`\_:5,hZҙ:1(̽f xsZUU%F\X wƞEH^'f0#@/n#D*"¯0: TЈxb* kX`ĭPbu^o/hx~H>/$E#Mru&sAF6&Tb 撚k[wr]z0]4hxfZ5uU["~-\ޥw!!:fCIe _ _,/S kn\:@:8 NUԃ52۳R] T(.=g̲}a V]+(Sܖ_R d'C#X6@F)$U㎑'݊a|8}_WDgQ7{EV2xt(}*zJm1P |+l8` Tj5b1UmT$.ʽ}2TPؼvXA09\1e JErCÍT)_SG̹WYyckwh3WpR߬( J)ۻ:}IYKI}:dX^PÛԽCWŮG^R +,%"5f/Xu9 o%, QU7 yOCӄч[C1k!]n=_UysZ35"[ ! }<C$̬B3\wK[ݕu+[rZdyݙ\'8\檫{)lY~h PVo@b !˛-)Zw i0!F(5g\E~XT,_^eUa[IBc_(;AQcmn cHxEv>x2PQtv W={jSnwS7) W=)bWĥ,)OPKn7n s6EЕ8fu/0gt 0na\1 vpO wej.HE"gUE%\]UAv7pgf=W\U{E˦ꧾ"ԴGC!l"&мmx{w}KCc̿`ppܰ[yDQ1 *Y _K\p00˯htrLI|K )q\3QP+H^5ROp_$XߘّakWMqw%=,5d&KL|=i[Cs͠<|%RFA+p1h`w:4߼ҋѰQ G[fKcXv =lpFa|}T80qKd 1+doC3)t7xmgO |B'X/s>`+SSx Ӵs5_Cp*k^?P*{O: ʶ7Ĥ&P.opD + V5.%h ųMQڳASP`=8\x+mMscjSXhu0ټ>?p+Z 뤤PVDs N-BP'YL-9D]KzIfDDE={9}̪YᯧYp{3_<)7Lo2Ƌsȑ14eI-ΑQڦ3 Њ6^Yj(]-:Dp]W7W(Ur3` Hq'!+ s妇 b)oN+-5_£UܽjE^4ƌ9/%23z:u;*Tw7ʋVin`cAΆbR4u!2X}+|!iNOJ',rRZYBC|W*?4^.OyGlUJ EK~D0uUfeyL?e)T"c*G*a]Mtf `֦Q7V}%t2ܵn@`UW|F[r.#o qk2Jeu!J*XjxM*Q\X3JnؕZfZ2ZSʑls h'q}%!3 8|Fٗ6^e`}F@FLJT6"PuKS6z\*`V%!@Mo9=k^gEc YE8g ΰeij]Nj`jYQc:x+^&xow%j_uAFrYWo)grէ|@5oӃ"YqKE0C8` ޙǣ)3,|1]:RRgp*1s+m 3̝ƠNg&"w)2[e0\^%x7e̤hus27ۄho{ʮe]َ(r-py& 5eCO%9B;p* 9~p}1Q(r}FMYh3SW dgă\)5*b0{Mk%a6T_JfȯN8xN%WnqT f)KEs+U;T|jfAl') أU ^quBU|V唀71洩BATfoO,mc/QIXܫX?>˓L,߅&Dw8+ۦR gik=yv+$1<̐ 9AabcAP%9;(у T/`dި@ +,XL)oFf2Nk]X~[:0eYJ?/r`Ju=i"2TOvk~ UhȦ`ؗDzx5?j@0;)CA %pfo8= u#E[u0WǔFXg껉)Ȣb R Zlkأe:|k-q5u?ƺnW[n! ~ *iws(0 h{6ߴɢ|9i`fjW3yS ħшUZlj4j|)'L/hh(zxyQekMį/MSN{9QmQ=~jkH-%H+dЫ f?rڶc#uI4IVm/^ qU}Dݗ]nx%a [2Y(e idP7oц/kx9{E*C*iWcCl?&3`yjF%JQ60+=bfLbjɻ{L im__k'C|Ɖrh vc@{ @PInU%: (ZV[bĪ=6'I*,ӴJv(kCN^(z~ [jug*]˯?g{GDq9KtԢeah*F]@>+ g흝x#Ry`׺\.l~%ՂRy@ wG^;g.PIA >'Ǎ˴% QG= jW* k5h-GĬe@_! iQz!AP'YIo ?cZ+OF*<2Òt\6K^bl R-nrr\;['/(JxƎ*3%B6{1+%R`U;gi`sR`23nk*]^OHDfn-f76/M:|xKgC8n*~'H&8:$[s[^ לKVK6/ VuF#(S)u<ĸ)̸nT/C? 2Y1 'h2dYq^Q'bɚL&USraIĭ)0؟:{G1췅J=]6,[+d 4&lf;W ^8T:s0YE[KZ.1gĺXʪ%FZ+zEHە8LPiKҢ<3ސ[{3Uc @PɼKQL'@xu)-ע (Yo3]1wb(9]04(2 >+u7]awY`95w4;63m'.MqPJ4Bw=Jul E,)d0C*Fx Pq:%J[bmrfZDLжh0LѰ_ºÅT˥(_ ӡ>1 ­Pq)2`Rwmw]g~ڇOtǺkePcZ׼#b.tno!Su+jO P=%A]5̩4pMpMqJE`JA5{b6toE}I2 sz2湉xXϴ7%w/;YypZjvx#y]iy&UX_ͽ w 'Ĵfh]n=wX52%7USqe&̵itm2}4ځ_x<&-QDZt[Xnixu:1bEkq~+6$Kÿ, ^ocڂ%u5׍F^ba+}&4CǜLB*+1cqfh,K#P_\Qt8P|ww{cetGZ}[,b`W rjcv,gw.O\-K²=\ElbɶUaXb%cFzoG̲?B2Wijq{>DL6xPX,Nӓ3^;eN訅ԛ i9^%D܍mYpK}Uɴ,Li F7JcU1Nsixn{N"7nj *`y~l_6}%[Iph(JQ YZrrO㫩{`u!9Q 0jD&˨UcU'qVB@ܷo#ͽ'tT|j Wߍ@]Q5܀HbZ+ck,Y;9WRZPͯb*PFwݍBfPSM*kPGő`ִ"0ȋs4ixV u-_`üi̱)Y,C; * VPٚFj:O@ʋsXJTZEGG$Б1 \VZYۉ\W& \1 N͆. ڊ)DmNDƄ\/6Įccɾ1xFWnl2@YJ/5G30gFy/:3@ 0|'kMJWk8" vrA]ʈ\%Epmm7O,QCyY{NյWS*@BHr"xPJъ{@L9RsZCSSefT)z=Z+*P7)݂ua(kϒŽe`E:CEw,! >hAr+-L=e3 q20ʪfs3+*j Z0h^c A]C@Mw(.X LRRK[]° ,7)[[Tuށ~%RK[! SSX(يYIɨ7(cl+s)@CXCtn4ĭ@#xԤ Jo* W= k4*5-[&9!+nQw&e0.iɀ KʳM"5dJPb7V19Qn1{ږƇe"E}`#pDyϝiu 9n*_)K2r,Nvrq ǩ]!1yoauj~9żw kvW6K?3m/f>EǼ!qysff!im@ee1ab S&q7fXQnXB>ٍ\1hJspAkvK|y'ͨ_ b(֫h%Oe /x_ݠQʱqtY'y0HJfDYB$}tr*޻dMf%<}my!]Cx^f w'e(S9A1*WtiNz88rR bPkwv7"Nqq܂U4 ɓċR(.,3tGȽK)+ _5z3o#/{8@R/ۥ䰚p ѿzZwb:^`˲qCXXGs9Cv^JED{Ok!:&y.XL͂)9*IEj#vv!x0Wzr%ܿ$7snǐtaUPQJLs_M$ֻxQ~޸G~x͵tt/ߍc#@w)Ƌ)ʃc-b]oY9gW;!N?PI˖S90ȳeɎDώ}Bh t9JM׽NZ7阘6 W^g?7]Dg8|eϣI_Cn1:ij)/{Ǻ _yCG⬗7BN_7qcs$EUD3xT_0ށѺ&B鑀G= ~񴄒joΊt=N#@np n B:-4yD?'@x^$*|wDpbPg(!1AQaq 0?dtTc,rr/ .\X2d2b dIJR\\&.\d\pTⱹMJ`<33f`)_<473+e2TG̢iӅT*5ʕ*/$pI&%pt814_p)# 8IAƸioSOž;2:o7!s K.35ixI*iTn x\׋3ID cRŗ1¥pT #"GLCYDI\H*7t8\2g2m.ԇ qKpq Ik&x^8Y-EKb\]Kl˖’^%Tr˖.I|.1~ R˗.[lps.lV Y|sn.k0cŁ3///2¸\L&/#0bbJ4±+RDMB^c-xWۈFiW+|*0%M8 ,1!5[/n,ۂI#* ʗ.U48fTLJWN5K1`?QKJ*W Ӆ/`ܼ%ˉDo!C,;dȖv[fP4}{2͟fٙt}K7o'˖˗ M u@Յ5uNtEdЀ ^0 D C T\ RkEuM}7&/|f', Qj-+F ˋp.L% ]掬1BGR? vh&hCQZ@(.Q..x.Y8JDr,ഖZ^Me%%u YTcz aՉos aD {p\;dVV^3i 72 Դ.%/4 ̌\Yg7Z*Y=%'GiraJ 3V1y}r,=c`ΌTA3-6I[J4-W4 W(՜W)E;Τ~ BZ(&Is0s ! IZpd FqQ1EUFQZs4f4"<"0. JϨ& stjqpEF#D#tT\-0,2oNwuβw6y*Ǭ zT3PR]m 1 ` Cmue@+̶UQK% r-.h /pFk"]k, f q-(41lhQAZ`\[ye&Pҩ_H Ey)rSa.CBPFU, ťoJ0qVX_U{̛P+dZJ:DhKFֽ#q^(5 `7_1f`!vqqGH"&M \F̠o ;*ؕTp^e 0=XšR͓I\RחYK+l5}D5Y>*h"{BbL.ȸIPX y#@jsѶXeXuJaIQ`Q LrIɄHQ W0>5(P aP\P՝`(ZNrm0dד3t[%c-}b] ;4}sԮds,ϷP|fgib-c \Zed!NaN̩JFM82\n.*X`5FNp4>|]]EZ͝fӇׄ0;K%EBѐIan"DڐQKv w5{uyL'~િªLr QL n`7k"R TENf0J{k3+2C̕4E J,"η /ZVl0g%s_^KXj)&DqmwIFW5-CPٕG<$}e7Ӱo_W+ZQu:_0 >\s}҆1ME0e%E /#~RXEfCR[=/1W %cJ3r/0m|Xw__pZFR70D-n#XDYL+/V^O=5ޒS.6zMz?=z^*R˷4;jsQ-O71DH+3p TB7?P-+wo=H9%^~n;;E%i,!pϨ59h$\A*F!#ngHO)AYKT·~|EM L¨ۘp5w9A- %5ǿJ]PާܯiIKܱҵ&y@@.9]]Cr7L@f1 X~C-o3::FfkԼ="a1,i BczC3xGA&P%(-Ma0S z0#va>\?q:՟66kMBa҇2b iHPD9ۤj !uJoX!_K*笥%h"DT>y%!;$ϧM)By,Ԇʦ2فdYؾx XV?Fv5P.,.l!ST-D vz|i|}ċuټe.ȝ0ΖG y,+a}3h )%Y!i^X#hwȿDiΪ3"޳(F qS/ >Tc)ʺ>C\Ȕ I'oj%/?ԯ܃߼0[#00SX0;+AtWjU6%"2mw lJ}p~;Pi5hT֟VFu8)ɷjc)Z(s)Nx{ #^GMyYQ F%X2i2~O3r&Zy>b}ɶ==_Kiyܧt_*qpvs %3,:O!iéa~}3C)M;LdšBUKp8.v=L7ߵƧ*~Z:i )es;1ftAs?"tKޯ(pz৴4J%Zm4WNbJrTP͟q'!T=nfZya?R?1|X.ge|:"d>WCmz\ģQ'JuhGG;m J\XsL.( zf%˖$O{fXNyN"hE}D+]IfYDuTn s%i8X\ƩOa'@nw)dR b9ryg0h#Zop O_k6QKMoܶHvkƯ=D$((7 [K hzM%L5vo紦ҋX̒W5*"hVlwTڙ=Mܗ I12 |=QJ58ci5h_#SS.CYN&\cAg;C{Ϩcrh"/Nl0\$H ^saIJDuRR?P;uBǽOpFǼac0+t:F+U{rv|ctm2XPkC$4e+_S/`M$|umYVlk_("u|T XtK kjĨ2NkGX\.F5TNOv~BfF#R#yTL n^.+ _\G2f&۶J6f9ǗoRo2/ifPcViLJ@#0t#1LcV.9`+eBAK}߼kEĀc#E> 1xMWle7֖ hPRxgaM. u ro֗ЂUvu40A*`uberTIfnG|/iPȘ! l` }&h`5_p,#L(Z 78j?^|ADb\)yC, .؆Yw*Ad(Yl0۶G:f 2oTc[:BܺG#ݘ*.zu.OZ"+\՘msxgXB"V“h.`Lӧ?;#XL <[pLհy8 Z K1ʚVD˝C0^=FHtK:PXe\ Qq+&&פ!G f6fKӔ {4qW0 B4T ,Bf Mt-k129{y,\2Җo5t^&<ߝ&QdTFFr9HJ! g}*/=xجj'1P()"5j,"("Zù2/hZQ#C1܄˝u3\ÓMuTKhrRA*u>b*W.ӜcϨk-k =uh22;uw//R0f=_>:F7S3"JYɘk1l+Z,49eQfXl.=b sLL3EQu3W,X}ɾY-T/>P*W gj~P8Kк oB[ygx`"91kFJDB/15M"3p}[<_"vQUa#e沚FW܃ZAs X-(6aH'NsW2+DtJۛFUx^fa[Wo>+T>db̈́+A?鴬sͣiYSj ؕ^}‴1 =&MڡcP`lrUϒ Q{WϮQ 3,* zzs&hmlyF$+mVouX iq2q^w KӀf$IJd[&'G*6 fO9DYjKQbS&k $ކc)`7hړ,@D*Ad{˛ obg&VN^1i19pSyjM2hMa0tMt޳&0r#l-a7 }.D@!ֳ k* LB03i;cQtEF0fm.6"JԼbna oG]#9}CFu{X e<0[3o,b4]Ep"!lvk9Lݕ \Tdee]f  䖔沦B NpreK"`D(8`G!Ԑb3*TILx+jMʂ2KŒON[%7=W0Ҵ1.];LPLoEE:`SϜa3"•C FcT3ۤqD648bL&EX)BQ( x> p8K0Ҝ\0U,&S-e]G4 !H$\rBQKt!!5hFKLbA*\ʾmTM$7JJ99 U=&̚4foUJb%B:)ACniM6h}!yG2>ղqJ2J$XeaSpLCPL$ŵf)R*bQ sNJ1mĢ^+C*RݺafI4CۜOsL 6UVX7lsFL˺xȲȠfAQ<.ӈILbřYKs*MKKL#dn$fv')d E7*i"&BV KvcBgFY)=u, .9T  .0ī[R<:4;SdJxߔq. #kyu]A08q0dtQ/O8CAAw0 WpF,Uʓ#eoX|FVZ& ۼNMk T! <P1o=JXƠncEJK. 8*(KI*Dqy{ ls9/s|JIVsEB'E5𛈇H_0NQ,f l"]D52F®XqQ[" ]aY$hVXxl-PNJ˽> z,}fbo&#$}8>?Ti]C7sD h(֙f(8*U EJ1Sm(3(pNZqѤneXH"`hGЕbYL?wXJb\pl26BeMRr~c4P?`(m@nϓ/0oҠ#J_,{T/d!㉨RD,`i)A| ]OĠb#9`.%k,N_~"㟈20x*V3,9~`";R"fXj! (q(s1x*t kcCӝvw!W,k3-@UDg+?r awd[/IhH|Ws>,+oZ\,a9Xs(.xqvAi0_8b-ʻ|aQc,@Cw+-gDQme"*^g0A2]2ǻzGAy)6/g PE8G Yxs|e~*GINS 2~JP0C)2bS(|!3"F꽻iYޠDϜg~" ]aw~`v3]̺Yt׏vRɓԱKQn: oS x"C!hiKfoTYFύ0ukwDcSF^/nݿ'w0>L=QE>p ĩ -Dj|~p Y,FVyM4J9Y{&I0̝Z_8R1%EQ>ywxοZOl˿:"oqDBes(G<>0 \ r,Ԩ*}雑Itr P`p* vb+bkes/Y,u~5(Ys_]c'僻qdUpG (}vc׾](< 4Ly}`S3GS&wJ 1 P_~L+=y:2 ^3z>r#dvEB HQwEO2[bwwǼa3)DFz~}Ry39 AϬ~)A/+>-w*uw 1iOxY}*WeJUqej>KYNB8Bmƈ_Jqܦ Q/g >tߺJ+*O {OĹKwKjL|Yl#ұLGQTڿ0LBl,L8ŝ b쨪G 8;} ˜A3S(hjg7qHɭf!0j(_̰_(|?ŋ L9t{{0/,Lp1-Ȯ#Bhǁ9nhWƭ؃){,2*+fE]*a)1Y0_Z7lT!,-PLt 5b*|K_•0SL wcF?i|Gd-+MZ,τ+^k/:)L9>_ X4g*;FCK3*ڣ_07:(đqberE$"S  _Slf+,m!fc\H[,vBRid}a4+JΏrZa{0ДEpD[Y~KCx&$nG 3NVvXԓD|\$UIՈ*^7Hi.ۙ>Rs)rV%P/1) E1o2tjd:8?x~Z v?Q 8 yPFbx5*BX`TX Dwɯ-,=(ptplDLF&n JYU -RQ.m#}Yw.hH"hb _;U!(jq9i0 &!{JWho+Jn'l;vXgc >bA+2Z0dLTw.?}@cpӸ,n qɘ4q trX1F?" s"zJQ#aAwGh.zf]T9@hT}3nYv4Q@Jb&Ozl'%!1AQaq?" b^l8Sb gf>O<l|/.Fu)IZ㼥>7?/16.67p)܊W~1MsARL"gqi^9Sم;l2g_ ,sNfs}p:_7|k7=㥅Zq!&'8sLDZqSnM90[S4M^3SI{N5[#NѼLG`hy502sB]kF|jG~r#Erx{lCg1sqvjN5UpP&Tq_9},i?)5@3k;Wu594ygjϬ}je﯌~6CMwCY+CguOy&ΰ4Κ'RU~v|M995aO|ş^qfG ]ɿY0uTMM9Ώg3Խdme@t(pod>22bgcyB]\oB`ru~Gu#|1nM6S9 <|#6|ft׋V?\a?:|S+[q#xܟ Uqis{ &Ǿȏ&[3V2qMYlއy%uW{pjo:=Ğ8%p.޼&sU׬8~s'1qO$'Y7=n[C Nq|9oY7oY?AN\O XXՙ/ 'quyB9CZ1Wkg_8 9XZ9y~qJ[yYNjGY[3.ϜwD2hkntk!yӉ(oy`tx<5a*=23.X0Sw& :!{pNqӬҷs _9 ߼Թ%%ƾ.U@+6Rξ1%kx97NUɺt!\3Ǟu7 닇[|e]B\ 'AZmŢ\e߬G&Kk=b]oxN.śf9 R`WȷNt}V)t]3ZvxK퓥EhgX%=-)8_P 0ZYv9_k8p]0J`_C*ǣKW`F í:FxBP/Pz- .4>l#e8'p?8 #?[w1 σa2fM8CNh+E+8\:90|8Xm<.4nse B ?8Ixfm`&Qdg/Xñŀ]K<ʇ3x Mkaψ <乆ł!]ך37ل儥_\|;4הw21n6:8vCWf3T9(xKc>1`dE d "_7fnU9`8Xi{7BTyW r(kHǞFY>{ĻQ _Y :DS y1@RpFijf+nNʟ: v'BAEKo~"JI &|ʂ8B9$ط1?"mAyNQz)Pp.OM>:w?xp+ėWW {m2 #?&NL i]b0b^.sD_GkjMp7MOSW@H__9 +8Ԝ2bX?<:!tlq*YASiMCX0q$;pXf-k_!,uc OyX@7fʌ? ![GoPCO/ xIC"<HArd>~{TYD1 F?/Û!JcR.#˅.,c~M1c=w*<.u/'ȹcPSAqKüb@'6: q%}V ×?B)Eɬ"G#Hp!z_rV;GB_%ͮfYF񚈍

"ΓaEO@)X yJVb{@Fi@Ӿw *Ο|Y>o.9nsO~՝ 810Ep0kB8(du"t8)(X~5_h'Yz/Po"8&yzu93fZ+e?X>^J`)ʨ85ؿ9 9?8R&׆5KB󕢛VP`V8vc8E9hs.<I0A/0 L`옉hxCR`&OzW0EC3P S(:MeCN<f  ӄZ8/)zB^:$7mI} /S#?9˥ӛ?P<]T?9&Xg,Qd.!?0E.y,NL+ū& 7x^XUͲci˃6KRH)W)Ȥ3$aDVf'D|CzZ6 H@Q XZ}y`zYAkʏNL(['XKkT-ݦ1FknlདYMuyofsYrVyƞ)؂of ;0J<n w q5u9BHn58g8te^M>PT>2"'qj^5u$/}2@Z9_`IioMr,:opsf( 6F`Bce(i9(x.0ecjH`~9qek[ zߌn-htܢ* pAM:u JUC<ue3;2Q?Voq-&@$%YcV1LUQIrĴVq,.V&Ȃy0&8,.y7؝nY/j7ex?i%wjrѫ- {{9MQ  9rPb!nGK^<媄 7MuUYf&qv ˅5Yj ĻM; mKG &t[фF%)[80x]2C/Ľ)ӻcHlH@I %-ӄHIԅ[4zCmT4wnNN-z;*:R~QSuRVᢗplR#—xUɆ3Qn3R2ab]F^:ʢi=a oh" 0(`s?4 Gvh1xǁ0>j:N,i $HK @+Ǟ3XlGb<TT2E٢.' txP}0::u$f+٤}YVI=ejuKBy/7mAEq@hDbt-:RB?YdAfg5(p{ź e*, ×[9Gg5SmmR e&6:u.k9Ԏ{l[̧]Lm6wȳLUdu&41yo!8E;<7 :Kz,Ɯ" j u8"" g! sÖ;)Qp4A?";u 5e7qPbth-3 @]56б۾Zأ9'6õ's iQ/8_;8]V1{;~ߌ M}`0ŴM#59aWWru5$jh"Nݷ%O?J_76K'jD!ѠxpόM>Ѿ{FJd)Ce6^v`:ɨ>ԓj;-4R ,xE:V/ֺCA^g~,#AGqN29Ģ(ɜ#)(uh">K&l3oY#L-nw촤6'RUCQ^܄hy;ubx,vuF(ToqaϲmUEBqPz DP$c5$m~OcDWPlDo!cf7' 6̠^kĊ:;8\YJ0wG&Ίl]bGq9=.<+8i "P٧x*T7zٖS>pߐf8w%(GS[Rs#cml]ʪ(A«:5 WG]\M`)-LPh+"Kh-%OوEh}<$v"i:X $.aV [m†9Z57ڗFR ugWI' HdcPD@i ؗi@|*8zhzyHo8cuM]75-nuļ ]ךxx2uq=hPn@Xuno% :{48;'MPz/*t E 81MMV@bFZ +~60.N q̽02K(߭iYD6YBv"c{R^C5wpʉ)܄0ا+N@v}'D5]!~ifC!8ӫRJህHs8t/%&rrOT5(U%Xkfn xo9@q j3D߼_꒻hkJ6eid{ِi''XP p?8i+dhB9E 5ϼ0 |VdC=o}!QC^q͠ÊR S3b ,A1D%%Z Њ}h8 2ǦY\ m嬩7llgN/CKsB`cJBi}xˇ< տX%(: Qw(pf vq7 TjDhgPO+i."9;A3 C}XH̥uW9h(j~pE&3~1f`¨kpLZ,T /P]SեIv$eju@8J gL!z+*k1k7 `ͥʒl tM 1BUFtY iiG@a⸑I&.T ӊxƷ$^›*VM f)35L܂~tW:ZSBb 1" 8+Q3Lk6s3f=P; )+aۀm by\!s(ʝM;L'KR2"|7>aP6Ot^;JE0,l1ڣLgǽ1IdC}[-"= [&¡ȝ*.?(h@FeӮ^05AdL@hV 8j!|+VG@C\7/qfv4#00KtLJBya)>XP>9N[cqTD^Xlm UEuneDȀP8}|Ocүd- #ݧY0SxDi ym/@Ѯ4ק0[H`'(%]yok8 ݼI$ 80v)&q~3_"hkKf 44td^Jޡ=]wY. T).ǣKj!'LFě P  vX%}c9ID&Ck[` 3\Ht/Ȁ7w t7pqdFyg(0D^}|b98J AqH րDunP*tː_ KzTc J*֌e$5=J rlnprhTdMdCӝ80VZ 4ok;!)ӯ+7^#5N& RA3Nt;4E|^Nwrh$ ZL+^#w^ *8c2 Bt;+3j:>n0.(J&=brh 0<`cт~t|0X;chD0NzP^ _X$HG )Ds(6 4$5%?a]C%(xdY4HXJi%7P$1^BbEEHL*I! t,DRĉD#̴I"a[K $JH!ena zC PbJ,T#R<=p9UoEr6jD|a 1bnMzmҚ0 wԖӤă:-ఀP&h߼)!$(y5(Tҝ8_iGW, œR}/ U n=fIxx{߮^)nU"0 zŸa%5 0I:BPwJ2! OO /v%\U?4~4wI'b,[t-ڌLL!FÂX:8D'қ.-puʳPWJۅfL?-8HT{]dII_N{ͺRLl+E~AtTr'8[p}S@ݧ >Gm!kkCv>Āl<+*~LBۅ^b1E zR ^w4)S"֎Z@č1D:P&-t:T 3XA;z”h$Vmy(WvX%Ǿq{ 4R5pm&@Yrc1e$ ۠d3 `z'Qf0>y6\~B{<>0T8`@#hZU ֮-SP5[ Mnh=L! Pw{z@Br^O8Ш=_6ގiۨ**m@o7;b)ATzP[`3bbI.U:СCNt႘WAT.P H#ʧ Ry;P}1nwgPmXz@~9nV׋2$ғΣcpǒ~DwL E܆)Ƣ}듔L`4%x6AZ3;Th8%* `T1 M!wr&tmt]Xpng/8Mg+>,Ukf^>vRJ8y5΅n\/B )I {n;h)U  Diyk DSI@ *66*ĭWMq.x %{b6.ٓ-mV)GwʤVϑ=AJJ-QX8 㠕6"/F")_ ZL1P'Ӗ$A?igh-TNI1A?1Q&͞4L}"@uwA$wNtHyB=]|k 1^g^0X+fXAӲ}e-[W(.dAsK\ r'qg((qCCG!An?1!VYSLu]}ak@ .9o_X֗W$yJ]>ypz8B|i.pDl(Υ䈨Nrp_kj)V dl^7aR]Ϝp.ydx'XXx;åZ$.M[~]2m a 3s;$#J=$HlU9(%auQ!ʊs%݀+NBM Wd6bq FarwuvI!kSGi QR_b*l~3ISPIx<$I׭9= `ofL@v*{ȩ<o{pH|7@+xxBT+G|f%VwN!:'v lI٬\>. 8KPM%I/u.l)Qm2>+E/ 5MƐTѝ6"NDA}L^ cӖ``y @`։y\J(a;/ sƳd؊v-a71;e"8Rw(tOXq@zŋЬ ٮ1]X//HCsho4ۀZBhZ@h"y 2#wS1s:p0SMݧ3ҕ{6FHvybIQ: uWq8i&s|(hMU;cu<NTs8sTpD8h -Eyz}a̬[&9B»:bGy4bzUsCa,p~wOe\ђXNGvzG7!îq}h\EzAqtv u8@nc#9 0t# mU1`A6-5eh|ܗ@STǭB*kr Uǁפn޽(>ҡD|h5}Yu /H*(!V0/qтMt 4Jq(Td =T&j 9̼D0u}k/B-ò3 y=Q\Ҳk*ySɮy¡ ] 1xi5)"Q]AkiT1*{BN`M[!Ad*Q=RT5bkc0,F*>fM͊iUA3$ܚ󇝠=EkBxtrHjk]Qt6ђVҏ0% |"FJդݘ^@]Dۑ ph eAd*v)P^7KD@r0.`y+_S"M`ݮ8rx[of*zx)G7cU9~#C8Bmybp&͊`Cwdm.0±$?8Mzfҥn.iO2,/ d>U uMY~qy%i\:Mw\GA4u9RGٌ[1ߣUwY8xufAٰv҆䶶$.{^dWu.+꘩O9 ΗUP"Ԑ䒉.Qp^!Bз‚ `BI!;E (u`mjR.kwHAڞzyOG* /V Kjq*/lgMTĪ41YDG=^P z4<PD uRqZ=Ӟ̚ĄɅxH#VX! D!$"C :8R)͆5a>ڥV2 (t2E_]#6w01k48~\R7 _^}S3ׅxT{h[GR_ܱϢ.80 dYF+L^{}bl4;ߞ:>ܢ. D=–pC#PEp]vΌ\MV\b*mBu4$!hlٮK j֖y_֥߬D."(jQKQR3ѧo<{1f"d ]q;(1h5Ij1NNמO €|UOqg3 wPw˞QO׆{:oAQ#ktv\`p[m:T "%NPaZʅM.'H0c(c,C"+qMu%$U{?]fN`\M$pn 6kZ9wrүAMeD!8uJud yq`wçzDsHy/Vv Vmly)vN89,$\ f yC{eq^oU׬IN]Kn^V|sVǼe% hk}b1(na[Xnn~^qCM9X /O8AnRF1HVӞi![Spͬz.I5 -6"UuZW] !6 K$DZ/n{[{ޏ K-:ÖNUXcKO 3o VۂK?8э :x/w*eX6^z8z!$S}.*ѨZ^w @&}`ٴW1iS>B뿣w6oVx,H4yê%#x:.HaLôqeiI->Gk[ʼB6|(Zrs?˜7rK^.9OG0x`{$ޘy uz}D3&q' ,~\x|M>ۃopyq\K!_[*ONiCk'_4ZmJ5="O1'wW>޶BqNoennLü' L_".ah{_*Z={s23lojkMbi-H5Kg_?D} O\8 GazTICN񺍝'b^Zc޲$8.?|(Eu/+'C*;#(ľx NB(!^D"aZ^nj~''.1Ym|a"옙zMdEmb*njCM'%"o-uH8Mw^ ԫ@g7ē 6]w_xVQ/Ygn\dnK0O UV*-yu 28Nt6;.XpP;Lj[U|4ΝFP<7DVlLqX(@8EYT ;%CPCzؑ{)Y#,: YVB8$'.Vq@^vYQ-k)pK:BڅMӮLNUrĨHkd(o_ Dm&q!B/oKrest-client-2.1.0/spec/unit/0000755000004100000410000000000013531144232015702 5ustar www-datawww-datarest-client-2.1.0/spec/unit/raw_response_spec.rb0000644000004100000410000000122413531144232021747 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::RawResponse do before do @tf = double("Tempfile", :read => "the answer is 42", :open => true, :rewind => true) @net_http_res = double('net http response') @request = double('restclient request', :redirection_history => nil) @response = RestClient::RawResponse.new(@tf, @net_http_res, @request) end it "behaves like string" do expect(@response.to_s).to eq 'the answer is 42' end it "exposes a Tempfile" do expect(@response.file).to eq @tf end it "includes AbstractResponse" do expect(RestClient::RawResponse.ancestors).to include(RestClient::AbstractResponse) end end rest-client-2.1.0/spec/unit/request_spec.rb0000644000004100000410000014556313531144232020747 0ustar www-datawww-datarequire_relative './_lib' describe RestClient::Request, :include_helpers do before do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload') @uri = double("uri") allow(@uri).to receive(:request_uri).and_return('/resource') allow(@uri).to receive(:hostname).and_return('some') allow(@uri).to receive(:port).and_return(80) @net = double("net::http base") @http = double("net::http connection") allow(Net::HTTP).to receive(:new).and_return(@net) allow(@net).to receive(:start).and_yield(@http) allow(@net).to receive(:use_ssl=) allow(@net).to receive(:verify_mode=) allow(@net).to receive(:verify_callback=) allow(@net).to receive(:ciphers=) allow(@net).to receive(:cert_store=) RestClient.log = nil end it "accept */* mimetype" do expect(@request.default_headers[:accept]).to eq '*/*' end it "processes a successful result" do res = res_double allow(res).to receive(:code).and_return("200") allow(res).to receive(:body).and_return('body') allow(res).to receive(:[]).with('content-encoding').and_return(nil) expect(@request.send(:process_result, res, Time.now).body).to eq 'body' expect(@request.send(:process_result, res, Time.now).to_s).to eq 'body' end it "doesn't classify successful requests as failed" do 203.upto(207) do |code| res = res_double allow(res).to receive(:code).and_return(code.to_s) allow(res).to receive(:body).and_return("") allow(res).to receive(:[]).with('content-encoding').and_return(nil) expect(@request.send(:process_result, res, Time.now)).to be_empty end end describe '.normalize_url' do it "adds http:// to the front of resources specified in the syntax example.com/resource" do expect(@request.normalize_url('example.com/resource')).to eq 'http://example.com/resource' end it 'adds http:// to resources containing a colon' do expect(@request.normalize_url('example.com:1234')).to eq 'http://example.com:1234' end it 'does not add http:// to the front of https resources' do expect(@request.normalize_url('https://example.com/resource')).to eq 'https://example.com/resource' end it 'does not add http:// to the front of capital HTTP resources' do expect(@request.normalize_url('HTTP://example.com/resource')).to eq 'HTTP://example.com/resource' end it 'does not add http:// to the front of capital HTTPS resources' do expect(@request.normalize_url('HTTPS://example.com/resource')).to eq 'HTTPS://example.com/resource' end it 'raises with invalid URI' do expect { RestClient::Request.new(method: :get, url: 'http://a@b:c') }.to raise_error(URI::InvalidURIError) expect { RestClient::Request.new(method: :get, url: 'http://::') }.to raise_error(URI::InvalidURIError) end end describe "user - password" do it "extracts the username and password when parsing http://user:password@example.com/" do @request.send(:parse_url_with_auth!, 'http://joe:pass1@example.com/resource') expect(@request.user).to eq 'joe' expect(@request.password).to eq 'pass1' end it "extracts with escaping the username and password when parsing http://user:password@example.com/" do @request.send(:parse_url_with_auth!, 'http://joe%20:pass1@example.com/resource') expect(@request.user).to eq 'joe ' expect(@request.password).to eq 'pass1' end it "doesn't overwrite user and password (which may have already been set by the Resource constructor) if there is no user/password in the url" do request = RestClient::Request.new(method: :get, url: 'http://example.com/resource', user: 'beth', password: 'pass2') expect(request.user).to eq 'beth' expect(request.password).to eq 'pass2' end it 'uses the username and password from the URL' do request = RestClient::Request.new(method: :get, url: 'http://person:secret@example.com/resource') expect(request.user).to eq 'person' expect(request.password).to eq 'secret' end it 'overrides URL user/pass with explicit options' do request = RestClient::Request.new(method: :get, url: 'http://person:secret@example.com/resource', user: 'beth', password: 'pass2') expect(request.user).to eq 'beth' expect(request.password).to eq 'pass2' end end it "correctly formats cookies provided to the constructor" do cookies_arr = [ HTTP::Cookie.new('session_id', '1', domain: 'example.com', path: '/'), HTTP::Cookie.new('user_id', 'someone', domain: 'example.com', path: '/'), ] jar = HTTP::CookieJar.new cookies_arr.each {|c| jar << c } # test Hash, HTTP::CookieJar, and Array modes [ {session_id: '1', user_id: 'someone'}, jar, cookies_arr ].each do |cookies| [true, false].each do |in_headers| if in_headers opts = {headers: {cookies: cookies}} else opts = {cookies: cookies} end request = RestClient::Request.new(method: :get, url: 'example.com', **opts) expect(request).to receive(:default_headers).and_return({'Foo' => 'bar'}) expect(request.make_headers({})).to eq({'Foo' => 'bar', 'Cookie' => 'session_id=1; user_id=someone'}) expect(request.make_cookie_header).to eq 'session_id=1; user_id=someone' expect(request.cookies).to eq({'session_id' => '1', 'user_id' => 'someone'}) expect(request.cookie_jar.cookies.length).to eq 2 expect(request.cookie_jar.object_id).not_to eq jar.object_id # make sure we dup it end end # test with no cookies request = RestClient::Request.new(method: :get, url: 'example.com') expect(request).to receive(:default_headers).and_return({'Foo' => 'bar'}) expect(request.make_headers({})).to eq({'Foo' => 'bar'}) expect(request.make_cookie_header).to be_nil expect(request.cookies).to eq({}) expect(request.cookie_jar.cookies.length).to eq 0 end it 'strips out cookies set for a different domain name' do jar = HTTP::CookieJar.new jar << HTTP::Cookie.new('session_id', '1', domain: 'other.example.com', path: '/') jar << HTTP::Cookie.new('user_id', 'someone', domain: 'other.example.com', path: '/') request = RestClient::Request.new(method: :get, url: 'www.example.com', cookies: jar) expect(request).to receive(:default_headers).and_return({'Foo' => 'bar'}) expect(request.make_headers({})).to eq({'Foo' => 'bar'}) expect(request.make_cookie_header).to eq nil expect(request.cookies).to eq({}) expect(request.cookie_jar.cookies.length).to eq 2 end it 'assumes default domain and path for cookies set by hash' do request = RestClient::Request.new(method: :get, url: 'www.example.com', cookies: {'session_id' => '1'}) expect(request.cookie_jar.cookies.length).to eq 1 cookie = request.cookie_jar.cookies.first expect(cookie).to be_a(HTTP::Cookie) expect(cookie.domain).to eq('www.example.com') expect(cookie.for_domain?).to be_truthy expect(cookie.path).to eq('/') end it 'rejects or warns with contradictory cookie options' do # same opt in two different places expect { RestClient::Request.new(method: :get, url: 'example.com', cookies: {bar: '456'}, headers: {cookies: {foo: '123'}}) }.to raise_error(ArgumentError, /Cannot pass :cookies in Request.*headers/) # :cookies opt and Cookie header [ {cookies: {foo: '123'}, headers: {cookie: 'foo'}}, {cookies: {foo: '123'}, headers: {'Cookie' => 'foo'}}, {headers: {cookies: {foo: '123'}, cookie: 'foo'}}, {headers: {cookies: {foo: '123'}, 'Cookie' => 'foo'}}, ].each do |opts| expect(fake_stderr { RestClient::Request.new(method: :get, url: 'example.com', **opts) }).to match(/warning: overriding "Cookie" header with :cookies option/) end end it "does not escape or unescape cookies" do cookie = 'Foo%20:Bar%0A~' @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {:test => cookie}) expect(@request).to receive(:default_headers).and_return({'Foo' => 'bar'}) expect(@request.make_headers({})).to eq({ 'Foo' => 'bar', 'Cookie' => "test=#{cookie}" }) end it "rejects cookie names containing invalid characters" do # Cookie validity is something of a mess, but we should reject the worst of # the RFC 6265 (4.1.1) prohibited characters such as control characters. ['foo=bar', 'foo;bar', "foo\nbar"].each do |cookie_name| expect { RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {cookie_name => 'value'}) }.to raise_error(ArgumentError, /\AInvalid cookie name/i) end cookie_name = '' expect { RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {cookie_name => 'value'}) }.to raise_error(ArgumentError, /cookie name cannot be empty/i) end it "rejects cookie values containing invalid characters" do # Cookie validity is something of a mess, but we should reject the worst of # the RFC 6265 (4.1.1) prohibited characters such as control characters. ["foo\tbar", "foo\nbar"].each do |cookie_value| expect { RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {'test' => cookie_value}) }.to raise_error(ArgumentError, /\AInvalid cookie value/i) end end it 'warns when overriding existing headers via payload' do expect(fake_stderr { RestClient::Request.new(method: :post, url: 'example.com', payload: {'foo' => 1}, headers: {content_type: :json}) }).to match(/warning: Overriding "Content-Type" header/i) expect(fake_stderr { RestClient::Request.new(method: :post, url: 'example.com', payload: {'foo' => 1}, headers: {'Content-Type' => 'application/json'}) }).to match(/warning: Overriding "Content-Type" header/i) expect(fake_stderr { RestClient::Request.new(method: :post, url: 'example.com', payload: '123456', headers: {content_length: '20'}) }).to match(/warning: Overriding "Content-Length" header/i) expect(fake_stderr { RestClient::Request.new(method: :post, url: 'example.com', payload: '123456', headers: {'Content-Length' => '20'}) }).to match(/warning: Overriding "Content-Length" header/i) end it "does not warn when overriding user header with header derived from payload if those header values were identical" do expect(fake_stderr { RestClient::Request.new(method: :post, url: 'example.com', payload: {'foo' => '123456'}, headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }) }).not_to match(/warning: Overriding "Content-Type" header/i) end it 'does not warn for a normal looking payload' do expect(fake_stderr { RestClient::Request.new(method: :post, url: 'example.com', payload: 'payload') RestClient::Request.new(method: :post, url: 'example.com', payload: 'payload', headers: {content_type: :json}) RestClient::Request.new(method: :post, url: 'example.com', payload: {'foo' => 'bar'}) }).to eq '' end it "uses netrc credentials" do expect(Netrc).to receive(:read).and_return('example.com' => ['a', 'b']) request = RestClient::Request.new(:method => :put, :url => 'http://example.com/', :payload => 'payload') expect(request.user).to eq 'a' expect(request.password).to eq 'b' end it "uses credentials in the url in preference to netrc" do allow(Netrc).to receive(:read).and_return('example.com' => ['a', 'b']) request = RestClient::Request.new(:method => :put, :url => 'http://joe%20:pass1@example.com/', :payload => 'payload') expect(request.user).to eq 'joe ' expect(request.password).to eq 'pass1' end it "determines the Net::HTTP class to instantiate by the method name" do expect(@request.net_http_request_class(:put)).to eq Net::HTTP::Put end describe "user headers" do it "merges user headers with the default headers" do expect(@request).to receive(:default_headers).and_return({:accept => '*/*'}) headers = @request.make_headers("Accept" => "application/json", :accept_encoding => 'gzip') expect(headers).to have_key "Accept-Encoding" expect(headers["Accept-Encoding"]).to eq "gzip" expect(headers).to have_key "Accept" expect(headers["Accept"]).to eq "application/json" end it "prefers the user header when the same header exists in the defaults" do expect(@request).to receive(:default_headers).and_return({ '1' => '2' }) headers = @request.make_headers('1' => '3') expect(headers).to have_key('1') expect(headers['1']).to eq '3' end it "converts user headers to string before calling CGI::unescape which fails on non string values" do expect(@request).to receive(:default_headers).and_return({ '1' => '2' }) headers = @request.make_headers('1' => 3) expect(headers).to have_key('1') expect(headers['1']).to eq '3' end end describe "header symbols" do it "converts header symbols from :content_type to 'Content-Type'" do expect(@request).to receive(:default_headers).and_return({}) headers = @request.make_headers(:content_type => 'abc') expect(headers).to have_key('Content-Type') expect(headers['Content-Type']).to eq 'abc' end it "converts content-type from extension to real content-type" do expect(@request).to receive(:default_headers).and_return({}) headers = @request.make_headers(:content_type => 'json') expect(headers).to have_key('Content-Type') expect(headers['Content-Type']).to eq 'application/json' end it "converts accept from extension(s) to real content-type(s)" do expect(@request).to receive(:default_headers).and_return({}) headers = @request.make_headers(:accept => 'json, mp3') expect(headers).to have_key('Accept') expect(headers['Accept']).to eq 'application/json, audio/mpeg' expect(@request).to receive(:default_headers).and_return({}) headers = @request.make_headers(:accept => :json) expect(headers).to have_key('Accept') expect(headers['Accept']).to eq 'application/json' end it "only convert symbols in header" do expect(@request).to receive(:default_headers).and_return({}) headers = @request.make_headers({:foo_bar => 'value', "bar_bar" => 'value'}) expect(headers['Foo-Bar']).to eq 'value' expect(headers['bar_bar']).to eq 'value' end it "converts header values to strings" do expect(@request.make_headers('A' => 1)['A']).to eq '1' end end it "executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do klass = double("net:http class") expect(@request).to receive(:net_http_request_class).with('put').and_return(klass) expect(klass).to receive(:new).and_return('result') expect(@request).to receive(:transmit).with(@request.uri, 'result', kind_of(RestClient::Payload::Base)) @request.execute end it "IPv6: executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do @request = RestClient::Request.new(:method => :put, :url => 'http://[::1]/some/resource', :payload => 'payload') klass = double("net:http class") expect(@request).to receive(:net_http_request_class).with('put').and_return(klass) if RUBY_VERSION >= "2.0.0" expect(klass).to receive(:new).with(kind_of(URI), kind_of(Hash)).and_return('result') else expect(klass).to receive(:new).with(kind_of(String), kind_of(Hash)).and_return('result') end expect(@request).to receive(:transmit) @request.execute end # TODO: almost none of these tests should actually call transmit, which is # part of the private API it "transmits the request with Net::HTTP" do expect(@http).to receive(:request).with('req', 'payload') expect(@request).to receive(:process_result) @request.send(:transmit, @uri, 'req', 'payload') end # TODO: most of these payload tests are historical relics that actually # belong in payload_spec.rb. Or we need new tests that actually cover the way # that Request#initialize or Request#execute uses the payload. describe "payload" do it "sends nil payloads" do expect(@http).to receive(:request).with('req', nil) expect(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', nil) end it "passes non-hash payloads straight through" do expect(RestClient::Payload.generate("x").to_s).to eq "x" end it "converts a hash payload to urlencoded data" do expect(RestClient::Payload.generate(:a => 'b c+d').to_s).to eq "a=b+c%2Bd" end it "accepts nested hashes in payload" do payload = RestClient::Payload.generate(:user => { :name => 'joe', :location => { :country => 'USA', :state => 'CA' }}).to_s expect(payload).to include('user[name]=joe') expect(payload).to include('user[location][country]=USA') expect(payload).to include('user[location][state]=CA') end end it "set urlencoded content_type header on hash payloads" do req = RestClient::Request.new(method: :post, url: 'http://some/resource', payload: {a: 1}) expect(req.processed_headers.fetch('Content-Type')).to eq 'application/x-www-form-urlencoded' end describe "credentials" do it "sets up the credentials prior to the request" do allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) allow(@request).to receive(:user).and_return('joe') allow(@request).to receive(:password).and_return('mypass') expect(@request).to receive(:setup_credentials).with('req') @request.send(:transmit, @uri, 'req', nil) end it "does not attempt to send any credentials if user is nil" do allow(@request).to receive(:user).and_return(nil) req = double("request") expect(req).not_to receive(:basic_auth) @request.send(:setup_credentials, req) end it "setup credentials when there's a user" do allow(@request).to receive(:user).and_return('joe') allow(@request).to receive(:password).and_return('mypass') req = double("request") expect(req).to receive(:basic_auth).with('joe', 'mypass') @request.send(:setup_credentials, req) end it "does not attempt to send credentials if Authorization header is set" do ['Authorization', 'authorization', 'auTHORization', :authorization].each do |authorization| headers = {authorization => 'Token abc123'} request = RestClient::Request.new(method: :get, url: 'http://some/resource', headers: headers, user: 'joe', password: 'mypass') req = double("net::http request") expect(req).not_to receive(:basic_auth) request.send(:setup_credentials, req) end end end it "catches EOFError and shows the more informative ServerBrokeConnection" do allow(@http).to receive(:request).and_raise(EOFError) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(RestClient::ServerBrokeConnection) end it "catches OpenSSL::SSL::SSLError and raise it back without more informative message" do allow(@http).to receive(:request).and_raise(OpenSSL::SSL::SSLError) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(OpenSSL::SSL::SSLError) end it "catches Timeout::Error and raise the more informative ReadTimeout" do allow(@http).to receive(:request).and_raise(Timeout::Error) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(RestClient::Exceptions::ReadTimeout) end it "catches Errno::ETIMEDOUT and raise the more informative ReadTimeout" do allow(@http).to receive(:request).and_raise(Errno::ETIMEDOUT) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(RestClient::Exceptions::ReadTimeout) end it "catches Net::ReadTimeout and raises RestClient's ReadTimeout", :if => defined?(Net::ReadTimeout) do allow(@http).to receive(:request).and_raise(Net::ReadTimeout) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(RestClient::Exceptions::ReadTimeout) end it "catches Net::OpenTimeout and raises RestClient's OpenTimeout", :if => defined?(Net::OpenTimeout) do allow(@http).to receive(:request).and_raise(Net::OpenTimeout) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(RestClient::Exceptions::OpenTimeout) end it "uses correct error message for ReadTimeout", :if => defined?(Net::ReadTimeout) do allow(@http).to receive(:request).and_raise(Net::ReadTimeout) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(RestClient::Exceptions::ReadTimeout, 'Timed out reading data from server') end it "uses correct error message for OpenTimeout", :if => defined?(Net::OpenTimeout) do allow(@http).to receive(:request).and_raise(Net::OpenTimeout) expect { @request.send(:transmit, @uri, 'req', nil) }.to raise_error(RestClient::Exceptions::OpenTimeout, 'Timed out connecting to server') end it "class method execute wraps constructor" do req = double("rest request") expect(RestClient::Request).to receive(:new).with(1 => 2).and_return(req) expect(req).to receive(:execute) RestClient::Request.execute(1 => 2) end describe "exception" do it "raises Unauthorized when the response is 401" do res = res_double(:code => '401', :[] => ['content-encoding' => ''], :body => '' ) expect { @request.send(:process_result, res, Time.now) }.to raise_error(RestClient::Unauthorized) end it "raises ResourceNotFound when the response is 404" do res = res_double(:code => '404', :[] => ['content-encoding' => ''], :body => '' ) expect { @request.send(:process_result, res, Time.now) }.to raise_error(RestClient::ResourceNotFound) end it "raises RequestFailed otherwise" do res = res_double(:code => '500', :[] => ['content-encoding' => ''], :body => '' ) expect { @request.send(:process_result, res, Time.now) }.to raise_error(RestClient::InternalServerError) end end describe "block usage" do it "returns what asked to" do res = res_double(:code => '401', :[] => ['content-encoding' => ''], :body => '' ) expect(@request.send(:process_result, res, Time.now){|response, request| "foo"}).to eq "foo" end end describe "proxy" do before do # unstub Net::HTTP creation since we need to test it allow(Net::HTTP).to receive(:new).and_call_original @proxy_req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload') end it "creates a proxy class if a proxy url is given" do allow(RestClient).to receive(:proxy).and_return("http://example.com/") allow(RestClient).to receive(:proxy_set?).and_return(true) expect(@proxy_req.net_http_object('host', 80).proxy?).to be true end it "creates a proxy class with the correct address if a IPv6 proxy url is given" do allow(RestClient).to receive(:proxy).and_return("http://[::1]/") allow(RestClient).to receive(:proxy_set?).and_return(true) expect(@proxy_req.net_http_object('host', 80).proxy?).to be true expect(@proxy_req.net_http_object('host', 80).proxy_address).to eq('::1') end it "creates a non-proxy class if a proxy url is not given" do expect(@proxy_req.net_http_object('host', 80).proxy?).to be_falsey end it "disables proxy on a per-request basis" do allow(RestClient).to receive(:proxy).and_return('http://example.com') allow(RestClient).to receive(:proxy_set?).and_return(true) expect(@proxy_req.net_http_object('host', 80).proxy?).to be true disabled_req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :proxy => nil) expect(disabled_req.net_http_object('host', 80).proxy?).to be_falsey end it "sets proxy on a per-request basis" do expect(@proxy_req.net_http_object('some', 80).proxy?).to be_falsey req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :proxy => 'http://example.com') expect(req.net_http_object('host', 80).proxy?).to be true end it "overrides proxy from environment", if: RUBY_VERSION >= '2.0' do allow(ENV).to receive(:[]).with("http_proxy").and_return("http://127.0.0.1") allow(ENV).to receive(:[]).with("no_proxy").and_return(nil) allow(ENV).to receive(:[]).with("NO_PROXY").and_return(nil) allow(Netrc).to receive(:read).and_return({}) req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload') obj = req.net_http_object('host', 80) expect(obj.proxy?).to be true expect(obj.proxy_address).to eq '127.0.0.1' # test original method .proxy? req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :proxy => nil) obj = req.net_http_object('host', 80) expect(obj.proxy?).to be_falsey # stub RestClient.proxy_set? to peek into implementation allow(RestClient).to receive(:proxy_set?).and_return(true) req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload') obj = req.net_http_object('host', 80) expect(obj.proxy?).to be_falsey # test stubbed Net::HTTP.new req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :proxy => nil) expect(Net::HTTP).to receive(:new).with('host', 80, nil, nil, nil, nil) req.net_http_object('host', 80) end it "overrides global proxy with per-request proxy" do allow(RestClient).to receive(:proxy).and_return('http://example.com') allow(RestClient).to receive(:proxy_set?).and_return(true) obj = @proxy_req.net_http_object('host', 80) expect(obj.proxy?).to be true expect(obj.proxy_address).to eq 'example.com' req = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :proxy => 'http://127.0.0.1/') expect(req.net_http_object('host', 80).proxy?).to be true expect(req.net_http_object('host', 80).proxy_address).to eq('127.0.0.1') end end describe "logging" do it "logs a get request" do log = RestClient.log = [] RestClient::Request.new(:method => :get, :url => 'http://url', :headers => {:user_agent => 'rest-client'}).log_request expect(log[0]).to eq %Q{RestClient.get "http://url", "Accept"=>"*/*", "User-Agent"=>"rest-client"\n} end it "logs a post request with a small payload" do log = RestClient.log = [] RestClient::Request.new(:method => :post, :url => 'http://url', :payload => 'foo', :headers => {:user_agent => 'rest-client'}).log_request expect(log[0]).to eq %Q{RestClient.post "http://url", "foo", "Accept"=>"*/*", "Content-Length"=>"3", "User-Agent"=>"rest-client"\n} end it "logs a post request with a large payload" do log = RestClient.log = [] RestClient::Request.new(:method => :post, :url => 'http://url', :payload => ('x' * 1000), :headers => {:user_agent => 'rest-client'}).log_request expect(log[0]).to eq %Q{RestClient.post "http://url", 1000 byte(s) length, "Accept"=>"*/*", "Content-Length"=>"1000", "User-Agent"=>"rest-client"\n} end it "logs input headers as a hash" do log = RestClient.log = [] RestClient::Request.new(:method => :get, :url => 'http://url', :headers => { :accept => 'text/plain', :user_agent => 'rest-client' }).log_request expect(log[0]).to eq %Q{RestClient.get "http://url", "Accept"=>"text/plain", "User-Agent"=>"rest-client"\n} end it "logs a response including the status code, content type, and result body size in bytes" do log = RestClient.log = [] res = res_double(code: '200', class: Net::HTTPOK, body: 'abcd') allow(res).to receive(:[]).with('Content-type').and_return('text/html') response = response_from_res_double(res, @request) response.log_response expect(log).to eq ["# => 200 OK | text/html 4 bytes, 1.00s\n"] end it "logs a response with a nil Content-type" do log = RestClient.log = [] res = res_double(code: '200', class: Net::HTTPOK, body: 'abcd') allow(res).to receive(:[]).with('Content-type').and_return(nil) response = response_from_res_double(res, @request) response.log_response expect(log).to eq ["# => 200 OK | 4 bytes, 1.00s\n"] end it "logs a response with a nil body" do log = RestClient.log = [] res = res_double(code: '200', class: Net::HTTPOK, body: nil) allow(res).to receive(:[]).with('Content-type').and_return('text/html; charset=utf-8') response = response_from_res_double(res, @request) response.log_response expect(log).to eq ["# => 200 OK | text/html 0 bytes, 1.00s\n"] end it 'does not log request password' do log = RestClient.log = [] RestClient::Request.new(:method => :get, :url => 'http://user:password@url', :headers => {:user_agent => 'rest-client'}).log_request expect(log[0]).to eq %Q{RestClient.get "http://user:REDACTED@url", "Accept"=>"*/*", "User-Agent"=>"rest-client"\n} end it 'logs to a passed logger, if provided' do logger = double('logger', '<<' => true) expect(logger).to receive(:<<) RestClient::Request.new(:method => :get, :url => 'http://user:password@url', log: logger).log_request end end it "strips the charset from the response content type" do log = RestClient.log = [] res = res_double(code: '200', class: Net::HTTPOK, body: 'abcd') allow(res).to receive(:[]).with('Content-type').and_return('text/html; charset=utf-8') response = response_from_res_double(res, @request) response.log_response expect(log).to eq ["# => 200 OK | text/html 4 bytes, 1.00s\n"] end describe "timeout" do it "does not set timeouts if not specified" do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload') allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@net).not_to receive(:read_timeout=) expect(@net).not_to receive(:open_timeout=) @request.send(:transmit, @uri, 'req', nil) end it 'sets read_timeout' do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :read_timeout => 123) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@net).to receive(:read_timeout=).with(123) @request.send(:transmit, @uri, 'req', nil) end it "sets open_timeout" do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :open_timeout => 123) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@net).to receive(:open_timeout=).with(123) @request.send(:transmit, @uri, 'req', nil) end it 'sets both timeouts with :timeout' do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => 123) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@net).to receive(:open_timeout=).with(123) expect(@net).to receive(:read_timeout=).with(123) @request.send(:transmit, @uri, 'req', nil) end it 'supersedes :timeout with open/read_timeout' do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => 123, :open_timeout => 34, :read_timeout => 56) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@net).to receive(:open_timeout=).with(34) expect(@net).to receive(:read_timeout=).with(56) @request.send(:transmit, @uri, 'req', nil) end it "disable timeout by setting it to nil" do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :read_timeout => nil, :open_timeout => nil) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@net).to receive(:read_timeout=).with(nil) expect(@net).to receive(:open_timeout=).with(nil) @request.send(:transmit, @uri, 'req', nil) end it 'deprecated: warns when disabling timeout by setting it to -1' do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :read_timeout => -1) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@net).to receive(:read_timeout=).with(nil) expect(fake_stderr { @request.send(:transmit, @uri, 'req', nil) }).to match(/^Deprecated: .*timeout.* nil instead of -1$/) end it "deprecated: disable timeout by setting it to -1" do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :read_timeout => -1, :open_timeout => -1) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) expect(@request).to receive(:warn) expect(@net).to receive(:read_timeout=).with(nil) expect(@request).to receive(:warn) expect(@net).to receive(:open_timeout=).with(nil) @request.send(:transmit, @uri, 'req', nil) end end describe "ssl" do it "uses SSL when the URI refers to a https address" do allow(@uri).to receive(:is_a?).with(URI::HTTPS).and_return(true) expect(@net).to receive(:use_ssl=).with(true) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should default to verifying ssl certificates" do expect(@request.verify_ssl).to eq OpenSSL::SSL::VERIFY_PEER end it "should have expected values for VERIFY_PEER and VERIFY_NONE" do expect(OpenSSL::SSL::VERIFY_NONE).to eq(0) expect(OpenSSL::SSL::VERIFY_PEER).to eq(1) end it "should set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is false" do @request = RestClient::Request.new(:method => :put, :verify_ssl => false, :url => 'http://some/resource', :payload => 'payload') expect(@net).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set net.verify_mode to OpenSSL::SSL::VERIFY_NONE if verify_ssl is true" do @request = RestClient::Request.new(:method => :put, :url => 'https://some/resource', :payload => 'payload', :verify_ssl => true) expect(@net).not_to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should set net.verify_mode to OpenSSL::SSL::VERIFY_PEER if verify_ssl is true" do @request = RestClient::Request.new(:method => :put, :url => 'https://some/resource', :payload => 'payload', :verify_ssl => true) expect(@net).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should set net.verify_mode to OpenSSL::SSL::VERIFY_PEER if verify_ssl is not given" do @request = RestClient::Request.new(:method => :put, :url => 'https://some/resource', :payload => 'payload') expect(@net).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should set net.verify_mode to the passed value if verify_ssl is an OpenSSL constant" do mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :verify_ssl => mode ) expect(@net).to receive(:verify_mode=).with(mode) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should default to not having an ssl_client_cert" do expect(@request.ssl_client_cert).to be(nil) end it "should set the ssl_version if provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_version => "TLSv1" ) expect(@net).to receive(:ssl_version=).with("TLSv1") allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_version if not provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload' ) expect(@net).not_to receive(:ssl_version=).with("TLSv1") allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should set the ssl_ciphers if provided" do ciphers = 'AESGCM:HIGH:!aNULL:!eNULL:RC4+RSA' @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_ciphers => ciphers ) expect(@net).to receive(:ciphers=).with(ciphers) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_ciphers if set to nil" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_ciphers => nil, ) expect(@net).not_to receive(:ciphers=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should set the ssl_client_cert if provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_client_cert => "whatsupdoc!" ) expect(@net).to receive(:cert=).with("whatsupdoc!") allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_client_cert if it is not provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload' ) expect(@net).not_to receive(:cert=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should default to not having an ssl_client_key" do expect(@request.ssl_client_key).to be(nil) end it "should set the ssl_client_key if provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_client_key => "whatsupdoc!" ) expect(@net).to receive(:key=).with("whatsupdoc!") allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_client_key if it is not provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload' ) expect(@net).not_to receive(:key=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should default to not having an ssl_ca_file" do expect(@request.ssl_ca_file).to be(nil) end it "should set the ssl_ca_file if provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_ca_file => "Certificate Authority File" ) expect(@net).to receive(:ca_file=).with("Certificate Authority File") expect(@net).not_to receive(:cert_store=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_ca_file if it is not provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload' ) expect(@net).not_to receive(:ca_file=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should default to not having an ssl_ca_path" do expect(@request.ssl_ca_path).to be(nil) end it "should set the ssl_ca_path if provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_ca_path => "Certificate Authority Path" ) expect(@net).to receive(:ca_path=).with("Certificate Authority Path") expect(@net).not_to receive(:cert_store=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_ca_path if it is not provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload' ) expect(@net).not_to receive(:ca_path=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should set the ssl_cert_store if provided" do store = OpenSSL::X509::Store.new store.set_default_paths @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_cert_store => store ) expect(@net).to receive(:cert_store=).with(store) expect(@net).not_to receive(:ca_path=) expect(@net).not_to receive(:ca_file=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should by default set the ssl_cert_store if no CA info is provided" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload' ) expect(@net).to receive(:cert_store=) expect(@net).not_to receive(:ca_path=) expect(@net).not_to receive(:ca_file=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_cert_store if it is set falsy" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_cert_store => nil, ) expect(@net).not_to receive(:cert_store=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should not set the ssl_verify_callback by default" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', ) expect(@net).not_to receive(:verify_callback=) allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end it "should set the ssl_verify_callback if passed" do callback = lambda {} @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', :ssl_verify_callback => callback, ) expect(@net).to receive(:verify_callback=).with(callback) # we'll read cert_store on jruby # https://github.com/jruby/jruby/issues/597 if RestClient::Platform.jruby? allow(@net).to receive(:cert_store) end allow(@http).to receive(:request) allow(@request).to receive(:process_result) allow(@request).to receive(:response_log) @request.send(:transmit, @uri, 'req', 'payload') end # end it "should still return a response object for 204 No Content responses" do @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload' ) net_http_res = Net::HTTPNoContent.new("", "204", "No Content") allow(net_http_res).to receive(:read_body).and_return(nil) expect(@http).to receive(:request).and_return(net_http_res) response = @request.send(:transmit, @uri, 'req', 'payload') expect(response).not_to be_nil expect(response.code).to eq 204 end describe "raw response" do it "should read the response into a binary-mode tempfile" do @request = RestClient::Request.new(:method => "get", :url => "example.com", :raw_response => true) tempfile = double("tempfile") expect(tempfile).to receive(:binmode) allow(tempfile).to receive(:open) allow(tempfile).to receive(:close) expect(Tempfile).to receive(:new).with("rest-client.").and_return(tempfile) net_http_res = Net::HTTPOK.new(nil, "200", "body") allow(net_http_res).to receive(:read_body).and_return("body") received_tempfile = @request.send(:fetch_body_to_tempfile, net_http_res) expect(received_tempfile).to eq tempfile end end describe 'payloads' do it 'should accept string payloads' do payload = 'Foo' @request = RestClient::Request.new(method: :get, url: 'example.com', :payload => payload) expect(@request).to receive(:process_result) expect(@http).to receive(:request).with('req', payload) @request.send(:transmit, @uri, 'req', payload) end it 'should accept streaming IO payloads' do payload = StringIO.new('streamed') @request = RestClient::Request.new(method: :get, url: 'example.com', :payload => payload) expect(@request).to receive(:process_result) @get = double('net::http::get') expect(@get).to receive(:body_stream=).with(instance_of(RestClient::Payload::Streamed)) allow(@request.net_http_request_class(:GET)).to receive(:new).and_return(@get) expect(@http).to receive(:request).with(@get, nil) @request.execute end end describe 'constructor' do it 'should reject valid URIs with no hostname' do expect(URI.parse('http:///').hostname).to be_nil expect { RestClient::Request.new(method: :get, url: 'http:///') }.to raise_error(URI::InvalidURIError, /\Abad URI/) end it 'should reject invalid URIs' do expect { RestClient::Request.new(method: :get, url: 'http://::') }.to raise_error(URI::InvalidURIError) end end describe 'process_url_params' do it 'should handle basic URL params' do expect(@request.process_url_params('https://example.com/foo', params: {key1: 123, key2: 'abc'})). to eq 'https://example.com/foo?key1=123&key2=abc' expect(@request.process_url_params('https://example.com/foo', params: {'key1' => 123})). to eq 'https://example.com/foo?key1=123' expect(@request.process_url_params('https://example.com/path', params: {foo: 'one two', bar: 'three + four == seven'})). to eq 'https://example.com/path?foo=one+two&bar=three+%2B+four+%3D%3D+seven' end it 'should combine with & when URL params already exist' do expect(@request.process_url_params('https://example.com/path?foo=1', params: {bar: 2})). to eq 'https://example.com/path?foo=1&bar=2' end it 'should handle complex nested URL params per Rack / Rails conventions' do expect(@request.process_url_params('https://example.com/', params: { foo: [1,2,3], null: nil, falsy: false, math: '2+2=4', nested: {'key + escaped' => 'value + escaped', other: [], arr: [1,2]}, })).to eq 'https://example.com/?foo[]=1&foo[]=2&foo[]=3&null&falsy=false&math=2%2B2%3D4' \ '&nested[key+%2B+escaped]=value+%2B+escaped&nested[other]' \ '&nested[arr][]=1&nested[arr][]=2' end it 'should handle ParamsArray objects' do expect(@request.process_url_params('https://example.com/', params: RestClient::ParamsArray.new([[:foo, 1], [:foo, 2]]) )).to eq 'https://example.com/?foo=1&foo=2' end end end rest-client-2.1.0/spec/unit/payload_spec.rb0000644000004100000410000002422613531144232020700 0ustar www-datawww-data# encoding: binary require_relative '_lib' describe RestClient::Payload, :include_helpers do context "Base Payload" do it "should reset stream after to_s" do payload = RestClient::Payload::Base.new('foobar') expect(payload.to_s).to eq 'foobar' expect(payload.to_s).to eq 'foobar' end end context "A regular Payload" do it "should use standard enctype as default content-type" do expect(RestClient::Payload::UrlEncoded.new({}).headers['Content-Type']). to eq 'application/x-www-form-urlencoded' end it "should form properly encoded params" do expect(RestClient::Payload::UrlEncoded.new({:foo => 'bar'}).to_s). to eq "foo=bar" expect(["foo=bar&baz=qux", "baz=qux&foo=bar"]).to include( RestClient::Payload::UrlEncoded.new({:foo => 'bar', :baz => 'qux'}).to_s) end it "should escape parameters" do expect(RestClient::Payload::UrlEncoded.new({'foo + bar' => 'baz'}).to_s). to eq "foo+%2B+bar=baz" end it "should properly handle hashes as parameter" do expect(RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz'}}).to_s). to eq "foo[bar]=baz" expect(RestClient::Payload::UrlEncoded.new({:foo => {:bar => {:baz => 'qux'}}}).to_s). to eq "foo[bar][baz]=qux" end it "should handle many attributes inside a hash" do parameters = RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz', :baz => 'qux'}}).to_s expect(parameters).to eq 'foo[bar]=baz&foo[baz]=qux' end it "should handle attributes inside an array inside an hash" do parameters = RestClient::Payload::UrlEncoded.new({"foo" => [{"bar" => 'baz'}, {"bar" => 'qux'}]}).to_s expect(parameters).to eq 'foo[][bar]=baz&foo[][bar]=qux' end it "should handle arrays inside a hash inside a hash" do parameters = RestClient::Payload::UrlEncoded.new({"foo" => {'even' => [0, 2], 'odd' => [1, 3]}}).to_s expect(parameters).to eq 'foo[even][]=0&foo[even][]=2&foo[odd][]=1&foo[odd][]=3' end it "should form properly use symbols as parameters" do expect(RestClient::Payload::UrlEncoded.new({:foo => :bar}).to_s). to eq "foo=bar" expect(RestClient::Payload::UrlEncoded.new({:foo => {:bar => :baz}}).to_s). to eq "foo[bar]=baz" end it "should properly handle arrays as repeated parameters" do expect(RestClient::Payload::UrlEncoded.new({:foo => ['bar']}).to_s). to eq "foo[]=bar" expect(RestClient::Payload::UrlEncoded.new({:foo => ['bar', 'baz']}).to_s). to eq "foo[]=bar&foo[]=baz" end it 'should not close if stream already closed' do p = RestClient::Payload::UrlEncoded.new({'foo ' => 'bar'}) 3.times {p.close} end end context "A multipart Payload" do it "should use standard enctype as default content-type" do m = RestClient::Payload::Multipart.new({}) allow(m).to receive(:boundary).and_return(123) expect(m.headers['Content-Type']).to eq 'multipart/form-data; boundary=123' end it 'should not error on close if stream already closed' do m = RestClient::Payload::Multipart.new(:file => File.new(test_image_path)) 3.times {m.close} end it "should form properly separated multipart data" do m = RestClient::Payload::Multipart.new([[:bar, "baz"], [:foo, "bar"]]) expect(m.to_s).to eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="bar"\r \r baz\r --#{m.boundary}\r Content-Disposition: form-data; name="foo"\r \r bar\r --#{m.boundary}--\r EOS end it "should not escape parameters names" do m = RestClient::Payload::Multipart.new([["bar ", "baz"]]) expect(m.to_s).to eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="bar "\r \r baz\r --#{m.boundary}--\r EOS end it "should form properly separated multipart data" do f = File.new(test_image_path) m = RestClient::Payload::Multipart.new({:foo => f}) expect(m.to_s).to eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="foo"; filename="ISS.jpg"\r Content-Type: image/jpeg\r \r #{File.open(f.path, 'rb'){|bin| bin.read}}\r --#{m.boundary}--\r EOS end it "should ignore the name attribute when it's not set" do f = File.new(test_image_path) m = RestClient::Payload::Multipart.new({nil => f}) expect(m.to_s).to eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; filename="ISS.jpg"\r Content-Type: image/jpeg\r \r #{File.open(f.path, 'rb'){|bin| bin.read}}\r --#{m.boundary}--\r EOS end it "should detect optional (original) content type and filename" do f = File.new(test_image_path) expect(f).to receive(:content_type).and_return('text/plain') expect(f).to receive(:original_filename).and_return('foo.txt') m = RestClient::Payload::Multipart.new({:foo => f}) expect(m.to_s).to eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="foo"; filename="foo.txt"\r Content-Type: text/plain\r \r #{File.open(f.path, 'rb'){|bin| bin.read}}\r --#{m.boundary}--\r EOS end it "should handle hash in hash parameters" do m = RestClient::Payload::Multipart.new({:bar => {:baz => "foo"}}) expect(m.to_s).to eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="bar[baz]"\r \r foo\r --#{m.boundary}--\r EOS f = File.new(test_image_path) f.instance_eval "def content_type; 'text/plain'; end" f.instance_eval "def original_filename; 'foo.txt'; end" m = RestClient::Payload::Multipart.new({:foo => {:bar => f}}) expect(m.to_s).to eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="foo[bar]"; filename="foo.txt"\r Content-Type: text/plain\r \r #{File.open(f.path, 'rb'){|bin| bin.read}}\r --#{m.boundary}--\r EOS end it 'should correctly format hex boundary' do allow(SecureRandom).to receive(:base64).with(12).and_return('TGs89+ttw/xna6TV') f = File.new(test_image_path) m = RestClient::Payload::Multipart.new({:foo => f}) expect(m.boundary).to eq('-' * 4 + 'RubyFormBoundary' + 'TGs89AttwBxna6TV') end end context "streamed payloads" do it "should properly determine the size of file payloads" do f = File.new(test_image_path) payload = RestClient::Payload.generate(f) expect(payload.size).to eq 72_463 expect(payload.length).to eq 72_463 end it "should properly determine the size of other kinds of streaming payloads" do s = StringIO.new 'foo' payload = RestClient::Payload.generate(s) expect(payload.size).to eq 3 expect(payload.length).to eq 3 begin f = Tempfile.new "rest-client" f.write 'foo bar' payload = RestClient::Payload.generate(f) expect(payload.size).to eq 7 expect(payload.length).to eq 7 ensure f.close end end it "should have a closed? method" do f = File.new(test_image_path) payload = RestClient::Payload.generate(f) expect(payload.closed?).to be_falsey payload.close expect(payload.closed?).to be_truthy end end context "Payload generation" do it "should recognize standard urlencoded params" do expect(RestClient::Payload.generate({"foo" => 'bar'})).to be_kind_of(RestClient::Payload::UrlEncoded) end it "should recognize multipart params" do f = File.new(test_image_path) expect(RestClient::Payload.generate({"foo" => f})).to be_kind_of(RestClient::Payload::Multipart) end it "should be multipart if forced" do expect(RestClient::Payload.generate({"foo" => "bar", :multipart => true})).to be_kind_of(RestClient::Payload::Multipart) end it "should handle deeply nested multipart" do f = File.new(test_image_path) params = {foo: RestClient::ParamsArray.new({nested: f})} expect(RestClient::Payload.generate(params)).to be_kind_of(RestClient::Payload::Multipart) end it "should return data if no of the above" do expect(RestClient::Payload.generate("data")).to be_kind_of(RestClient::Payload::Base) end it "should recognize nested multipart payloads in hashes" do f = File.new(test_image_path) expect(RestClient::Payload.generate({"foo" => {"file" => f}})).to be_kind_of(RestClient::Payload::Multipart) end it "should recognize nested multipart payloads in arrays" do f = File.new(test_image_path) expect(RestClient::Payload.generate({"foo" => [f]})).to be_kind_of(RestClient::Payload::Multipart) end it "should recognize file payloads that can be streamed" do f = File.new(test_image_path) expect(RestClient::Payload.generate(f)).to be_kind_of(RestClient::Payload::Streamed) end it "should recognize other payloads that can be streamed" do expect(RestClient::Payload.generate(StringIO.new('foo'))).to be_kind_of(RestClient::Payload::Streamed) end # hashery gem introduces Hash#read convenience method. Existence of #read method used to determine of content is streameable :/ it "shouldn't treat hashes as streameable" do expect(RestClient::Payload.generate({"foo" => 'bar'})).to be_kind_of(RestClient::Payload::UrlEncoded) end it "should recognize multipart payload wrapped in ParamsArray" do f = File.new(test_image_path) params = RestClient::ParamsArray.new([[:image, f]]) expect(RestClient::Payload.generate(params)).to be_kind_of(RestClient::Payload::Multipart) end it "should handle non-multipart payload wrapped in ParamsArray" do params = RestClient::ParamsArray.new([[:arg, 'value1'], [:arg, 'value2']]) expect(RestClient::Payload.generate(params)).to be_kind_of(RestClient::Payload::UrlEncoded) end it "should pass through Payload::Base and subclasses unchanged" do payloads = [ RestClient::Payload::Base.new('foobar'), RestClient::Payload::UrlEncoded.new({:foo => 'bar'}), RestClient::Payload::Streamed.new(File.new(test_image_path)), RestClient::Payload::Multipart.new({myfile: File.new(test_image_path)}), ] payloads.each do |payload| expect(RestClient::Payload.generate(payload)).to equal(payload) end end end end rest-client-2.1.0/spec/unit/params_array_spec.rb0000644000004100000410000000212013531144232021715 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::ParamsArray do describe '.new' do it 'accepts various types of containers' do as_array = [[:foo, 123], [:foo, 456], [:bar, 789], [:empty, nil]] [ [[:foo, 123], [:foo, 456], [:bar, 789], [:empty, nil]], [{foo: 123}, {foo: 456}, {bar: 789}, {empty: nil}], [{foo: 123}, {foo: 456}, {bar: 789}, {empty: nil}], [{foo: 123}, [:foo, 456], {bar: 789}, {empty: nil}], [{foo: 123}, [:foo, 456], {bar: 789}, [:empty]], ].each do |input| expect(RestClient::ParamsArray.new(input).to_a).to eq as_array end expect(RestClient::ParamsArray.new([]).to_a).to eq [] expect(RestClient::ParamsArray.new([]).empty?).to eq true end it 'rejects various invalid input' do expect { RestClient::ParamsArray.new([[]]) }.to raise_error(IndexError) expect { RestClient::ParamsArray.new([[1,2,3]]) }.to raise_error(ArgumentError) expect { RestClient::ParamsArray.new([1,2,3]) }.to raise_error(NoMethodError) end end end rest-client-2.1.0/spec/unit/restclient_spec.rb0000644000004100000410000000526313531144232021423 0ustar www-datawww-datarequire_relative '_lib' describe RestClient do describe "API" do it "GET" do expect(RestClient::Request).to receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {}) RestClient.get('http://some/resource') end it "POST" do expect(RestClient::Request).to receive(:execute).with(:method => :post, :url => 'http://some/resource', :payload => 'payload', :headers => {}) RestClient.post('http://some/resource', 'payload') end it "PUT" do expect(RestClient::Request).to receive(:execute).with(:method => :put, :url => 'http://some/resource', :payload => 'payload', :headers => {}) RestClient.put('http://some/resource', 'payload') end it "PATCH" do expect(RestClient::Request).to receive(:execute).with(:method => :patch, :url => 'http://some/resource', :payload => 'payload', :headers => {}) RestClient.patch('http://some/resource', 'payload') end it "DELETE" do expect(RestClient::Request).to receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {}) RestClient.delete('http://some/resource') end it "HEAD" do expect(RestClient::Request).to receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {}) RestClient.head('http://some/resource') end it "OPTIONS" do expect(RestClient::Request).to receive(:execute).with(:method => :options, :url => 'http://some/resource', :headers => {}) RestClient.options('http://some/resource') end end describe "logging" do after do RestClient.log = nil end it "uses << if the log is not a string" do log = RestClient.log = [] expect(log).to receive(:<<).with('xyz') RestClient.log << 'xyz' end it "displays the log to stdout" do RestClient.log = 'stdout' expect(STDOUT).to receive(:puts).with('xyz') RestClient.log << 'xyz' end it "displays the log to stderr" do RestClient.log = 'stderr' expect(STDERR).to receive(:puts).with('xyz') RestClient.log << 'xyz' end it "append the log to the requested filename" do RestClient.log = '/tmp/restclient.log' f = double('file handle') expect(File).to receive(:open).with('/tmp/restclient.log', 'a').and_yield(f) expect(f).to receive(:puts).with('xyz') RestClient.log << 'xyz' end end describe 'version' do # test that there is a sane version number to avoid accidental 0.0.0 again it 'has a version > 2.0.0.alpha, < 3.0' do ver = Gem::Version.new(RestClient.version) expect(Gem::Requirement.new('> 2.0.0.alpha', '< 3.0')).to be_satisfied_by(ver) end end end rest-client-2.1.0/spec/unit/utils_spec.rb0000644000004100000410000001267513531144232020414 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::Utils do describe '.get_encoding_from_headers' do it 'assumes no encoding by default for text' do headers = {:content_type => 'text/plain'} expect(RestClient::Utils.get_encoding_from_headers(headers)). to eq nil end it 'returns nil on failures' do expect(RestClient::Utils.get_encoding_from_headers( {:content_type => 'blah'})).to eq nil expect(RestClient::Utils.get_encoding_from_headers( {})).to eq nil expect(RestClient::Utils.get_encoding_from_headers( {:content_type => 'foo; bar=baz'})).to eq nil end it 'handles various charsets' do expect(RestClient::Utils.get_encoding_from_headers( {:content_type => 'text/plain; charset=UTF-8'})).to eq 'UTF-8' expect(RestClient::Utils.get_encoding_from_headers( {:content_type => 'application/json; charset=ISO-8859-1'})). to eq 'ISO-8859-1' expect(RestClient::Utils.get_encoding_from_headers( {:content_type => 'text/html; charset=windows-1251'})). to eq 'windows-1251' expect(RestClient::Utils.get_encoding_from_headers( {:content_type => 'text/html; charset="UTF-16"'})). to eq 'UTF-16' end end describe '.cgi_parse_header' do it 'parses headers', :unless => RUBY_VERSION.start_with?('2.0') do expect(RestClient::Utils.cgi_parse_header('text/plain')). to eq ['text/plain', {}] expect(RestClient::Utils.cgi_parse_header('text/vnd.just.made.this.up')). to eq ['text/vnd.just.made.this.up', {}] expect(RestClient::Utils.cgi_parse_header('text/plain;charset=us-ascii')). to eq ['text/plain', {'charset' => 'us-ascii'}] expect(RestClient::Utils.cgi_parse_header('text/plain ; charset="us-ascii"')). to eq ['text/plain', {'charset' => 'us-ascii'}] expect(RestClient::Utils.cgi_parse_header( 'text/plain ; charset="us-ascii"; another=opt')). to eq ['text/plain', {'charset' => 'us-ascii', 'another' => 'opt'}] expect(RestClient::Utils.cgi_parse_header( 'foo/bar; filename="silly.txt"')). to eq ['foo/bar', {'filename' => 'silly.txt'}] expect(RestClient::Utils.cgi_parse_header( 'foo/bar; filename="strange;name"')). to eq ['foo/bar', {'filename' => 'strange;name'}] expect(RestClient::Utils.cgi_parse_header( 'foo/bar; filename="strange;name";size=123')).to eq \ ['foo/bar', {'filename' => 'strange;name', 'size' => '123'}] expect(RestClient::Utils.cgi_parse_header( 'foo/bar; name="files"; filename="fo\\"o;bar"')).to eq \ ['foo/bar', {'name' => 'files', 'filename' => 'fo"o;bar'}] end end describe '.encode_query_string' do it 'handles simple hashes' do { {foo: 123, bar: 456} => 'foo=123&bar=456', {'foo' => 123, 'bar' => 456} => 'foo=123&bar=456', {foo: 'abc', bar: 'one two'} => 'foo=abc&bar=one+two', {escaped: '1+2=3'} => 'escaped=1%2B2%3D3', {'escaped + key' => 'foo'} => 'escaped+%2B+key=foo', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end it 'handles simple arrays' do { {foo: [1, 2, 3]} => 'foo[]=1&foo[]=2&foo[]=3', {foo: %w{a b c}, bar: [1, 2, 3]} => 'foo[]=a&foo[]=b&foo[]=c&bar[]=1&bar[]=2&bar[]=3', {foo: ['one two', 3]} => 'foo[]=one+two&foo[]=3', {'a+b' => [1,2,3]} => 'a%2Bb[]=1&a%2Bb[]=2&a%2Bb[]=3', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end it 'handles nested hashes' do { {outer: {foo: 123, bar: 456}} => 'outer[foo]=123&outer[bar]=456', {outer: {foo: [1, 2, 3], bar: 'baz'}} => 'outer[foo][]=1&outer[foo][]=2&outer[foo][]=3&outer[bar]=baz', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end it 'handles null and empty values' do { {string: '', empty: nil, list: [], hash: {}, falsey: false } => 'string=&empty&list&hash&falsey=false', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end it 'handles nested nulls' do { {foo: {string: '', empty: nil}} => 'foo[string]=&foo[empty]', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end it 'handles deep nesting' do { {coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]} => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end it 'handles multiple fields with the same name using ParamsArray' do { RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]]) => 'foo=1&foo=2&foo=3', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end it 'handles nested ParamsArrays' do { {foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])} => 'foo[a]=1&foo[a]=2', RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]]) => 'foo[a]=1&foo[a]=2', }.each_pair do |input, expected| expect(RestClient::Utils.encode_query_string(input)).to eq expected end end end end rest-client-2.1.0/spec/unit/resource_spec.rb0000644000004100000410000001323413531144232021073 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::Resource do before do @resource = RestClient::Resource.new('http://some/resource', :user => 'jane', :password => 'mypass', :headers => {'X-Something' => '1'}) end context "Resource delegation" do it "GET" do expect(RestClient::Request).to receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil) @resource.get end it "HEAD" do expect(RestClient::Request).to receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil) @resource.head end it "POST" do 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) @resource.post 'abc', :content_type => 'image/jpg' end it "PUT" do 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) @resource.put 'abc', :content_type => 'image/jpg' end it "PATCH" do 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) @resource.patch 'abc', :content_type => 'image/jpg' end it "DELETE" do expect(RestClient::Request).to receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass', :log => nil) @resource.delete end it "overrides resource headers" do expect(RestClient::Request).to receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {'X-Something' => '2'}, :user => 'jane', :password => 'mypass', :log => nil) @resource.get 'X-Something' => '2' end end it "can instantiate with no user/password" do @resource = RestClient::Resource.new('http://some/resource') end it "is backwards compatible with previous constructor" do @resource = RestClient::Resource.new('http://some/resource', 'user', 'pass') expect(@resource.user).to eq 'user' expect(@resource.password).to eq 'pass' end it "concatenates urls, inserting a slash when it needs one" do expect(@resource.concat_urls('http://example.com', 'resource')).to eq 'http://example.com/resource' end it "concatenates urls, using no slash if the first url ends with a slash" do expect(@resource.concat_urls('http://example.com/', 'resource')).to eq 'http://example.com/resource' end it "concatenates urls, using no slash if the second url starts with a slash" do expect(@resource.concat_urls('http://example.com', '/resource')).to eq 'http://example.com/resource' end it "concatenates even non-string urls, :posts + 1 => 'posts/1'" do expect(@resource.concat_urls(:posts, 1)).to eq 'posts/1' end it "offers subresources via []" do parent = RestClient::Resource.new('http://example.com') expect(parent['posts'].url).to eq 'http://example.com/posts' end it "transports options to subresources" do parent = RestClient::Resource.new('http://example.com', :user => 'user', :password => 'password') expect(parent['posts'].user).to eq 'user' expect(parent['posts'].password).to eq 'password' end it "passes a given block to subresources" do block = proc {|r| r} parent = RestClient::Resource.new('http://example.com', &block) expect(parent['posts'].block).to eq block end it "the block should be overrideable" do block1 = proc {|r| r} block2 = proc {|r| } parent = RestClient::Resource.new('http://example.com', &block1) # parent['posts', &block2].block.should eq block2 # ruby 1.9 syntax expect(parent.send(:[], 'posts', &block2).block).to eq block2 expect(parent.send(:[], 'posts', &block2).block).not_to eq block1 end # Test fails on jruby 9.1.[0-5].* due to # https://github.com/jruby/jruby/issues/4217 it "the block should be overrideable in ruby 1.9 syntax", :unless => (RUBY_ENGINE == 'jruby' && JRUBY_VERSION =~ /\A9\.1\.[0-5]\./) \ do block1 = proc {|r| r} block2 = ->(r) {} parent = RestClient::Resource.new('http://example.com', &block1) expect(parent['posts', &block2].block).to eq block2 expect(parent['posts', &block2].block).not_to eq block1 end it "prints its url with to_s" do expect(RestClient::Resource.new('x').to_s).to eq 'x' end describe 'block' do it 'can use block when creating the resource' do stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404) resource = RestClient::Resource.new('www.example.com') { |response, request| 'foo' } expect(resource.get).to eq 'foo' end it 'can use block when executing the resource' do stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404) resource = RestClient::Resource.new('www.example.com') expect(resource.get { |response, request| 'foo' }).to eq 'foo' end it 'execution block override resource block' do stub_request(:get, 'www.example.com').to_return(:body => '', :status => 404) resource = RestClient::Resource.new('www.example.com') { |response, request| 'foo' } expect(resource.get { |response, request| 'bar' }).to eq 'bar' end end end rest-client-2.1.0/spec/unit/response_spec.rb0000644000004100000410000003051513531144232021103 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::Response, :include_helpers do before do @net_http_res = res_double(to_hash: {'Status' => ['200 OK']}, code: '200', body: 'abc') @example_url = 'http://example.com' @request = request_double(url: @example_url, method: 'get') @response = response_from_res_double(@net_http_res, @request, duration: 1) end it "behaves like string" do expect(@response.to_s).to eq 'abc' expect(@response.to_str).to eq 'abc' expect(@response).to receive(:warn) expect(@response.to_i).to eq 0 end it "accepts nil strings and sets it to empty for the case of HEAD" do # TODO expect(RestClient::Response.create(nil, @net_http_res, @request).to_s).to eq "" end describe 'header processing' do it "test headers and raw headers" do expect(@response.raw_headers["Status"][0]).to eq "200 OK" expect(@response.headers[:status]).to eq "200 OK" end it 'handles multiple headers by joining with comma' do net_http_res = res_double(to_hash: {'My-Header' => ['foo', 'bar']}, code: '200', body: nil) example_url = 'http://example.com' request = request_double(url: example_url, method: 'get') response = response_from_res_double(net_http_res, request) expect(response.raw_headers['My-Header']).to eq ['foo', 'bar'] expect(response.headers[:my_header]).to eq 'foo, bar' end end describe "cookie processing" do it "should correctly deal with one Set-Cookie header with one cookie inside" do header_val = "main_page=main_page_no_rewrite; path=/; expires=Sat, 10-Jan-2037 15:03:14 GMT".freeze net_http_res = double('net http response', :to_hash => {"etag" => ["\"e1ac1a2df945942ef4cac8116366baad\""], "set-cookie" => [header_val]}) response = RestClient::Response.create('abc', net_http_res, @request) expect(response.headers[:set_cookie]).to eq [header_val] expect(response.cookies).to eq({ "main_page" => "main_page_no_rewrite" }) end it "should correctly deal with multiple cookies [multiple Set-Cookie headers]" do 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"]}) response = RestClient::Response.create('abc', net_http_res, @request) 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"] expect(response.cookies).to eq({ "main_page" => "main_page_no_rewrite", "remember_me" => "", "user" => "somebody" }) end it "should correctly deal with multiple cookies [one Set-Cookie header with multiple cookies]" do 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"]}) response = RestClient::Response.create('abc', net_http_res, @request) expect(response.cookies).to eq({ "main_page" => "main_page_no_rewrite", "remember_me" => "", "user" => "somebody" }) end end describe "exceptions processing" do it "should return itself for normal codes" do (200..206).each do |code| net_http_res = res_double(:code => '200') resp = RestClient::Response.create('abc', net_http_res, @request) resp.return! end end it "should throw an exception for other codes" do RestClient::Exceptions::EXCEPTIONS_MAP.each_pair do |code, exc| unless (200..207).include? code net_http_res = res_double(:code => code.to_i) resp = RestClient::Response.create('abc', net_http_res, @request) allow(@request).to receive(:max_redirects).and_return(5) expect { resp.return! }.to raise_error(exc) end end end end describe "redirection" do it "follows a redirection when the request is a get" do stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'}) stub_request(:get, 'http://new/resource').to_return(:body => 'Foo') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo' end it "keeps redirection history" do stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'}) stub_request(:get, 'http://new/resource').to_return(:body => 'Foo') r = RestClient::Request.execute(url: 'http://some/resource', method: :get) expect(r.body).to eq 'Foo' expect(r.history.length).to eq 1 expect(r.history.fetch(0)).to be_a(RestClient::Response) expect(r.history.fetch(0).code).to be 301 end it "follows a redirection and keep the parameters" do 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'}) stub_request(:get, 'http://new/resource').with(:headers => {'Accept' => 'application/json'}, :basic_auth => ['foo', 'bar']).to_return(:body => 'Foo') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :user => 'foo', :password => 'bar', :headers => {:accept => :json}).body).to eq 'Foo' end it "follows a redirection and keep the cookies" do stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Set-Cookie' => 'Foo=Bar', 'Location' => 'http://some/new_resource', }) stub_request(:get, 'http://some/new_resource').with(:headers => {'Cookie' => 'Foo=Bar'}).to_return(:body => 'Qux') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Qux' end it 'respects cookie domains on redirect' do stub_request(:get, 'http://some.example.com/').to_return(:body => '', :status => 301, :headers => {'Set-Cookie' => 'Foo=Bar', 'Location' => 'http://new.example.com/', }) stub_request(:get, 'http://new.example.com/').with( :headers => {'Cookie' => 'passedthrough=1'}).to_return(:body => 'Qux') 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' end it "doesn't follow a 301 when the request is a post" do net_http_res = res_double(:code => 301) response = response_from_res_double(net_http_res, request_double(method: 'post')) expect { response.return! }.to raise_error(RestClient::MovedPermanently) end it "doesn't follow a 302 when the request is a post" do net_http_res = res_double(:code => 302) response = response_from_res_double(net_http_res, request_double(method: 'post')) expect { response.return! }.to raise_error(RestClient::Found) end it "doesn't follow a 307 when the request is a post" do net_http_res = res_double(:code => 307) response = response_from_res_double(net_http_res, request_double(method: 'post')) expect(response).not_to receive(:follow_redirection) expect { response.return! }.to raise_error(RestClient::TemporaryRedirect) end it "doesn't follow a redirection when the request is a put" do net_http_res = res_double(:code => 301) response = response_from_res_double(net_http_res, request_double(method: 'put')) expect { response.return! }.to raise_error(RestClient::MovedPermanently) end it "follows a redirection when the request is a post and result is a 303" do stub_request(:put, 'http://some/resource').to_return(:body => '', :status => 303, :headers => {'Location' => 'http://new/resource'}) stub_request(:get, 'http://new/resource').to_return(:body => 'Foo') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :put).body).to eq 'Foo' end it "follows a redirection when the request is a head" do stub_request(:head, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'}) stub_request(:head, 'http://new/resource').to_return(:body => 'Foo') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :head).body).to eq 'Foo' end it "handles redirects with relative paths" do stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'index'}) stub_request(:get, 'http://some/index').to_return(:body => 'Foo') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo' end it "handles redirects with relative path and query string" do stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'index?q=1'}) stub_request(:get, 'http://some/index?q=1').to_return(:body => 'Foo') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo' end it "follow a redirection when the request is a get and the response is in the 30x range" do stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'}) stub_request(:get, 'http://new/resource').to_return(:body => 'Foo') expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body).to eq 'Foo' end it "follows no more than 10 redirections before raising error" do stub_request(:get, 'http://some/redirect-1').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'}) stub_request(:get, 'http://some/redirect-2').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'}) expect { RestClient::Request.execute(url: 'http://some/redirect-1', method: :get) }.to raise_error(RestClient::MovedPermanently) { |ex| ex.response.history.each {|r| expect(r).to be_a(RestClient::Response) } expect(ex.response.history.length).to eq 10 } expect(WebMock).to have_requested(:get, 'http://some/redirect-2').times(10) end it "follows no more than max_redirects redirections, if specified" do stub_request(:get, 'http://some/redirect-1').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'}) stub_request(:get, 'http://some/redirect-2').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/redirect-2'}) expect { RestClient::Request.execute(url: 'http://some/redirect-1', method: :get, max_redirects: 5) }.to raise_error(RestClient::MovedPermanently) { |ex| expect(ex.response.history.length).to eq 5 } expect(WebMock).to have_requested(:get, 'http://some/redirect-2').times(5) end it "allows for manual following of redirects" do stub_request(:get, 'http://some/redirect-1').to_return(:body => '', :status => 301, :headers => {'Location' => 'http://some/resource'}) stub_request(:get, 'http://some/resource').to_return(:body => 'Qux', :status => 200) begin RestClient::Request.execute(url: 'http://some/redirect-1', method: :get, max_redirects: 0) rescue RestClient::MovedPermanently => err resp = err.response.follow_redirection else raise 'notreached' end expect(resp.code).to eq 200 expect(resp.body).to eq 'Qux' end end describe "logging" do it "uses the request's logger" do stub_request(:get, 'http://some/resource').to_return(body: 'potato', status: 200) logger = double('logger', '<<' => true) request = RestClient::Request.new(url: 'http://some/resource', method: :get, log: logger) expect(logger).to receive(:<<) request.execute end end end rest-client-2.1.0/spec/unit/exceptions_spec.rb0000644000004100000410000000722513531144232021430 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::Exception do it "returns a 'message' equal to the class name if the message is not set, because 'message' should not be nil" do e = RestClient::Exception.new expect(e.message).to eq "RestClient::Exception" end it "returns the 'message' that was set" do e = RestClient::Exception.new message = "An explicitly set message" e.message = message expect(e.message).to eq message end it "sets the exception message to ErrorMessage" do expect(RestClient::ResourceNotFound.new.message).to eq 'Not Found' end it "contains exceptions in RestClient" do expect(RestClient::Unauthorized.new).to be_a_kind_of(RestClient::Exception) expect(RestClient::ServerBrokeConnection.new).to be_a_kind_of(RestClient::Exception) end end describe RestClient::ServerBrokeConnection do it "should have a default message of 'Server broke connection'" do e = RestClient::ServerBrokeConnection.new expect(e.message).to eq 'Server broke connection' end end describe RestClient::RequestFailed do before do @response = double('HTTP Response', :code => '502') end it "stores the http response on the exception" do response = "response" begin raise RestClient::RequestFailed, response rescue RestClient::RequestFailed => e expect(e.response).to eq response end end it "http_code convenience method for fetching the code as an integer" do expect(RestClient::RequestFailed.new(@response).http_code).to eq 502 end it "http_body convenience method for fetching the body (decoding when necessary)" do expect(RestClient::RequestFailed.new(@response).http_code).to eq 502 expect(RestClient::RequestFailed.new(@response).message).to eq 'HTTP status code 502' end it "shows the status code in the message" do expect(RestClient::RequestFailed.new(@response).to_s).to match(/502/) end end describe RestClient::ResourceNotFound do it "also has the http response attached" do response = "response" begin raise RestClient::ResourceNotFound, response rescue RestClient::ResourceNotFound => e expect(e.response).to eq response end end it 'stores the body on the response of the exception' do body = "body" stub_request(:get, "www.example.com").to_return(:body => body, :status => 404) begin RestClient.get "www.example.com" raise rescue RestClient::ResourceNotFound => e expect(e.response.body).to eq body end end end describe "backwards compatibility" do it 'aliases RestClient::NotFound as ResourceNotFound' do expect(RestClient::ResourceNotFound).to eq RestClient::NotFound end it 'aliases old names for HTTP 413, 414, 416' do expect(RestClient::RequestEntityTooLarge).to eq RestClient::PayloadTooLarge expect(RestClient::RequestURITooLong).to eq RestClient::URITooLong expect(RestClient::RequestedRangeNotSatisfiable).to eq RestClient::RangeNotSatisfiable end it 'subclasses NotFound from RequestFailed, ExceptionWithResponse' do expect(RestClient::NotFound).to be < RestClient::RequestFailed expect(RestClient::NotFound).to be < RestClient::ExceptionWithResponse end it 'subclasses timeout from RestClient::RequestTimeout, RequestFailed, EWR' do expect(RestClient::Exceptions::OpenTimeout).to be < RestClient::Exceptions::Timeout expect(RestClient::Exceptions::ReadTimeout).to be < RestClient::Exceptions::Timeout expect(RestClient::Exceptions::Timeout).to be < RestClient::RequestTimeout expect(RestClient::Exceptions::Timeout).to be < RestClient::RequestFailed expect(RestClient::Exceptions::Timeout).to be < RestClient::ExceptionWithResponse end end rest-client-2.1.0/spec/unit/windows/0000755000004100000410000000000013531144232017374 5ustar www-datawww-datarest-client-2.1.0/spec/unit/windows/root_certs_spec.rb0000644000004100000410000000111513531144232023114 0ustar www-datawww-datarequire_relative '../_lib' describe 'RestClient::Windows::RootCerts', :if => RestClient::Platform.windows? do let(:x509_store) { RestClient::Windows::RootCerts.instance.to_a } it 'should return at least one X509 certificate' do expect(x509_store.to_a.size).to be >= 1 end it 'should return an X509 certificate with a subject' do x509 = x509_store.first expect(x509.subject.to_s).to match(/CN=.*/) end it 'should return X509 certificate objects' do x509_store.each do |cert| expect(cert).to be_a(OpenSSL::X509::Certificate) end end end rest-client-2.1.0/spec/unit/request2_spec.rb0000644000004100000410000000502013531144232021010 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::Request, :include_helpers do context 'params for GET requests' do it "manage params for get requests" do stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200) expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => {:a => :b, 'c' => 'd'}}).body).to eq 'foo' stub_request(:get, 'http://some/resource').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200) expect(RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => :a}).body).to eq 'foo' end it 'adds GET params when params are present in URL' do stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200) expect(RestClient::Request.execute(:url => 'http://some/resource?a=b', :method => :get, :headers => {:foo => :bar, :params => {:c => 'd'}}).body).to eq 'foo' end it 'encodes nested GET params' do 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) expect(RestClient::Request.execute(url: 'http://some/resource', method: :get, headers: { params: { a: { foo: [1,2], bar: nil, }, b: 'foo bar', math: '2 + 2 == 4', } }).body).to eq 'foo' end end it "can use a block to process response" do response_value = nil block = proc do |http_response| response_value = http_response.body end stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*', 'Foo'=>'bar'}).to_return(:body => 'foo', :status => 200) RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => {:a => :b, 'c' => 'd'}}, :block_response => block) expect(response_value).to eq "foo" end it 'closes payload if not nil' do test_file = File.new(test_image_path) stub_request(:post, 'http://some/resource').with(:headers => {'Accept'=>'*/*'}).to_return(:body => 'foo', :status => 200) RestClient::Request.execute(:url => 'http://some/resource', :method => :post, :payload => {:file => test_file}) expect(test_file.closed?).to be true end end rest-client-2.1.0/spec/unit/_lib.rb0000644000004100000410000000004213531144232017130 0ustar www-datawww-datarequire_relative '../spec_helper' rest-client-2.1.0/spec/unit/abstract_response_spec.rb0000644000004100000410000001337013531144232022766 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::AbstractResponse, :include_helpers do # Sample class implementing AbstractResponse used for testing. class MyAbstractResponse include RestClient::AbstractResponse attr_accessor :size def initialize(net_http_res, request) response_set_vars(net_http_res, request, Time.now - 1) end end before do @net_http_res = res_double() @request = request_double(url: 'http://example.com', method: 'get') @response = MyAbstractResponse.new(@net_http_res, @request) end it "fetches the numeric response code" do expect(@net_http_res).to receive(:code).and_return('200') expect(@response.code).to eq 200 end it "has a nice description" do expect(@net_http_res).to receive(:to_hash).and_return({'Content-Type' => ['application/pdf']}) expect(@net_http_res).to receive(:code).and_return('200') expect(@response.description).to eq "200 OK | application/pdf bytes\n" end describe '.beautify_headers' do it "beautifies the headers by turning the keys to symbols" do h = RestClient::AbstractResponse.beautify_headers('content-type' => [ 'x' ]) expect(h.keys.first).to eq :content_type end it "beautifies the headers by turning the values to strings instead of one-element arrays" do h = RestClient::AbstractResponse.beautify_headers('x' => [ 'text/html' ] ) expect(h.values.first).to eq 'text/html' end it 'joins multiple header values by comma' do expect(RestClient::AbstractResponse.beautify_headers( {'My-Header' => ['one', 'two']} )).to eq({:my_header => 'one, two'}) end it 'leaves set-cookie headers as array' do expect(RestClient::AbstractResponse.beautify_headers( {'Set-Cookie' => ['cookie1=foo', 'cookie2=bar']} )).to eq({:set_cookie => ['cookie1=foo', 'cookie2=bar']}) end end it "fetches the headers" do expect(@net_http_res).to receive(:to_hash).and_return('content-type' => [ 'text/html' ]) expect(@response.headers).to eq({ :content_type => 'text/html' }) end it "extracts cookies from response headers" do expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/']) expect(@response.cookies).to eq({ 'session_id' => '1' }) end it "extract strange cookies" do expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/']) expect(@response.headers).to eq({:set_cookie => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/']}) expect(@response.cookies).to eq({ 'session_id' => 'ZJ/HQVH6YE+rVkTpn0zvTQ==' }) end it "doesn't escape cookies" do expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca; path=/']) expect(@response.cookies).to eq({ 'session_id' => 'BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca' }) end describe '.cookie_jar' do it 'extracts cookies into cookie jar' do expect(@net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/']) expect(@response.cookie_jar).to be_a HTTP::CookieJar cookie = @response.cookie_jar.cookies.first expect(cookie.domain).to eq 'example.com' expect(cookie.name).to eq 'session_id' expect(cookie.value).to eq '1' expect(cookie.path).to eq '/' end it 'handles cookies when URI scheme is implicit' do net_http_res = double('net http response') expect(net_http_res).to receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/']) request = double('request', url: 'example.com', uri: URI.parse('http://example.com'), method: 'get', cookie_jar: HTTP::CookieJar.new, redirection_history: nil) response = MyAbstractResponse.new(net_http_res, request) expect(response.cookie_jar).to be_a HTTP::CookieJar cookie = response.cookie_jar.cookies.first expect(cookie.domain).to eq 'example.com' expect(cookie.name).to eq 'session_id' expect(cookie.value).to eq '1' expect(cookie.path).to eq '/' end end it "can access the net http result directly" do expect(@response.net_http_res).to eq @net_http_res end describe "#return!" do it "should return the response itself on 200-codes" do expect(@net_http_res).to receive(:code).and_return('200') expect(@response.return!).to be_equal(@response) end it "should raise RequestFailed on unknown codes" do expect(@net_http_res).to receive(:code).and_return('1000') expect { @response.return! }.to raise_error RestClient::RequestFailed end it "should raise an error on a redirection after non-GET/HEAD requests" do expect(@net_http_res).to receive(:code).and_return('301') expect(@request).to receive(:method).and_return('put') expect(@response).not_to receive(:follow_redirection) expect { @response.return! }.to raise_error RestClient::RequestFailed end it "should follow 302 redirect" do expect(@net_http_res).to receive(:code).and_return('302') expect(@response).to receive(:check_max_redirects).and_return('fake-check') expect(@response).to receive(:follow_redirection).and_return('fake-redirection') expect(@response.return!).to eq 'fake-redirection' end it "should gracefully handle 302 redirect with no location header" do @net_http_res = res_double(code: 302) @request = request_double() @response = MyAbstractResponse.new(@net_http_res, @request) expect(@response).to receive(:check_max_redirects).and_return('fake-check') expect { @response.return! }.to raise_error RestClient::Found end end end rest-client-2.1.0/spec/spec_helper.rb0000644000004100000410000000151213531144232017540 0ustar www-datawww-datarequire 'webmock/rspec' require 'rest-client' require_relative './helpers' # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| config.raise_errors_for_deprecations! # Run specs in random order to surface order dependencies. If you find an # order dependency and want to debug it, you can fix the order by providing # the seed, which is printed after each run. # --seed 1234 config.order = 'random' # always run with ruby warnings enabled # TODO: figure out why this is so obscenely noisy (rspec bug?) # config.warnings = true # add helpers config.include Helpers, :include_helpers config.mock_with :rspec do |mocks| mocks.yield_receiver_to_any_instance_implementation_blocks = true end end # always run with ruby warnings enabled (see above) $VERBOSE = true rest-client-2.1.0/spec/integration/0000755000004100000410000000000013531144232017246 5ustar www-datawww-datarest-client-2.1.0/spec/integration/httpbin_spec.rb0000644000004100000410000000711413531144232022260 0ustar www-datawww-datarequire_relative '_lib' require 'json' require 'zlib' describe RestClient::Request do before(:all) do WebMock.disable! end after(:all) do WebMock.enable! end def default_httpbin_url # add a hack to work around java/jruby bug # java.lang.RuntimeException: Could not generate DH keypair with backtrace # Also (2017-04-09) Travis Jruby versions have a broken CA keystore if ENV['TRAVIS_RUBY_VERSION'] =~ /\Ajruby-/ 'http://httpbin.org/' else 'https://httpbin.org/' end end def httpbin(suffix='') url = ENV.fetch('HTTPBIN_URL', default_httpbin_url) unless url.end_with?('/') url += '/' end url + suffix end def execute_httpbin(suffix, opts={}) opts = {url: httpbin(suffix)}.merge(opts) RestClient::Request.execute(opts) end def execute_httpbin_json(suffix, opts={}) JSON.parse(execute_httpbin(suffix, opts)) end describe '.execute' do it 'sends a user agent' do data = execute_httpbin_json('user-agent', method: :get) expect(data['user-agent']).to match(/rest-client/) end it 'receives cookies on 302' do expect { execute_httpbin('cookies/set?foo=bar', method: :get, max_redirects: 0) }.to raise_error(RestClient::Found) { |ex| expect(ex.http_code).to eq 302 expect(ex.response.cookies['foo']).to eq 'bar' } end it 'passes along cookies through 302' do data = execute_httpbin_json('cookies/set?foo=bar', method: :get) expect(data).to have_key('cookies') expect(data['cookies']['foo']).to eq 'bar' end it 'handles quote wrapped cookies' do expect { execute_httpbin('cookies/set?foo=' + CGI.escape('"bar:baz"'), method: :get, max_redirects: 0) }.to raise_error(RestClient::Found) { |ex| expect(ex.http_code).to eq 302 expect(ex.response.cookies['foo']).to eq '"bar:baz"' } end it 'sends basic auth' do user = 'user' pass = 'pass' data = execute_httpbin_json("basic-auth/#{user}/#{pass}", method: :get, user: user, password: pass) expect(data).to eq({'authenticated' => true, 'user' => user}) expect { execute_httpbin_json("basic-auth/#{user}/#{pass}", method: :get, user: user, password: 'badpass') }.to raise_error(RestClient::Unauthorized) { |ex| expect(ex.http_code).to eq 401 } end it 'handles gzipped/deflated responses' do [['gzip', 'gzipped'], ['deflate', 'deflated']].each do |encoding, var| raw = execute_httpbin(encoding, method: :get) begin data = JSON.parse(raw) rescue StandardError puts "Failed to parse: " + raw.inspect raise end expect(data['method']).to eq 'GET' expect(data.fetch(var)).to be true end end it 'does not uncompress response when accept-encoding is set' do # == gzip == raw = execute_httpbin('gzip', method: :get, headers: {accept_encoding: 'gzip, deflate'}) # check for gzip magic number expect(raw.body).to start_with("\x1F\x8B".b) decoded = Zlib::GzipReader.new(StringIO.new(raw.body)).read parsed = JSON.parse(decoded) expect(parsed['method']).to eq 'GET' expect(parsed.fetch('gzipped')).to be true # == delate == raw = execute_httpbin('deflate', method: :get, headers: {accept_encoding: 'gzip, deflate'}) decoded = Zlib::Inflate.new.inflate(raw.body) parsed = JSON.parse(decoded) expect(parsed['method']).to eq 'GET' expect(parsed.fetch('deflated')).to be true end end end rest-client-2.1.0/spec/integration/request_spec.rb0000644000004100000410000001030213531144232022271 0ustar www-datawww-datarequire_relative '_lib' describe RestClient::Request do before(:all) do WebMock.disable! end after(:all) do WebMock.enable! end describe "ssl verification" do it "is successful with the correct ca_file" do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "digicert.crt") ) expect { request.execute }.to_not raise_error end it "is successful with the correct ca_path" do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :ssl_ca_path => File.join(File.dirname(__FILE__), "capath_digicert") ) expect { request.execute }.to_not raise_error end # TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just # pass through OpenSSL::SSL::SSLError directly. See note in # lib/restclient/request.rb. # # On OS X, this test fails since Apple has patched OpenSSL to always fall # back on the system CA store. it "is unsuccessful with an incorrect ca_file", :unless => RestClient::Platform.mac_mri? do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt") ) expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified) end # On OS X, this test fails since Apple has patched OpenSSL to always fall # back on the system CA store. it "is unsuccessful with an incorrect ca_path", :unless => RestClient::Platform.mac_mri? do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :ssl_ca_path => File.join(File.dirname(__FILE__), "capath_verisign") ) expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified) end it "is successful using the default system cert store" do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :verify_ssl => true, ) expect {request.execute }.to_not raise_error end it "executes the verify_callback" do ran_callback = false request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :verify_ssl => true, :ssl_verify_callback => lambda { |preverify_ok, store_ctx| ran_callback = true preverify_ok }, ) expect {request.execute }.to_not raise_error expect(ran_callback).to eq(true) end it "fails verification when the callback returns false", :unless => RestClient::Platform.mac_mri? do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :verify_ssl => true, :ssl_verify_callback => lambda { |preverify_ok, store_ctx| false }, ) expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified) end it "succeeds verification when the callback returns true", :unless => RestClient::Platform.mac_mri? do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :verify_ssl => true, :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt"), :ssl_verify_callback => lambda { |preverify_ok, store_ctx| true }, ) expect { request.execute }.to_not raise_error end end describe "timeouts" do it "raises OpenTimeout when it hits an open timeout" do request = RestClient::Request.new( :method => :get, :url => 'http://www.mozilla.org', :open_timeout => 1e-10, ) expect { request.execute }.to( raise_error(RestClient::Exceptions::OpenTimeout)) end it "raises ReadTimeout when it hits a read timeout via :read_timeout" do request = RestClient::Request.new( :method => :get, :url => 'https://www.mozilla.org', :read_timeout => 1e-10, ) expect { request.execute }.to( raise_error(RestClient::Exceptions::ReadTimeout)) end end end rest-client-2.1.0/spec/integration/capath_digicert/0000755000004100000410000000000013531144232022360 5ustar www-datawww-datarest-client-2.1.0/spec/integration/capath_digicert/3513523f.00000644000004100000410000000252013531144232023433 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/capath_digicert/digicert.crt0000644000004100000410000000247213531144232024671 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/capath_digicert/README0000644000004100000410000000047713531144232023250 0ustar www-datawww-dataThe CA path symlinks can be created by c_rehash(1ssl). But in order for the tests to work on Windows, they have to be regular files. You can turn them all into regular files by running this on a GNU system: for file in $(find . -type l); do cp -iv --remove-destination $(readlink -e $file) $file done rest-client-2.1.0/spec/integration/capath_digicert/399e7759.00000644000004100000410000000252013531144232023465 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/certs/0000755000004100000410000000000013531144232020366 5ustar www-datawww-datarest-client-2.1.0/spec/integration/certs/digicert.crt0000644000004100000410000000247213531144232022677 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt 43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg 06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/certs/verisign.crt0000644000004100000410000000150213531144232022724 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/integration_spec.rb0000644000004100000410000000762213531144232023137 0ustar www-datawww-data# -*- coding: utf-8 -*- require_relative '_lib' require 'base64' describe RestClient do it "a simple request" do body = 'abc' stub_request(:get, "www.example.com").to_return(:body => body, :status => 200) response = RestClient.get "www.example.com" expect(response.code).to eq 200 expect(response.body).to eq body end it "a 404" do body = "Ho hai ! I'm not here !" stub_request(:get, "www.example.com").to_return(:body => body, :status => 404) begin RestClient.get "www.example.com" raise rescue RestClient::ResourceNotFound => e expect(e.http_code).to eq 404 expect(e.response.code).to eq 404 expect(e.response.body).to eq body expect(e.http_body).to eq body end end describe 'charset parsing' do it 'handles utf-8' do body = "λ".force_encoding('ASCII-8BIT') stub_request(:get, "www.example.com").to_return( :body => body, :status => 200, :headers => { 'Content-Type' => 'text/plain; charset=UTF-8' }) response = RestClient.get "www.example.com" expect(response.encoding).to eq Encoding::UTF_8 expect(response.valid_encoding?).to eq true end it 'handles windows-1252' do body = "\xff".force_encoding('ASCII-8BIT') stub_request(:get, "www.example.com").to_return( :body => body, :status => 200, :headers => { 'Content-Type' => 'text/plain; charset=windows-1252' }) response = RestClient.get "www.example.com" expect(response.encoding).to eq Encoding::WINDOWS_1252 expect(response.encode('utf-8')).to eq "ÿ" expect(response.valid_encoding?).to eq true end it 'handles binary' do body = "\xfe".force_encoding('ASCII-8BIT') stub_request(:get, "www.example.com").to_return( :body => body, :status => 200, :headers => { 'Content-Type' => 'application/octet-stream; charset=binary' }) response = RestClient.get "www.example.com" expect(response.encoding).to eq Encoding::BINARY expect { response.encode('utf-8') }.to raise_error(Encoding::UndefinedConversionError) expect(response.valid_encoding?).to eq true end it 'handles euc-jp' do body = "\xA4\xA2\xA4\xA4\xA4\xA6\xA4\xA8\xA4\xAA". force_encoding(Encoding::BINARY) body_utf8 = 'あいうえお' expect(body_utf8.encoding).to eq Encoding::UTF_8 stub_request(:get, 'www.example.com').to_return( :body => body, :status => 200, :headers => { 'Content-Type' => 'text/plain; charset=EUC-JP' }) response = RestClient.get 'www.example.com' expect(response.encoding).to eq Encoding::EUC_JP expect(response.valid_encoding?).to eq true expect(response.length).to eq 5 expect(response.encode('utf-8')).to eq body_utf8 end it 'defaults to the default encoding' do stub_request(:get, 'www.example.com').to_return( body: 'abc', status: 200, headers: { 'Content-Type' => 'text/plain' }) response = RestClient.get 'www.example.com' # expect(response.encoding).to eq Encoding.default_external expect(response.encoding).to eq Encoding::UTF_8 end it 'handles invalid encoding' do stub_request(:get, 'www.example.com').to_return( body: 'abc', status: 200, headers: { 'Content-Type' => 'text; charset=plain' }) response = RestClient.get 'www.example.com' # expect(response.encoding).to eq Encoding.default_external expect(response.encoding).to eq Encoding::UTF_8 end it 'leaves images as binary' do gif = Base64.strict_decode64('R0lGODlhAQABAAAAADs=') stub_request(:get, 'www.example.com').to_return( body: gif, status: 200, headers: { 'Content-Type' => 'image/gif' }) response = RestClient.get 'www.example.com' expect(response.encoding).to eq Encoding::BINARY end end end rest-client-2.1.0/spec/integration/capath_verisign/0000755000004100000410000000000013531144232022414 5ustar www-datawww-datarest-client-2.1.0/spec/integration/capath_verisign/7651b327.00000644000004100000410000000150213531144232023473 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/capath_verisign/415660c1.00000644000004100000410000000150213531144232023464 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/capath_verisign/verisign.crt0000644000004100000410000000150213531144232024752 0ustar www-datawww-data-----BEGIN CERTIFICATE----- MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k -----END CERTIFICATE----- rest-client-2.1.0/spec/integration/capath_verisign/README0000644000004100000410000000047713531144232023304 0ustar www-datawww-dataThe CA path symlinks can be created by c_rehash(1ssl). But in order for the tests to work on Windows, they have to be regular files. You can turn them all into regular files by running this on a GNU system: for file in $(find . -type l); do cp -iv --remove-destination $(readlink -e $file) $file done rest-client-2.1.0/spec/integration/_lib.rb0000644000004100000410000000004213531144232020474 0ustar www-datawww-datarequire_relative '../spec_helper' rest-client-2.1.0/.rubocop.yml0000644000004100000410000000014413531144232016242 0ustar www-datawww-data--- inherit_from: - .rubocop-disables.yml AllCops: Exclude: - 'tmp/*.rb' - 'vendor/**/*' rest-client-2.1.0/AUTHORS0000644000004100000410000000275113531144232015046 0ustar www-datawww-dataThe Ruby REST Client would not be what it is today without the help of the following kind souls: Adam Jacob Adam Wiggins Adrian Rangel Alex Tomlins Aman Gupta Andy Brody Avi Deitcher Blake Mizerany Brad Ediger Braintree Brian Donovan Caleb Land Chris Dinn Chris Frohoff Chris Green Coda Hale Crawford Cyril Rohr Dan Mayer Dario Hamidi Darren Coxall David Backeus David Perkowski Dmitri Dolguikh Dusty Doris Dylan Egan El Draper Evan Broder Evan Smith François Beausoleil Gabriele Cirulli Garry Shutler Giovanni Cappellotto Greg Borenstein Harm Aarts Hiro Asari Hugh McGowan Ian Warshak Igor Zubkov Ivan Makfinsky JH. Chabran James Edward Gray II Jari Bakken Jeff Pereira Jeff Remer Jeffrey Hardy Jeremy Kemper Joe Rafaniello John Barnette Jon Rowe Jordi Massaguer Pla Joshua J. Campoverde Juan Alvarez Julien Kirch Jun Aruga Justin Coyne Justin Lambert Keith Rarick Kenichi Kamiya Kevin Read Kosuke Asami Kyle Meyer Kyle VanderBeek Lars Gierth Lawrence Leonard Gilbert Lee Jarvis Lennon Day-Reynolds Lin Jen-Shin Magne Matre Gåsland Marc-André Cournoyer Marius Butuc Matthew Manning Michael Klett Michael Rykov Michael Westbom Mike Fletcher Nelson Elhage Nicholas Wieland Nick Hammond Nick Plante Niko Dittmann Nikolay Shebanov Oscar Del Ben Pablo Astigarraga Paul Dlug Pedro Belo Pedro Chambino Philip Corliss Pierre-Louis Gottfrois Rafael Ssouza Richard Schneeman Rick Olson Robert Eanes Rodrigo Panachi Sam Norbury Samuel Cochran Syl Turner T. Watanabe Tekin W. Andrew Loe III Waynn Lue Xavier Shay tpresa rest-client-2.1.0/.gitignore0000644000004100000410000000010313531144232015753 0ustar www-datawww-data*.gem /Gemfile.lock /.bundle /vendor /doc /pkg /rdoc /.yardoc /tmp rest-client-2.1.0/.mailmap0000644000004100000410000000062613531144232015416 0ustar www-datawww-dataBlake Mizerany Lawrence Leonard Gilbert Marc-André Cournoyer Matthew Manning Nicholas Wieland Rafael Ssouza Richard Schneeman Rick Olson T. Watanabe rest-client-2.1.0/LICENSE0000644000004100000410000000210313531144232014772 0ustar www-datawww-dataThe MIT License (MIT) Copyright (c) 2008-2014 Rest Client Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rest-client-2.1.0/Rakefile0000644000004100000410000000703613531144232015444 0ustar www-datawww-data# load `rake build/install/release tasks' require 'bundler/setup' require_relative './lib/restclient/version' namespace :ruby do Bundler::GemHelper.install_tasks(:name => 'rest-client') end require "rspec/core/rake_task" desc "Run all specs" RSpec::Core::RakeTask.new('spec') desc "Run unit specs" RSpec::Core::RakeTask.new('spec:unit') do |t| t.pattern = 'spec/unit/*_spec.rb' end desc "Run integration specs" RSpec::Core::RakeTask.new('spec:integration') do |t| t.pattern = 'spec/integration/*_spec.rb' end desc "Print specdocs" RSpec::Core::RakeTask.new(:doc) do |t| t.rspec_opts = ["--format", "specdoc", "--dry-run"] t.pattern = 'spec/**/*_spec.rb' end desc "Run all examples with RCov" RSpec::Core::RakeTask.new('rcov') do |t| t.pattern = 'spec/*_spec.rb' t.rcov = true t.rcov_opts = ['--exclude', 'examples'] end desc 'Regenerate authors file' task :authors do Dir.chdir(File.dirname(__FILE__)) do File.open('AUTHORS', 'w') do |f| f.write <<-EOM The Ruby REST Client would not be what it is today without the help of the following kind souls: EOM end sh 'git shortlog -s | cut -f 2 >> AUTHORS' end end task :default do sh 'rake -T' end def alias_task(alias_task, original) desc "Alias for rake #{original}" task alias_task, Rake.application[original].arg_names => original end alias_task(:test, :spec) ############################ WindowsPlatforms = %w{x86-mingw32 x64-mingw32 x86-mswin32} namespace :all do desc "Build rest-client #{RestClient::VERSION} for all platforms" task :build => ['ruby:build'] + \ WindowsPlatforms.map {|p| "windows:#{p}:build"} desc "Create tag v#{RestClient::VERSION} and for all platforms build and " \ "push rest-client #{RestClient::VERSION} to Rubygems" task :release => ['build', 'ruby:release'] + \ WindowsPlatforms.map {|p| "windows:#{p}:push"} end namespace :windows do spec_path = File.join(File.dirname(__FILE__), 'rest-client.windows.gemspec') WindowsPlatforms.each do |platform| namespace platform do gem_filename = "rest-client-#{RestClient::VERSION}-#{platform}.gem" base = File.dirname(__FILE__) pkg_dir = File.join(base, 'pkg') gem_file_path = File.join(pkg_dir, gem_filename) desc "Build #{gem_filename} into the pkg directory" task 'build' do orig_platform = ENV['BUILD_PLATFORM'] begin ENV['BUILD_PLATFORM'] = platform sh("gem build -V #{spec_path}") do |ok, res| if ok FileUtils.mkdir_p(pkg_dir) FileUtils.mv(File.join(base, gem_filename), pkg_dir) Bundler.ui.confirm("rest-client #{RestClient::VERSION} " \ "built to pkg/#{gem_filename}") else abort "Command `gem build` failed: #{res}" end end ensure ENV['BUILD_PLATFORM'] = orig_platform end end desc "Push #{gem_filename} to Rubygems" task 'push' do sh("gem push #{gem_file_path}") end end end end ############################ require 'rdoc/task' Rake::RDocTask.new do |t| t.rdoc_dir = 'rdoc' t.title = "rest-client, fetch RESTful resources effortlessly" t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object' t.options << '--charset' << 'utf-8' t.rdoc_files.include('README.md') t.rdoc_files.include('lib/*.rb') end ############################ require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) do |t| t.options = ['--display-cop-names'] end alias_task(:lint, :rubocop) rest-client-2.1.0/lib/0000755000004100000410000000000013531144232014537 5ustar www-datawww-datarest-client-2.1.0/lib/restclient.rb0000644000004100000410000001341413531144232017243 0ustar www-datawww-datarequire 'net/http' require 'openssl' require 'stringio' require 'uri' require File.dirname(__FILE__) + '/restclient/version' require File.dirname(__FILE__) + '/restclient/platform' require File.dirname(__FILE__) + '/restclient/exceptions' require File.dirname(__FILE__) + '/restclient/utils' require File.dirname(__FILE__) + '/restclient/request' require File.dirname(__FILE__) + '/restclient/abstract_response' require File.dirname(__FILE__) + '/restclient/response' require File.dirname(__FILE__) + '/restclient/raw_response' require File.dirname(__FILE__) + '/restclient/resource' require File.dirname(__FILE__) + '/restclient/params_array' require File.dirname(__FILE__) + '/restclient/payload' require File.dirname(__FILE__) + '/restclient/windows' # This module's static methods are the entry point for using the REST client. # # # GET # xml = RestClient.get 'http://example.com/resource' # jpg = RestClient.get 'http://example.com/resource', :accept => 'image/jpg' # # # authentication and SSL # RestClient.get 'https://user:password@example.com/private/resource' # # # POST or PUT with a hash sends parameters as a urlencoded form body # RestClient.post 'http://example.com/resource', :param1 => 'one' # # # nest hash parameters # RestClient.post 'http://example.com/resource', :nested => { :param1 => 'one' } # # # POST and PUT with raw payloads # RestClient.post 'http://example.com/resource', 'the post body', :content_type => 'text/plain' # RestClient.post 'http://example.com/resource.xml', xml_doc # RestClient.put 'http://example.com/resource.pdf', File.read('my.pdf'), :content_type => 'application/pdf' # # # DELETE # RestClient.delete 'http://example.com/resource' # # # retreive the response http code and headers # res = RestClient.get 'http://example.com/some.jpg' # res.code # => 200 # res.headers[:content_type] # => 'image/jpg' # # # HEAD # RestClient.head('http://example.com').headers # # To use with a proxy, just set RestClient.proxy to the proper http proxy: # # RestClient.proxy = "http://proxy.example.com/" # # Or inherit the proxy from the environment: # # RestClient.proxy = ENV['http_proxy'] # # For live tests of RestClient, try using http://rest-test.heroku.com, which echoes back information about the rest call: # # >> RestClient.put 'http://rest-test.heroku.com/resource', :foo => 'baz' # => "PUT http://rest-test.heroku.com/resource with a 7 byte payload, content type application/x-www-form-urlencoded {\"foo\"=>\"baz\"}" # module RestClient def self.get(url, headers={}, &block) Request.execute(:method => :get, :url => url, :headers => headers, &block) end def self.post(url, payload, headers={}, &block) Request.execute(:method => :post, :url => url, :payload => payload, :headers => headers, &block) end def self.patch(url, payload, headers={}, &block) Request.execute(:method => :patch, :url => url, :payload => payload, :headers => headers, &block) end def self.put(url, payload, headers={}, &block) Request.execute(:method => :put, :url => url, :payload => payload, :headers => headers, &block) end def self.delete(url, headers={}, &block) Request.execute(:method => :delete, :url => url, :headers => headers, &block) end def self.head(url, headers={}, &block) Request.execute(:method => :head, :url => url, :headers => headers, &block) end def self.options(url, headers={}, &block) Request.execute(:method => :options, :url => url, :headers => headers, &block) end # A global proxy URL to use for all requests. This can be overridden on a # per-request basis by passing `:proxy` to RestClient::Request. def self.proxy @proxy ||= nil end def self.proxy=(value) @proxy = value @proxy_set = true end # Return whether RestClient.proxy was set explicitly. We use this to # differentiate between no value being set and a value explicitly set to nil. # # @return [Boolean] # def self.proxy_set? @proxy_set ||= false end # Setup the log for RestClient calls. # Value should be a logger but can can be stdout, stderr, or a filename. # You can also configure logging by the environment variable RESTCLIENT_LOG. def self.log= log @@log = create_log log end # Create a log that respond to << like a logger # param can be 'stdout', 'stderr', a string (then we will log to that file) or a logger (then we return it) def self.create_log param if param if param.is_a? String if param == 'stdout' stdout_logger = Class.new do def << obj STDOUT.puts obj end end stdout_logger.new elsif param == 'stderr' stderr_logger = Class.new do def << obj STDERR.puts obj end end stderr_logger.new else file_logger = Class.new do attr_writer :target_file def << obj File.open(@target_file, 'a') { |f| f.puts obj } end end logger = file_logger.new logger.target_file = param logger end else param end end end @@env_log = create_log ENV['RESTCLIENT_LOG'] @@log = nil def self.log # :nodoc: @@env_log || @@log end @@before_execution_procs = [] # Add a Proc to be called before each request in executed. # The proc parameters will be the http request and the request params. def self.add_before_execution_proc &proc raise ArgumentError.new('block is required') unless proc @@before_execution_procs << proc end # Reset the procs to be called before each request is executed. def self.reset_before_execution_procs @@before_execution_procs = [] end def self.before_execution_procs # :nodoc: @@before_execution_procs end end rest-client-2.1.0/lib/rest_client.rb0000644000004100000410000000016713531144232017403 0ustar www-datawww-data# This file exists for backward compatbility with require 'rest_client' require File.dirname(__FILE__) + '/restclient' rest-client-2.1.0/lib/restclient/0000755000004100000410000000000013531144232016713 5ustar www-datawww-datarest-client-2.1.0/lib/restclient/version.rb0000644000004100000410000000022413531144232020723 0ustar www-datawww-datamodule RestClient VERSION_INFO = [2, 1, 0].freeze VERSION = VERSION_INFO.map(&:to_s).join('.').freeze def self.version VERSION end end rest-client-2.1.0/lib/restclient/windows.rb0000644000004100000410000000017513531144232020735 0ustar www-datawww-datamodule RestClient module Windows end end if RestClient::Platform.windows? require_relative './windows/root_certs' end rest-client-2.1.0/lib/restclient/abstract_response.rb0000644000004100000410000001521613531144232022766 0ustar www-datawww-datarequire 'cgi' require 'http-cookie' module RestClient module AbstractResponse attr_reader :net_http_res, :request, :start_time, :end_time, :duration def inspect raise NotImplementedError.new('must override in subclass') end # Logger from the request, potentially nil. def log request.log end def log_response return unless log code = net_http_res.code res_name = net_http_res.class.to_s.gsub(/\ANet::HTTP/, '') content_type = (net_http_res['Content-type'] || '').gsub(/;.*\z/, '') log << "# => #{code} #{res_name} | #{content_type} #{size} bytes, #{sprintf('%.2f', duration)}s\n" end # HTTP status code def code @code ||= @net_http_res.code.to_i end def history @history ||= request.redirection_history || [] end # A hash of the headers, beautified with symbols and underscores. # e.g. "Content-type" will become :content_type. def headers @headers ||= AbstractResponse.beautify_headers(@net_http_res.to_hash) end # The raw headers. def raw_headers @raw_headers ||= @net_http_res.to_hash end # @param [Net::HTTPResponse] net_http_res # @param [RestClient::Request] request # @param [Time] start_time def response_set_vars(net_http_res, request, start_time) @net_http_res = net_http_res @request = request @start_time = start_time @end_time = Time.now if @start_time @duration = @end_time - @start_time else @duration = nil end # prime redirection history history end # Hash of cookies extracted from response headers. # # NB: This will return only cookies whose domain matches this request, and # may not even return all of those cookies if there are duplicate names. # Use the full cookie_jar for more nuanced access. # # @see #cookie_jar # # @return [Hash] # def cookies hash = {} cookie_jar.cookies(@request.uri).each do |cookie| hash[cookie.name] = cookie.value end hash end # Cookie jar extracted from response headers. # # @return [HTTP::CookieJar] # def cookie_jar return @cookie_jar if defined?(@cookie_jar) && @cookie_jar jar = @request.cookie_jar.dup headers.fetch(:set_cookie, []).each do |cookie| jar.parse(cookie, @request.uri) end @cookie_jar = jar end # Return the default behavior corresponding to the response code: # # For 20x status codes: return the response itself # # For 30x status codes: # 301, 302, 307: redirect GET / HEAD if there is a Location header # 303: redirect, changing method to GET, if there is a Location header # # For all other responses, raise a response exception # def return!(&block) case code when 200..207 self when 301, 302, 307 case request.method when 'get', 'head' check_max_redirects follow_redirection(&block) else raise exception_with_response end when 303 check_max_redirects follow_get_redirection(&block) else raise exception_with_response end end def to_i warn('warning: calling Response#to_i is not recommended') super end def description "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n" end # Follow a redirection response by making a new HTTP request to the # redirection target. def follow_redirection(&block) _follow_redirection(request.args.dup, &block) end # Follow a redirection response, but change the HTTP method to GET and drop # the payload from the original request. def follow_get_redirection(&block) new_args = request.args.dup new_args[:method] = :get new_args.delete(:payload) _follow_redirection(new_args, &block) end # Convert headers hash into canonical form. # # Header names will be converted to lowercase symbols with underscores # instead of hyphens. # # Headers specified multiple times will be joined by comma and space, # except for Set-Cookie, which will always be an array. # # Per RFC 2616, if a server sends multiple headers with the same key, they # MUST be able to be joined into a single header by a comma. However, # Set-Cookie (RFC 6265) cannot because commas are valid within cookie # definitions. The newer RFC 7230 notes (3.2.2) that Set-Cookie should be # handled as a special case. # # http://tools.ietf.org/html/rfc2616#section-4.2 # http://tools.ietf.org/html/rfc7230#section-3.2.2 # http://tools.ietf.org/html/rfc6265 # # @param headers [Hash] # @return [Hash] # def self.beautify_headers(headers) headers.inject({}) do |out, (key, value)| key_sym = key.tr('-', '_').downcase.to_sym # Handle Set-Cookie specially since it cannot be joined by comma. if key.downcase == 'set-cookie' out[key_sym] = value else out[key_sym] = value.join(', ') end out end end private # Follow a redirection # # @param new_args [Hash] Start with this hash of arguments for the # redirection request. The hash will be mutated, so be sure to dup any # existing hash that should not be modified. # def _follow_redirection(new_args, &block) # parse location header and merge into existing URL url = headers[:location] # cannot follow redirection if there is no location header unless url raise exception_with_response end # handle relative redirects unless url.start_with?('http') url = URI.parse(request.url).merge(url).to_s end new_args[:url] = url new_args[:password] = request.password new_args[:user] = request.user new_args[:headers] = request.headers new_args[:max_redirects] = request.max_redirects - 1 # pass through our new cookie jar new_args[:cookies] = cookie_jar # prepare new request new_req = Request.new(new_args) # append self to redirection history new_req.redirection_history = history + [self] # execute redirected request new_req.execute(&block) end def check_max_redirects if request.max_redirects <= 0 raise exception_with_response end end def exception_with_response begin klass = Exceptions::EXCEPTIONS_MAP.fetch(code) rescue KeyError raise RequestFailed.new(self, code) end raise klass.new(self, code) end end end rest-client-2.1.0/lib/restclient/exceptions.rb0000644000004100000410000001745613531144232021436 0ustar www-datawww-datamodule RestClient # Hash of HTTP status code => message. # # 1xx: Informational - Request received, continuing process # 2xx: Success - The action was successfully received, understood, and # accepted # 3xx: Redirection - Further action must be taken in order to complete the # request # 4xx: Client Error - The request contains bad syntax or cannot be fulfilled # 5xx: Server Error - The server failed to fulfill an apparently valid # request # # @see # http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml # STATUSES = {100 => 'Continue', 101 => 'Switching Protocols', 102 => 'Processing', #WebDAV 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', # http/1.1 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 207 => 'Multi-Status', #WebDAV 208 => 'Already Reported', # RFC5842 226 => 'IM Used', # RFC3229 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', # http/1.1 304 => 'Not Modified', 305 => 'Use Proxy', # http/1.1 306 => 'Switch Proxy', # no longer used 307 => 'Temporary Redirect', # http/1.1 308 => 'Permanent Redirect', # RFC7538 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Payload Too Large', # RFC7231 (renamed, see below) 414 => 'URI Too Long', # RFC7231 (renamed, see below) 415 => 'Unsupported Media Type', 416 => 'Range Not Satisfiable', # RFC7233 (renamed, see below) 417 => 'Expectation Failed', 418 => 'I\'m A Teapot', #RFC2324 421 => 'Too Many Connections From This IP', 422 => 'Unprocessable Entity', #WebDAV 423 => 'Locked', #WebDAV 424 => 'Failed Dependency', #WebDAV 425 => 'Unordered Collection', #WebDAV 426 => 'Upgrade Required', 428 => 'Precondition Required', #RFC6585 429 => 'Too Many Requests', #RFC6585 431 => 'Request Header Fields Too Large', #RFC6585 449 => 'Retry With', #Microsoft 450 => 'Blocked By Windows Parental Controls', #Microsoft 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported', 506 => 'Variant Also Negotiates', 507 => 'Insufficient Storage', #WebDAV 508 => 'Loop Detected', # RFC5842 509 => 'Bandwidth Limit Exceeded', #Apache 510 => 'Not Extended', 511 => 'Network Authentication Required', # RFC6585 } STATUSES_COMPATIBILITY = { # The RFCs all specify "Not Found", but "Resource Not Found" was used in # earlier RestClient releases. 404 => ['ResourceNotFound'], # HTTP 413 was renamed to "Payload Too Large" in RFC7231. 413 => ['RequestEntityTooLarge'], # HTTP 414 was renamed to "URI Too Long" in RFC7231. 414 => ['RequestURITooLong'], # HTTP 416 was renamed to "Range Not Satisfiable" in RFC7233. 416 => ['RequestedRangeNotSatisfiable'], } # This is the base RestClient exception class. Rescue it if you want to # catch any exception that your request might raise # You can get the status code by e.http_code, or see anything about the # response via e.response. # For example, the entire result body (which is # probably an HTML error page) is e.response. class Exception < RuntimeError attr_accessor :response attr_accessor :original_exception attr_writer :message def initialize response = nil, initial_response_code = nil @response = response @message = nil @initial_response_code = initial_response_code end def http_code # return integer for compatibility if @response @response.code.to_i else @initial_response_code end end def http_headers @response.headers if @response end def http_body @response.body if @response end def to_s message end def message @message || default_message end def default_message self.class.name end end # Compatibility class ExceptionWithResponse < RestClient::Exception end # The request failed with an error code not managed by the code class RequestFailed < ExceptionWithResponse def default_message "HTTP status code #{http_code}" end def to_s message end end # RestClient exception classes. TODO: move all exceptions into this module. # # We will a create an exception for each status code, see # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html # module Exceptions # Map http status codes to the corresponding exception class EXCEPTIONS_MAP = {} end # Create HTTP status exception classes STATUSES.each_pair do |code, message| klass = Class.new(RequestFailed) do send(:define_method, :default_message) {"#{http_code ? "#{http_code} " : ''}#{message}"} end klass_constant = const_set(message.delete(' \-\''), klass) Exceptions::EXCEPTIONS_MAP[code] = klass_constant end # Create HTTP status exception classes used for backwards compatibility STATUSES_COMPATIBILITY.each_pair do |code, compat_list| klass = Exceptions::EXCEPTIONS_MAP.fetch(code) compat_list.each do |old_name| const_set(old_name, klass) end end module Exceptions # We have to split the Exceptions module like we do here because the # EXCEPTIONS_MAP is under Exceptions, but we depend on # RestClient::RequestTimeout below. # Base class for request timeouts. # # NB: Previous releases of rest-client would raise RequestTimeout both for # HTTP 408 responses and for actual connection timeouts. class Timeout < RestClient::RequestTimeout def initialize(message=nil, original_exception=nil) super(nil, nil) self.message = message if message self.original_exception = original_exception if original_exception end end # Timeout when connecting to a server. Typically wraps Net::OpenTimeout (in # ruby 2.0 or greater). class OpenTimeout < Timeout def default_message 'Timed out connecting to server' end end # Timeout when reading from a server. Typically wraps Net::ReadTimeout (in # ruby 2.0 or greater). class ReadTimeout < Timeout def default_message 'Timed out reading data from server' end end end # The server broke the connection prior to the request completing. Usually # this means it crashed, or sometimes that your network connection was # severed before it could complete. class ServerBrokeConnection < RestClient::Exception def initialize(message = 'Server broke connection') super nil, nil self.message = message end end class SSLCertificateNotVerified < RestClient::Exception def initialize(message = 'SSL certificate not verified') super nil, nil self.message = message end end end rest-client-2.1.0/lib/restclient/platform.rb0000644000004100000410000000223613531144232021067 0ustar www-datawww-datarequire 'rbconfig' module RestClient module Platform # Return true if we are running on a darwin-based Ruby platform. This will # be false for jruby even on OS X. # # @return [Boolean] def self.mac_mri? RUBY_PLATFORM.include?('darwin') end # Return true if we are running on Windows. # # @return [Boolean] # def self.windows? # Ruby only sets File::ALT_SEPARATOR on Windows, and the Ruby standard # library uses that to test what platform it's on. !!File::ALT_SEPARATOR end # Return true if we are running on jruby. # # @return [Boolean] # def self.jruby? # defined on mri >= 1.9 RUBY_ENGINE == 'jruby' end def self.architecture "#{RbConfig::CONFIG['host_os']} #{RbConfig::CONFIG['host_cpu']}" end def self.ruby_agent_version case RUBY_ENGINE when 'jruby' "jruby/#{JRUBY_VERSION} (#{RUBY_VERSION}p#{RUBY_PATCHLEVEL})" else "#{RUBY_ENGINE}/#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}" end end def self.default_user_agent "rest-client/#{VERSION} (#{architecture}) #{ruby_agent_version}" end end end rest-client-2.1.0/lib/restclient/payload.rb0000644000004100000410000001271513531144232020677 0ustar www-datawww-datarequire 'tempfile' require 'securerandom' require 'stringio' begin # Use mime/types/columnar if available, for reduced memory usage require 'mime/types/columnar' rescue LoadError require 'mime/types' end module RestClient module Payload extend self def generate(params) if params.is_a?(RestClient::Payload::Base) # pass through Payload objects unchanged params elsif params.is_a?(String) Base.new(params) elsif params.is_a?(Hash) if params.delete(:multipart) == true || has_file?(params) Multipart.new(params) else UrlEncoded.new(params) end elsif params.is_a?(ParamsArray) if _has_file?(params) Multipart.new(params) else UrlEncoded.new(params) end elsif params.respond_to?(:read) Streamed.new(params) else nil end end def has_file?(params) unless params.is_a?(Hash) raise ArgumentError.new("Must pass Hash, not #{params.inspect}") end _has_file?(params) end def _has_file?(obj) case obj when Hash, ParamsArray obj.any? {|_, v| _has_file?(v) } when Array obj.any? {|v| _has_file?(v) } else obj.respond_to?(:path) && obj.respond_to?(:read) end end class Base def initialize(params) build_stream(params) end def build_stream(params) @stream = StringIO.new(params) @stream.seek(0) end def read(*args) @stream.read(*args) end def to_s result = read @stream.seek(0) result end def headers {'Content-Length' => size.to_s} end def size @stream.size end alias :length :size def close @stream.close unless @stream.closed? end def closed? @stream.closed? end def to_s_inspect to_s.inspect end def short_inspect if size && size > 500 "#{size} byte(s) length" else to_s_inspect end end end class Streamed < Base def build_stream(params = nil) @stream = params end def size if @stream.respond_to?(:size) @stream.size elsif @stream.is_a?(IO) @stream.stat.size end end # TODO (breaks compatibility): ought to use mime_for() to autodetect the # Content-Type for stream objects that have a filename. alias :length :size end class UrlEncoded < Base def build_stream(params = nil) @stream = StringIO.new(Utils.encode_query_string(params)) @stream.seek(0) end def headers super.merge({'Content-Type' => 'application/x-www-form-urlencoded'}) end end class Multipart < Base EOL = "\r\n" def build_stream(params) b = '--' + boundary @stream = Tempfile.new('rest-client.multipart.') @stream.binmode @stream.write(b + EOL) case params when Hash, ParamsArray x = Utils.flatten_params(params) else x = params end last_index = x.length - 1 x.each_with_index do |a, index| k, v = * a if v.respond_to?(:read) && v.respond_to?(:path) create_file_field(@stream, k, v) else create_regular_field(@stream, k, v) end @stream.write(EOL + b) @stream.write(EOL) unless last_index == index end @stream.write('--') @stream.write(EOL) @stream.seek(0) end def create_regular_field(s, k, v) s.write("Content-Disposition: form-data; name=\"#{k}\"") s.write(EOL) s.write(EOL) s.write(v) end def create_file_field(s, k, v) begin s.write("Content-Disposition: form-data;") s.write(" name=\"#{k}\";") unless (k.nil? || k=='') s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}") s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}") s.write(EOL) while (data = v.read(8124)) s.write(data) end ensure v.close if v.respond_to?(:close) end end def mime_for(path) mime = MIME::Types.type_for path mime.empty? ? 'text/plain' : mime[0].content_type end def boundary return @boundary if defined?(@boundary) && @boundary # Use the same algorithm used by WebKit: generate 16 random # alphanumeric characters, replacing `+` `/` with `A` `B` (included in # the list twice) to round out the set of 64. s = SecureRandom.base64(12) s.tr!('+/', 'AB') @boundary = '----RubyFormBoundary' + s end # for Multipart do not escape the keys # # Ostensibly multipart keys MAY be percent encoded per RFC 7578, but in # practice no major browser that I'm aware of uses percent encoding. # # Further discussion of multipart encoding: # https://github.com/rest-client/rest-client/pull/403#issuecomment-156976930 # def handle_key key key end def headers super.merge({'Content-Type' => %Q{multipart/form-data; boundary=#{boundary}}}) end def close @stream.close! end end end end rest-client-2.1.0/lib/restclient/request.rb0000644000004100000410000007102213531144232020732 0ustar www-datawww-datarequire 'tempfile' require 'cgi' require 'netrc' require 'set' begin # Use mime/types/columnar if available, for reduced memory usage require 'mime/types/columnar' rescue LoadError require 'mime/types' end module RestClient # This class is used internally by RestClient to send the request, but you can also # call it directly if you'd like to use a method not supported by the # main API. For example: # # RestClient::Request.execute(:method => :head, :url => 'http://example.com') # # Mandatory parameters: # * :method # * :url # Optional parameters (have a look at ssl and/or uri for some explanations): # * :headers a hash containing the request headers # * :cookies may be a Hash{String/Symbol => String} of cookie values, an # Array, or an HTTP::CookieJar containing cookies. These # will be added to a cookie jar before the request is sent. # * :user and :password for basic auth, will be replaced by a user/password available in the :url # * :block_response call the provided block with the HTTPResponse as parameter # * :raw_response return a low-level RawResponse instead of a Response # * :log Set the log for this request only, overriding RestClient.log, if # any. # * :stream_log_percent (Only relevant with :raw_response => true) Customize # the interval at which download progress is logged. Defaults to every # 10% complete. # * :max_redirects maximum number of redirections (default to 10) # * :proxy An HTTP proxy URI to use for this request. Any value here # (including nil) will override RestClient.proxy. # * :verify_ssl enable ssl verification, possible values are constants from # OpenSSL::SSL::VERIFY_*, defaults to OpenSSL::SSL::VERIFY_PEER # * :read_timeout and :open_timeout are how long to wait for a response and # to open a connection, in seconds. Pass nil to disable the timeout. # * :timeout can be used to set both timeouts # * :ssl_client_cert, :ssl_client_key, :ssl_ca_file, :ssl_ca_path, # :ssl_cert_store, :ssl_verify_callback, :ssl_verify_callback_warnings # * :ssl_version specifies the SSL version for the underlying Net::HTTP connection # * :ssl_ciphers sets SSL ciphers for the connection. See # OpenSSL::SSL::SSLContext#ciphers= # * :before_execution_proc a Proc to call before executing the request. This # proc, like procs from RestClient.before_execution_procs, will be # called with the HTTP request and request params. class Request attr_reader :method, :uri, :url, :headers, :payload, :proxy, :user, :password, :read_timeout, :max_redirects, :open_timeout, :raw_response, :processed_headers, :args, :ssl_opts # An array of previous redirection responses attr_accessor :redirection_history def self.execute(args, & block) new(args).execute(& block) end SSLOptionList = %w{client_cert client_key ca_file ca_path cert_store version ciphers verify_callback verify_callback_warnings} def inspect "" end def initialize args @method = normalize_method(args[:method]) @headers = (args[:headers] || {}).dup if args[:url] @url = process_url_params(normalize_url(args[:url]), headers) else raise ArgumentError, "must pass :url" end @user = @password = nil parse_url_with_auth!(url) # process cookie arguments found in headers or args @cookie_jar = process_cookie_args!(@uri, @headers, args) @payload = Payload.generate(args[:payload]) @user = args[:user] if args.include?(:user) @password = args[:password] if args.include?(:password) if args.include?(:timeout) @read_timeout = args[:timeout] @open_timeout = args[:timeout] end if args.include?(:read_timeout) @read_timeout = args[:read_timeout] end if args.include?(:open_timeout) @open_timeout = args[:open_timeout] end @block_response = args[:block_response] @raw_response = args[:raw_response] || false @stream_log_percent = args[:stream_log_percent] || 10 if @stream_log_percent <= 0 || @stream_log_percent > 100 raise ArgumentError.new( "Invalid :stream_log_percent #{@stream_log_percent.inspect}") end @proxy = args.fetch(:proxy) if args.include?(:proxy) @ssl_opts = {} if args.include?(:verify_ssl) v_ssl = args.fetch(:verify_ssl) if v_ssl if v_ssl == true # interpret :verify_ssl => true as VERIFY_PEER @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER else # otherwise pass through any truthy values @ssl_opts[:verify_ssl] = v_ssl end else # interpret all falsy :verify_ssl values as VERIFY_NONE @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE end else # if :verify_ssl was not passed, default to VERIFY_PEER @ssl_opts[:verify_ssl] = OpenSSL::SSL::VERIFY_PEER end SSLOptionList.each do |key| source_key = ('ssl_' + key).to_sym if args.has_key?(source_key) @ssl_opts[key.to_sym] = args.fetch(source_key) end end # Set some other default SSL options, but only if we have an HTTPS URI. if use_ssl? # If there's no CA file, CA path, or cert store provided, use default if !ssl_ca_file && !ssl_ca_path && !@ssl_opts.include?(:cert_store) @ssl_opts[:cert_store] = self.class.default_ssl_cert_store end end @log = args[:log] @max_redirects = args[:max_redirects] || 10 @processed_headers = make_headers headers @processed_headers_lowercase = Hash[@processed_headers.map {|k, v| [k.downcase, v]}] @args = args @before_execution_proc = args[:before_execution_proc] end def execute & block # With 2.0.0+, net/http accepts URI objects in requests and handles wrapping # IPv6 addresses in [] for use in the Host request header. transmit uri, net_http_request_class(method).new(uri, processed_headers), payload, & block ensure payload.close if payload end # SSL-related options def verify_ssl @ssl_opts.fetch(:verify_ssl) end SSLOptionList.each do |key| define_method('ssl_' + key) do @ssl_opts[key.to_sym] end end # Return true if the request URI will use HTTPS. # # @return [Boolean] # def use_ssl? uri.is_a?(URI::HTTPS) end # Extract the query parameters and append them to the url # # Look through the headers hash for a :params option (case-insensitive, # may be string or symbol). If present and the value is a Hash or # RestClient::ParamsArray, *delete* the key/value pair from the headers # hash and encode the value into a query string. Append this query string # to the URL and return the resulting URL. # # @param [String] url # @param [Hash] headers An options/headers hash to process. Mutation # warning: the params key may be removed if present! # # @return [String] resulting url with query string # def process_url_params(url, headers) url_params = nil # find and extract/remove "params" key if the value is a Hash/ParamsArray headers.delete_if do |key, value| if key.to_s.downcase == 'params' && (value.is_a?(Hash) || value.is_a?(RestClient::ParamsArray)) if url_params raise ArgumentError.new("Multiple 'params' options passed") end url_params = value true else false end end # build resulting URL with query string if url_params && !url_params.empty? query_string = RestClient::Utils.encode_query_string(url_params) if url.include?('?') url + '&' + query_string else url + '?' + query_string end else url end end # Render a hash of key => value pairs for cookies in the Request#cookie_jar # that are valid for the Request#uri. This will not necessarily include all # cookies if there are duplicate keys. It's safer to use the cookie_jar # directly if that's a concern. # # @see Request#cookie_jar # # @return [Hash] # def cookies hash = {} @cookie_jar.cookies(uri).each do |c| hash[c.name] = c.value end hash end # @return [HTTP::CookieJar] def cookie_jar @cookie_jar end # Render a Cookie HTTP request header from the contents of the @cookie_jar, # or nil if the jar is empty. # # @see Request#cookie_jar # # @return [String, nil] # def make_cookie_header return nil if cookie_jar.nil? arr = cookie_jar.cookies(url) return nil if arr.empty? return HTTP::Cookie.cookie_value(arr) end # Process cookies passed as hash or as HTTP::CookieJar. For backwards # compatibility, these may be passed as a :cookies option masquerading # inside the headers hash. To avoid confusion, if :cookies is passed in # both headers and Request#initialize, raise an error. # # :cookies may be a: # - Hash{String/Symbol => String} # - Array # - HTTP::CookieJar # # Passing as a hash: # Keys may be symbols or strings. Values must be strings. # Infer the domain name from the request URI and allow subdomains (as # though '.example.com' had been set in a Set-Cookie header). Assume a # path of '/'. # # RestClient::Request.new(url: 'http://example.com', method: :get, # :cookies => {:foo => 'Value', 'bar' => '123'} # ) # # results in cookies as though set from the server by: # Set-Cookie: foo=Value; Domain=.example.com; Path=/ # Set-Cookie: bar=123; Domain=.example.com; Path=/ # # which yields a client cookie header of: # Cookie: foo=Value; bar=123 # # Passing as HTTP::CookieJar, which will be passed through directly: # # jar = HTTP::CookieJar.new # jar.add(HTTP::Cookie.new('foo', 'Value', domain: 'example.com', # path: '/', for_domain: false)) # # RestClient::Request.new(..., :cookies => jar) # # @param [URI::HTTP] uri The URI for the request. This will be used to # infer the domain name for cookies passed as strings in a hash. To avoid # this implicit behavior, pass a full cookie jar or use HTTP::Cookie hash # values. # @param [Hash] headers The headers hash from which to pull the :cookies # option. MUTATION NOTE: This key will be deleted from the hash if # present. # @param [Hash] args The options passed to Request#initialize. This hash # will be used as another potential source for the :cookies key. # These args will not be mutated. # # @return [HTTP::CookieJar] A cookie jar containing the parsed cookies. # def process_cookie_args!(uri, headers, args) # Avoid ambiguity in whether options from headers or options from # Request#initialize should take precedence by raising ArgumentError when # both are present. Prior versions of rest-client claimed to give # precedence to init options, but actually gave precedence to headers. # Avoid that mess by erroring out instead. if headers[:cookies] && args[:cookies] raise ArgumentError.new( "Cannot pass :cookies in Request.new() and in headers hash") end cookies_data = headers.delete(:cookies) || args[:cookies] # return copy of cookie jar as is if cookies_data.is_a?(HTTP::CookieJar) return cookies_data.dup end # convert cookies hash into a CookieJar jar = HTTP::CookieJar.new (cookies_data || []).each do |key, val| # Support for Array mode: # If key is a cookie object, add it to the jar directly and assert that # there is no separate val. if key.is_a?(HTTP::Cookie) if val raise ArgumentError.new("extra cookie val: #{val.inspect}") end jar.add(key) next end if key.is_a?(Symbol) key = key.to_s end # assume implicit domain from the request URI, and set for_domain to # permit subdomains jar.add(HTTP::Cookie.new(key, val, domain: uri.hostname.downcase, path: '/', for_domain: true)) end jar end # Generate headers for use by a request. Header keys will be stringified # using `#stringify_headers` to normalize them as capitalized strings. # # The final headers consist of: # - default headers from #default_headers # - user_headers provided here # - headers from the payload object (e.g. Content-Type, Content-Lenth) # - cookie headers from #make_cookie_header # # BUG: stringify_headers does not alter the capitalization of headers that # are passed as strings, it only normalizes those passed as symbols. This # behavior will probably remain for a while for compatibility, but it means # that the warnings that attempt to detect accidental header overrides may # not always work. # https://github.com/rest-client/rest-client/issues/599 # # @param [Hash] user_headers User-provided headers to include # # @return [Hash] A hash of HTTP headers => values # def make_headers(user_headers) headers = stringify_headers(default_headers).merge(stringify_headers(user_headers)) # override headers from the payload (e.g. Content-Type, Content-Length) if @payload payload_headers = @payload.headers # Warn the user if we override any headers that were previously # present. This usually indicates that rest-client was passed # conflicting information, e.g. if it was asked to render a payload as # x-www-form-urlencoded but a Content-Type application/json was # also supplied by the user. payload_headers.each_pair do |key, val| if headers.include?(key) && headers[key] != val warn("warning: Overriding #{key.inspect} header " + "#{headers.fetch(key).inspect} with #{val.inspect} " + "due to payload") end end headers.merge!(payload_headers) end # merge in cookies cookies = make_cookie_header if cookies && !cookies.empty? if headers['Cookie'] warn('warning: overriding "Cookie" header with :cookies option') end headers['Cookie'] = cookies end headers end # The proxy URI for this request. If `:proxy` was provided on this request, # use it over `RestClient.proxy`. # # Return false if a proxy was explicitly set and is falsy. # # @return [URI, false, nil] # def proxy_uri if defined?(@proxy) if @proxy URI.parse(@proxy) else false end elsif RestClient.proxy_set? if RestClient.proxy URI.parse(RestClient.proxy) else false end else nil end end def net_http_object(hostname, port) p_uri = proxy_uri if p_uri.nil? # no proxy set Net::HTTP.new(hostname, port) elsif !p_uri # proxy explicitly set to none Net::HTTP.new(hostname, port, nil, nil, nil, nil) else Net::HTTP.new(hostname, port, p_uri.hostname, p_uri.port, p_uri.user, p_uri.password) end end def net_http_request_class(method) Net::HTTP.const_get(method.capitalize, false) end def net_http_do_request(http, req, body=nil, &block) if body && body.respond_to?(:read) req.body_stream = body return http.request(req, nil, &block) else return http.request(req, body, &block) end end # Normalize a URL by adding a protocol if none is present. # # If the string has no HTTP-like scheme (i.e. scheme followed by '//'), a # scheme of 'http' will be added. This mimics the behavior of browsers and # user agents like cURL. # # @param [String] url A URL string. # # @return [String] # def normalize_url(url) url = 'http://' + url unless url.match(%r{\A[a-z][a-z0-9+.-]*://}i) url end # Return a certificate store that can be used to validate certificates with # the system certificate authorities. This will probably not do anything on # OS X, which monkey patches OpenSSL in terrible ways to insert its own # validation. On most *nix platforms, this will add the system certifcates # using OpenSSL::X509::Store#set_default_paths. On Windows, this will use # RestClient::Windows::RootCerts to look up the CAs trusted by the system. # # @return [OpenSSL::X509::Store] # def self.default_ssl_cert_store cert_store = OpenSSL::X509::Store.new cert_store.set_default_paths # set_default_paths() doesn't do anything on Windows, so look up # certificates using the win32 API. if RestClient::Platform.windows? RestClient::Windows::RootCerts.instance.to_a.uniq.each do |cert| begin cert_store.add_cert(cert) rescue OpenSSL::X509::StoreError => err # ignore duplicate certs raise unless err.message == 'cert already in hash table' end end end cert_store end def redacted_uri if uri.password sanitized_uri = uri.dup sanitized_uri.password = 'REDACTED' sanitized_uri else uri end end def redacted_url redacted_uri.to_s end # Default to the global logger if there's not a request-specific one def log @log || RestClient.log end def log_request return unless log out = [] out << "RestClient.#{method} #{redacted_url.inspect}" out << payload.short_inspect if payload out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ") log << out.join(', ') + "\n" end # Return a hash of headers whose keys are capitalized strings # # BUG: stringify_headers does not fix the capitalization of headers that # are already Strings. Leaving this behavior as is for now for # backwards compatibility. # https://github.com/rest-client/rest-client/issues/599 # def stringify_headers headers headers.inject({}) do |result, (key, value)| if key.is_a? Symbol key = key.to_s.split(/_/).map(&:capitalize).join('-') end if 'CONTENT-TYPE' == key.upcase result[key] = maybe_convert_extension(value.to_s) elsif 'ACCEPT' == key.upcase # Accept can be composed of several comma-separated values if value.is_a? Array target_values = value else target_values = value.to_s.split ',' end result[key] = target_values.map { |ext| maybe_convert_extension(ext.to_s.strip) }.join(', ') else result[key] = value.to_s end result end end # Default headers set by RestClient. In addition to these headers, servers # will receive headers set by Net::HTTP, such as Accept-Encoding and Host. # # @return [Hash] def default_headers { :accept => '*/*', :user_agent => RestClient::Platform.default_user_agent, } end private # Parse the `@url` string into a URI object and save it as # `@uri`. Also save any basic auth user or password as @user and @password. # If no auth info was passed, check for credentials in a Netrc file. # # @param [String] url A URL string. # # @return [URI] # # @raise URI::InvalidURIError on invalid URIs # def parse_url_with_auth!(url) uri = URI.parse(url) if uri.hostname.nil? raise URI::InvalidURIError.new("bad URI(no host provided): #{url}") end @user = CGI.unescape(uri.user) if uri.user @password = CGI.unescape(uri.password) if uri.password if !@user && !@password @user, @password = Netrc.read[uri.hostname] end @uri = uri end def print_verify_callback_warnings warned = false if RestClient::Platform.mac_mri? warn('warning: ssl_verify_callback return code is ignored on OS X') warned = true end if RestClient::Platform.jruby? warn('warning: SSL verify_callback may not work correctly in jruby') warn('see https://github.com/jruby/jruby/issues/597') warned = true end warned end # Parse a method and return a normalized string version. # # Raise ArgumentError if the method is falsy, but otherwise do no # validation. # # @param method [String, Symbol] # # @return [String] # # @see net_http_request_class # def normalize_method(method) raise ArgumentError.new('must pass :method') unless method method.to_s.downcase end def transmit uri, req, payload, & block # We set this to true in the net/http block so that we can distinguish # read_timeout from open_timeout. Now that we only support Ruby 2.0+, # this is only needed for Timeout exceptions thrown outside of Net::HTTP. established_connection = false setup_credentials req net = net_http_object(uri.hostname, uri.port) net.use_ssl = uri.is_a?(URI::HTTPS) net.ssl_version = ssl_version if ssl_version net.ciphers = ssl_ciphers if ssl_ciphers net.verify_mode = verify_ssl net.cert = ssl_client_cert if ssl_client_cert net.key = ssl_client_key if ssl_client_key net.ca_file = ssl_ca_file if ssl_ca_file net.ca_path = ssl_ca_path if ssl_ca_path net.cert_store = ssl_cert_store if ssl_cert_store # We no longer rely on net.verify_callback for the main SSL verification # because it's not well supported on all platforms (see comments below). # But do allow users to set one if they want. if ssl_verify_callback net.verify_callback = ssl_verify_callback # Hilariously, jruby only calls the callback when cert_store is set to # something, so make sure to set one. # https://github.com/jruby/jruby/issues/597 if RestClient::Platform.jruby? net.cert_store ||= OpenSSL::X509::Store.new end if ssl_verify_callback_warnings != false if print_verify_callback_warnings warn('pass :ssl_verify_callback_warnings => false to silence this') end end end if OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE warn('WARNING: OpenSSL::SSL::VERIFY_PEER == OpenSSL::SSL::VERIFY_NONE') warn('This dangerous monkey patch leaves you open to MITM attacks!') warn('Try passing :verify_ssl => false instead.') end if defined? @read_timeout if @read_timeout == -1 warn 'Deprecated: to disable timeouts, please use nil instead of -1' @read_timeout = nil end net.read_timeout = @read_timeout end if defined? @open_timeout if @open_timeout == -1 warn 'Deprecated: to disable timeouts, please use nil instead of -1' @open_timeout = nil end net.open_timeout = @open_timeout end RestClient.before_execution_procs.each do |before_proc| before_proc.call(req, args) end if @before_execution_proc @before_execution_proc.call(req, args) end log_request start_time = Time.now tempfile = nil net.start do |http| established_connection = true if @block_response net_http_do_request(http, req, payload, &@block_response) else res = net_http_do_request(http, req, payload) { |http_response| if @raw_response # fetch body into tempfile tempfile = fetch_body_to_tempfile(http_response) else # fetch body http_response.read_body end http_response } process_result(res, start_time, tempfile, &block) end end rescue EOFError raise RestClient::ServerBrokeConnection rescue Net::OpenTimeout => err raise RestClient::Exceptions::OpenTimeout.new(nil, err) rescue Net::ReadTimeout => err raise RestClient::Exceptions::ReadTimeout.new(nil, err) rescue Timeout::Error, Errno::ETIMEDOUT => err # handling for non-Net::HTTP timeouts if established_connection raise RestClient::Exceptions::ReadTimeout.new(nil, err) else raise RestClient::Exceptions::OpenTimeout.new(nil, err) end rescue OpenSSL::SSL::SSLError => error # TODO: deprecate and remove RestClient::SSLCertificateNotVerified and just # pass through OpenSSL::SSL::SSLError directly. # # Exceptions in verify_callback are ignored [1], and jruby doesn't support # it at all [2]. RestClient has to catch OpenSSL::SSL::SSLError and either # re-throw it as is, or throw SSLCertificateNotVerified based on the # contents of the message field of the original exception. # # The client has to handle OpenSSL::SSL::SSLError exceptions anyway, so # we shouldn't make them handle both OpenSSL and RestClient exceptions. # # [1] https://github.com/ruby/ruby/blob/89e70fe8e7/ext/openssl/ossl.c#L238 # [2] https://github.com/jruby/jruby/issues/597 if error.message.include?("certificate verify failed") raise SSLCertificateNotVerified.new(error.message) else raise error end end def setup_credentials(req) if user && !@processed_headers_lowercase.include?('authorization') req.basic_auth(user, password) end end def fetch_body_to_tempfile(http_response) # Taken from Chef, which as in turn... # Stolen from http://www.ruby-forum.com/topic/166423 # Kudos to _why! tf = Tempfile.new('rest-client.') tf.binmode size = 0 total = http_response['Content-Length'].to_i stream_log_bucket = nil http_response.read_body do |chunk| tf.write chunk size += chunk.size if log if total == 0 log << "streaming %s %s (%d of unknown) [0 Content-Length]\n" % [@method.upcase, @url, size] else percent = (size * 100) / total current_log_bucket, _ = percent.divmod(@stream_log_percent) if current_log_bucket != stream_log_bucket stream_log_bucket = current_log_bucket log << "streaming %s %s %d%% done (%d of %d)\n" % [@method.upcase, @url, (size * 100) / total, size, total] end end end end tf.close tf end # @param res The Net::HTTP response object # @param start_time [Time] Time of request start def process_result(res, start_time, tempfile=nil, &block) if @raw_response unless tempfile raise ArgumentError.new('tempfile is required') end response = RawResponse.new(tempfile, res, self, start_time) else response = Response.create(res.body, res, self, start_time) end response.log_response if block_given? block.call(response, self, res, & block) else response.return!(&block) end end def parser URI.const_defined?(:Parser) ? URI::Parser.new : URI end # Given a MIME type or file extension, return either a MIME type or, if # none is found, the input unchanged. # # >> maybe_convert_extension('json') # => 'application/json' # # >> maybe_convert_extension('unknown') # => 'unknown' # # >> maybe_convert_extension('application/xml') # => 'application/xml' # # @param ext [String] # # @return [String] # def maybe_convert_extension(ext) unless ext =~ /\A[a-zA-Z0-9_@-]+\z/ # Don't look up strings unless they look like they could be a file # extension known to mime-types. # # There currently isn't any API public way to look up extensions # directly out of MIME::Types, but the type_for() method only strips # off after a period anyway. return ext end types = MIME::Types.type_for(ext) if types.empty? ext else types.first.content_type end end end end rest-client-2.1.0/lib/restclient/params_array.rb0000644000004100000410000000377513531144232021735 0ustar www-datawww-datamodule RestClient # The ParamsArray class is used to represent an ordered list of [key, value] # pairs. Use this when you need to include a key multiple times or want # explicit control over parameter ordering. # # Most of the request payload & parameter functions normally accept a Hash of # keys => values, which does not allow for duplicated keys. # # @see RestClient::Utils.encode_query_string # @see RestClient::Utils.flatten_params # class ParamsArray include Enumerable # @param array [Array] An array of parameter key,value pairs. These # pairs may be 2 element arrays [key, value] or single element hashes # {key => value}. They may also be single element arrays to represent a # key with no value. # # @example # >> ParamsArray.new([[:foo, 123], [:foo, 456], [:bar, 789]]) # This will be encoded as "foo=123&foo=456&bar=789" # # @example # >> ParamsArray.new({foo: 123, bar: 456}) # This is valid, but there's no reason not to just use the Hash directly # instead of a ParamsArray. # # def initialize(array) @array = process_input(array) end def each(*args, &blk) @array.each(*args, &blk) end def empty? @array.empty? end private def process_input(array) array.map {|v| process_pair(v) } end # A pair may be: # - A single element hash, e.g. {foo: 'bar'} # - A two element array, e.g. ['foo', 'bar'] # - A one element array, e.g. ['foo'] # def process_pair(pair) case pair when Hash if pair.length != 1 raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}") end pair.to_a.fetch(0) when Array if pair.length > 2 raise ArgumentError.new("Bad # of fields for pair: #{pair.inspect}") end [pair.fetch(0), pair[1]] else # recurse, converting any non-array to an array process_pair(pair.to_a) end end end end rest-client-2.1.0/lib/restclient/resource.rb0000644000004100000410000001256313531144232021076 0ustar www-datawww-datamodule RestClient # A class that can be instantiated for access to a RESTful resource, # including authentication. # # Example: # # resource = RestClient::Resource.new('http://some/resource') # jpg = resource.get(:accept => 'image/jpg') # # With HTTP basic authentication: # # resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password') # resource.delete # # With a timeout (seconds): # # RestClient::Resource.new('http://slow', :read_timeout => 10) # # With an open timeout (seconds): # # RestClient::Resource.new('http://behindfirewall', :open_timeout => 10) # # You can also use resources to share common headers. For headers keys, # symbols are converted to strings. Example: # # resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 }) # # This header will be transported as X-Client-Version (notice the X prefix, # capitalization and hyphens) # # Use the [] syntax to allocate subresources: # # site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd') # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain' # class Resource attr_reader :url, :options, :block def initialize(url, options={}, backwards_compatibility=nil, &block) @url = url @block = block if options.class == Hash @options = options else # compatibility with previous versions @options = { :user => options, :password => backwards_compatibility } end end def get(additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :get, :url => url, :headers => headers, :log => log), &(block || @block)) end def head(additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :head, :url => url, :headers => headers, :log => log), &(block || @block)) end def post(payload, additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :post, :url => url, :payload => payload, :headers => headers, :log => log), &(block || @block)) end def put(payload, additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :put, :url => url, :payload => payload, :headers => headers, :log => log), &(block || @block)) end def patch(payload, additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :patch, :url => url, :payload => payload, :headers => headers, :log => log), &(block || @block)) end def delete(additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :delete, :url => url, :headers => headers, :log => log), &(block || @block)) end def to_s url end def user options[:user] end def password options[:password] end def headers options[:headers] || {} end def read_timeout options[:read_timeout] end def open_timeout options[:open_timeout] end def log options[:log] || RestClient.log end # Construct a subresource, preserving authentication. # # Example: # # site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd') # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain' # # This is especially useful if you wish to define your site in one place and # call it in multiple locations: # # def orders # RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd') # end # # orders.get # GET http://example.com/orders # orders['1'].get # GET http://example.com/orders/1 # orders['1/items'].delete # DELETE http://example.com/orders/1/items # # Nest resources as far as you want: # # site = RestClient::Resource.new('http://example.com') # posts = site['posts'] # first_post = posts['1'] # comments = first_post['comments'] # comments.post 'Hello', :content_type => 'text/plain' # def [](suburl, &new_block) case when block_given? then self.class.new(concat_urls(url, suburl), options, &new_block) when block then self.class.new(concat_urls(url, suburl), options, &block) else self.class.new(concat_urls(url, suburl), options) end end def concat_urls(url, suburl) # :nodoc: url = url.to_s suburl = suburl.to_s if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/' url + suburl else "#{url}/#{suburl}" end end end end rest-client-2.1.0/lib/restclient/utils.rb0000644000004100000410000002153313531144232020404 0ustar www-datawww-datarequire 'http/accept' module RestClient # Various utility methods module Utils # Return encoding from an HTTP header hash. # # We use the RFC 7231 specification and do not impose a default encoding on # text. This differs from the older RFC 2616 behavior, which specifies # using ISO-8859-1 for text/* content types without a charset. # # Strings will use the default encoding when this method returns nil. This # default is likely to be UTF-8 for Ruby >= 2.0 # # @param headers [Hash] # # @return [String, nil] Return the string encoding or nil if no header is # found. # # @example # >> get_encoding_from_headers({:content_type => 'text/plain; charset=UTF-8'}) # => "UTF-8" # def self.get_encoding_from_headers(headers) type_header = headers[:content_type] return nil unless type_header # TODO: remove this hack once we drop support for Ruby 2.0 if RUBY_VERSION.start_with?('2.0') _content_type, params = deprecated_cgi_parse_header(type_header) if params.include?('charset') return params.fetch('charset').gsub(/(\A["']*)|(["']*\z)/, '') end else begin _content_type, params = cgi_parse_header(type_header) rescue HTTP::Accept::ParseError return nil else params['charset'] end end end # Parse a Content-Type like header. # # Return the main content-type and a hash of params. # # @param [String] line # @return [Array(String, Hash)] # def self.cgi_parse_header(line) types = HTTP::Accept::MediaTypes.parse(line) if types.empty? raise HTTP::Accept::ParseError.new("Found no types in header line") end [types.first.mime_type, types.first.parameters] end # Parse semi-colon separated, potentially quoted header string iteratively. # # @private # # @deprecated This method is deprecated and only exists to support Ruby # 2.0, which is not supported by HTTP::Accept. # # @todo remove this method when dropping support for Ruby 2.0 # def self._cgi_parseparam(s) return enum_for(__method__, s) unless block_given? while s[0] == ';' s = s[1..-1] ends = s.index(';') while ends && ends > 0 \ && (s[0...ends].count('"') - s[0...ends].scan('\"').count) % 2 != 0 ends = s.index(';', ends + 1) end if ends.nil? ends = s.length end f = s[0...ends] yield f.strip s = s[ends..-1] end nil end # Parse a Content-Type like header. # # Return the main content-type and a hash of options. # # This method was ported directly from Python's cgi.parse_header(). It # probably doesn't read or perform particularly well in ruby. # https://github.com/python/cpython/blob/3.4/Lib/cgi.py#L301-L331 # # @param [String] line # @return [Array(String, Hash)] # # @deprecated This method is deprecated and only exists to support Ruby # 2.0, which is not supported by HTTP::Accept. # # @todo remove this method when dropping support for Ruby 2.0 # def self.deprecated_cgi_parse_header(line) parts = _cgi_parseparam(';' + line) key = parts.next pdict = {} begin while (p = parts.next) i = p.index('=') if i name = p[0...i].strip.downcase value = p[i+1..-1].strip if value.length >= 2 && value[0] == '"' && value[-1] == '"' value = value[1...-1] value = value.gsub('\\\\', '\\').gsub('\\"', '"') end pdict[name] = value end end rescue StopIteration end [key, pdict] end # Serialize a ruby object into HTTP query string parameters. # # There is no standard for doing this, so we choose our own slightly # idiosyncratic format. The output closely matches the format understood by # Rails, Rack, and PHP. # # If you don't want handling of complex objects and only want to handle # simple flat hashes, you may want to use `URI.encode_www_form` instead, # which implements HTML5-compliant URL encoded form data. # # @param [Hash,ParamsArray] object The object to serialize # # @return [String] A string appropriate for use as an HTTP query string # # @see {flatten_params} # # @see URI.encode_www_form # # @see See also Object#to_query in ActiveSupport # @see http://php.net/manual/en/function.http-build-query.php # http_build_query in PHP # @see See also Rack::Utils.build_nested_query in Rack # # Notable differences from the ActiveSupport implementation: # # - Empty hash and empty array are treated the same as nil instead of being # omitted entirely from the output. Rather than disappearing, they will # appear to be nil instead. # # It's most common to pass a Hash as the object to serialize, but you can # also use a ParamsArray if you want to be able to pass the same key with # multiple values and not use the rack/rails array convention. # # @since 2.0.0 # # @example Simple hashes # >> encode_query_string({foo: 123, bar: 456}) # => 'foo=123&bar=456' # # @example Simple arrays # >> encode_query_string({foo: [1,2,3]}) # => 'foo[]=1&foo[]=2&foo[]=3' # # @example Nested hashes # >> encode_query_string({outer: {foo: 123, bar: 456}}) # => 'outer[foo]=123&outer[bar]=456' # # @example Deeply nesting # >> encode_query_string({coords: [{x: 1, y: 0}, {x: 2}, {x: 3}]}) # => 'coords[][x]=1&coords[][y]=0&coords[][x]=2&coords[][x]=3' # # @example Null and empty values # >> encode_query_string({string: '', empty: nil, list: [], hash: {}}) # => 'string=&empty&list&hash' # # @example Nested nulls # >> encode_query_string({foo: {string: '', empty: nil}}) # => 'foo[string]=&foo[empty]' # # @example Multiple fields with the same name using ParamsArray # >> encode_query_string(RestClient::ParamsArray.new([[:foo, 1], [:foo, 2], [:foo, 3]])) # => 'foo=1&foo=2&foo=3' # # @example Nested ParamsArray # >> encode_query_string({foo: RestClient::ParamsArray.new([[:a, 1], [:a, 2]])}) # => 'foo[a]=1&foo[a]=2' # # >> encode_query_string(RestClient::ParamsArray.new([[:foo, {a: 1}], [:foo, {a: 2}]])) # => 'foo[a]=1&foo[a]=2' # def self.encode_query_string(object) flatten_params(object, true).map {|k, v| v.nil? ? k : "#{k}=#{v}" }.join('&') end # Transform deeply nested param containers into a flat array of [key, # value] pairs. # # @example # >> flatten_params({key1: {key2: 123}}) # => [["key1[key2]", 123]] # # @example # >> flatten_params({key1: {key2: 123, arr: [1,2,3]}}) # => [["key1[key2]", 123], ["key1[arr][]", 1], ["key1[arr][]", 2], ["key1[arr][]", 3]] # # @param object [Hash, ParamsArray] The container to flatten # @param uri_escape [Boolean] Whether to URI escape keys and values # @param parent_key [String] Should not be passed (used for recursion) # def self.flatten_params(object, uri_escape=false, parent_key=nil) unless object.is_a?(Hash) || object.is_a?(ParamsArray) || (parent_key && object.is_a?(Array)) raise ArgumentError.new('expected Hash or ParamsArray, got: ' + object.inspect) end # transform empty collections into nil, where possible if object.empty? && parent_key return [[parent_key, nil]] end # This is essentially .map(), but we need to do += for nested containers object.reduce([]) { |result, item| if object.is_a?(Array) # item is already the value k = nil v = item else # item is a key, value pair k, v = item k = escape(k.to_s) if uri_escape end processed_key = parent_key ? "#{parent_key}[#{k}]" : k case v when Array, Hash, ParamsArray result.concat flatten_params(v, uri_escape, processed_key) else v = escape(v.to_s) if uri_escape && v result << [processed_key, v] end } end # Encode string for safe transport by URI or form encoding. This uses a CGI # style escape, which transforms ` ` into `+` and various special # characters into percent encoded forms. # # This calls URI.encode_www_form_component for the implementation. The only # difference between this and CGI.escape is that it does not escape `*`. # http://stackoverflow.com/questions/25085992/ # # @see URI.encode_www_form_component # def self.escape(string) URI.encode_www_form_component(string) end end end rest-client-2.1.0/lib/restclient/raw_response.rb0000644000004100000410000000243713531144232021755 0ustar www-datawww-datamodule RestClient # The response from RestClient on a raw request looks like a string, but is # actually one of these. 99% of the time you're making a rest call all you # care about is the body, but on the occassion you want to fetch the # headers you can: # # RestClient.get('http://example.com').headers[:content_type] # # In addition, if you do not use the response as a string, you can access # a Tempfile object at res.file, which contains the path to the raw # downloaded request body. class RawResponse include AbstractResponse attr_reader :file, :request, :start_time, :end_time def inspect "" end # @param [Tempfile] tempfile The temporary file containing the body # @param [Net::HTTPResponse] net_http_res # @param [RestClient::Request] request # @param [Time] start_time def initialize(tempfile, net_http_res, request, start_time=nil) @file = tempfile # reopen the tempfile so we can read it @file.open response_set_vars(net_http_res, request, start_time) end def to_s body end def body @file.rewind @file.read end def size file.size end end end rest-client-2.1.0/lib/restclient/windows/0000755000004100000410000000000013531144232020405 5ustar www-datawww-datarest-client-2.1.0/lib/restclient/windows/root_certs.rb0000644000004100000410000000520313531144232023115 0ustar www-datawww-datarequire 'openssl' require 'ffi' # Adapted from Puppet, Copyright (c) Puppet Labs Inc, # licensed under the Apache License, Version 2.0. # # https://github.com/puppetlabs/puppet/blob/bbe30e0a/lib/puppet/util/windows/root_certs.rb # Represents a collection of trusted root certificates. # # @api public class RestClient::Windows::RootCerts include Enumerable extend FFI::Library typedef :ulong, :dword typedef :uintptr_t, :handle def initialize(roots) @roots = roots end # Enumerates each root certificate. # @yieldparam cert [OpenSSL::X509::Certificate] each root certificate # @api public def each @roots.each {|cert| yield cert} end # Returns a new instance. # @return [RestClient::Windows::RootCerts] object constructed from current root certificates def self.instance new(self.load_certs) end # Returns an array of root certificates. # # @return [Array<[OpenSSL::X509::Certificate]>] an array of root certificates # @api private def self.load_certs certs = [] # This is based on a patch submitted to openssl: # http://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html ptr = FFI::Pointer::NULL store = CertOpenSystemStoreA(nil, "ROOT") begin while (ptr = CertEnumCertificatesInStore(store, ptr)) and not ptr.null? context = CERT_CONTEXT.new(ptr) cert_buf = context[:pbCertEncoded].read_bytes(context[:cbCertEncoded]) begin certs << OpenSSL::X509::Certificate.new(cert_buf) rescue => detail warn("Failed to import root certificate: #{detail.inspect}") end end ensure CertCloseStore(store, 0) end certs end private # typedef ULONG_PTR HCRYPTPROV_LEGACY; # typedef void *HCERTSTORE; class CERT_CONTEXT < FFI::Struct layout( :dwCertEncodingType, :dword, :pbCertEncoded, :pointer, :cbCertEncoded, :dword, :pCertInfo, :pointer, :hCertStore, :handle ) end # HCERTSTORE # WINAPI # CertOpenSystemStoreA( # __in_opt HCRYPTPROV_LEGACY hProv, # __in LPCSTR szSubsystemProtocol # ); ffi_lib :crypt32 attach_function :CertOpenSystemStoreA, [:pointer, :string], :handle # PCCERT_CONTEXT # WINAPI # CertEnumCertificatesInStore( # __in HCERTSTORE hCertStore, # __in_opt PCCERT_CONTEXT pPrevCertContext # ); ffi_lib :crypt32 attach_function :CertEnumCertificatesInStore, [:handle, :pointer], :pointer # BOOL # WINAPI # CertCloseStore( # __in_opt HCERTSTORE hCertStore, # __in DWORD dwFlags # ); ffi_lib :crypt32 attach_function :CertCloseStore, [:handle, :dword], :bool end rest-client-2.1.0/lib/restclient/response.rb0000644000004100000410000000436613531144232021107 0ustar www-datawww-datamodule RestClient # A Response from RestClient, you can access the response body, the code or the headers. # class Response < String include AbstractResponse # Return the HTTP response body. # # Future versions of RestClient will deprecate treating response objects # directly as strings, so it will be necessary to call `.body`. # # @return [String] # def body # Benchmarking suggests that "#{self}" is fastest, and that caching the # body string in an instance variable doesn't make it enough faster to be # worth the extra memory storage. String.new(self) end # Convert the HTTP response body to a pure String object. # # @return [String] def to_s body end # Convert the HTTP response body to a pure String object. # # @return [String] def to_str body end def inspect "" end # Initialize a Response object. Because RestClient::Response is # (unfortunately) a subclass of String for historical reasons, # Response.create is the preferred initializer. # # @param [String, nil] body The response body from the Net::HTTPResponse # @param [Net::HTTPResponse] net_http_res # @param [RestClient::Request] request # @param [Time] start_time def self.create(body, net_http_res, request, start_time=nil) result = self.new(body || '') result.response_set_vars(net_http_res, request, start_time) fix_encoding(result) result end # Set the String encoding according to the 'Content-Type: charset' header, # if possible. def self.fix_encoding(response) charset = RestClient::Utils.get_encoding_from_headers(response.headers) encoding = nil begin encoding = Encoding.find(charset) if charset rescue ArgumentError if response.log response.log << "No such encoding: #{charset.inspect}" end end return unless encoding response.force_encoding(encoding) response end private def body_truncated(length) b = body if b.length > length b[0..length] + '...' else b end end end end rest-client-2.1.0/lib/rest-client.rb0000644000004100000410000000013313531144232017312 0ustar www-datawww-data# More logical way to require 'rest-client' require File.dirname(__FILE__) + '/restclient' rest-client-2.1.0/rest-client.windows.gemspec0000644000004100000410000000105513531144232021261 0ustar www-datawww-data# # Gemspec for Windows platforms. We can't put these in the main gemspec because # it results in bundler platform hell when trying to build the gem. # # Set $BUILD_PLATFORM when calling gem build with this gemspec to build for # Windows platforms like x86-mingw32. # s = eval(File.read(File.join(File.dirname(__FILE__), 'rest-client.gemspec'))) platform = ENV['BUILD_PLATFORM'] || RUBY_PLATFORM case platform when /(mingw32|mswin32)/ # ffi is needed for RestClient::Windows::RootCerts s.add_dependency('ffi', '~> 1.9') s.platform = platform end s rest-client-2.1.0/rest-client.gemspec0000644000004100000410000000247513531144232017577 0ustar www-datawww-data# -*- encoding: utf-8 -*- require File.expand_path('../lib/restclient/version', __FILE__) Gem::Specification.new do |s| s.name = 'rest-client' s.version = RestClient::VERSION s.authors = ['REST Client Team'] s.description = 'A simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete.' s.license = 'MIT' s.email = 'discuss@rest-client.groups.io' s.executables = ['restclient'] s.extra_rdoc_files = ['README.md', 'history.md'] s.files = `git ls-files -z`.split("\0") s.test_files = `git ls-files -z spec/`.split("\0") s.homepage = 'https://github.com/rest-client/rest-client' s.summary = 'Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.' s.add_development_dependency('webmock', '~> 2.0') s.add_development_dependency('rspec', '~> 3.0') s.add_development_dependency('pry', '~> 0') s.add_development_dependency('pry-doc', '~> 0') s.add_development_dependency('rdoc', '>= 2.4.2', '< 6.0') s.add_development_dependency('rubocop', '~> 0.49') s.add_dependency('http-accept', '>= 1.7.0', '< 2.0') s.add_dependency('http-cookie', '>= 1.0.2', '< 2.0') s.add_dependency('mime-types', '>= 1.16', '< 4.0') s.add_dependency('netrc', '~> 0.8') s.required_ruby_version = '>= 2.0.0' end rest-client-2.1.0/history.md0000644000004100000410000004601213531144232016017 0ustar www-datawww-data# 2.1.0 - Add a dependency on http-accept for parsing Content-Type charset headers. This works around a bad memory leak introduced in MRI Ruby 2.4.0 and fixed in Ruby 2.4.2. (#615) - Use mime/types/columnar from mime-types 2.6.1+, which is leaner in memory usage than the older storage model of mime-types. (#393) - Add `:log` option to individual requests. This allows users to set a log on a per-request / per-resource basis instead of the kludgy global log. (#538) - Log request duration by tracking request start and end times. Make `log_response` a method on the Response object, and ensure the `size` method works on RawResponse objects. (#126) - `# => 200 OK | text/html 1270 bytes, 0.08s` - Also add a new `:stream_log_percent` parameter, which is applicable only when `:raw_response => true` is set. This causes progress logs to be emitted only on every N% (default 10%) of the total download size rather than on every chunk. - Drop custom handling of compression and use built-in Net::HTTP support for supported Content-Encodings like gzip and deflate. Don't set any explicit `Accept-Encoding` header, rely instead on Net::HTTP defaults. (#597) - Note: this changes behavior for compressed responses when using `:raw_response => true`. Previously the raw response would not have been uncompressed by rest-client, but now Net::HTTP will uncompress it. - The previous fix to avoid having Netrc username/password override an Authorization header was case-sensitive and incomplete. Fix this by respecting existing Authorization headers, regardless of letter case. (#550) - Handle ParamsArray payloads. Previously, rest-client would silently drop a ParamsArray passed as the payload. Instead, automatically use Payload::Multipart if the ParamsArray contains a file handle, or use Payload::UrlEncoded if it doesn't. (#508) - Gracefully handle Payload objects (Payload::Base or subclasses) that are passed as a payload argument. Previously, `Payload.generate` would wrap a Payload object in Payload::Streamed, creating a pointlessly nested payload. Also add a `closed?` method to Payload objects, and don't error in `short_inspect` if `size` returns nil. (#603) - Test with an image in the public domain to avoid licensing complexity. (#607) # 2.0.2 - Suppress the header override warning introduced in 2.0.1 if the value is the same. There's no conflict if the value is unchanged. (#578) # 2.0.1 - Warn if auto-generated headers from the payload, such as Content-Type, override headers set by the user. This is usually not what the user wants to happen, and can be surprising. (#554) - Drop the old check for weak default TLS ciphers, and use the built-in Ruby defaults. Ruby versions from Oct. 2014 onward use sane defaults, so this is no longer needed. (#573) # 2.0.0 This release is largely API compatible, but makes several breaking changes. - Drop support for Ruby 1.9 - Allow mime-types as new as 3.x (requires ruby 2.0) - Respect Content-Type charset header provided by server. Previously, rest-client would not override the string encoding chosen by Net::HTTP. Now responses that specify a charset will yield a body string in that encoding. For example, `Content-Type: text/plain; charset=EUC-JP` will return a String encoded with `Encoding::EUC_JP`. (#361) - Change exceptions raised on request timeout. Instead of `RestClient::RequestTimeout` (which is still used for HTTP 408), network timeouts will now raise either `RestClient::Exceptions::ReadTimeout` or `RestClient::Exceptions::OpenTimeout`, both of which inherit from `RestClient::Exceptions::Timeout`. For backwards compatibility, this still inherits from `RestClient::RequestTimeout` so existing uses will still work. This may change in a future major release. These new timeout classes also make the original wrapped exception available as `#original_exception`. - Unify request exceptions under `RestClient::RequestFailed`, which still inherits from `ExceptionWithResponse`. Previously, HTTP 304, 401, and 404 inherited directly from `ExceptionWithResponse` rather than from `RequestFailed`. Now _all_ HTTP status code exceptions inherit from both. - Rename the `:timeout` request option to `:read_timeout`. When `:timeout` is passed, now set both `:read_timeout` and `:open_timeout`. - Change default HTTP Accept header to `*/*` - Use a more descriptive User-Agent header by default - Drop RC4-MD5 from default cipher list - Only prepend http:// to URIs without a scheme - Fix some support for using IPv6 addresses in URLs (still affected by Ruby 2.0+ bug https://bugs.ruby-lang.org/issues/9129, with the fix expected to be backported to 2.0 and 2.1) - `Response` objects are now a subclass of `String` rather than a `String` that mixes in the response functionality. Most of the methods remain unchanged, but this makes it much easier to understand what is happening when you look at a RestClient response object. There are a few additional changes: - Response objects now implement `.inspect` to make this distinction clearer. - `Response#to_i` will now behave like `String#to_i` instead of returning the HTTP response code, which was very surprising behavior. - `Response#body` and `#to_s` will now return a true `String` object rather than self. Previously there was no easy way to get the true `String` response instead of the Frankenstein response string object with AbstractResponse mixed in. - Response objects no longer accept an extra request args hash, but instead access request args directly from the request object, which reduces confusion and duplication. - Handle multiple HTTP response headers with the same name (except for Set-Cookie, which is special) by joining the values with a comma space, compliant with RFC 7230 - Rewrite cookie support to be much smarter and to use cookie jars consistently for requests, responses, and redirection in order to resolve long-standing complaints about the previously broken behavior: (#498) - The `:cookies` option may now be a Hash of Strings, an Array of HTTP::Cookie objects, or a full HTTP::CookieJar. - Add `RestClient::Request#cookie_jar` and reimplement `Request#cookies` to be a wrapper around the cookie jar. - Still support passing the `:cookies` option in the headers hash, but now raise ArgumentError if that option is also passed to `Request#initialize`. - Warn if both `:cookies` and a `Cookie` header are supplied. - Use the `Request#cookie_jar` as the basis for `Response#cookie_jar`, creating a copy of the jar and adding any newly received cookies. - When following redirection, also use this same strategy so that cookies from the original request are carried through in a standards-compliant way by the cookie jar. - Don't set basic auth header if explicit `Authorization` header is specified - Add `:proxy` option to requests, which can be used for thread-safe per-request proxy configuration, overriding `RestClient.proxy` - Allow overriding `ENV['http_proxy']` to disable proxies by setting `RestClient.proxy` to a falsey value. Previously there was no way in Ruby 2.x to turn off a proxy specified in the environment without changing `ENV`. - Add actual support for streaming request payloads. Previously rest-client would call `.to_s` even on RestClient::Payload::Streamed objects. Instead, treat any object that responds to `.read` as a streaming payload and pass it through to `.body_stream=` on the Net:HTTP object. This massively reduces the memory required for large file uploads. - Changes to redirection behavior: (#381, #484) - Remove `RestClient::MaxRedirectsReached` in favor of the normal `ExceptionWithResponse` subclasses. This makes the response accessible on the exception object as `.response`, making it possible for callers to tell what has actually happened when the redirect limit is reached. - When following HTTP redirection, store a list of each previous response on the response object as `.history`. This makes it possible to access the original response headers and body before the redirection was followed. - Follow redirection consistently, regardless of whether the HTTP method was passed as a symbol or string. Under the hood rest-client now normalizes the HTTP request method to a lowercase string. - Add `:before_execution_proc` option to `RestClient::Request`. This makes it possible to add procs like `RestClient.add_before_execution_proc` to a single request without global state. - Run tests on Travis's beta OS X support. - Make `Request#transmit` a private method, along with a few others. - Refactor URI parsing to happen earlier, in Request initialization. - Improve consistency and functionality of complex URL parameter handling: - When adding URL params, handle URLs that already contain params. - Add new convention for handling URL params containing deeply nested arrays and hashes, unify handling of null/empty values, and use the same code for GET and POST params. (#437) - Add the RestClient::ParamsArray class, a simple array-like container that can be used to pass multiple keys with same name or keys where the ordering is significant. - Add a few more exception classes for obscure HTTP status codes. - Multipart: use a much more robust multipart boundary with greater entropy. - Make `RestClient::Payload::Base#inspect` stop pretending to be a String. - Add `Request#redacted_uri` and `Request#redacted_url` to display the URI with any password redacted. # 2.0.0.rc1 Changes in the release candidate that did not persist through the final 2.0.0 release: - RestClient::Exceptions::Timeout was originally going to be a direct subclass of RestClient::Exception in the release candidate. This exception tree was made a subclass of RestClient::RequestTimeout prior to the final release. # 1.8.0 - Security: implement standards compliant cookie handling by adding a dependency on http-cookie. This breaks compatibility, but was necessary to address a session fixation / cookie disclosure vulnerability. (#369 / CVE-2015-1820) Previously, any Set-Cookie headers found in an HTTP 30x response would be sent to the redirection target, regardless of domain. Responses now expose a cookie jar and respect standards compliant domain / path flags in Set-Cookie headers. # 1.7.3 - Security: redact password in URI from logs (#349 / OSVDB-117461) - Drop monkey patch on MIME::Types (added `type_for_extension` method, use the public interface instead. # 1.7.2 - Ignore duplicate certificates in CA store on Windows # 1.7.1 - Relax mime-types dependency to continue supporting mime-types 1.x series. There seem to be a large number of popular gems that have depended on mime-types '~> 1.16' until very recently. - Improve urlencode performance - Clean up a number of style points # 1.7.0 - This release drops support for Ruby 1.8.7 and breaks compatibility in a few other relatively minor ways - Upgrade to mime-types ~> 2.0 - Don't CGI.unescape cookie values sent to the server (issue #89) - Add support for reading credentials from netrc - Lots of SSL changes and enhancements: (#268) - Enable peer verification by default (setting `VERIFY_PEER` with OpenSSL) - By default, use the system default certificate store for SSL verification, even on Windows (this uses a separate Windows build that pulls in ffi) - Add support for SSL `ca_path` - Add support for SSL `cert_store` - Add support for SSL `verify_callback` (with some caveats for jruby, OS X, #277) - Add support for SSL ciphers, and choose secure ones by default - Run tests under travis - Several other bugfixes and test improvements - Convert Errno::ETIMEDOUT to RestClient::RequestTimeout - Handle more HTTP response codes from recent standards - Save raw responses to binary mode tempfile (#110) - Disable timeouts with :timeout => nil rather than :timeout => -1 - Drop all Net::HTTP monkey patches # 1.6.14 - This release is unchanged from 1.6.9. It was published in order to supersede the malicious 1.6.10-13 versions, even for users who are still pinning to the legacy 1.6.x series. All users are encouraged to upgrade to rest-client 2.x. # 1.6.10, 1.6.11, 1.6.12, 1.6.13 (CVE-2019-15224) - These versions were pushed by a malicious actor and included a backdoor permitting remote code execution in Rails environments. (#713) - They were live for about five days before being yanked. # 1.6.9 - Move rdoc to a development dependency # 1.6.8 - The 1.6.x series will be the last to support Ruby 1.8.7 - Pin mime-types to < 2.0 to maintain Ruby 1.8.7 support - Add Gemfile, AUTHORS, add license to gemspec - Point homepage at https://github.com/rest-client/rest-client - Clean up and fix various tests and ruby warnings - Backport `ssl_verify_callback` functionality from 1.7.0 # 1.6.7 - rebuild with 1.8.7 to avoid https://github.com/rubygems/rubygems/pull/57 # 1.6.6 - 1.6.5 was yanked # 1.6.5 - RFC6265 requires single SP after ';' for separating parameters pairs in the 'Cookie:' header (patch provided by Hiroshi Nakamura) - enable url parameters for all actions - detect file parameters in arrays - allow disabling the timeouts by passing -1 (patch provided by Sven Böhm) # 1.6.4 - fix restclient script compatibility with 1.9.2 - fix unlinking temp file (patch provided by Evan Smith) - monkeypatching ruby for http patch method (patch provided by Syl Turner) # 1.6.3 - 1.6.2 was yanked # 1.6.2 - add support for HEAD in resources (patch provided by tpresa) - fix shell for 1.9.2 - workaround when some gem monkeypatch net/http (patch provided by Ian Warshak) - DELETE requests should process parameters just like GET and HEAD - adding :block_response parameter for manual processing - limit number of redirections (patch provided by Chris Dinn) - close and unlink the temp file created by playload (patch provided by Chris Green) - make gemspec Rubygems 1.8 compatible (patch provided by David Backeus) - added RestClient.reset_before_execution_procs (patch provided by Cloudify) - added PATCH method (patch provided by Jeff Remer) - hack for HTTP servers that use raw DEFLATE compression, see http://www.ruby-forum.com/topic/136825 (path provided by James Reeves) # 1.6.1 - add response body in Exception#inspect - add support for RestClient.options - fix tests for 1.9.2 (patch provided by Niko Dittmann) - block passing in Resource#[] (patch provided by Niko Dittmann) - cookies set in a response should be kept in a redirect - HEAD requests should process parameters just like GET (patch provided by Rob Eanes) - exception message should never be nil (patch provided by Michael Klett) # 1.6.0 - forgot to include rest-client.rb in the gem - user, password and user-defined headers should survive a redirect - added all missing status codes - added parameter passing for get request using the :param key in header - the warning about the logger when using a string was a bad idea - multipart parameters names should not be escaped - remove the cookie escaping introduced by migrating to CGI cookie parsing in 1.5.1 - add a streamed payload type (patch provided by Caleb Land) - Exception#http_body works even when no response # 1.5.1 - only converts headers keys which are Symbols - use CGI for cookie parsing instead of custom code - unescape user and password before using them (patch provided by Lars Gierth) - expand ~ in ~/.restclientrc (patch provided by Mike Fletcher) - ssl verification raise an exception when the ca certificate is incorrect (patch provided by Braintree) # 1.5.0 - 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) - added AbstractResponse.to_i to improve semantic - multipart Payloads ignores the name attribute if it's not set (patch provided by Tekin Suleyman) - correctly takes into account user headers whose keys are strings (path provided by Cyril Rohr) - use binary mode for payload temp file - concatenate cookies with ';' - fixed deeper parameter handling - do not quote the boundary in the Content-Type header (patch provided by W. Andrew Loe III) # 1.4.2 - fixed RestClient.add_before_execution_proc (patch provided by Nicholas Wieland) - fixed error when an exception is raised without a response (patch provided by Caleb Land) # 1.4.1 - fixed parameters managment when using hash # 1.4.0 - 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. - enable repeated parameters RestClient.post 'http://example.com/resource', :param1 => ['one', 'two', 'three'], => :param2 => 'foo' (patch provided by Rodrigo Panachi) - fixed the redirect code concerning relative path and query string combination (patch provided by Kevin Read) - redirection code moved to Response so redirection can be customized using the block syntax - only get and head redirections are now followed by default, as stated in the specification - added RestClient.add_before_execution_proc to hack the http request, like for oauth The response change may be breaking in rare cases. # 1.3.1 - added compatibility to enable responses in exception to act like Net::HTTPResponse # 1.3.0 - a block can be used to process a request's result, this enable to handle custom error codes or paththrought (design by Cyril Rohr) - cleaner log API, add a warning for some cases but should be compatible - accept multiple "Set-Cookie" headers, see http://www.ietf.org/rfc/rfc2109.txt (patch provided by Cyril Rohr) - remove "Content-Length" and "Content-Type" headers when following a redirection (patch provided by haarts) - 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 - changed "Content-Disposition: multipart/form-data" to "Content-Disposition: form-data" per RFC 2388 (patch provided by Kyle Crawford) 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. # 1.2.0 - formatting changed from tabs to spaces - logged requests now include generated headers - 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 - should be 1.1.1 but renamed to 1.2.0 because 1.1.X versions has already been packaged on Debian # 1.1.0 - new maintainer: Archiloque, the working repo is now at http://github.com/archiloque/rest-client - a mailing list has been created at rest.client@librelist.com and an freenode irc channel #rest-client - François Beausoleil' multipart code from http://github.com/francois/rest-client has been merged - ability to use hash in hash as payload - the mime-type code now rely on the mime-types gem http://mime-types.rubyforge.org/ instead of an internal partial list - 204 response returns a Response instead of nil (patch provided by Elliott Draper) All changes exept the last one should be fully compatible with the previous version. 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. rest-client-2.1.0/Gemfile0000644000004100000410000000025413531144232015265 0ustar www-datawww-datasource "https://rubygems.org" if !!File::ALT_SEPARATOR gemspec :name => 'rest-client.windows' else gemspec :name => 'rest-client' end group :test do gem 'rake' end rest-client-2.1.0/.rubocop0000644000004100000410000000004313531144232015440 0ustar www-datawww-data--display-cop-names --fail-level=W