rest-client-1.8.0/0000755000175000017500000000000012522136372012754 5ustar lucaslucasrest-client-1.8.0/.travis.yml0000644000175000017500000000045012522136372015064 0ustar lucaslucaslanguage: ruby rvm: - "1.9.2" - "1.9.3" - "2.0.0" # Forgo 2.1.0 until Travis has a satisfactory fix for # https://github.com/travis-ci/travis-ci/issues/2220 - "2.1" # always the latest 2.1.x - "jruby-19mode" script: bundle exec rake test branches: except: - "readme-edits" rest-client-1.8.0/history.md0000644000175000017500000002136312522136372015004 0ustar lucaslucas# 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.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-1.8.0/rest-client.gemspec0000644000175000017500000000227712522136372016562 0ustar lucaslucas# -*- 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 = 'rest.client@librelist.com' s.executables = ['restclient'] s.extra_rdoc_files = ['README.rdoc', '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', '~> 1.4') s.add_development_dependency('rspec', '~> 2.4') s.add_development_dependency('pry') s.add_development_dependency('pry-doc') s.add_development_dependency('rdoc', '>= 2.4.2', '< 5.0') s.add_dependency('http-cookie', '>= 1.0.2', '< 2.0') s.add_dependency('mime-types', '>= 1.16', '< 3.0') s.add_dependency('netrc', '~> 0.7') s.required_ruby_version = '>= 1.9.2' end rest-client-1.8.0/bin/0000755000175000017500000000000012522136372013524 5ustar lucaslucasrest-client-1.8.0/bin/restclient0000755000175000017500000000350412522136372015630 0ustar lucaslucas#!/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| eval <<-end_eval def #{m}(path, *args, &b) r[path].#{m}(*args, &b) end end_eval 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-1.8.0/lib/0000755000175000017500000000000012522136372013522 5ustar lucaslucasrest-client-1.8.0/lib/rest_client.rb0000644000175000017500000000016712522136372016366 0ustar lucaslucas# This file exists for backward compatbility with require 'rest_client' require File.dirname(__FILE__) + '/restclient' rest-client-1.8.0/lib/rest-client.rb0000644000175000017500000000013312522136372016275 0ustar lucaslucas# More logical way to require 'rest-client' require File.dirname(__FILE__) + '/restclient' rest-client-1.8.0/lib/restclient.rb0000644000175000017500000001226012522136372016224 0ustar lucaslucasrequire 'net/http' require 'openssl' require 'stringio' require 'uri' require 'zlib' require File.dirname(__FILE__) + '/restclient/version' require File.dirname(__FILE__) + '/restclient/platform' require File.dirname(__FILE__) + '/restclient/exceptions' 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/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 class << self attr_accessor :proxy 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 @@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-1.8.0/lib/restclient/0000755000175000017500000000000012522136372015676 5ustar lucaslucasrest-client-1.8.0/lib/restclient/response.rb0000644000175000017500000000061512522136372020063 0ustar lucaslucasmodule RestClient # A Response from RestClient, you can access the response body, the code or the headers. # module Response include AbstractResponse def body self end def self.create body, net_http_res, args, request result = body || '' result.extend Response result.response_set_vars(net_http_res, args, request) result end end end rest-client-1.8.0/lib/restclient/payload.rb0000644000175000017500000001311512522136372017655 0ustar lucaslucasrequire 'tempfile' require 'stringio' require 'mime/types' module RestClient module Payload extend self def generate(params) if 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.respond_to?(:read) Streamed.new(params) else nil end end def has_file?(params) params.any? do |_, v| case v when Hash has_file?(v) when Array has_file_array?(v) else v.respond_to?(:path) && v.respond_to?(:read) end end end def has_file_array?(params) params.any? do |v| case v when Hash has_file?(v) when Array has_file_array?(v) else v.respond_to?(:path) && v.respond_to?(:read) end 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(bytes=nil) @stream.read(bytes) end alias :to_s :read # Flatten parameters by converting hashes of hashes to flat hashes # {keys1 => {keys2 => value}} will be transformed into [keys1[key2], value] def flatten_params(params, parent_key = nil) result = [] params.each do |key, value| calculated_key = parent_key ? "#{parent_key}[#{handle_key(key)}]" : handle_key(key) if value.is_a? Hash result += flatten_params(value, calculated_key) elsif value.is_a? Array result += flatten_params_array(value, calculated_key) else result << [calculated_key, value] end end result end def flatten_params_array value, calculated_key result = [] value.each do |elem| if elem.is_a? Hash result += flatten_params(elem, calculated_key) elsif elem.is_a? Array result += flatten_params_array(elem, calculated_key) else result << ["#{calculated_key}[]", elem] end end 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 inspect result = to_s.inspect @stream.seek(0) result end def short_inspect (size > 500 ? "#{size} byte(s) length" : inspect) 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 alias :length :size end class UrlEncoded < Base def build_stream(params = nil) @stream = StringIO.new(flatten_params(params).collect do |entry| "#{entry[0]}=#{handle_key(entry[1])}" end.join("&")) @stream.seek(0) end # for UrlEncoded escape the keys def handle_key key Parser.escape(key.to_s, Escape) end def headers super.merge({'Content-Type' => 'application/x-www-form-urlencoded'}) end Parser = URI.const_defined?(:Parser) ? URI::Parser.new : URI Escape = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]") end class Multipart < Base EOL = "\r\n" def build_stream(params) b = "--#{boundary}" @stream = Tempfile.new("RESTClient.Stream.#{rand(1000)}") @stream.binmode @stream.write(b + EOL) if params.is_a? Hash x = 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 @boundary ||= rand(1_000_000).to_s end # for Multipart do not escape the keys 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-1.8.0/lib/restclient/raw_response.rb0000644000175000017500000000155512522136372020740 0ustar lucaslucasmodule 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 def initialize(tempfile, net_http_res, args, request) @net_http_res = net_http_res @args = args @file = tempfile @request = request end def to_s @file.open @file.read end def size File.size file end end end rest-client-1.8.0/lib/restclient/request.rb0000644000175000017500000004773612522136372017734 0ustar lucaslucasrequire 'tempfile' require 'mime/types' require 'cgi' require 'netrc' require 'set' 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 will replace possible cookies in the :headers # * :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 # * :max_redirects maximum number of redirections (default to 10) # * :verify_ssl enable ssl verification, possible values are constants from # OpenSSL::SSL::VERIFY_*, defaults to OpenSSL::SSL::VERIFY_PEER # * :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. # * :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= class Request attr_reader :method, :url, :headers, :cookies, :payload, :user, :password, :timeout, :max_redirects, :open_timeout, :raw_response, :processed_headers, :args, :ssl_opts def self.execute(args, & block) new(args).execute(& block) end # This is similar to the list now in ruby core, but adds HIGH and RC4-MD5 # for better compatibility (similar to Firefox) and moves AES-GCM cipher # suites above DHE/ECDHE CBC suites (similar to Chromium). # https://github.com/ruby/ruby/commit/699b209cf8cf11809620e12985ad33ae33b119ee # # This list will be used by default if the Ruby global OpenSSL default # ciphers appear to be a weak list. DefaultCiphers = %w{ !aNULL !eNULL !EXPORT !SSLV2 !LOW ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-DSS-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-DSS-AES256-GCM-SHA384 AES128-GCM-SHA256 AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 ECDHE-ECDSA-AES128-SHA ECDHE-RSA-AES128-SHA ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA384 ECDHE-ECDSA-AES256-SHA ECDHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA DHE-DSS-AES128-SHA256 DHE-DSS-AES256-SHA256 DHE-DSS-AES128-SHA DHE-DSS-AES256-SHA AES128-SHA256 AES256-SHA256 AES128-SHA AES256-SHA ECDHE-ECDSA-RC4-SHA ECDHE-RSA-RC4-SHA RC4-SHA HIGH +RC4 RC4-MD5 }.join(":") # A set of weak default ciphers that we will override by default. WeakDefaultCiphers = Set.new([ "ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW", ]) SSLOptionList = %w{client_cert client_key ca_file ca_path cert_store version ciphers verify_callback verify_callback_warnings} def initialize args @method = args[:method] or raise ArgumentError, "must pass :method" @headers = args[:headers] || {} if args[:url] @url = process_url_params(args[:url], headers) else raise ArgumentError, "must pass :url" end @cookies = @headers.delete(:cookies) || args[:cookies] || {} @payload = Payload.generate(args[:payload]) @user = args[:user] @password = args[:password] if args.include?(:timeout) @timeout = args[:timeout] end if args.include?(:open_timeout) @open_timeout = args[:open_timeout] end @block_response = args[:block_response] @raw_response = args[:raw_response] || false @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 # 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 unless @ssl_opts.include?(:ciphers) # If we're on a Ruby version that has insecure default ciphers, # override it with our default list. if WeakDefaultCiphers.include?( OpenSSL::SSL::SSLContext::DEFAULT_PARAMS.fetch(:ciphers)) @ssl_opts[:ciphers] = DefaultCiphers end end @tf = nil # If you are a raw request, this is your tempfile @max_redirects = args[:max_redirects] || 10 @processed_headers = make_headers headers @args = args end def execute & block uri = parse_url_with_auth(url) transmit uri, net_http_request_class(method).new(uri.request_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 # Extract the query parameters and append them to the url def process_url_params url, headers url_params = {} headers.delete_if do |key, value| if 'params' == key.to_s.downcase && value.is_a?(Hash) url_params.merge! value true else false end end unless url_params.empty? query_string = url_params.collect { |k, v| "#{k.to_s}=#{CGI::escape(v.to_s)}" }.join('&') url + "?#{query_string}" else url end end def make_headers user_headers unless @cookies.empty? # Validate that the cookie names and values look sane. If you really # want to pass scary characters, just set the Cookie header directly. # RFC6265 is actually much more restrictive than we are. @cookies.each do |key, val| unless valid_cookie_key?(key) raise ArgumentError.new("Invalid cookie name: #{key.inspect}") end unless valid_cookie_value?(val) raise ArgumentError.new("Invalid cookie value: #{val.inspect}") end end user_headers[:cookie] = @cookies.map { |key, val| "#{key}=#{val}" }.sort.join('; ') end headers = stringify_headers(default_headers).merge(stringify_headers(user_headers)) headers.merge!(@payload.headers) if @payload headers end # Do some sanity checks on cookie keys. # # Properly it should be a valid TOKEN per RFC 2616, but lots of servers are # more liberal. # # Disallow the empty string as well as keys containing control characters, # equals sign, semicolon, comma, or space. # def valid_cookie_key?(string) return false if string.empty? ! Regexp.new('[\x0-\x1f\x7f=;, ]').match(string) end # Validate cookie values. Rather than following RFC 6265, allow anything # but control characters, comma, and semicolon. def valid_cookie_value?(value) ! Regexp.new('[\x0-\x1f\x7f,;]').match(value) end def net_http_class if RestClient.proxy proxy_uri = URI.parse(RestClient.proxy) Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password) else Net::HTTP end end def net_http_request_class(method) Net::HTTP.const_get(method.to_s.capitalize) end def net_http_do_request(http, req, body=nil, &block) if body != nil && body.respond_to?(:read) req.body_stream = body return http.request(req, nil, &block) else return http.request(req, body, &block) end end def parse_url(url) url = "http://#{url}" unless url.match(/^http/) URI.parse(url) end def parse_url_with_auth(url) uri = parse_url(url) @user = CGI.unescape(uri.user) if uri.user @password = CGI.unescape(uri.password) if uri.password if !@user && !@password @user, @password = Netrc.read[uri.host] end uri end def process_payload(p=nil, parent_key=nil) unless p.is_a?(Hash) p else @headers[:content_type] ||= 'application/x-www-form-urlencoded' p.keys.map do |k| key = parent_key ? "#{parent_key}[#{k}]" : k if p[k].is_a? Hash process_payload(p[k], key) else value = parser.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")) "#{key}=#{value}" end end.join("&") end 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 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 def transmit uri, req, payload, & block setup_credentials req net = net_http_class.new(uri.host, 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? @timeout if @timeout == -1 warn 'To disable read timeouts, please set timeout to nil instead of -1' @timeout = nil end net.read_timeout = @timeout end if defined? @open_timeout if @open_timeout == -1 warn 'To disable open timeouts, please set open_timeout to 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 log_request net.start do |http| if @block_response net_http_do_request(http, req, payload ? payload.to_s : nil, &@block_response) else res = net_http_do_request(http, req, payload ? payload.to_s : nil) \ { |http_response| fetch_body(http_response) } log_response res process_result res, & block end end rescue EOFError raise RestClient::ServerBrokeConnection rescue Timeout::Error, Errno::ETIMEDOUT raise RestClient::RequestTimeout 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) req.basic_auth(user, password) if user end def fetch_body(http_response) if @raw_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, total = 0, http_response.header['Content-Length'].to_i http_response.read_body do |chunk| @tf.write chunk size += chunk.size if RestClient.log if size == 0 RestClient.log << "%s %s done (0 length file)\n" % [@method, @url] elsif total == 0 RestClient.log << "%s %s (zero content length)\n" % [@method, @url] else RestClient.log << "%s %s %d%% done (%d of %d)\n" % [@method, @url, (size * 100) / total, size, total] end end end @tf.close @tf else http_response.read_body end http_response end def process_result res, & block if @raw_response # We don't decode raw requests response = RawResponse.new(@tf, res, args, self) else response = Response.create(Request.decode(res['content-encoding'], res.body), res, args, self) end if block_given? block.call(response, self, res, & block) else response.return!(self, res, & block) end end def self.decode content_encoding, body if (!body) || body.empty? body elsif content_encoding == 'gzip' Zlib::GzipReader.new(StringIO.new(body)).read elsif content_encoding == 'deflate' begin Zlib::Inflate.new.inflate body rescue Zlib::DataError # No luck with Zlib decompression. Let's try with raw deflate, # like some broken web servers do. Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate body end else body end end def log_request return unless RestClient.log out = [] sanitized_url = begin uri = URI.parse(url) uri.password = "REDACTED" if uri.password uri.to_s rescue URI::InvalidURIError # An attacker may be able to manipulate the URL to be # invalid, which could force discloure of a password if # we show any of the un-parsed URL here. "[invalid uri]" end out << "RestClient.#{method} #{sanitized_url.inspect}" out << payload.short_inspect if payload out << processed_headers.to_a.sort.map { |(k, v)| [k.inspect, v.inspect].join("=>") }.join(", ") RestClient.log << out.join(', ') + "\n" end def log_response res return unless RestClient.log size = if @raw_response File.size(@tf.path) else res.body.nil? ? 0 : res.body.size end RestClient.log << "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes\n" end # Return a hash of headers whose keys are capitalized strings def stringify_headers headers headers.inject({}) do |result, (key, value)| if key.is_a? Symbol key = key.to_s.split(/_/).map { |w| w.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 def default_headers {:accept => '*/*; q=0.5, application/xml', :accept_encoding => 'gzip, deflate'} end private 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-1.8.0/lib/restclient/windows.rb0000644000175000017500000000017512522136372017720 0ustar lucaslucasmodule RestClient module Windows end end if RestClient::Platform.windows? require_relative './windows/root_certs' end rest-client-1.8.0/lib/restclient/abstract_response.rb0000644000175000017500000000713612522136372021753 0ustar lucaslucasrequire 'cgi' require 'http-cookie' module RestClient module AbstractResponse attr_reader :net_http_res, :args, :request # HTTP status code def code @code ||= @net_http_res.code.to_i 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 def response_set_vars(net_http_res, args, request) @net_http_res = net_http_res @args = args @request = request end # Hash of cookies extracted from response headers def cookies hash = {} cookie_jar.cookies.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 @cookie_jar jar = HTTP::CookieJar.new headers.fetch(:set_cookie, []).each do |cookie| jar.parse(cookie, @request.url) end @cookie_jar = jar end # Return the default behavior corresponding to the response code: # the response itself for code in 200..206, redirection for 301, 302 and 307 in get and head cases, redirection for 303 and an exception in other cases def return! request = nil, result = nil, & block if (200..207).include? code self elsif [301, 302, 307].include? code unless [:get, :head].include? args[:method] raise Exceptions::EXCEPTIONS_MAP[code].new(self, code) else follow_redirection(request, result, & block) end elsif code == 303 args[:method] = :get args.delete :payload follow_redirection(request, result, & block) elsif Exceptions::EXCEPTIONS_MAP[code] raise Exceptions::EXCEPTIONS_MAP[code].new(self, code) else raise RequestFailed.new(self, code) end end def to_i code end def description "#{code} #{STATUSES[code]} | #{(headers[:content_type] || '').gsub(/;.*$/, '')} #{size} bytes\n" end # Follow a redirection def follow_redirection request = nil, result = nil, & block new_args = @args.dup url = headers[:location] if url !~ /^http/ url = URI.parse(request.url).merge(url).to_s end new_args[:url] = url if request if request.max_redirects == 0 raise MaxRedirectsReached end new_args[:password] = request.password new_args[:user] = request.user new_args[:headers] = request.headers new_args[:max_redirects] = request.max_redirects - 1 # TODO: figure out what to do with original :cookie, :cookies values new_args[:headers]['Cookie'] = HTTP::Cookie.cookie_value( cookie_jar.cookies(new_args.fetch(:url))) end Request.execute(new_args, &block) end def self.beautify_headers(headers) headers.inject({}) do |out, (key, value)| out[key.gsub(/-/, '_').downcase.to_sym] = %w{ set-cookie }.include?(key.downcase) ? value : value.first out end end private # Parse a cookie value and return its content in an Hash def parse_cookie cookie_content out = {} CGI::Cookie::parse(cookie_content).each do |key, cookie| unless ['expires', 'path'].include? key out[CGI::escape(key)] = cookie.value[0] ? (CGI::escape(cookie.value[0]) || '') : '' end end out end end end rest-client-1.8.0/lib/restclient/resource.rb0000644000175000017500000001220712522136372020054 0ustar lucaslucasmodule 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', :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), &(block || @block)) end def head(additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :head, :url => url, :headers => headers), &(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), &(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), &(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), &(block || @block)) end def delete(additional_headers={}, &block) headers = (options[:headers] || {}).merge(additional_headers) Request.execute(options.merge( :method => :delete, :url => url, :headers => headers), &(block || @block)) end def to_s url end def user options[:user] end def password options[:password] end def headers options[:headers] || {} end def timeout options[:timeout] end def open_timeout options[:open_timeout] 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-1.8.0/lib/restclient/exceptions.rb0000644000175000017500000001407012522136372020406 0ustar lucaslucasmodule RestClient 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 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 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Resource 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 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 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 509 => 'Bandwidth Limit Exceeded', #Apache 510 => 'Not Extended', 511 => 'Network Authentication Required', # RFC6585 } # Compatibility : make the Response act like a Net::HTTPResponse when needed module ResponseForException def method_missing symbol, *args if net_http_res.respond_to? symbol warn "[warning] The response contained in an RestClient::Exception is now a RestClient::Response instead of a Net::HTTPResponse, please update your code" net_http_res.send symbol, *args else super end end end # 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_writer :message def initialize response = nil, initial_response_code = nil @response = response @message = nil @initial_response_code = initial_response_code # compatibility: this make the exception behave like a Net::HTTPResponse response.extend ResponseForException if response end def http_code # return integer for compatibility if @response @response.code.to_i else @initial_response_code end end def http_body @response.body if @response end def inspect "#{message}: #{http_body}" end def to_s inspect end def message @message || self.class.name end end # Compatibility class ExceptionWithResponse < Exception end # The request failed with an error code not managed by the code class RequestFailed < ExceptionWithResponse def message "HTTP status code #{http_code}" end def to_s message end end # 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 STATUSES.each_pair do |code, message| # Compatibility superclass = ([304, 401, 404].include? code) ? ExceptionWithResponse : RequestFailed klass = Class.new(superclass) do send(:define_method, :message) {"#{http_code ? "#{http_code} " : ''}#{message}"} end klass_constant = const_set message.delete(' \-\''), klass Exceptions::EXCEPTIONS_MAP[code] = klass_constant end # A redirect was encountered; caught by execute to retry with the new url. class Redirect < Exception def message 'Redirect' end attr_accessor :url def initialize(url) @url = url end end class MaxRedirectsReached < Exception def message 'Maximum number of redirect reached' 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 < Exception def initialize(message = 'Server broke connection') super nil, nil self.message = message end end class SSLCertificateNotVerified < Exception def initialize(message) super nil, nil self.message = message end end end class RestClient::Request # backwards compatibility Redirect = RestClient::Redirect Unauthorized = RestClient::Unauthorized RequestFailed = RestClient::RequestFailed end rest-client-1.8.0/lib/restclient/version.rb0000644000175000017500000000015712522136372017713 0ustar lucaslucasmodule RestClient VERSION = '1.8.0' unless defined?(self::VERSION) def self.version VERSION end end rest-client-1.8.0/lib/restclient/windows/0000755000175000017500000000000012522136372017370 5ustar lucaslucasrest-client-1.8.0/lib/restclient/windows/root_certs.rb0000644000175000017500000000520312522136372022100 0ustar lucaslucasrequire '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-1.8.0/lib/restclient/platform.rb0000644000175000017500000000130612522136372020047 0ustar lucaslucasmodule 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 end end rest-client-1.8.0/spec/0000755000175000017500000000000012522136372013706 5ustar lucaslucasrest-client-1.8.0/spec/spec_helper.rb0000644000175000017500000000005512522136372016524 0ustar lucaslucasrequire 'webmock/rspec' require 'restclient' rest-client-1.8.0/spec/integration/0000755000175000017500000000000012522136372016231 5ustar lucaslucasrest-client-1.8.0/spec/integration/integration_spec.rb0000644000175000017500000000217712522136372022122 0ustar lucaslucasrequire 'spec_helper' 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" response.code.should eq 200 response.body.should eq body end it "a simple request with gzipped content" do stub_request(:get, "www.example.com").with(:headers => { 'Accept-Encoding' => 'gzip, deflate' }).to_return(:body => "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000", :status => 200, :headers => { 'Content-Encoding' => 'gzip' } ) response = RestClient.get "www.example.com" response.code.should eq 200 response.body.should eq "i'm gziped\n" 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 e.http_code.should eq 404 e.response.code.should eq 404 e.response.body.should eq body e.http_body.should eq body end end end rest-client-1.8.0/spec/integration/request_spec.rb0000644000175000017500000000703212522136372021262 0ustar lucaslucasrequire 'spec_helper' 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 ran_callback.should 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 end rest-client-1.8.0/spec/integration/capath_verisign/0000755000175000017500000000000012522136372021377 5ustar lucaslucasrest-client-1.8.0/spec/integration/capath_verisign/README0000644000175000017500000000047712522136372022267 0ustar lucaslucasThe 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-1.8.0/spec/integration/capath_verisign/7651b327.00000644000175000017500000000150212522136372022456 0ustar lucaslucas-----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-1.8.0/spec/integration/capath_verisign/415660c1.00000644000175000017500000000150212522136372022447 0ustar lucaslucas-----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-1.8.0/spec/integration/capath_verisign/verisign.crt0000644000175000017500000000150212522136372023735 0ustar lucaslucas-----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-1.8.0/spec/integration/certs/0000755000175000017500000000000012522136372017351 5ustar lucaslucasrest-client-1.8.0/spec/integration/certs/digicert.crt0000644000175000017500000000252312522136372021657 0ustar lucaslucas-----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- rest-client-1.8.0/spec/integration/certs/verisign.crt0000644000175000017500000000150212522136372021707 0ustar lucaslucas-----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-1.8.0/spec/integration/capath_digicert/0000755000175000017500000000000012522136372021343 5ustar lucaslucasrest-client-1.8.0/spec/integration/capath_digicert/81b9768f.00000644000175000017500000000252312522136372022524 0ustar lucaslucas-----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- rest-client-1.8.0/spec/integration/capath_digicert/README0000644000175000017500000000047712522136372022233 0ustar lucaslucasThe 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-1.8.0/spec/integration/capath_digicert/digicert.crt0000644000175000017500000000252312522136372023651 0ustar lucaslucas-----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- rest-client-1.8.0/spec/integration/capath_digicert/244b5494.00000644000175000017500000000252312522136372022427 0ustar lucaslucas-----BEGIN CERTIFICATE----- MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBsMQswCQYDVQQG EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSsw KQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5jZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAw MFoXDTMxMTExMDAwMDAwMFowbDELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZ MBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFu Y2UgRVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm+9S75S0t Mqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTWPNt0OKRKzE0lgvdKpVMS OO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEMxChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3 MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFBIk5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQ NAQTXKFx01p8VdteZOE3hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUe h10aUAsgEsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMB Af8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaAFLE+w2kD+L9HAdSY JhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3NecnzyIZgYIVyHbIUf4KmeqvxgydkAQ V8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6zeM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFp myPInngiK3BD41VHMWEZ71jFhS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkK mNEVX58Svnw2Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep+OkuE6N36B9K -----END CERTIFICATE----- rest-client-1.8.0/spec/unit/0000755000175000017500000000000012522136372014665 5ustar lucaslucasrest-client-1.8.0/spec/unit/abstract_response_spec.rb0000644000175000017500000000630512522136372021751 0ustar lucaslucasrequire 'spec_helper' describe RestClient::AbstractResponse do class MyAbstractResponse include RestClient::AbstractResponse attr_accessor :size def initialize net_http_res, args, request @net_http_res = net_http_res @args = args @request = request end end before do @net_http_res = double('net http response') @request = double('restclient request', :url => 'http://example.com') @response = MyAbstractResponse.new(@net_http_res, {}, @request) end it "fetches the numeric response code" do @net_http_res.should_receive(:code).and_return('200') @response.code.should eq 200 end it "has a nice description" do @net_http_res.should_receive(:to_hash).and_return({'Content-Type' => ['application/pdf']}) @net_http_res.should_receive(:code).and_return('200') @response.description.should eq "200 OK | application/pdf bytes\n" end it "beautifies the headers by turning the keys to symbols" do h = RestClient::AbstractResponse.beautify_headers('content-type' => [ 'x' ]) h.keys.first.should 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' ] ) h.values.first.should eq 'text/html' end it "fetches the headers" do @net_http_res.should_receive(:to_hash).and_return('content-type' => [ 'text/html' ]) @response.headers.should eq({ :content_type => 'text/html' }) end it "extracts cookies from response headers" do @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=1; path=/']) @response.cookies.should eq({ 'session_id' => '1' }) end it "extract strange cookies" do @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/']) @response.headers.should eq({:set_cookie => ['session_id=ZJ/HQVH6YE+rVkTpn0zvTQ==; path=/']}) @response.cookies.should eq({ 'session_id' => 'ZJ/HQVH6YE+rVkTpn0zvTQ==' }) end it "doesn't escape cookies" do @net_http_res.should_receive(:to_hash).and_return('set-cookie' => ['session_id=BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca; path=/']) @response.cookies.should eq({ 'session_id' => 'BAh7BzoNYXBwX25hbWUiEGFwcGxpY2F0aW9uOgpsb2dpbiIKYWRtaW4%3D%0A--08114ba654f17c04d20dcc5228ec672508f738ca' }) end it "can access the net http result directly" do @response.net_http_res.should eq @net_http_res end describe "#return!" do it "should return the response itself on 200-codes" do @net_http_res.should_receive(:code).and_return('200') @response.return!.should be_equal(@response) end it "should raise RequestFailed on unknown codes" do @net_http_res.should_receive(:code).and_return('1000') lambda { @response.return! }.should raise_error RestClient::RequestFailed end it "should raise an error on a redirection after non-GET/HEAD requests" do @net_http_res.should_receive(:code).and_return('301') @response.args.merge(:method => :put) lambda { @response.return! }.should raise_error RestClient::RequestFailed end end end rest-client-1.8.0/spec/unit/restclient_spec.rb0000644000175000017500000000504012522136372020377 0ustar lucaslucasrequire 'spec_helper' describe RestClient do describe "API" do it "GET" do RestClient::Request.should_receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {}) RestClient.get('http://some/resource') end it "POST" do RestClient::Request.should_receive(:execute).with(:method => :post, :url => 'http://some/resource', :payload => 'payload', :headers => {}) RestClient.post('http://some/resource', 'payload') end it "PUT" do RestClient::Request.should_receive(:execute).with(:method => :put, :url => 'http://some/resource', :payload => 'payload', :headers => {}) RestClient.put('http://some/resource', 'payload') end it "PATCH" do RestClient::Request.should_receive(:execute).with(:method => :patch, :url => 'http://some/resource', :payload => 'payload', :headers => {}) RestClient.patch('http://some/resource', 'payload') end it "DELETE" do RestClient::Request.should_receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {}) RestClient.delete('http://some/resource') end it "HEAD" do RestClient::Request.should_receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {}) RestClient.head('http://some/resource') end it "OPTIONS" do RestClient::Request.should_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 = [] log.should_receive(:<<).with('xyz') RestClient.log << 'xyz' end it "displays the log to stdout" do RestClient.log = 'stdout' STDOUT.should_receive(:puts).with('xyz') RestClient.log << 'xyz' end it "displays the log to stderr" do RestClient.log = 'stderr' STDERR.should_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') File.should_receive(:open).with('/tmp/restclient.log', 'a').and_yield(f) f.should_receive(:puts).with('xyz') RestClient.log << 'xyz' end end describe 'version' do it 'has a version ~> 1.8.0.alpha' do ver = Gem::Version.new(RestClient.version) Gem::Requirement.new('~> 1.8.0.alpha').should be_satisfied_by(ver) end end end rest-client-1.8.0/spec/unit/request_spec.rb0000644000175000017500000010365612522136372017727 0ustar lucaslucasrequire 'spec_helper' describe RestClient::Request do before do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload') @uri = double("uri") @uri.stub(:request_uri).and_return('/resource') @uri.stub(:host).and_return('some') @uri.stub(:port).and_return(80) @net = double("net::http base") @http = double("net::http connection") Net::HTTP.stub(:new).and_return(@net) @net.stub(:start).and_yield(@http) @net.stub(:use_ssl=) @net.stub(:verify_mode=) @net.stub(:verify_callback=) allow(@net).to receive(:ciphers=) allow(@net).to receive(:cert_store=) RestClient.log = nil end it "accept */* mimetype, preferring xml" do @request.default_headers[:accept].should eq '*/*; q=0.5, application/xml' end describe "compression" do it "decodes an uncompressed result body by passing it straight through" do RestClient::Request.decode(nil, 'xyz').should eq 'xyz' end it "doesn't fail for nil bodies" do RestClient::Request.decode('gzip', nil).should be_nil end it "decodes a gzip body" do RestClient::Request.decode('gzip', "\037\213\b\b\006'\252H\000\003t\000\313T\317UH\257\312,HM\341\002\000G\242(\r\v\000\000\000").should eq "i'm gziped\n" end it "ingores gzip for empty bodies" do RestClient::Request.decode('gzip', '').should be_empty end it "decodes a deflated body" do RestClient::Request.decode('deflate', "x\234+\316\317MUHIM\313I,IMQ(I\255(\001\000A\223\006\363").should eq "some deflated text" end end it "processes a successful result" do res = double("result") res.stub(:code).and_return("200") res.stub(:body).and_return('body') res.stub(:[]).with('content-encoding').and_return(nil) @request.process_result(res).body.should eq 'body' @request.process_result(res).to_s.should eq 'body' end it "doesn't classify successful requests as failed" do 203.upto(207) do |code| res = double("result") res.stub(:code).and_return(code.to_s) res.stub(:body).and_return("") res.stub(:[]).with('content-encoding').and_return(nil) @request.process_result(res).should be_empty end end it "parses a url into a URI object" do URI.should_receive(:parse).with('http://example.com/resource') @request.parse_url('http://example.com/resource') end it "adds http:// to the front of resources specified in the syntax example.com/resource" do URI.should_receive(:parse).with('http://example.com/resource') @request.parse_url('example.com/resource') end describe "user - password" do it "extracts the username and password when parsing http://user:password@example.com/" do URI.stub(:parse).and_return(double('uri', :user => 'joe', :password => 'pass1')) @request.parse_url_with_auth('http://joe:pass1@example.com/resource') @request.user.should eq 'joe' @request.password.should eq 'pass1' end it "extracts with escaping the username and password when parsing http://user:password@example.com/" do URI.stub(:parse).and_return(double('uri', :user => 'joe%20', :password => 'pass1')) @request.parse_url_with_auth('http://joe%20:pass1@example.com/resource') @request.user.should eq 'joe ' @request.password.should 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 URI.stub(:parse).and_return(double('uri', :user => nil, :password => nil)) @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :user => 'beth', :password => 'pass2') @request.parse_url_with_auth('http://example.com/resource') @request.user.should eq 'beth' @request.password.should eq 'pass2' end end it "correctly formats cookies provided to the constructor" do URI.stub(:parse).and_return(double('uri', :user => nil, :password => nil)) @request = RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {:session_id => '1', :user_id => "someone" }) @request.should_receive(:default_headers).and_return({'Foo' => 'bar'}) @request.make_headers({}).should eq({ 'Foo' => 'bar', 'Cookie' => 'session_id=1; user_id=someone'}) 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}) @request.should_receive(:default_headers).and_return({'Foo' => 'bar'}) @request.make_headers({}).should 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| lambda { RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {cookie_name => 'value'}) }.should raise_error(ArgumentError, /\AInvalid cookie name/) end 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,bar', 'foo;bar', "foo\nbar"].each do |cookie_value| lambda { RestClient::Request.new(:method => 'get', :url => 'example.com', :cookies => {'test' => cookie_value}) }.should raise_error(ArgumentError, /\AInvalid cookie value/) end end it "uses netrc credentials" do URI.stub(:parse).and_return(double('uri', :user => nil, :password => nil, :host => 'example.com')) Netrc.stub(:read).and_return('example.com' => ['a', 'b']) @request.parse_url_with_auth('http://example.com/resource') @request.user.should eq 'a' @request.password.should eq 'b' end it "uses credentials in the url in preference to netrc" do URI.stub(:parse).and_return(double('uri', :user => 'joe%20', :password => 'pass1', :host => 'example.com')) Netrc.stub(:read).and_return('example.com' => ['a', 'b']) @request.parse_url_with_auth('http://joe%20:pass1@example.com/resource') @request.user.should eq 'joe ' @request.password.should eq 'pass1' end it "determines the Net::HTTP class to instantiate by the method name" do @request.net_http_request_class(:put).should eq Net::HTTP::Put end describe "user headers" do it "merges user headers with the default headers" do @request.should_receive(:default_headers).and_return({ :accept => '*/*; q=0.5, application/xml', :accept_encoding => 'gzip, deflate' }) headers = @request.make_headers("Accept" => "application/json", :accept_encoding => 'gzip') headers.should have_key "Accept-Encoding" headers["Accept-Encoding"].should eq "gzip" headers.should have_key "Accept" headers["Accept"].should eq "application/json" end it "prefers the user header when the same header exists in the defaults" do @request.should_receive(:default_headers).and_return({ '1' => '2' }) headers = @request.make_headers('1' => '3') headers.should have_key('1') headers['1'].should eq '3' end it "converts user headers to string before calling CGI::unescape which fails on non string values" do @request.should_receive(:default_headers).and_return({ '1' => '2' }) headers = @request.make_headers('1' => 3) headers.should have_key('1') headers['1'].should eq '3' end end describe "header symbols" do it "converts header symbols from :content_type to 'Content-Type'" do @request.should_receive(:default_headers).and_return({}) headers = @request.make_headers(:content_type => 'abc') headers.should have_key('Content-Type') headers['Content-Type'].should eq 'abc' end it "converts content-type from extension to real content-type" do @request.should_receive(:default_headers).and_return({}) headers = @request.make_headers(:content_type => 'json') headers.should have_key('Content-Type') headers['Content-Type'].should eq 'application/json' end it "converts accept from extension(s) to real content-type(s)" do @request.should_receive(:default_headers).and_return({}) headers = @request.make_headers(:accept => 'json, mp3') headers.should have_key('Accept') headers['Accept'].should eq 'application/json, audio/mpeg' @request.should_receive(:default_headers).and_return({}) headers = @request.make_headers(:accept => :json) headers.should have_key('Accept') headers['Accept'].should eq 'application/json' end it "only convert symbols in header" do @request.should_receive(:default_headers).and_return({}) headers = @request.make_headers({:foo_bar => 'value', "bar_bar" => 'value'}) headers['Foo-Bar'].should eq 'value' headers['bar_bar'].should eq 'value' end it "converts header values to strings" do @request.make_headers('A' => 1)['A'].should eq '1' end end it "executes by constructing the Net::HTTP object, headers, and payload and calling transmit" do @request.should_receive(:parse_url_with_auth).with('http://some/resource').and_return(@uri) klass = double("net:http class") @request.should_receive(:net_http_request_class).with(:put).and_return(klass) klass.should_receive(:new).and_return('result') @request.should_receive(:transmit).with(@uri, 'result', kind_of(RestClient::Payload::Base)) @request.execute end it "transmits the request with Net::HTTP" do @http.should_receive(:request).with('req', 'payload') @request.should_receive(:process_result) @request.transmit(@uri, 'req', 'payload') end describe "payload" do it "sends nil payloads" do @http.should_receive(:request).with('req', nil) @request.should_receive(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', nil) end it "passes non-hash payloads straight through" do @request.process_payload("x").should eq "x" end it "converts a hash payload to urlencoded data" do @request.process_payload(:a => 'b c+d').should eq "a=b%20c%2Bd" end it "accepts nested hashes in payload" do payload = @request.process_payload(:user => { :name => 'joe', :location => { :country => 'USA', :state => 'CA' }}) payload.should include('user[name]=joe') payload.should include('user[location][country]=USA') payload.should include('user[location][state]=CA') end end it "set urlencoded content_type header on hash payloads" do @request.process_payload(:a => 1) @request.headers[:content_type].should eq 'application/x-www-form-urlencoded' end describe "credentials" do it "sets up the credentials prior to the request" do @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.stub(:user).and_return('joe') @request.stub(:password).and_return('mypass') @request.should_receive(:setup_credentials).with('req') @request.transmit(@uri, 'req', nil) end it "does not attempt to send any credentials if user is nil" do @request.stub(:user).and_return(nil) req = double("request") req.should_not_receive(:basic_auth) @request.setup_credentials(req) end it "setup credentials when there's a user" do @request.stub(:user).and_return('joe') @request.stub(:password).and_return('mypass') req = double("request") req.should_receive(:basic_auth).with('joe', 'mypass') @request.setup_credentials(req) end end it "catches EOFError and shows the more informative ServerBrokeConnection" do @http.stub(:request).and_raise(EOFError) lambda { @request.transmit(@uri, 'req', nil) }.should raise_error(RestClient::ServerBrokeConnection) end it "catches OpenSSL::SSL::SSLError and raise it back without more informative message" do @http.stub(:request).and_raise(OpenSSL::SSL::SSLError) lambda { @request.transmit(@uri, 'req', nil) }.should raise_error(OpenSSL::SSL::SSLError) end it "catches Timeout::Error and raise the more informative RequestTimeout" do @http.stub(:request).and_raise(Timeout::Error) lambda { @request.transmit(@uri, 'req', nil) }.should raise_error(RestClient::RequestTimeout) end it "catches Timeout::Error and raise the more informative RequestTimeout" do @http.stub(:request).and_raise(Errno::ETIMEDOUT) lambda { @request.transmit(@uri, 'req', nil) }.should raise_error(RestClient::RequestTimeout) end it "class method execute wraps constructor" do req = double("rest request") RestClient::Request.should_receive(:new).with(1 => 2).and_return(req) req.should_receive(:execute) RestClient::Request.execute(1 => 2) end describe "exception" do it "raises Unauthorized when the response is 401" do res = double('response', :code => '401', :[] => ['content-encoding' => ''], :body => '' ) lambda { @request.process_result(res) }.should raise_error(RestClient::Unauthorized) end it "raises ResourceNotFound when the response is 404" do res = double('response', :code => '404', :[] => ['content-encoding' => ''], :body => '' ) lambda { @request.process_result(res) }.should raise_error(RestClient::ResourceNotFound) end it "raises RequestFailed otherwise" do res = double('response', :code => '500', :[] => ['content-encoding' => ''], :body => '' ) lambda { @request.process_result(res) }.should raise_error(RestClient::InternalServerError) end end describe "block usage" do it "returns what asked to" do res = double('response', :code => '401', :[] => ['content-encoding' => ''], :body => '' ) @request.process_result(res){|response, request| "foo"}.should eq "foo" end end describe "proxy" do it "creates a proxy class if a proxy url is given" do RestClient.stub(:proxy).and_return("http://example.com/") @request.net_http_class.proxy_class?.should be_true end it "creates a non-proxy class if a proxy url is not given" do @request.net_http_class.proxy_class?.should be_false end end describe "logging" do it "logs a get request" do log = RestClient.log = [] RestClient::Request.new(:method => :get, :url => 'http://url').log_request log[0].should eq %Q{RestClient.get "http://url", "Accept"=>"*/*; q=0.5, application/xml", "Accept-Encoding"=>"gzip, deflate"\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').log_request log[0].should eq %Q{RestClient.post "http://url", "foo", "Accept"=>"*/*; q=0.5, application/xml", "Accept-Encoding"=>"gzip, deflate", "Content-Length"=>"3"\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)).log_request log[0].should eq %Q{RestClient.post "http://url", 1000 byte(s) length, "Accept"=>"*/*; q=0.5, application/xml", "Accept-Encoding"=>"gzip, deflate", "Content-Length"=>"1000"\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' }).log_request log[0].should eq %Q{RestClient.get "http://url", "Accept"=>"text/plain", "Accept-Encoding"=>"gzip, deflate"\n} end it "logs a response including the status code, content type, and result body size in bytes" do log = RestClient.log = [] res = double('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd') res.stub(:[]).with('Content-type').and_return('text/html') @request.log_response res log[0].should eq "# => 200 OK | text/html 4 bytes\n" end it "logs a response with a nil Content-type" do log = RestClient.log = [] res = double('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd') res.stub(:[]).with('Content-type').and_return(nil) @request.log_response res log[0].should eq "# => 200 OK | 4 bytes\n" end it "logs a response with a nil body" do log = RestClient.log = [] res = double('result', :code => '200', :class => Net::HTTPOK, :body => nil) res.stub(:[]).with('Content-type').and_return('text/html; charset=utf-8') @request.log_response res log[0].should eq "# => 200 OK | text/html 0 bytes\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', :accept => '*/*'}).log_request log[0].should eq %Q{RestClient.get "http://user:REDACTED@url", "Accept"=>"*/*", "Accept-Encoding"=>"gzip, deflate", "User-Agent"=>"rest-client"\n} end it 'logs invalid URIs, even though they will fail elsewhere' do log = RestClient.log = [] RestClient::Request.new(:method => :get, :url => 'http://a@b:c', :headers => {:user_agent => 'rest-client', :accept => '*/*'}).log_request log[0].should eq %Q{RestClient.get "[invalid uri]", "Accept"=>"*/*", "Accept-Encoding"=>"gzip, deflate", "User-Agent"=>"rest-client"\n} end end it "strips the charset from the response content type" do log = RestClient.log = [] res = double('result', :code => '200', :class => Net::HTTPOK, :body => 'abcd') res.stub(:[]).with('Content-type').and_return('text/html; charset=utf-8') @request.log_response res log[0].should eq "# => 200 OK | text/html 4 bytes\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') @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @net.should_not_receive(:read_timeout=) @net.should_not_receive(:open_timeout=) @request.transmit(@uri, 'req', nil) end it "set read_timeout" do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => 123) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @net.should_receive(:read_timeout=).with(123) @request.transmit(@uri, 'req', nil) end it "set open_timeout" do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :open_timeout => 123) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @net.should_receive(:open_timeout=).with(123) @request.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', :timeout => nil, :open_timeout => nil) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @net.should_receive(:read_timeout=).with(nil) @net.should_receive(:open_timeout=).with(nil) @request.transmit(@uri, 'req', nil) end it "deprecated: disable timeout by setting it to -1" do @request = RestClient::Request.new(:method => :put, :url => 'http://some/resource', :payload => 'payload', :timeout => -1, :open_timeout => -1) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.should_receive(:warn) @net.should_receive(:read_timeout=).with(nil) @request.should_receive(:warn) @net.should_receive(:open_timeout=).with(nil) @request.transmit(@uri, 'req', nil) end end describe "ssl" do it "uses SSL when the URI refers to a https address" do @uri.stub(:is_a?).with(URI::HTTPS).and_return(true) @net.should_receive(:use_ssl=).with(true) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', 'payload') end it "should default to verifying ssl certificates" do @request.verify_ssl.should eq OpenSSL::SSL::VERIFY_PEER end it "should have expected values for VERIFY_PEER and VERIFY_NONE" do OpenSSL::SSL::VERIFY_NONE.should eq(0) OpenSSL::SSL::VERIFY_PEER.should 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') @net.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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) @net.should_not_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_NONE) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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) @net.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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') @net.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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 ) @net.should_receive(:verify_mode=).with(mode) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', 'payload') end it "should default to not having an ssl_client_cert" do @request.ssl_client_cert.should 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" ) @net.should_receive(:ssl_version=).with("TLSv1") @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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' ) @net.should_not_receive(:ssl_version=).with("TLSv1") @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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 ) @net.should_receive(:ciphers=).with(ciphers) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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, ) @net.should_not_receive(:ciphers=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', 'payload') end it "should override ssl_ciphers with better defaults with weak default ciphers" do stub_const( '::OpenSSL::SSL::SSLContext::DEFAULT_PARAMS', { :ssl_version=>"SSLv23", :verify_mode=>1, :ciphers=>"ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW", :options=>-2147480577, } ) @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', ) @net.should_receive(:ciphers=).with(RestClient::Request::DefaultCiphers) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', 'payload') end it "should not override ssl_ciphers with better defaults with different default ciphers" do stub_const( '::OpenSSL::SSL::SSLContext::DEFAULT_PARAMS', { :ssl_version=>"SSLv23", :verify_mode=>1, :ciphers=>"HIGH:!aNULL:!eNULL:!EXPORT:!LOW:!MEDIUM:!SSLv2", :options=>-2147480577, } ) @request = RestClient::Request.new( :method => :put, :url => 'https://some/resource', :payload => 'payload', ) @net.should_not_receive(:ciphers=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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!" ) @net.should_receive(:cert=).with("whatsupdoc!") @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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' ) @net.should_not_receive(:cert=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', 'payload') end it "should default to not having an ssl_client_key" do @request.ssl_client_key.should 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!" ) @net.should_receive(:key=).with("whatsupdoc!") @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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' ) @net.should_not_receive(:key=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', 'payload') end it "should default to not having an ssl_ca_file" do @request.ssl_ca_file.should 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" ) @net.should_receive(:ca_file=).with("Certificate Authority File") @net.should_not_receive(:cert_store=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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' ) @net.should_not_receive(:ca_file=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.transmit(@uri, 'req', 'payload') end it "should default to not having an ssl_ca_path" do @request.ssl_ca_path.should 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" ) @net.should_receive(:ca_path=).with("Certificate Authority Path") @net.should_not_receive(:cert_store=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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' ) @net.should_not_receive(:ca_path=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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 ) @net.should_receive(:cert_store=).with(store) @net.should_not_receive(:ca_path=) @net.should_not_receive(:ca_file=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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' ) @net.should_receive(:cert_store=) @net.should_not_receive(:ca_path=) @net.should_not_receive(:ca_file=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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, ) @net.should_not_receive(:cert_store=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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', ) @net.should_not_receive(:verify_callback=) @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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, ) @net.should_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 @http.stub(:request) @request.stub(:process_result) @request.stub(:response_log) @request.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") net_http_res.stub(:read_body).and_return(nil) @http.should_receive(:request).and_return(@request.fetch_body(net_http_res)) response = @request.transmit(@uri, 'req', 'payload') response.should_not be_nil response.code.should 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") tempfile.should_receive(:binmode) tempfile.stub(:open) tempfile.stub(:close) Tempfile.should_receive(:new).with("rest-client").and_return(tempfile) net_http_res = Net::HTTPOK.new(nil, "200", "body") net_http_res.stub(:read_body).and_return("body") @request.fetch_body(net_http_res) end end end rest-client-1.8.0/spec/unit/resource_spec.rb0000644000175000017500000001245412522136372020061 0ustar lucaslucasrequire 'spec_helper' 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 RestClient::Request.should_receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass') @resource.get end it "HEAD" do RestClient::Request.should_receive(:execute).with(:method => :head, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass') @resource.head end it "POST" do RestClient::Request.should_receive(:execute).with(:method => :post, :url => 'http://some/resource', :payload => 'abc', :headers => {:content_type => 'image/jpg', 'X-Something' => '1'}, :user => 'jane', :password => 'mypass') @resource.post 'abc', :content_type => 'image/jpg' end it "PUT" do RestClient::Request.should_receive(:execute).with(:method => :put, :url => 'http://some/resource', :payload => 'abc', :headers => {:content_type => 'image/jpg', 'X-Something' => '1'}, :user => 'jane', :password => 'mypass') @resource.put 'abc', :content_type => 'image/jpg' end it "PATCH" do RestClient::Request.should_receive(:execute).with(:method => :patch, :url => 'http://some/resource', :payload => 'abc', :headers => {:content_type => 'image/jpg', 'X-Something' => '1'}, :user => 'jane', :password => 'mypass') @resource.patch 'abc', :content_type => 'image/jpg' end it "DELETE" do RestClient::Request.should_receive(:execute).with(:method => :delete, :url => 'http://some/resource', :headers => {'X-Something' => '1'}, :user => 'jane', :password => 'mypass') @resource.delete end it "overrides resource headers" do RestClient::Request.should_receive(:execute).with(:method => :get, :url => 'http://some/resource', :headers => {'X-Something' => '2'}, :user => 'jane', :password => 'mypass') @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') @resource.user.should eq 'user' @resource.password.should eq 'pass' end it "concatenates urls, inserting a slash when it needs one" do @resource.concat_urls('http://example.com', 'resource').should eq 'http://example.com/resource' end it "concatenates urls, using no slash if the first url ends with a slash" do @resource.concat_urls('http://example.com/', 'resource').should eq 'http://example.com/resource' end it "concatenates urls, using no slash if the second url starts with a slash" do @resource.concat_urls('http://example.com', '/resource').should eq 'http://example.com/resource' end it "concatenates even non-string urls, :posts + 1 => 'posts/1'" do @resource.concat_urls(:posts, 1).should eq 'posts/1' end it "offers subresources via []" do parent = RestClient::Resource.new('http://example.com') parent['posts'].url.should eq 'http://example.com/posts' end it "transports options to subresources" do parent = RestClient::Resource.new('http://example.com', :user => 'user', :password => 'password') parent['posts'].user.should eq 'user' parent['posts'].password.should eq 'password' end it "passes a given block to subresources" do block = proc {|r| r} parent = RestClient::Resource.new('http://example.com', &block) parent['posts'].block.should 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 parent.send(:[], 'posts', &block2).block.should eq block2 parent.send(:[], 'posts', &block2).block.should_not eq block1 end it "the block should be overrideable in ruby 1.9 syntax" do block1 = proc {|r| r} block2 = ->(r) {} parent = RestClient::Resource.new('http://example.com', &block1) parent['posts', &block2].block.should eq block2 parent['posts', &block2].block.should_not eq block1 end it "prints its url with to_s" do RestClient::Resource.new('x').to_s.should 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' } resource.get.should 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') resource.get { |response, request| 'foo' }.should 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' } resource.get { |response, request| 'bar' }.should eq 'bar' end end end rest-client-1.8.0/spec/unit/payload_spec.rb0000644000175000017500000002057312522136372017664 0ustar lucaslucas# encoding: binary require 'spec_helper' describe RestClient::Payload do context "A regular Payload" do it "should use standard enctype as default content-type" do RestClient::Payload::UrlEncoded.new({}).headers['Content-Type']. should eq 'application/x-www-form-urlencoded' end it "should form properly encoded params" do RestClient::Payload::UrlEncoded.new({:foo => 'bar'}).to_s. should eq "foo=bar" ["foo=bar&baz=qux", "baz=qux&foo=bar"].should include( RestClient::Payload::UrlEncoded.new({:foo => 'bar', :baz => 'qux'}).to_s) end it "should escape parameters" do RestClient::Payload::UrlEncoded.new({'foo ' => 'bar'}).to_s. should eq "foo%20=bar" end it "should properly handle hashes as parameter" do RestClient::Payload::UrlEncoded.new({:foo => {:bar => 'baz'}}).to_s. should eq "foo[bar]=baz" RestClient::Payload::UrlEncoded.new({:foo => {:bar => {:baz => 'qux'}}}).to_s. should 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 parameters.should include("foo[bar]=baz", "foo[baz]=qux") end it "should handle attributes inside a an array inside an hash" do parameters = RestClient::Payload::UrlEncoded.new({"foo" => [{"bar" => 'baz'}, {"bar" => 'qux'}]}).to_s parameters.should include("foo[bar]=baz", "foo[bar]=qux") end it "should handle attributes inside a an array inside an array inside an hash" do parameters = RestClient::Payload::UrlEncoded.new({"foo" => [[{"bar" => 'baz'}, {"bar" => 'qux'}]]}).to_s parameters.should include("foo[bar]=baz", "foo[bar]=qux") end it "should form properly use symbols as parameters" do RestClient::Payload::UrlEncoded.new({:foo => :bar}).to_s. should eq "foo=bar" RestClient::Payload::UrlEncoded.new({:foo => {:bar => :baz}}).to_s. should eq "foo[bar]=baz" end it "should properly handle arrays as repeated parameters" do RestClient::Payload::UrlEncoded.new({:foo => ['bar']}).to_s. should eq "foo[]=bar" RestClient::Payload::UrlEncoded.new({:foo => ['bar', 'baz']}).to_s. should 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({}) m.stub(:boundary).and_return(123) m.headers['Content-Type'].should 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(File.join(File.dirname(File.expand_path(__FILE__)), 'master_shake.jpg'))) 3.times {m.close} end it "should form properly separated multipart data" do m = RestClient::Payload::Multipart.new([[:bar, "baz"], [:foo, "bar"]]) m.to_s.should 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"]]) m.to_s.should 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(File.dirname(__FILE__) + "/master_shake.jpg") m = RestClient::Payload::Multipart.new({:foo => f}) m.to_s.should eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="foo"; filename="master_shake.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(File.dirname(__FILE__) + "/master_shake.jpg") m = RestClient::Payload::Multipart.new({nil => f}) m.to_s.should eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; filename="master_shake.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(File.dirname(__FILE__) + "/master_shake.jpg") f.instance_eval "def content_type; 'text/plain'; end" f.instance_eval "def original_filename; 'foo.txt'; end" m = RestClient::Payload::Multipart.new({:foo => f}) m.to_s.should 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"}}) m.to_s.should eq <<-EOS --#{m.boundary}\r Content-Disposition: form-data; name="bar[baz]"\r \r foo\r --#{m.boundary}--\r EOS f = File.new(File.dirname(__FILE__) + "/master_shake.jpg") 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}}) m.to_s.should 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 end context "streamed payloads" do it "should properly determine the size of file payloads" do f = File.new(File.dirname(__FILE__) + "/master_shake.jpg") payload = RestClient::Payload.generate(f) payload.size.should eq 76_988 payload.length.should eq 76_988 end it "should properly determine the size of other kinds of streaming payloads" do s = StringIO.new 'foo' payload = RestClient::Payload.generate(s) payload.size.should eq 3 payload.length.should eq 3 begin f = Tempfile.new "rest-client" f.write 'foo bar' payload = RestClient::Payload.generate(f) payload.size.should eq 7 payload.length.should eq 7 ensure f.close end end end context "Payload generation" do it "should recognize standard urlencoded params" do RestClient::Payload.generate({"foo" => 'bar'}).should be_kind_of(RestClient::Payload::UrlEncoded) end it "should recognize multipart params" do f = File.new(File.dirname(__FILE__) + "/master_shake.jpg") RestClient::Payload.generate({"foo" => f}).should be_kind_of(RestClient::Payload::Multipart) end it "should be multipart if forced" do RestClient::Payload.generate({"foo" => "bar", :multipart => true}).should be_kind_of(RestClient::Payload::Multipart) end it "should return data if no of the above" do RestClient::Payload.generate("data").should be_kind_of(RestClient::Payload::Base) end it "should recognize nested multipart payloads in hashes" do f = File.new(File.dirname(__FILE__) + "/master_shake.jpg") RestClient::Payload.generate({"foo" => {"file" => f}}).should be_kind_of(RestClient::Payload::Multipart) end it "should recognize nested multipart payloads in arrays" do f = File.new(File.dirname(__FILE__) + "/master_shake.jpg") RestClient::Payload.generate({"foo" => [f]}).should be_kind_of(RestClient::Payload::Multipart) end it "should recognize file payloads that can be streamed" do f = File.new(File.dirname(__FILE__) + "/master_shake.jpg") RestClient::Payload.generate(f).should be_kind_of(RestClient::Payload::Streamed) end it "should recognize other payloads that can be streamed" do RestClient::Payload.generate(StringIO.new('foo')).should 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 RestClient::Payload.generate({"foo" => 'bar'}).should be_kind_of(RestClient::Payload::UrlEncoded) end end class HashMapForTesting < Hash alias :read :[] end end rest-client-1.8.0/spec/unit/response_spec.rb0000644000175000017500000002361012522136372020064 0ustar lucaslucasrequire 'spec_helper' describe RestClient::Response do before do @net_http_res = double('net http response', :to_hash => {"Status" => ["200 OK"]}, :code => 200) @example_url = 'http://example.com' @request = double('http request', :user => nil, :password => nil, :url => @example_url) @response = RestClient::Response.create('abc', @net_http_res, {}, @request) end it "behaves like string" do @response.to_s.should eq 'abc' @response.to_str.should eq 'abc' @response.to_i.should eq 200 end it "accepts nil strings and sets it to empty for the case of HEAD" do RestClient::Response.create(nil, @net_http_res, {}, @request).to_s.should eq "" end it "test headers and raw headers" do @response.raw_headers["Status"][0].should eq "200 OK" @response.headers[:status].should eq "200 OK" 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) response.headers[:set_cookie].should eq [header_val] response.cookies.should 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) response.headers[:set_cookie].should 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"] response.cookies.should 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) response.cookies.should 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 = double('net http response', :code => '200') response = RestClient::Response.create('abc', net_http_res, {}, @request) response.return! @request end end it "should throw an exception for other codes" do RestClient::Exceptions::EXCEPTIONS_MAP.each_key do |code| unless (200..207).include? code net_http_res = double('net http response', :code => code.to_i) response = RestClient::Response.create('abc', net_http_res, {}, @request) lambda { response.return!}.should raise_error 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') RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body.should eq 'Foo' end it "follows a redirection and keep the parameters" do stub_request(:get, 'http://foo:bar@some/resource').with(:headers => {'Accept' => 'application/json'}).to_return(:body => '', :status => 301, :headers => {'Location' => 'http://new/resource'}) stub_request(:get, 'http://foo:bar@new/resource').with(:headers => {'Accept' => 'application/json'}).to_return(:body => 'Foo') RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :user => 'foo', :password => 'bar', :headers => {:accept => :json}).body.should 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') RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body.should eq 'Qux' end it 'does not keep cookies across domains' do stub_request(:get, 'http://some/resource').to_return(:body => '', :status => 301, :headers => {'Set-Cookie' => 'Foo=Bar', 'Location' => 'http://new/resource', }) stub_request(:get, 'http://new/resource').with(:headers => {'Cookie' => ''}).to_return(:body => 'Qux') RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body.should eq 'Qux' end it "doesn't follow a 301 when the request is a post" do net_http_res = double('net http response', :code => 301) response = RestClient::Response.create('abc', net_http_res, {:method => :post}, @request) lambda { response.return!(@request)}.should raise_error(RestClient::MovedPermanently) end it "doesn't follow a 302 when the request is a post" do net_http_res = double('net http response', :code => 302) response = RestClient::Response.create('abc', net_http_res, {:method => :post}, @request) lambda { response.return!(@request)}.should raise_error(RestClient::Found) end it "doesn't follow a 307 when the request is a post" do net_http_res = double('net http response', :code => 307) response = RestClient::Response.create('abc', net_http_res, {:method => :post}, @request) lambda { response.return!(@request)}.should raise_error(RestClient::TemporaryRedirect) end it "doesn't follow a redirection when the request is a put" do net_http_res = double('net http response', :code => 301) response = RestClient::Response.create('abc', net_http_res, {:method => :put}, @request) lambda { response.return!(@request)}.should 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') RestClient::Request.execute(:url => 'http://some/resource', :method => :put).body.should 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') RestClient::Request.execute(:url => 'http://some/resource', :method => :head).body.should 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') RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body.should 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') RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body.should 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') RestClient::Request.execute(:url => 'http://some/resource', :method => :get).body.should 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'}) lambda { RestClient::Request.execute(:url => 'http://some/redirect-1', :method => :get) }.should raise_error(RestClient::MaxRedirectsReached) WebMock.should 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'}) lambda { RestClient::Request.execute(:url => 'http://some/redirect-1', :method => :get, :max_redirects => 5) }.should raise_error(RestClient::MaxRedirectsReached) WebMock.should have_requested(:get, 'http://some/redirect-2').times(5) end end end rest-client-1.8.0/spec/unit/raw_response_spec.rb0000644000175000017500000000073012522136372020733 0ustar lucaslucasrequire 'spec_helper' describe RestClient::RawResponse do before do @tf = double("Tempfile", :read => "the answer is 42", :open => true) @net_http_res = double('net http response') @request = double('http request') @response = RestClient::RawResponse.new(@tf, @net_http_res, {}, @request) end it "behaves like string" do @response.to_s.should eq 'the answer is 42' end it "exposes a Tempfile" do @response.file.should eq @tf end end rest-client-1.8.0/spec/unit/request2_spec.rb0000644000175000017500000000355512522136372020006 0ustar lucaslucasrequire 'spec_helper' describe RestClient::Request do it "manage params for get requests" do stub_request(:get, 'http://some/resource?a=b&c=d').with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', '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'}}).body.should eq 'foo' stub_request(:get, 'http://some/resource').with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', 'Foo'=>'bar', 'params' => 'a'}).to_return(:body => 'foo', :status => 200) RestClient::Request.execute(:url => 'http://some/resource', :method => :get, :headers => {:foo => :bar, :params => :a}).body.should eq 'foo' 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'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate', '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) response_value.should eq "foo" end it 'closes payload if not nil' do test_file = File.new(File.join( File.dirname(File.expand_path(__FILE__)), 'master_shake.jpg')) stub_request(:post, 'http://some/resource').with(:headers => {'Accept'=>'*/*; q=0.5, application/xml', 'Accept-Encoding'=>'gzip, deflate'}).to_return(:body => 'foo', :status => 200) RestClient::Request.execute(:url => 'http://some/resource', :method => :post, :payload => {:file => test_file}) test_file.closed?.should be_true end end rest-client-1.8.0/spec/unit/exceptions_spec.rb0000644000175000017500000000566512522136372020421 0ustar lucaslucasrequire 'spec_helper' 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 e.message.should eq "RestClient::Exception" end it "returns the 'message' that was set" do e = RestClient::Exception.new message = "An explicitly set message" e.message = message e.message.should eq message end it "sets the exception message to ErrorMessage" do RestClient::ResourceNotFound.new.message.should eq 'Resource Not Found' end it "contains exceptions in RestClient" do RestClient::Unauthorized.new.should be_a_kind_of(RestClient::Exception) RestClient::ServerBrokeConnection.new.should 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 e.message.should 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 e.response.should eq response end end it "http_code convenience method for fetching the code as an integer" do RestClient::RequestFailed.new(@response).http_code.should eq 502 end it "http_body convenience method for fetching the body (decoding when necessary)" do RestClient::RequestFailed.new(@response).http_code.should eq 502 RestClient::RequestFailed.new(@response).message.should eq 'HTTP status code 502' end it "shows the status code in the message" do RestClient::RequestFailed.new(@response).to_s.should 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 e.response.should eq response end end end describe "backwards compatibility" do it "alias RestClient::Request::Redirect to RestClient::Redirect" do RestClient::Request::Redirect.should eq RestClient::Redirect end it "alias RestClient::Request::Unauthorized to RestClient::Unauthorized" do RestClient::Request::Unauthorized.should eq RestClient::Unauthorized end it "alias RestClient::Request::RequestFailed to RestClient::RequestFailed" do RestClient::Request::RequestFailed.should eq RestClient::RequestFailed end it "make the exception's response act like an Net::HTTPResponse" 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 e.response.body.should eq body end end end rest-client-1.8.0/spec/unit/master_shake.jpg0000644000175000017500000022627412522136372020052 0ustar lucaslucasJFIFHH(ICC_PROFILEappl scnrRGB XYZ acspAPPLappl-appl rXYZgXYZbXYZ0wtptDchadX,rTRCgTRCbTRCdesc=cprtAdscmXYZ tK>XYZ Zs&XYZ (W3XYZ Rsf32 B&lcurv3descCamera RGB ProfileCamera RGB Profilemluc enUS$esES,LdaDK4deDE,fiFI(frFU<itIT,rnlNL$noNO xptBR(JsvSE*jaJPkoKR2zhTW2zhCNKameran RGB-profiiliRGB-profil fr Kamera000 RGB 000000exOMvj_ RGB r_icϏPerfil RGB para CmaraRGB-kameraprofilRGB-Profil fr Kamerasvg: RGB cϏeNRGB-beskrivelse til KameraRGB-profiel CameratT| RGB \ |Perfil RGB de CmeraProfilo RGB FotocameraCamera RGB ProfileProfil RVB de l appareil-phototextCopyright 2003 Apple Computer Inc., all rights reserved.C   Cw U !1A"Qaq2 #BRbr$3C %S'4DTcd&Us >!1A"Qa2qB#R3br$Sc ?CGdAtǮ#yGI]5:o{ O\gNRG ]-M sn1Ui Cz^qb*[d"erd/NfQ~cxȷ%mq@ը LbM}? 8 2x%SM+6GkՐ )[TfKXiT e}Po} AT", cˠW#. 1J71#sQ$L \^6-x$پxv $J, *;z"b6+ax3FABzN-}MjnIRAzt-<ҒM+1Zs媸-KLKw`ِ۟#xcy{^ػ+RHBH!bWUʅ J6w x@-(! qjֶ`ְ뀊 ky!Zċam8QhUX-=VI}Kl\d?t#1 rqiZ2Q*f"? L*TRM*|12w2*dOQ*ۡ>& rT#25U)nA^I# ,m]=S O wm?ؿnz_ޟVRFS1Q謷moI^_ 3Z{t+,rj< %- 7ğ/tY40;8.% ʭӏ,z!IFIiM8&t3"̩sL6ʧIQ]kzN_i2u=ZQX1$u5Nn2` VX`6Lrܝb'ʛj䒵˩2}?8c8LO<*<sn%Bo z%I.{`D`(  kqymzl,kEd"Xn>mgc$$[ V!S I@F̗R O\ _%I<6[cpKl}1w*[xQV:MM##I[I0CF6% U=e<44Tx06$i+}ozų4#,bYagl*i4҇RObEV/k*཭:,5v} ._FƼ,s]F`~XX3b7OCc`_qcPOO Gp)y2!ta-ٗK+s۶% %_0GFTx$[\|Qq(543 PK+$FǠH>dartZMzyV5iO^`o1KCktIid V1|5CĨvM JcQ9)"j{t'2}l]Dٚ)j=np:jnDkv]<"2XB2_F}1aoc?]ia/԰YULLIJJ. 6m_<'OEHj*8N,ϲ)UF o>WVn^8>)]2ZX(;VĞtJQeTmbzEkn.ncAGĊ274He X3m[p[v1m N0q$tWL92@?,E$n°ǥ*32m|-,G*_mmEJIhjѮ m{&$ _Up+tp%T+H K$4+7U% u.R_Qoy`T^_5#*z$7!OKR<9#LH$Y3*ڣrt^:xz:xl?#IS5²u'߮+795%AqS$QR6`o4-F69O <{l4rV5iR.^y<#ޠ&I.Qi`_-F=؝5ع6?.7qspQBz1n1Wh%mH&3B\n>CYd$|ft r: ؔNcҊY[OEdJT0 @?/L[h* RC5:C$Jom{"1Axe=\ xEa0 UJMK$7#R߯*IwoV9!p4ډ\R j)n~I/ NAvh$1:kv8 kQrO}skQfHEn}az鴓:="I9paiuU:Ƭ`܎|)*uFK]nxRH0R.Qy$077-Ko#Ģcb)  JC цOyC(N+SJ;0bԒT[tINUEf'ޣܵ$e2tSO~ m#&=/C1$Q,5ۏ*ΌdTKqo#p#c 5 $rbq/u{pA {eD+{MĥvQMGCir&uASo/ʸEE.l?ꄠJmXlxQȐjC,# :nN%̲HJpbOj,Ohnm1V[|LRdM1 zy>cL%4kw%v ժY#P}؁p>_+G>Jzb,9ɷHl2uNQ) ѪfkצߎM>DɻHn{[*ÔmXљ3M  AZAFԌ30X ii)2<&ͥy tl0kk/MEmD)#&Qj".=Ԝ2=M=olH`Etb`K lP׿S%j"H׻A?wZ1iU B6;}1[zuI49F:I$ŶLSmd;ZO*Fù |+<,1Rh:5b ]R*@G`ЭH? }]?ձ7zUb;wF`;/ⅨR*`mǡ}0I5eX`OMk"E35C#bk_co.rR%@b q؆*,&,mvD@FlVU;G2VJmErQUzĭU|% Q=lNUӖ͙X\ien޽jr"o /0+$r iXL\؈Gt>+eh/YDCp{sD4ǦzIߌ@튋 iHFb9'x@~~|JTdNuzRGp.zqdN<u5eeQNCiԲ+WGj% &#` mn|5d!GWH %XTuꫭ0zrm;]YęoΩ)QEֻlDkӖ; %?hL8+U!UԴéGK2C'IWf +7dِ0ӥ|utԕu=pHO>э$`cZz^S^Q%{ZA7ֱ= aSזǑI}~:jE1LZM}[TJO OJI3IR )6+7r\v ȑ(s{oqnaSaobߩ6\k\I5@Nۡ#xk\N61Y"QN#zKV6]QW~'-D 吣e==dkEfTYO8+ %ZV*;#qԔT\:XI)1U͞ 6E`Ksi_hp頛QHyY(7ꩺӔ, `ޝTz(pEr[%bkm@&K}u5▂^b>M|Ê!TEibHt)Opq@VM؃ÅaULsFc[Bwbmڽ2.[1S;xq/QWYdn~jùT$e(L\A|Ynq0M?E&:+j>SB{d"/.E-!%b<$ö-9N6z=2W8}t|3*3~`s4s t'wc =$Wʖ"A%Y3.]%44y&Qd%:đۦ`N.]'6qקǸhDeII P F*b( j)7-{ǹ aЩ7r^fXjj3`k2*{;u/oLy'Y%t5tٌ/)H8M>кvnI a* *A#$m4:n=*z6RRCmrJ=0]6._Q t Aliūiٖ5cdjpoVqdŲָ>`Y)\IsN*'g1EQ`]u ^ITxƬ!řP$M"5/%?(yq(L8Y,X+$'k2ExvP=@q[0i,<8ku77rwi7oJ*z~8BK74ATUܻ!;EF>-0 LjdH7|H`J$7L :/_<N ٚPXۦp)Zqe5UU\mYeF]E2~,1ХޞB~&ߖ.sk=m Tߥ>G6i̗18N6r>ɖكK_D]r.^(8gPp~E'*znuQ+(a|J,j"OdT>XvPuf[b/4ڐnC(̹@_'MvRӊJ:.LԾUUmFfB(/g^EE㚜 (L* :A"5UUsP}=OAt(݆T:-M]E}Ri/veeOⲸTP$BDВ}WEPMo`Oni/E=]4NnTu%kdV]~8R4{n-l^B*JBK|8d!i*ֈH2oZ.Zi6]ę;RISf-i) . U|/QʎM*"Ed-eCvܑ{4mun77| ŭ$探c TOʶ {l*qOT%]pe>sVg-FkaSI4,>#ke1VP.JQ{ՂQe *:Cjf0'u@=SGt=\\iU rUM vbAS@Aj@6j!=IU`ʨ  e:t'_KW׋M*Slf)hMSl֛.h ]F-zn8*HSMVj3jAac.ihӶ2#i5[咴D}cd!J̹m"5ͧ[l%W_xRvH:bG Q◆H-Xc]ޘчJZ77dtQI`j*6 >p-j7bs/.SF݊@6OǧO@W'\mH ^daMYku%`jҮLzq7Sߑ-)}lϥ|Sri1iC4;|]-%'Qg'6X٬>wwDnx֩(ZTL`4Mw\s<{SgEQkѽ-/只4~hq]F6(3/ヌm L"G#@.*vO8jo1$\?Q0I$1Ҩ*zLS%ÑpRd @=}ݶ܋ĝTHBc`BG;yc䥐(wbUC1F{Z@-$ UIrI,긪5)iaߥS$⊺cHrYR+;$Mrh5@  @6إ%݃Uy+ *):{XjA4Z,ulspWF߉I"JXo.+h% )D[XQURCN|*[]%=qh*N,H;ONۘϘ aULSj'vw1~aE[=סl_N` Sd$*fglH"]D+BmRryE0w!m~GY Pt 5iJՕ\vIJ+1S*]G5'_"ȳ:;2ZfXKDA1Sr҆ͳd5izz8T(6rI8np48I$7n^O>׿|z jI%oĚ> RV2obWv+Qn&?*()2aESZǫ$IãU݊*MblV* dq,Xǹ,r\[cQ{z`$!8f&!k Mw.ꪢPoJ^2Ra]$mϾSxW"zɘ,53\끕v F51-LڮDKmw-MWPm,@-$IJ1pSH%woQxc*c:p-o1NY9&QḄe5 u1VAmZ Mτ.Ro p/\ŸuRh`lMqr ,aeS@(I'*뽴o| RSYBlKeU4}L)ֲI8f7MY)aQ<To9T)j3nJmfK!=6$؍ưQ>d--6aPA&RVӊRvij47~c iGdP b'i C8&*.]MX.O60˲yRlyRYe4_ -߾/kA1A150cs#o!P,QEޞ U;y-t'i`)d'771̃AǶ zi #)B?e㤐RGd8pP_$,J,9_VhLoGSfC@ĆiWY, O]2\)c#$m#ОM5J nO{ iu>zUfТ&8?HYBT!q{|ԋ> 70Wa>?~ MBQH[[GNIVD5y*d`.G6+Y.NmkvF4ˤ8mld%zl63mm`|c,4PGp~{db-VW!(̫F r۾WV%4-W_Q+I4 `Ik|bKaAK çF[KG&TsYzp镐)ԓ`Uc+mmIV" =vYEc-Le`AJh4YƴK?`),&i%-uU`> Ir^imbumS*w]f^ݺb-LCt,xRNk{oܮ%+#طz\-]= 5QA [װ7r3KH{e'MZUL[l 1iP.JݟRna`D4ƭ rpP{]yg8HR@ѵfVp&3I:c0tSfqz&qЪ4Z,& IxFOKn?^3X.LJ܅ L?)p*dk*`hV+YU=]oShCJcgv̑yw R~iLH"J&@c\1x1Zz!U(;-P\ߦ)Psru؈PU9cr 4K2mr/j>655T2 fH`5/m놭Qn]IŘ@QTWSn#}>;/bʁ={i`Z:׶bhͫn|[Z+f l,M~5_-+HQ^{bdI MtĐ?ۿRitN*h8iihѬt݈AncQj)c-ϷIeJ:#KUT?oݠ=fk]Qoy;##asAZ[^z2^B4I뀜tmM@l)`MWIȸi8CVöZĚG4*`]s}Zt"̅So([PN<8oq$HRESK}(;퉺]YN@; Bi+g 0$w[wHbF\$c%K[e/z-(^ +B̕?Ycݐ[1όۍ5bc7(e;D~MRA$Zzo$aol*m\UtXME<bӣ4fpm3@NQ[NP*Ѱ ZX{w݀}r[l?N^AӦc*zT “͎iPHkm$5:+Ȑj`* as[_[{\3HaØ<oĴAZXB@{h=YZ`_H~%RM}Xo{`#[|_1Y8(F)[Hk$)-}[nK8%Hr+؂w?\T U[)<""`HlG)ӶPqrGxqe%o,dUiUB[sRvMn HWP{[#mTWY*lj\>=L9GtkUW h*Cye@K!jٜ5沞v׷^ع.KQ=pOD(ΪYj` U(K~v6cv8$ߩjqN쨪D` 2^v,2<$(wZƊ I'[6wi҇$yuhHψc:эsQ$ؘ#4Kt){pcRQO S oRVHyeT0%bqk#i0jl $az|&KȅVf-ӠucYSMSyr~ }RCJ4jZ**mPl:1&@NI4qds:NSmoJi \?[US(lWJ ÊB!{QMO]RӃJ nI`w{e7=>*t8z2z^xFnTZYq=p#oZ-ol`X( Ȼ6l6:I ՓDPta~9Z(52$4Q(6&rQK()n4VaX_~\*VmYp< 85)Qm$]u[Q?ʔ^yu26-r=׶./;{Je:ZV_V6ŹTKrcu,t67V\Sʳ<DK;chʱU`Vmq,%, JIU4y _]*Nq܏JtJ7{=)%^ъh7K6 ][o"ʁU wbҤ&C#4v=>,|I҈sI /\ Zq[4Φ/2w'W <Ҳ2QllQq,c H`7*P5|FJk\i"pmk(ZZ@#b1 J%YZR3_O]s0"%P=F'eoyL`R&YqO$T%q{e6;\OPF?cGY*E#C*ܴquiQ|d }ŷV~ dQƖSn/B6GuS6bX&m+mqnL9*C{E}\b&ʒY+婈-uUv2 UfQ؃bJkJ46mFM9[ ࣧ)$$3x(,-i$QeAprh{6A.EFڴ/"lz+I<@ 5uGE =8iR{{{oMKR /p]Y"wyi5 U@K/nJ*|ָ+}Hͦ1aWڂ@RvHO0gWX,40j/vD1rz}Ϭ -椕dr=qj2k/\ŗTBzb).dU3Ko^1>2y&Cj Rw^X6p[P1! ~8N%7H-Vaj,V`wcﰰŗ 6;X[-\QV5 ﵭO nBk-%>ERjd^ D ktI7CGH,ͯkz K19q˨0Q6S}/PA6~zr;KKSWUhCte/'&Ufuh'~fty}[?xbʿ\-63L2؞ R=#댚?g|OadVd̛3s Kc[cWiI~ؼ 5f̣)hVL8ͪ$i5ݔa+%HAkoGNNTYooW/^¯R6S-wFAe B8fη $7 ym3ᾗE-=5)#-S0+Hۇj&LLWO fH=6OM?Sl&jWI@[mؙymvYMaͷ`C_DFmYܫT.A\Ւa!ҹM)hxVb. K|{t{|sH#a;))#k_mS\&g,mIqq}{jΣV6r{\qNS9.G}^u~Veӊir40@"6YCY 9^ɺ(ڬָƣ9T}0_?LbiVtN+J̛~Tr"U90\E3w2.O*9Psjr$M%<5~\K'?ɒ;BdXk8˸Y?&|Niކ}dZr.+LRR,G〗(ii𭗲Ls'~GkMj1 r'jEZ[M3p_2t^2}FG~Kr.c=vW͐[|rcZk qތ2+,1Ǩ"Ơc튒Dy=c5AQqp,=JTB㍭k&MZJa רŒh7qa*^p_aSe.8 6ũi`k1xy+jx`MŻwLE%| |2!硾+Z5PFM{-;*qk \,QsRYGeRmı{??%qқ?f|qmAP#~qMC9_VVB$da݄ s?\ ks'5M tI!V ق0"m/K{p^UWC[MNK?MIU'اƥHR!YI+wQpO<&ij)n|xbu-$wtJz{4t_`Ĕ )#͟Li}倧+WKGNݑLߌ̙e:E[2Z˂4#1O|Bpq[-G;K;"n^IQErGR9/_(R;jv4g8s-ia\ޒzc0PIVo"<ǥR1|xoK^i[1agBs!b<{l8T&;Abݨ;Y?2zMH"gV@.a *˨3-ܻ w 2j^=+oWsZf`b-;b5$.,{ Ƌ[72t>QQ " [/DI7eXG1ݭ~--Fا .:e؋ĕ{QR rG%z)eEAn統JS_ (L\Dn>4K_{i[Yo@nZtjM \UR,aЍǾ )YJvrMA+"J˽ͿhU{(-g$w6aRՉx_ƭ* lvB&USJ5g1qE1QMYQM"FRQʁ|ZIFF@ެZD#c9Jl>QaNѡ5Ue/mHߥJ(˛vtkx6b1ۦ HMh )1A'YE@np{J~`5I)6=I_zq}-`{1|*o ZRk"ibVēLT$^DN=aOMrVŔ\]kF,q`)m*+żTsf%6hhsB-e<01Ld'~#:GSI'ww)2Lj*:iN+y`,F$%HA!(zkHO4i󗙪^¾&t.Z}4?H,~2|~:Op?P|ST)pUsb?wZ&p`@>QGWGmmO?7jtչ?9̼oQsx%ũȥR2$wMC\iυO{:m6-#~~˸\h*P*Z*p[0_ߙjKXH |ÖS ڍx{/ݚ]p~GV <3L#|ŕ.(7|tY7/ ^SRqE*'i(=0x\MQ>˾#+Xx[rOZ Fpm$&SW k'T۝UMgr*S8[oHDo$Uߣ7Jj3VL@bɸبJ-1/gÎMMC^\6vwZ%$(ʿBV47Sz&EOJ ㊏S7"O_M%jrۆ :*,%JrBA34NX4lbW 㹳h] R96ZTBMYUŃ;X0Nؕ#?O{OJ;귻qGBۙ2||1p/m7:Pb᧧sjOTr{̼1˳Or9˩ꩯks4?*~mKqI~ +M˹ C9sf4u dҢ7 {?<~Ihi$CǗ[xҋ1>5IKTMhݴi7H#:>jtzwSc$yO"9iSGKpVO2Bϩ,|G;Dׄ;i -yO[Sԗr[=Lh_ผyw)˙J<.IlG_LH??̀+P"[i>- /uzSIg+HM|  ` "9Vds`'~,9 z)] R^Qn3Y~/u 5nRp ,\! &(専hȗH3z՞'GU&nLKUe>cQVYfF096Q}QOXOf$"}LF͔aզ'ĖR /';s>4W zڼڣ%%-: ;(Qn; g[]\SB|/_ 3qnU"_$WhYeVY0 IheUe[Ђ|6]&n4C2,d9&]f ͮFS5L(%$.N{ഺm[C?{lYY3ѷ%ZG1[5 Ύw+f/; x2ʌ0$ *j2(jhA*L@A8$b6ӿΎ ,ػ.HܞDhnPv`n؄v !E788 e!3i^ M>.I9J*4fS,Fչ9ǵÜYeaQ%6A,KSI<wY.J $ȲWuJJaܝonoVWWNGSVmظuRhПVE{A.9YReq#O![]ۂ鋤إ+\h 6$'.%,yq$q^m{i @N;)j>JZ稒i-N#*ژnTŽ?%1:Ou>>)ؗʆPXy[ y4(Rhp^ds_Nm[4{@ob^99}6hH..8   ?Vezy.]T[fo; kY z lmc9(] UR}[-xKQh|S y{ᬽ x)b'ůam~'.D*a>cեm. )P1<$,& tJu *ZWR(ŀ-sO H`s4I!⬊1.znV RF!n|0n(8gTy|5Y=L.k R$'㌇ ^ʥ|߯ &n+%)jلu)Xa"uicN7SUSz: sNHAf E$fHKU0T(8Y/ .- HnQ~=[ʗe+. l:l):JvY!E ; ۮ;RIyjo5e&Q6S_EE)GI% $ƗkjT ?O5 Pxq}| gQrߖ].H8',?a{UtՔ,U5+Ev]_]||?9x<2r㝮~]>+~#ʢ/Ȳ\F#i6n.;5[u+kY ͿG7*>L$gl6CI%:lb^*ع̫;90x!K,< 7N5p]pEN8 Ƿ!8pܧLʪz^Z&v,u++6."PQ=78EHÓEfOAI'H߰ؔH]sT*.b`u14L$)7鋖sDi, HI!m_Ij tbk*VYAǻ Ml:UQʟOW`?,TZ"p4 2:9"b}$)|:ʫ3Uh&66FTS'K(9s ȡ/@5·dp#MطApE*̌0QүRHҬ O\/ q QWtWF eR9{S ;t";aL7&ߗ AS%db.wF0[A-*J&D]cYV()%uZֽ|4eC 2Xt m.捝$h[j?'-$fDWJN*94؄\Wu+]'P6=67H%J]iJ?Wp`GD+aSQ]Pz(˗&ʔ*pR AYo3+;Ca}[\ 2 S">j: (aC1rO.T#lΗ{RXEOp0Rөua@-da=8LܽQUX8[Ze+,Ɗ#$,Q`=12\|p>rXL/ Ҩ" .:d `.YH@1^5ؐY ,8*)n*QGqzzWi9E~(9 [4ʠ1Gy~-o7?Y=þ6hkN,1y'|iU&E +(é/WRzYNFIZ2RfN7˸kj2a(*Gjys4.:[NcwJ'4zG*iۉ"aL9*q@mH7OR-Q q/39exr/抽QKIY n}F8gwj\g1(*i-PyQ`M<Þ2m]~V{ߌ>}t>fCy?RܟiSnkGI⋀Wu?; Ilx]FހuEUfNz:zq=~b}2^EMePA\-,aQSso1,Jmۊ~~gaID&i)RuRO9&D&=ty7݇ګ$q_Ϳ6ysY7jV>>c9vo[((-g,3DcHg\;YnQYUժ@KQ'&|UTuq ,K*:X\_šVL`pmkd=;*)۲ǁG)..@67T\b/`]wm+n狃29I&۱aT+F ku/="%]u"A eWG2Է pqK͞uUÜ2.h|0xE7]b;q~,ѧ Ǻ߀24Us.̥Z>Q;18+9=.A0+xRս>k.MBK`EH<}wN$q\H!դT0JjH#dpZro+ ]gt^uxGgBTO&{⦳\R.Q5*ƚJy]VTf} >=6q]|-@z0Id>&_r r&3FN|gh Zh+_l!|6g|ަk$y-O *v!{5Kű/tGXu߶0.0iWFSˬdU96s߆2 ;Z@tSEr#(Esit߬v: nuqWTMCeATIcWWC,K(DAeEPYtlwn:^&s?N'G>OAMINY-:QUKPg@*Rt׊c.0r.ǤOF|^p];:GemGdE%HԌ?WI鶤\5U풩z?0 |[nGp>t'pm pcj$rM;rKUԟ071W!7n´J:ھM5Hdmԫnl?e&En-Or j㈭uT'::4+A$sgfǨ`튂uO.g6'K8bsj]7DЪ#ͯazXBwh X/{T-㈦7?r*,3?f5, :Uba=p{AEt[5d\9AY_V(iʛ]$] %i]LRK5ՈIkG׵ɿvQ2|(h]Ş5g=ǙGㄽ;|LpueUyx.,Z|NONIa}$'+qWJٟ߄-ʳ<||*JJ"31<#*  !A" /[ӌh/W P^5(G w#|S8['I*[i%n:ɨ3ܻ+E[$JӨfyϢ /wDYdEzm?<rl Q' ]TJ)ۯ{_ziEaB<-#+4qj%$6xSQӹt^j=LώN,3 p.=Ac폝QIN{wc%Jt=MQשR!+)hQ!m2ԩ4>iOZ!\!K< lNHLAVqӗK-õ4ޕ-N$sE2huʪbJ*ŋ͟PKS,PwzI-e?H]^ 4WOu?ˌNr)EAmSIAi+ if(Pju/̗ojB5{z6,YyqD*a>{Zs DbIn)Tjj>Oϻ|/?K^k5+7CLWuCBLpv#} i(ڹbAn\^Y-(Bib`d%%2O H#]%}Eˢ%2U46~DXuQ0cvp*XXG.8l ^дNڇъH7鶨MI.WGɞd*%U)RF?ɆY{7byZ]>Ջ\pE2 o$Ț )(pUVJjx]\ Pvx'r<,I2\'8AJz<)ԔXJ/vtx$ Wf|Vd[\5 L9oᤢ)D3xД/B~4-Xs̨HͬFbm__!jOPKjUrڈVDs~ug=cs >*~dy~]wCR׺k >'Ԓقyfs @Ҳ K+rWքlg3õw[bu9wBuyYhB/p^ݶ6Ǹیf(OVzIU3^*Vr *x#:WQH5$8Hw^ּa><.i7$ȧ n|ֳk29$'˦HbqfU=<.s9^!kkc*:j3.yL BĘI%(@i]@U{ƼMSz_ ztVv{zE]pGISia-9WE$weNe-ڍeCAR4w8x2鲊M FhhdhP"2: ݮ }}3% :^ύ|?zpb)&˫asGQM2I{v }֌i>SCZZ:DN(j[Y<>V' ̺W>ZC=M6w V_UfYtrGPk:J. V&M6n~X9צ,2y/+ v4A> 3S&؄I): XoҺ( p,yCpok V^e mTKqiI liwG2<'0~)HEͤf C@koNFTta7zϨk/?s3.['0Y@ rGuC~'n:n1U%轮 O,u+LuLw%I__jqLx߮##c\d',X|b.ۦl^F7yO/"ʩjxۃ)2 $ucRU)` tupw!xֺxc4wG:sfI4hYS^% u'c~| iٿ7] NhSf#M߹7CCT@םCq2K}8_`[M, Y!&%U@v= eՄ;$乄V8Iqc`>\Z,)6jP7gjЍחT@UtbIYHRBWْ:RIʆJ l^qSgJb6+,ŷֱ>&"sAu)125Y#-ssPVD&Xf&|,sPUTs$)hپb.J9(Gg\+SO hjbBq>[ג_~tTAu0mK{A~pe=M6_RRR^4r32K%ݐ|w#IINtumDnsh踄ORkWQ=1ՔT=όaKV'_o* IYC K2x_61fr741B"^+iJ|73Ϥյg.(>\CYNAKGURNK$kch^"3w1]^t^~Ӛj/ߵ>#"t/MөOͫ*K}Wd8fH󜶦 )@+ZOU8MR\.!$j u6.knE6;b0Si՞ҫq38/ʲv:n;Q8ORea׺TF5$F_AeIb *0}dzt\arygQ-=,%|]4W9cd/QɎggp&}_* KY_L9\ RhISf!"\ &?Sh|;[oTzΒ|".Q}6g9fyd_@PTVL¡E`==ǽަ_z㎊MnLo$9mϮsg' 923j3ց$IF:(H־?xOikSMS]?>KԸ"mOT9w q|vai4R#Td%Zx@ᩣMg]G!jkCcI6QS ގF]@.M0Fr~4c4> x'.#xn -J&H`AV6 9)K|a xvKF>DMe9o,HNefEw4jf5pK}1K̙c~E>Opd\1D Jz޵VZɎHQI'ʠ* TEQj)fO"fmd`I)"9Y܋X^a}*/_L]Ȥ^Rr$h dſBm{\l c:$p(%~;LmNc$ s:_ D.@Q?fo^Db{ڳJ-'x*>)Ȫ?Qx4o#p>ΧOL?t=7z߱#H,*C^Q:Ҁ/`mqz)CSQGv>vkm_6 v:>)raH +GhXX~)eoȔs.eu+b$Gc"΢EMo9dt _[*o=IUW?N&˹晄IO6TRX6wFpX#tmGl@iFMnnGOl,b9P`d#V ;ȨuĆۉ )? FKNQgn\Y_ UDEOUJDSB5L|;HJGʚ~{>=˦򺥵ԧScp|M`aHϖ s*F2Rߴ)l{KSkܞYZwr|G agq° Ku[^HgP\YF!Lk]ĭ #ʶoeK>Xs)gaG^P%Bm7;HSE ZSg{s&ssC*QHj#|^}Q,%Kk1q<8n(_{puL\k2 隷&պO&x|V݋$|fzwQnZzݰ/u6>nXnE ]%r*@pͤ<]r#6D>4}%{:j~sPX%wJTN/uH`Q.Oo5*@lTcڂr#,r;jXj,pFv=[.KsHxcfuҤ|ASho0fV'pAVa=șjX(0dT~lhbw qC|y:MyfԾ :X%;3ggeIJĖx@~)ES>jz- 5U&q]#(3SR5mUwڰ7Bqq JjC RD;}ApI,h_} O8gOFa#̻Q7_J/>46!Ps'y8Ykw%d#6x۷jyd/ɓ[ftu],,ƮΧqdӧڮUeUqS<.Fe9jf8t/+&tn;JVNn\˸\tۿ\ftE*$*Ƽ z`dA8mq\aP[{61dD!ğB1JcUU<].<S |1@h\cɁjM{z{/T}X+=S?0tHqz෥( v7 A~BuR\Hu'OP$Scmdэіj⨂9`ѺkF٬o;XYk?&S2R8ӜkqY{c 6iZ_)rlx*0zYh[{1Ԕ2wrz|K,7g5|-XZy- )!18=c({Tej2]Ne{ZnfhS$l>Nn,m 'co{t;IuH]hZ$-},,@R: gD.-1RA%# .2 fԧ=ˁ>Rt?8W&Vڱ6aסMcـ*msb2IZV $m6.3Y|Crm.כ zXwm0BWJooSp@sk'L_Quؼ5M5[&g41KHo&x]C2HU]4jiM9iVO?_MJ.7InO|^?/9Xsi&SiXj:[N |~rτ54wjk?oOܴpW+m4iK)Ҷַkz{c餝3iILL Y#*U=G|b{2: Y%D)[H> }Tjꋩ뢚6M>̄X|8M3[PiuopA5iܒJ씶&iUܓBld#E 1EURUCD4~ 1$F? tڿ9j%nz^Ukl}&y%Ny w SjPS((Ou &u·|kh[n;1M١iJYzJRXO){0%Obqeq'*4UvcLxI2#ڧWNTY"aNQO7>šp-'8^W`Zt:}7)`=_jjg;yLxnG&T|C)ZQ>3MVk9&hwPcNoҶyj Gt8l^ؙ_Q`mUEđ}p)6pMI:]UӞgGy[\pwIR5(^HEuǫ%zqovr8ӹub`+Q:_34NJ }yk ;]EHufhi Um 㝹Piw 쟚]4g\)ʤe &YH퍽n=KkR$Ωˤ6jD!n/ %T oGH5[vӲnoӸKI~ \.K _X'áMR!6}#k~}pu[>aNxJuMsQ\0=I6=b n[6$[jVhHIVꠏK.(FK1lvd\Է_^{tf_S?,f:UES$lY>ʊḬ̈/ρ-74l|*d+{QKg|_]7Sp2(S,l:̥$JD1F?O|}z5߹/閖SfP|v!ڷ:X96Gc3O M%xXr[kUKםMsgSiſDl 5:N$t-Aw3"B a{RCϔOl#swi%'5c>4ks,Q.s dI]Lԭ*c7+6؇5WWnWS5(s>,D$fI(K!XoD ? )**{LIF; `6ѹ:>!ycĜC\39N+&xSCJȅbFHض`#׽FޛãdiGI>otug߇f>ɨfJ'kH@ؓ&s\GqlC;NNZYaPXxx + :c{`!q 6~u?| %hQl5g/-k)mݙ' D*9?.O~jK haUӓr$얶wMG pǿj)>!9{K-Ca{Bj5R.GƗN'_JztK3˦_=N.J V)2j),>GCYLtv,'afv߮ ,N aN-< J;ݢǮDqGE2n߆-2YK؉4hQa6îMۋdnX9b+ʌy0%5d}w1%cn,QTm#-I  'fqIքoɫƍ 870TqVsCŽoJigohw$Kg<'OMUtt%U"(ԑQEP?p9I2^ -%MJmcG7ҭ%}\wS4>%Cq ^T*UuqVn܈3բ噌"<ΔOXܩAtt&NV2{X72JHh$hۭXCB*-~c@m}CIM\C gн*h 8_ t+WZ0=tz6vEoUT`6,}ZX>,6װ\;O;>tܑSV/$els %*+"ߎ ]^7uL/; =i;-*Fmm0Ou7s{q6Zk3~8sc?fuB5AȨL,BGt$%%SCNWB_g58ѸK3̳*JVzzBi$\_ \OaE=lb^= }'΀z#/n~3Z r ˅=OT4+FVL#F$xb m+4$_3k9 jiix*yU>sџj<7eTOqMm$Y[G.[JjdƕeH$Ci6ZQ!j6bQ_R}DvM{i~mfeDĜ)% `h*b3<QvzԎ(pm}?GބQ#WT11(BbX\ F3JQR@*66,;4h5-'6`(Ya#V!RX 8H bN uI+e啳?< 3siC$DO,P]ΝW8l2:ii]zM{a', 8,sf\ڪPe*SDL eRl 0::zMZ˿|teC#s#\*NAXFjgovhFJ֛"l.iqs2֫wIt2sҜ⢁]FZ_i[R¡CcKI?XVY @53_}^'{b^NAw6KZA.cZp=gRYƂ "TIhܐB"NU %se(JK"h r!/RKGOU#d&߲ ҲbJF/;u/R[aW&ϘYOfthTʾ+<'Rb1eqH:{a*1vRIlz uI)ƃTa}' X< kXX4VZ pܓn8 bOh0Luu&5 z߮눝nݑqsuť@+$ U GtٛQvFp@X +uk5z*>'/)κ 8)MŚjl"Gf1(=.VRMGYGP7M/̮Q Q?9G+Q[lwZLĐ"^=>gzR3A\Ξ*IF nḩ-uU崲IC1JzxH>Iݣ=>:{ V$7*T;|$y.#09\@u[v;}cΚ_5ϲG>7uptm]q{ș b/WU4_o슗$h|1^?NKdz.?I dS`匓JZ!_]G\V49e-6.Y c}6\i9&XDHLd,\@ç{cOvy&n696A1"jTn:l888ټ_F%@ƨ ܳǡ= ]cb5<檋J }Lo,Z.DsA\2Dr!LO[劔XuO7|,)x eY#uJ@M[|>qIG7h֧-:OExwqOʾdʺ(lGk[=ҥ˫5)ViZɊh+#'/{U[:bI+/M34~ʾdHtp빴xβͅ8&yUiXe?ƳIqTrؕ"rrMro2*ʎ s$&QQP$I2TnF# |`v֣/=8Qg!h U2"9f48GkkԷF7&fٗ&2@ry!o{2ACùK -$夐$6)iu Vn8^: Foq؝#Pv?]F9NlI'A.\a*A$5GԄqp}Ρ޾Ax!Ā~~-#xgܮr_z쎏Mz΢ 8g2jhG<*I nABAԑ}:<3t>vxkb?SL?>s5rA;b9z*iJ_tX,yurdB]+(lA ]R?\b9 [ 8Ru >鿯omKe!2UUSc$o{wkIHOxrne3dvL_ݸw%QOMGx^[sae'J|R|y,ؒwߦY.Ԏ[YO4. qÒ.."tzEr! mv|99<6hԓUL5܌aǑ'WTTU5'p, ;9DW. b.m/W}! fY' 7f%R8Rץ6Jz \wƧnC%xxÍ#$'D9&ZW0RmHdwPq{'Jk?uM~L|Ms!ϲxpYٽv_Ib+ E3ѻ0kb=Yot)e{0Ge|/> ,s e: ƦAajb1h[Nzi*l L՝KhmO(~deȗ=|O"S{Q,uUzbm)۲J}$4ePcXSS3]+v`wO n&?kh^8P'H&l~x,V/<=U0-OA]p̮b `E"!R7mI7]qWws>r/<%&lc/y䦎嫊0h 4*o{Zܗ/sX^L ܦnpqZ:▇gZbYggm$k*{%Ĝfj=t{-vWTR9EZ=4c7/?Wh,eYmy`sek٭6"n\uZkj㯆1L16]`%FLDӍ&-NL+3"_1$Ԫ#]*̲ʁ<ߨ4ҲAW+$3j`Et= ~jSogSSMWA.Yv7f^$l>թщc?X+ψڀm?2]뵛UA qT)i3 !Y[#SS/ㇷs_&95yi3ǤV-^",.V߸b/`<+HCG `5*=AX\[VJr@I_QqJ9&YuD9 *$Okd+ *ݤ6Yُ"exNBxAtخ[p5XtFI.O;ᬀәtf0r7Va־E4o"#Ϣ@LMt =@i&g}w@~)ɳ^27*R: [{ZWk æRWꌭ Ds{&ጇ(ᬞ `Xc5`* ; 7]s_MG>1KJݻ|~ռZ:1'q-8&Vd4 EKL!geBw ,I [['nM.9IE.G@:+Nɺ$( 꾜masL=]CJkcrck$6{b{cRy06"U+cƞK~Xӏő#Ù< >%ϟ3+zz)E<YV)))w3Rz'(O)_uޏ|fiW~%( 4L֜8xf]C~.ZR(]XBÈAs5t 7vһ_pb-~^]9_ٜni\,s#(QNa)^S(@]z]}Jݧ:=v~l4SI>kjO-7 ~tQo[ޗ[[( ʹ⑏s~ 71h46i 2X, )Ʒ4-4ڍףS;(^B~y3=>hrf Rſt5peYO.5Ύ"MŶg¿0w̳ lzw!AŦ "39oT֦'E,I 'A(YњSk,yk̷47Ү&aP%QÌpm%"l8(#83  &lZ2&Y4 m=IsQӌ6&56aFI!ef6cDeikKk}xm%;Ju׷oO&K-aC2l[oW|p' +}z*$m3G!攜9qmJ ~pf@dzQf[%Y3,ɖڋ\g6O(f/PIuzB*-$7ekAFta5 WLA5GS+갂txF]69 sA$Vj Zr{YI Sϟ_MT>}>OqgQ_OBWM Q:+hjѥu 6xv=X&&k~`mZD,m"ha k":; 2ҬېWMGGF83[ͽ|Y|[ȍfc!t%ؗcs ; cs |;O4coWFKMEn.G@*r%7'^(/s\&VEO 5TjgS>bIhZ:ަ~Ւ5jR|#r(^L7w2.MQ->c>쳧?FBG=-4s~j6ʌ|"J3r#ef6T e>kƺgꜦج2*GSy3BEt(>_MeA~f}#3sRo^n~sXHW?N8f %S ;~c賺;N|B[)&|šjR{zIR7iD%K%s`ڈPIʍEFy2-Thj^1j1 OKQ{%Jobc>C)3Z,LJMv0[I|:9Ɠqdԍg> p,:]q؄pJ=B TQ oԱ76{XF-fQi.A+H?j/fQi*7&1'Z[}EkJS,i_3 }nFƓv5WYL<3OCq°R$;^Y'&7UVwed)r.1} mX(IO^8V/lR¤)9rY&G =K$v-gD /06c% c{ 冽K/EVW Q*woნB2H뷯/s49 Si#Vk]犓S'$G%)y[V8b d]DbLШOPG]-^_%N#̱\) ] z ӕ67J&|G,KqS33}zZL9OoT^ץ˪Ѩ|ފ؀/ltcLɩ*;x?yA[o:ϲ/)hn54ˬX$7};ũYw)oQeGJk{rr=5~R/r{\쭤q.{\*ULq|NMAvo"BO`ȼ463s0]r3~ <:SiO좠-ڦJ?&;h[ih-c;K<%k#/eSHNIaK*F>GpHh,_rA=1V7RUծS)ܗszj*HYA&[f;/K'-1oguZh޿q"=- 6':-KEG8so+GlyAGf40Y*" +пvtƵ%zx_07~,r6"=(** UdNW5f u=5r IEp6GUŞWZuUMC%尸,6` &F2V|H|?Oq6X9rwP Rtْ}ۚ<әT9MY~`G biWVh؁b/%)VG8Ů'827*W^]5p҃[qo 3Kd89~' Qqe$f* hiUƝ'1ILX$xz p7`j$gTekZ;ԏ~V<4kyX=8yM^ǖH8ͧ緆[{uc$;r\̲i8œ:HLgԈJIM-r/~œ]R~ oEv2o56N~xvVQ)skq*VOr,u}{+N3Hu)GG% u/ŌSoLh#{(&wH|Dڼm$۱ 'gr㧢V,曍;d\R_P+W, v117틊Fy -"h$A)ܓaoSU'Ï%=$qg4yUzoTN>J1uu\tѨggy/[@-y񈝚 k?>|mIo㵭4&{@6Wrb+kR? |z^|Ya]/5w%E21o8*~l#*${k=[[]OdY\Sf59\Ӻ1I 7NPJHN9.Q8r\ƚ5d_=Aokc+̗8V S 8%nwfbxۖF%zWY_OQW`E,xhX*x*[jG_[/>N_VϗB ź_+vP8 ICME0Lɳ̆L)띥DuHGfKB)}8j?==}Mct yp#ؒ@u%t3 /$w*X樖R.I[P6*Z5,ٝS6iOWG\")e$3{K*%ŭSZhZ%(1l/|2f29VEDbK2Q8$I|/pc&yZ3JoMNt3 30ZN}SwF~/,ސxI,I#q{q8uގc{`/Q т&j >_tWK5Y/UG*,0$tZj@w|_VůD/mFWٿtn{ӷ0M|9KSeTU QF;͍(7n),:Y$^+[<4mSKL)jbR6bU:mM3>:r'2)ϲe(*"y`؏IH%,y'TA$%%M 0'&G&#cl=HD42EZ/i;LugiT1 _5g5`5Y]zmp1Qth!,,TV2L\ aRWAZxy.(V7Y(>{I8Kc_=m^ >gK3ػ8+> )ᛮ~a4GW|ۘl-%WISvc^IFM2~!E,bGW/;~77,TXPâiJW!ZX}(瀺?"jXnJT{/ULjbf5)*F$b5RBSMI#E9R5/ ảY]We(Sx%Bhib.އ%`B븏+rsM)$xL$ZTEs Ros~Wx[ߩh3㨤ɪ,SS;(ƣ \tS`ȉ49|MZ͊EJ|D"2#kԺY=,#c{뀨qm%7πyc VKUeM6̈ Cp?lɜU\t||O6q51aOTW Qa @o~HWI3lI4 !];7~;|3f2'AuS2] M_OΥe -).{sgj6ɟFP;@;?\lmבiw\O:~ -%g"=QsazF6_I~(+!ؠ4Zu)D{N,GMo}>Mİђb9I9ynouL.}pH.Gr6#j5 & -\/7|yfyjߪjV:xQRiAk~C+mDzcNJi'˪%sQ5achٯ-I52}5:>˨b{vbcȻHΙ|?xֿi:)zۮ,;izgEwF&zCu4+w`,'b=mma8 1{}BVIX8Vlj&`n JHqU'MHߨ&txnRB12OJ AI(JYIC\mg>3BU06.q2qYkW:+%Klvd("{{;n?9%- -MuU3˼(s!rY/BZI8Ǜ e$"%[0$˪ @s tdo=qMP}qȠqUjmWNqaJ4.d_h&)eʼn*K UDCnؐUěfl|6;p W(er7ؓZhͨ`~G׺յf d'fҜ ǜ75ܩ1..mEせ j=Ra{ᑕJ8S:($X+4zcUyMqb"av~/'tN|K"spCOYܗc)Toa=+NqL~/ IP(O%H{e+UsfC -E;/Iua ~ǓZnehF2.ݶ켎HԯG`o S_x鷪wNcµ۾&y)O.ÊW\_N&كu=r/cA}G8q^\ $y-Is%) V[o!Vf/c@o:+cTr~N 4˥HVwW.KɷbF4 _|%V$׸yu؎%A$0p.()cU`, !Hk⅗k;bcCH 1[WaV2^wUbBӠv"ʹm^T#/ISj9_dȿ)e2?wʞbzmf|MӳVj/59fgSW EǕ*]-'Xgpx/J^V3B(T cuoQN):Vg _"_eXkאFg:\C9NmbmeEwgo>`' 4qI0ɤcORo{:~pG|d2W)Lgl}hM}-IX'>_1+pOaېvm@(k}z |p w[%29Gk-XblO#{'.ҹʾ` و6#1+$]R1 d[,?L[ ߿llF>hµq5ͯ|t492Vp{-F)_XqԆP85ԯ(fo@& bn+l.Y\[=kF}ow5:J^YH7Fe@ӣ%9a9\2_߂(1=ǜ/̾*ee, uIkRȠ2 e]+9{L<.A폗VƏyQsTSfb"_ ON3>CFSIvC] td Hj"`qvv Uds}0QUd$F䍱mX8[0m~8@ؤU5zZנ-il<\ε#q{v`#ZBMKÑB:m|nٺ2].T{vFXLϪMp) <d,bA"ߘs^cd!*_d9QG#96cF*X#BR@y,Q-%2MJRZ~0vhQ Edw jX!#k S{׾(ElNHkZ8e.KHJI)&Yovsjt[#eT"H"QoW)>yUe|C_4\~oee ] G/2+0lf?uC lAqм c 9^!G _93B5KaJњ.7@=)qjk tȩ>@X[p>xbDv*Y]@`6"PHF$ ͎ Odcb !u] oDف,[lRYYY'mnI~aky{tp8B`c hau*B PNnIXss9>7kzELgacTvkrH+rȖWF|>'r-}qy9Z5TkwC\` RN&Ĕԓ W Zї2KH.Zg)* s|dPTѽGT s>E(.lNM,]qns%otLZ9x<)} ;a ;ևO[3jN+ʲcr;;iXrM$ߩ>EvGؑ/$֭2ءkB M>_){: kg˫u#i)V X+؁ኔq6#yQ%dtHէ!7B&9iv|\[QPA"p6͍^HQtsPlrZtuW,p#I#${ D)* :v={ӐGr7-/B6gN1++YvqСVdyRJN;鎆Ɋ8ɱf7Ǿo(W[b:*bsBrXrCd+5\m|28Smőc AdR(^ $ac@P/خq-{I ol`)>x P vh?+;?gf| G4D]X ,nFn[$=fZiv>SpY˅IMLUEY稸;V5̒i }M'(*A$sqMP|T(^@;Ǩydd(kıg\YTK| ;{ۚ< {zrlo?$w6kK]*=d00Zii`MFX֤o-4k|n]Or"* *)٠kImN2j,dК6.,,vۥPWc&UX5l/R)4K&k}{NËtIrcXY30`E-%NI7seMTM"^5dN{ o˔\w:=8J*e;[B҉,~g!DR4?XiqBU.߸2?}11z?4'_wN_YO r{,WO8bzlz-8;fW}+G_qfUꞢw?rH]7,dNzHhU e`RʰwV}AiK@\;DQopF4!F؂%nvŨ.R=Вd#џeScW8{"9#OWUJ/plej}F9UdeCQG_C]WI]7He |.ǐsbS5U"-u}SЋFMr3QG,cX㼃0>[UW%.j2 ~ߋڡ)jx2̰PMs(s[8[4IO ҵYCnfe >`Oa]c?;»|\֮s[e`Q}EKRߤ;DNWu-eV"׮"xo%__~+~$xY͹͊n=Xzi~ cB?uNsMm԰^Z.l l,VS )T'͚EӿqN]X/BFw6튋TR+.:ܹ).vvXavZh.cT$ !רmrlpĈ.[c9bV7#U|ssq%LX>@:؞ PG@?G7i9oufmZ9Q ƐaieÞn1>0gbΗNv ~.cwǜLzjLv$Tb$U'hВѓ~pJ0F~ 5RlI,72{M$zm l2b KtLNA_::<}PWcPΰ~T2c)".Ed+:~W©QmnLڍؠbۢŹm\[tqֽ')QW]cCLTZ(>o|J͕]m{-IE#r}6tE.&MLBHRQteULyWؚvgSA_&8eSw؂ PwO20 8C48dFPT)k{۾GSbszIrJ0szzÕho@V8mZ6ÃgbcRz0∛0r0Hp &lwR[~02i>Kf_W⛑0QO%@J^ㆢIv>Jbh oT34PI6vqJT%ΆqL?"V8>r,É*r3*iaeĐX{[V*.Muipv -B>_kanIr VJjr_ >Zw\Uf;_~xKFUEO_K::Iնذln3#wܑ JIo|jF*ܡflUVnW**Ѳ[I{>7HazXt'$Rg5ڲ K#z߾%eÌ# È*&jͨ0Z!-n{6Y#`)ZEВXڎA]=*>^&O,-NXFoqόO Q (bEݾX)/q)%Tr^q- ."a)[R\LAvbznC_L|2tbӰcTSj'zt^䖖d*x=o{ai0_8ܞl']՘#c h?0m. xX&=NmU8Rdف2$I9UÜ9\A=5lTԸ8cK1f:? %搝>g=D!VqMGd=t1o p~Ŝpv Iԏ/_Kyw?&yo{mǤ=+بSX|^@Q`APkZ@-%6,b eHƤ\h&((bELT* )%i,tk\8`2UOmܓ큗p-v$awO~D1%A[w07M,|ĵUg4V$uo;lI7sc#>9IfuT|yM+K$T32RĈ* nq^ʭuT6*cI}MӱMP :uF Gp\.$ /%XCUލ{z}Uk)}@_ }4RM36 X*ښ*)ph75 3?9>k*rl7@&TKtxW w9x=f(K/ s/;;˿o拒e.҂${#55}_= tP@U@N"ڬ>QoK[AY/~Hdf~)qe t͗,H'B0;H.aU,Aa7'},΂`fR*ĮԤ^-Q~Ei .#GMss*% r<q )5cLgTQ`5baMYBo1PHRI.vO gmE-i$N n\oQ.8v*iZEF)/ j!YPM*obM,%7܇i0M:XG,P"b@HJ; c_,ɥG%|qu^(~YaklJc@݈;A>B**xEo|Ri# U@1k$p=TBoxb0W z\Sv9p `12Ntۭn*VJ<22*kO1db-Ѵw؜FA{pobJUN\H{=1$|XTsDLM)gB{W,gG[Ūe7Pb@ĝ#~2gYRe ;m enFÕl߫lkڻ]"rEb/ 1:@:RX)P8$.ɳ<)r*I+ڈ` vW [r|!:Q?VS82TTȟJZI pO9?YV:%cleE ,}qQk!{R?umco߱ՊJ EV86$6/I$5\ όJ ; lc5,˶`~,F_P0*EB<ɫi>Yv9JԅHΓJT8 {t%.I]VRM$%+I*i[n\DRhWMMMNPmDnhO;()&$r84hPXݺ{؜Uf o}wYx0tA؏a;hX\酦UWek2d{4ÿL dP0a}$ʥV|iՖҴipq[MՁ+)Z56R}c#U =qI .Qc+^?oLIe(!Fχe766>l`_,,$r#B g2G _E&؞VOQgYY6g۹Ö Igv* mkt{b*D`;LI6.oڢIIVbK㯕b.jf7[2#C'VdCV>Oy[p ԱB# (IB4Za&7cIL/e)VؙgFr${1|St lQ!a!b9zIL@@á=17W%9e K;]|S~QQç`e|!ra(JnNkvr{cvD\Ew$7wT†RѪ*-S *} ddu"L. s۴#Kt,SkJYp Db^ 0v:E^o3b9$cOh$RBoldaR/a0)5YaZ('2EU"ܑJ0|AhiBK1 /G\ \$rrҥO!u*Җ&Mϖf޾-j_$҄eX]u؃.ECk{>s| ƤX~ĦSh(*h1.GU'DPy<6 J]HD^j[Pzzb";4d:9n||XjVp!UOA;QyhIMj3y "Б`6rk"2(7k ʊ3҆ڇ,67XFZTٵ$ BX9wβUF5c̲$IFqͯ2 8`aTA\455%Q<4O, x3$M{;[_p2K .&v'uiH*_HAT {yL$xyVFm١4yVj+촟ha4*ze^Ǵ:驣 #BOLZmAӍFҁ)닥V~ _q1e.hCY #_lRT.O!%Γ`:| z/x Ү}zũ7$]Xʬ_6 Fi)x`}M`(dN"u2F:ZߠM_sd5&`e6ž^ E>š4z R-/[A#{ G Lj[mQ.>p]:0y}lE&(AV4GE]ɅRVR䥺HtP1lAK$jԖ۩l7um,?QP!Xv;9GLƞ_3]@8˄:D\ .lz EYlp)<#xE{l|]&) 6bYmûר\R2NjQߠvlTdDX JXok0*a(m T݀9 +Qg'maL D@6 "?L :[|)͵a,P8 .mO!>ktdU"B-冬;*)Qff ǨB<.xF5Zs<.OND̳ʞ[ S>}4U @@~u&yg˜*\&rV8H$^ |:=;7؄x6 .xv? X&mlJyF`c,T]?偕$BRݮl?Qkdu?$B)[੥tUOmi}ʌ)K ~FbZ֒QMӼv#\Zji*ӹ]Oqm:-,%/kn~0H- :o)EmՎB8(v{ R\H~~޸HmX"Y m H% R(f _&a2v(KVr9U]BZx! KwE,Px)Mc@XvrKXh SA̷wzUC̋ҥ6ؽ8(Ri7pVmMӴ/:TDF(ť| CljI+a87e6lq؏-wLTjU4;dl<7=JQt4hlj `UHS{ãkr]XRl{o$ ߙIm*xӮXX׭q\sE*cp-\xBՉ'I{%}BX$} $/72J᡹?>m%hz##;jc}{lMՂqiѭ52yӸnv(342i 2=?[mkA5k2FcYnࣩNA:֒$V:nM;nILBչ>ùSOD7GR'XƸ1dɹ>` b'#0 O ]%gRTVHLP 3n:k'?osQOPB$j,744P{A]c= mk>= t =~Ud c#plj HbWTz;ȵ$o1Ҫbo0Nf\7^@SKʼntO3^\t8JeaO WYi=6bI3mk[_Z_)V#aoa{nzrYl2UXnz W%Gs  }D(Mi稆)"ߡ IU43e^6djcjuCx5bF]^mt'iƻiebg=02{з\հ'|x 5-j*g?t-wة<:iG1M'\U%ayFc){ZыL\U(f* rmpyvhNl)tT,\|t] PxTB@onpIP2Ucm,$ }R RTY xa8i* _l/SS#Jʌ >Ige;Ǧ j>o 1iUvw HNT$$ˡB4PZ;[mp.F= (Ey8=O|#R-$\,\=[-IUQy-&55,Cf"`e TREQ`w Va}~d0M$ǚ;+DZO|fqQBCzZIEmR oo۩Ƣé2Ii[ُ"9غ*x:m*+63U\-՘erv*lnvKr*Y [`=mKUO\w$a Z`?>b t ڋ W,QEɼo# J]=(!M54Krȸk=ͲKprOڌ-3U!%kN= 2$SKl  zrsTE3Ri%*X6z`J)6.X̼-wS 6v.2$ei> JѤi@[Sqk5*H( jhsZ[R4g":l6S#D)Rdܕ/QEC]= lv&؂ڣ3IW}MN20fÃȶOl-MN+yY$%`1ݛ`? RAIJQ a4^)Q-e `0+#Q60@o-{*p]o[>~ чx#Rmܞ.FAH 2l P]Xڤ5:Ggv>w)a$-i71<ԟd)ǞKnA&ta36 VQBe+tS,yɔ[MowȽW| !KI2r[k66UdEUX[^r~(qqTN$UtuXtOI I!4BH10K~>FƤup[)Z읏eVM1o3}'6[B.,lN{qrˌ[u7M K9BCuP߿۾5yLAeI;6}}ad_L (PX2 RA“mr˥Jr)ƬX%Gpێu \mF/IunF׹f\Y-BVI*LQ-'P*}S+F`mF5AH#} , ,:.snkC 4gi{bT. gM\Ҵ+1_%G[b=vod z'J*i wࣩJ&E'e Rඦ ~%4TL+Pme{qP X*UhK4c"+rozI]ڧi!dx@L QXZ[}.8 YH@Ӥ˪M@]EoqiIc,ΫiT""POAVeub;gl\2peHOm}rz5!;JMSS1Ӊ҄iP#Bb\ EUX-#1]ͻ|**d&jI &crG.sm:so ͹JfI 6 eME3bM|)jvU/fʭԐ-}Z|YUȏQt|EDנlKgv{uiͼ!I]Pi 8쨅E qh*BSGQuQq,*$4%KzP M'%DAMF;uq&.>zI9 2`6aJx,ݽ՗V]# x{z(:`oaGQiv\dL= "WHU=/s^f(qS]dպn?M&BȗelO^ER 4 ?uv*$ Ҥ9ӽvBa*P!޷,V2T3i/߶)A,#f-ONrTRwB"z}#CX7A.PME,β-]T#7˸m@ӎF=DM <,QE@ gW;aEFYIۨw5'FIkT'C3I(,r\@b):ˀLa5mnϦJa{Eq[1PM-]$K# F%v$V?StS!V7"7Z3B r(D5%n\ogLަ:c;(6L9&sjl<SS0Mb.wլ$3j* iiw>Pk ڔl\ӵIrʆzy*Z8U,)75zOPFhƝw= ˢB*iN>͎6dLX*-n#g~39WI]92@6\sz}4I> ~ǩtg؄>!!}Crest-client-1.8.0/spec/unit/windows/0000755000175000017500000000000012522136372016357 5ustar lucaslucasrest-client-1.8.0/spec/unit/windows/root_certs_spec.rb0000644000175000017500000000111612522136372022100 0ustar lucaslucasrequire 'spec_helper' 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).to have_at_least(1).items 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| cert.should be_a(OpenSSL::X509::Certificate) end end end rest-client-1.8.0/metadata.yml0000644000175000017500000001521312522136372015261 0ustar lucaslucas--- !ruby/object:Gem::Specification name: rest-client version: !ruby/object:Gem::Version version: 1.8.0 platform: ruby authors: - REST Client Team autorequire: bindir: bin cert_chain: [] date: 2015-03-24 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: webmock requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.4' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.4' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.4' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.4' - !ruby/object:Gem::Dependency name: pry requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: pry-doc requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rdoc requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 2.4.2 - - "<" - !ruby/object:Gem::Version version: '5.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 2.4.2 - - "<" - !ruby/object:Gem::Version version: '5.0' - !ruby/object:Gem::Dependency name: http-cookie requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.0.2 - - "<" - !ruby/object:Gem::Version version: '2.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.0.2 - - "<" - !ruby/object:Gem::Version version: '2.0' - !ruby/object:Gem::Dependency name: mime-types requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '1.16' - - "<" - !ruby/object:Gem::Version version: '3.0' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '1.16' - - "<" - !ruby/object:Gem::Version version: '3.0' - !ruby/object:Gem::Dependency name: netrc requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.7' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.7' description: 'A simple HTTP and REST client for Ruby, inspired by the Sinatra microframework style of specifying actions: get, put, post, delete.' email: rest.client@librelist.com executables: - restclient extensions: [] extra_rdoc_files: - README.rdoc - history.md files: - ".gitignore" - ".rspec" - ".travis.yml" - AUTHORS - Gemfile - LICENSE - README.rdoc - Rakefile - bin/restclient - history.md - lib/rest-client.rb - lib/rest_client.rb - lib/restclient.rb - lib/restclient/abstract_response.rb - lib/restclient/exceptions.rb - lib/restclient/payload.rb - lib/restclient/platform.rb - lib/restclient/raw_response.rb - lib/restclient/request.rb - lib/restclient/resource.rb - lib/restclient/response.rb - lib/restclient/version.rb - lib/restclient/windows.rb - lib/restclient/windows/root_certs.rb - rest-client.gemspec - rest-client.windows.gemspec - spec/integration/capath_digicert/244b5494.0 - spec/integration/capath_digicert/81b9768f.0 - spec/integration/capath_digicert/README - spec/integration/capath_digicert/digicert.crt - spec/integration/capath_verisign/415660c1.0 - spec/integration/capath_verisign/7651b327.0 - spec/integration/capath_verisign/README - spec/integration/capath_verisign/verisign.crt - spec/integration/certs/digicert.crt - spec/integration/certs/verisign.crt - spec/integration/integration_spec.rb - spec/integration/request_spec.rb - spec/spec_helper.rb - spec/unit/abstract_response_spec.rb - spec/unit/exceptions_spec.rb - spec/unit/master_shake.jpg - spec/unit/payload_spec.rb - spec/unit/raw_response_spec.rb - spec/unit/request2_spec.rb - spec/unit/request_spec.rb - spec/unit/resource_spec.rb - spec/unit/response_spec.rb - spec/unit/restclient_spec.rb - spec/unit/windows/root_certs_spec.rb homepage: https://github.com/rest-client/rest-client licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.9.2 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.2.2 signing_key: specification_version: 4 summary: Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions. test_files: - spec/integration/capath_digicert/244b5494.0 - spec/integration/capath_digicert/81b9768f.0 - spec/integration/capath_digicert/README - spec/integration/capath_digicert/digicert.crt - spec/integration/capath_verisign/415660c1.0 - spec/integration/capath_verisign/7651b327.0 - spec/integration/capath_verisign/README - spec/integration/capath_verisign/verisign.crt - spec/integration/certs/digicert.crt - spec/integration/certs/verisign.crt - spec/integration/integration_spec.rb - spec/integration/request_spec.rb - spec/spec_helper.rb - spec/unit/abstract_response_spec.rb - spec/unit/exceptions_spec.rb - spec/unit/master_shake.jpg - spec/unit/payload_spec.rb - spec/unit/raw_response_spec.rb - spec/unit/request2_spec.rb - spec/unit/request_spec.rb - spec/unit/resource_spec.rb - spec/unit/response_spec.rb - spec/unit/restclient_spec.rb - spec/unit/windows/root_certs_spec.rb has_rdoc: rest-client-1.8.0/LICENSE0000644000175000017500000000210312522136372013755 0ustar lucaslucasThe 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-1.8.0/.gitignore0000644000175000017500000000006512522136372014745 0ustar lucaslucas*.gem /Gemfile.lock /.bundle /vendor /doc /pkg /rdoc rest-client-1.8.0/.rspec0000644000175000017500000000005212522136372014066 0ustar lucaslucas--colour --format progress --order random rest-client-1.8.0/Gemfile0000644000175000017500000000025412522136372014250 0ustar lucaslucassource "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-1.8.0/rest-client.windows.gemspec0000644000175000017500000000105512522136372020244 0ustar lucaslucas# # 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-1.8.0/Rakefile0000644000175000017500000000606712522136372014432 0ustar lucaslucas# 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 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.rdoc') t.rdoc_files.include('lib/*.rb') end rest-client-1.8.0/AUTHORS0000644000175000017500000000222012522136372014020 0ustar lucaslucasThe Ruby REST Client would not be what it is today without the help of the following kind souls: Adam Jacob Adam Wiggins Aman Gupta Andy Brody Blake Mizerany Brad Ediger Braintree Brian Donovan Caleb Land Chris Dinn Chris Frohoff Chris Green Coda Hale Crawford Cyril Rohr Dan Mayer David Backeus David Perkowski Dmitri Dolguikh Dusty Doris Dylan Egan El Draper Evan Smith François Beausoleil Gabriele Cirulli Garry Shutler Giovanni Cappellotto Greg Borenstein Harm Aarts Hiro Asari Hugh McGowan Ian Warshak Ivan Makfinsky JH. Chabran James Edward Gray II Jari Bakken Jeff Remer Jeffrey Hardy Jeremy Kemper John Barnette Jon Rowe Jordi Massaguer Pla Juan Alvarez Julien Kirch Justin Coyne Justin Lambert Keith Rarick Kenichi Kamiya Kevin Read Kosuke Asami Kyle VanderBeek Lars Gierth Lawrence Leonard Gilbert Lee Jarvis Lennon Day-Reynolds Marc-André Cournoyer Matthew Manning Michael Klett Mike Fletcher Nicholas Wieland Nick Plante Niko Dittmann Oscar Del Ben Pablo Astigarraga Paul Dlug Pedro Belo Philip Corliss Pierre-Louis Gottfrois Rafael Ssouza Rick "technoweenie" Robert Eanes Rodrigo Panachi Syl Turner T. Watanabe Tekin W. Andrew Loe III Waynn Lue tpresa rest-client-1.8.0/README.rdoc0000644000175000017500000002500012522136372014557 0ustar lucaslucas= REST Client -- simple DSL for accessing HTTP and REST resources Build status: {Build Status}[https://travis-ci.org/rest-client/rest-client] 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: rest.client@librelist.com (send a mail to subscribe). == Requirements MRI Ruby 1.9.2 and newer are supported. Alternative interpreters compatible with 1.9.1+ should work as well. Ruby 1.8.7 is no longer supported. That's because the Ruby 1.8.7 interpreter itself no longer has official support, _not_ _even_ _security_ _patches!_ If you have been putting off upgrading your servers, now is the time. ({More info is on the Ruby developers' blog.}[http://www.ruby-lang.org/en/news/2013/06/30/we-retire-1-8-7/]) The rest-client gem depends on these other gems for installation and usage: * {mime-types}[http://rubygems.org/gems/mime-types] * {netrc}[http://rubygems.org/gems/netrc] * {rdoc}[http://rubygems.org/gems/rdoc] If you want to hack on the code, you should also have {the Bundler gem}[http://bundler.io/] installed so it can manage all necessary development dependencies for you. == Usage: Raw URL 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.to_str ➔ \n\n\n { :path => '/foo/bar', :owner => 'that_guy', :group => 'those_guys' }, :upload => { :file => File.new(path, 'rb') } }) == Multipart Yeah, that's right! This does multipart sends for you! 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: RestClient.post '/data', {:foo => 'bar', :multipart => true} == Usage: ActiveResource-Style 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 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::Exception holding the Response will be raised; a specific exception class will be thrown for known error codes RestClient.get 'http://example.com/resource' ➔ RestClient::ResourceNotFound: RestClient::ResourceNotFound begin RestClient.get 'http://example.com/resource' rescue => e e.response end ➔ 404 Resource Not Found | text/html 282 bytes == Result 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. # Don't raise exceptions but return the response RestClient.get('http://example.com/resource'){|response, request, result| response } ➔ 404 Resource Not Found | text/html 282 bytes # Manage a specific error code RestClient.get('http://my-rest-service.com/resource'){ |response, request, result, &block| case response.code when 200 p "It worked !" response when 423 raise SomeCustomExceptionIfYouWant else response.return!(request, result, &block) end } # 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." RestClient.get('http://my-rest-service.com/resource'){ |response, request, result, &block| if [301, 302, 307].include? response.code response.follow_redirection(request, result, &block) else response.return!(request, result, &block) 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: 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. == Shell The restclient shell command gives an IRB session with RestClient already loaded: $ restclient >> RestClient.get 'http://example.com' Specify a URL argument for get/post/put/delete on that resource: $ restclient http://example.com >> put '/resource', 'data' Add a user and password for authenticated resources: $ restclient https://example.com user pass >> delete '/private/resource' Create ~/.restclient for named sessions: sinatra: url: http://localhost:4567 rack: url: http://localhost:9292 private_site: url: http://example.com username: user password: pass Then invoke: $ restclient private_site Use as a one-off, curl-style: $ restclient get http://example.com/resource > output_body $ restclient put http://example.com/resource < input_body == Logging To enable logging you can: * set RestClient.log with a Ruby Logger, or * set an environment variable to avoid modifying the code (in this case you can use a file name, "stdout" or "stderr"): $ RESTCLIENT_LOG=stdout path/to/my/program Either produces logs like this: 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: 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: RestClient.proxy = ENV['http_proxy'] == Query parameters Request objects know about query parameters and will automatically add them to the URL for GET, HEAD and DELETE requests, escaping the keys and values as needed: RestClient.get 'http://example.com/resource', :params => {:foo => 'bar', :baz => 'qux'} # will GET http://example.com/resource?foo=bar&baz=qux == Cookies Request and Response objects know about HTTP cookies, and will automatically extract and set headers for them as needed: 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 == SSL Client Certificates 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: # 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:: Matthew Manning, Lawrence Leonard Gilbert, Andy Brody Creator:: Adam Wiggins Maintainer Emeritus:: Julien Kirch Major contributions:: Blake Mizerany, Julien Kirch Patches contributed by many, including Chris Anderson, Greg Borenstein, Ardekantur, Pedro Belo, Rafael Souza, Rick Olson, Aman Gupta, François Beausoleil and Nick Plante. == Legal Released under the MIT License: http://www.opensource.org/licenses/mit-license.php "Master Shake" photo (http://www.flickr.com/photos/solgrundy/924205581/) by "SolGrundy"; used under terms of the Creative Commons Attribution-ShareAlike 2.0 Generic license (http://creativecommons.org/licenses/by-sa/2.0/) Code for reading Windows root certificate store derived from work by Puppet; used under terms of the Apache License, Version 2.0.