oauth 0.3.6 → 0.3.7.pre1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of oauth might be problematic. Click here for more details.

Files changed (43) hide show
  1. data/.gitignore +1 -0
  2. data/History.txt +16 -0
  3. data/Manifest.txt +2 -0
  4. data/README.rdoc +13 -0
  5. data/Rakefile +27 -27
  6. data/TODO +1 -0
  7. data/lib/digest/hmac.rb +104 -0
  8. data/lib/oauth.rb +5 -1
  9. data/lib/oauth/client/action_controller_request.rb +1 -1
  10. data/lib/oauth/client/em_http.rb +94 -0
  11. data/lib/oauth/client/helper.rb +7 -4
  12. data/lib/oauth/client/net_http.rb +9 -6
  13. data/lib/oauth/consumer.rb +45 -25
  14. data/lib/oauth/core_ext.rb +31 -0
  15. data/lib/oauth/helper.rb +11 -1
  16. data/lib/oauth/request_proxy/base.rb +4 -3
  17. data/lib/oauth/request_proxy/curb_request.rb +55 -0
  18. data/lib/oauth/request_proxy/em_http_request.rb +67 -0
  19. data/lib/oauth/request_proxy/net_http.rb +9 -6
  20. data/lib/oauth/request_proxy/typhoeus_request.rb +53 -0
  21. data/lib/oauth/signature.rb +4 -1
  22. data/lib/oauth/signature/base.rb +9 -3
  23. data/lib/oauth/signature/hmac/base.rb +5 -2
  24. data/lib/oauth/signature/hmac/md5.rb +1 -2
  25. data/lib/oauth/signature/hmac/rmd160.rb +1 -2
  26. data/lib/oauth/signature/hmac/sha1.rb +2 -3
  27. data/lib/oauth/signature/hmac/sha2.rb +1 -2
  28. data/lib/oauth/signature/plaintext.rb +2 -2
  29. data/lib/oauth/version.rb +1 -1
  30. data/oauth.gemspec +157 -27
  31. data/test/integration/consumer_test.rb +304 -0
  32. data/test/test_action_controller_request_proxy.rb +4 -1
  33. data/test/test_consumer.rb +51 -254
  34. data/test/test_curb_request_proxy.rb +69 -0
  35. data/test/test_em_http_client.rb +74 -0
  36. data/test/test_em_http_request_proxy.rb +107 -0
  37. data/test/test_helper.rb +15 -9
  38. data/test/test_net_http_client.rb +59 -5
  39. data/test/test_net_http_request_proxy.rb +1 -1
  40. data/test/test_signature.rb +6 -3
  41. data/test/test_typhoeus_request_proxy.rb +73 -0
  42. data/website/index.html +2 -2
  43. metadata +43 -25
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg/*
data/History.txt CHANGED
@@ -1,3 +1,19 @@
1
+ == 0.3.7
2
+ * Better marshalling implementation (Yoan Blanc)
3
+ * Added optional block to OAuth::Consumer.get_*_token (Neill Pearman)
4
+ * Exclude `oauth_callback` with :exclude_callback (Neill Pearman)
5
+ * Strip extraneous spaces and line breaks from access_token responses
6
+ (observed in the wild with Yahoo!'s OAuth+OpenID hybrid) (Eric Hartmann)
7
+ * Stop double-escaping PLAINTEXT signatures (Jimmy Zimmerman)
8
+ * OAuth::Client::Helper won't override the specified `oauth_version`
9
+ (Philip Kromer)
10
+ * Support for Ruby 1.9 (Aaron Quint, Corey Donahoe, et al)
11
+ * Fixed an encoding / multibyte issue (成田 一生)
12
+ * Replaced hoe with Jeweler (Aaron Quint)
13
+ * Support for Typhoeus (Bill Kocik)
14
+ * Support for em-http (EventMachine) (Darcy Laycock)
15
+ * Support for curb (André Luis Leal Cardoso Junior)
16
+
1
17
  == 0.3.6 2009-09-14
2
18
 
3
19
  * Added -B CLI option to use the :body authentication scheme (Seth)
data/Manifest.txt CHANGED
@@ -13,6 +13,7 @@ lib/oauth/client.rb
13
13
  lib/oauth/client/action_controller_request.rb
14
14
  lib/oauth/client/helper.rb
15
15
  lib/oauth/client/net_http.rb
16
+ lib/oauth/client/em_http.rb
16
17
  lib/oauth/consumer.rb
17
18
  lib/oauth/errors.rb
18
19
  lib/oauth/errors/error.rb
@@ -27,6 +28,7 @@ lib/oauth/request_proxy/jabber_request.rb
27
28
  lib/oauth/request_proxy/mock_request.rb
28
29
  lib/oauth/request_proxy/net_http.rb
29
30
  lib/oauth/request_proxy/rack_request.rb
31
+ lib/oauth/request_proxy/em_http_request.rb
30
32
  lib/oauth/server.rb
31
33
  lib/oauth/signature.rb
32
34
  lib/oauth/signature/base.rb
data/README.rdoc CHANGED
@@ -37,8 +37,21 @@ When user returns create an access_token
37
37
  @access_token = @request_token.get_access_token
38
38
  @photos = @access_token.get('/photos.xml')
39
39
 
40
+ Now that you have an access token, you can use Typhoeus to interact with the OAuth provider if you choose.
41
+
42
+ oauth_params = {:consumer => oauth_consumer, :token => access_token}
43
+ hydra = Typhoeus::Hydra.new
44
+ req = Typhoeus::Request.new(uri, options)
45
+ oauth_helper = OAuth::Client::Helper.new(req, oauth_params.merge(:request_uri => uri))
46
+ req.headers.merge!({"Authorization" => oauth_helper.header}) # Signs the request
47
+ hydra.queue(req)
48
+ hydra.run
49
+ @response = req.response
50
+
40
51
  For more detailed instructions I have written this OAuth Client Tutorial http://stakeventures.com/articles/2008/02/23/developing-oauth-clients-in-ruby and "How to turn your rails site into an OAuth Provider ":http://stakeventures.com/articles/2007/11/26/how-to-turn-your-rails-site-into-an-oauth-provider .
41
52
 
53
+ If you wish to use em-http-request, you can find an example "in the official em-http-request repository":http://github.com/igrigorik/em-http-request/blob/master/examples/oauth-tweet.rb
54
+
42
55
  Finally be sure to check out the OAuth RDoc Manual http://oauth.rubyforge.org/rdoc/ .
43
56
 
44
57
  == Documentation Wiki
data/Rakefile CHANGED
@@ -1,36 +1,36 @@
1
- %w[rubygems rake rake/clean fileutils newgem rubigen hoe].each { |f| require f }
1
+ %w[rubygems rake rake/clean rake/testtask fileutils].each { |f| require f }
2
2
  $LOAD_PATH << File.dirname(__FILE__) + '/lib'
3
3
  require 'oauth'
4
4
  require 'oauth/version'
5
5
 
6
- # Generate all the Rake tasks
7
- # Run 'rake -T' to see list of generated tasks (from gem root directory)
8
- $hoe = Hoe.new('oauth', OAuth::VERSION) do |p|
9
- p.author = ['Pelle Braendgaard','Blaine Cook','Larry Halff','Jesse Clark','Jon Crosby', 'Seth Fitzsimmons']
10
- p.email = "oauth-ruby@googlegroups.com"
11
- p.description = "OAuth Core Ruby implementation"
12
- p.summary = p.description
13
- p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
14
- p.rubyforge_name = p.name # TODO this is default value
15
- p.url = "http://oauth.rubyforge.org"
16
-
17
- p.extra_deps = [
18
- ['ruby-hmac','>= 0.3.1']
19
- ]
20
- p.extra_dev_deps = [
21
- ['newgem', ">= #{::Newgem::VERSION}"],
22
- ['actionpack'],
23
- ['rack']
24
- ]
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |s|
9
+ s.name = %q{oauth}
10
+ s.version = OAuth::VERSION
11
+ s.date = %q{2009-08-10}
12
+ s.authors = ["Pelle Braendgaard", "Blaine Cook", "Larry Halff", "Jesse Clark", "Jon Crosby", "Seth Fitzsimmons", "Matt Sanford", "Aaron Quint"]
13
+ s.email = "oauth-ruby@googlegroups.com"
14
+ s.description = "OAuth Core Ruby implementation"
15
+ s.summary = s.description
16
+ s.rubyforge_project = %q{oauth}
17
+ s.add_development_dependency(%q<actionpack>, [">= 2.2.0"])
18
+ s.add_development_dependency(%q<rack>, [">= 1.0.0"])
19
+ s.add_development_dependency(%q<mocha>, [">= 0.9.8"])
20
+ s.add_development_dependency(%q<typhoeus>, [">= 0.1.13"])
21
+ s.add_development_dependency(%q<em-http-request>)
22
+ end
23
+ Jeweler::GemcutterTasks.new
24
+ rescue LoadError
25
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
26
+ end
25
27
 
26
- p.clean_globs |= %w[**/.DS_Store tmp *.log **/.*.sw? *.gem .config **/.DS_Store]
27
- path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
28
- p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
29
- p.rsync_args = '-av --delete --ignore-errors'
28
+ Rake::TestTask.new do |t|
29
+ t.libs << "test"
30
+ t.test_files = FileList['test/*test*.rb']
31
+ t.verbose = true
30
32
  end
31
33
 
32
- require 'newgem/tasks' # load /tasks/*.rake
33
34
  Dir['tasks/**/*.rake'].each { |t| load t }
34
35
 
35
- # TODO - want other tests/tasks run by default? Add them to the list
36
- # task :default => [:spec, :features]
36
+ task :default => :test
data/TODO CHANGED
@@ -29,3 +29,4 @@ Random TODOs:
29
29
  * sensible Exception hierarchy
30
30
  * Tokens as Modules
31
31
  * don't tie to Net::HTTP
32
+ * Take a look at Curb HTTP Verbs
@@ -0,0 +1,104 @@
1
+ # = digest/hmac.rb
2
+ #
3
+ # An implementation of HMAC keyed-hashing algorithm
4
+ #
5
+ # == Overview
6
+ #
7
+ # This library adds a method named hmac() to Digest classes, which
8
+ # creates a Digest class for calculating HMAC digests.
9
+ #
10
+ # == Examples
11
+ #
12
+ # require 'digest/hmac'
13
+ #
14
+ # # one-liner example
15
+ # puts Digest::HMAC.hexdigest("data", "hash key", Digest::SHA1)
16
+ #
17
+ # # rather longer one
18
+ # hmac = Digest::HMAC.new("foo", Digest::RMD160)
19
+ #
20
+ # buf = ""
21
+ # while stream.read(16384, buf)
22
+ # hmac.update(buf)
23
+ # end
24
+ #
25
+ # puts hmac.bubblebabble
26
+ #
27
+ # == License
28
+ #
29
+ # Copyright (c) 2006 Akinori MUSHA <knu@iDaemons.org>
30
+ #
31
+ # Documentation by Akinori MUSHA
32
+ #
33
+ # All rights reserved. You can redistribute and/or modify it under
34
+ # the same terms as Ruby.
35
+ #
36
+ # $Id: hmac.rb 14881 2008-01-04 07:26:14Z akr $
37
+ #
38
+
39
+ require 'digest'
40
+
41
+ unless defined?(Digest::HMAC)
42
+ module Digest
43
+ class HMAC < Digest::Class
44
+ def initialize(key, digester)
45
+ @md = digester.new
46
+
47
+ block_len = @md.block_length
48
+
49
+ if key.bytesize > block_len
50
+ key = @md.digest(key)
51
+ end
52
+
53
+ ipad = Array.new(block_len).fill(0x36)
54
+ opad = Array.new(block_len).fill(0x5c)
55
+
56
+ key.bytes.each_with_index { |c, i|
57
+ ipad[i] ^= c
58
+ opad[i] ^= c
59
+ }
60
+
61
+ @key = key.freeze
62
+ @ipad = ipad.inject('') { |s, c| s << c.chr }.freeze
63
+ @opad = opad.inject('') { |s, c| s << c.chr }.freeze
64
+ @md.update(@ipad)
65
+ end
66
+
67
+ def initialize_copy(other)
68
+ @md = other.instance_eval { @md.clone }
69
+ end
70
+
71
+ def update(text)
72
+ @md.update(text)
73
+ self
74
+ end
75
+ alias << update
76
+
77
+ def reset
78
+ @md.reset
79
+ @md.update(@ipad)
80
+ self
81
+ end
82
+
83
+ def finish
84
+ d = @md.digest!
85
+ @md.update(@opad)
86
+ @md.update(d)
87
+ @md.digest!
88
+ end
89
+ private :finish
90
+
91
+ def digest_length
92
+ @md.digest_length
93
+ end
94
+
95
+ def block_length
96
+ @md.block_length
97
+ end
98
+
99
+ def inspect
100
+ sprintf('#<%s: key=%s, digest=%s>', self.class.name, @key.inspect, @md.inspect.sub(/^\#<(.*)>$/) { $1 });
101
+ end
102
+ end
103
+ end
104
+ end
data/lib/oauth.rb CHANGED
@@ -1,4 +1,8 @@
1
+ $LOAD_PATH << File.dirname(__FILE__) unless $LOAD_PATH.include?(File.dirname(__FILE__))
2
+
1
3
  require 'oauth/oauth'
4
+ require 'oauth/core_ext'
5
+
2
6
  require 'oauth/client/helper'
3
7
  require 'oauth/signature/hmac/sha1'
4
- require 'oauth/request_proxy/mock_request'
8
+ require 'oauth/request_proxy/mock_request'
@@ -5,7 +5,7 @@ require 'action_controller/test_process'
5
5
  module ActionController
6
6
  class Base
7
7
  def process_with_oauth(request, response=nil)
8
- request.apply_oauth!
8
+ request.apply_oauth! if request.respond_to?(:apply_oauth!)
9
9
  process_without_oauth(request, response)
10
10
  end
11
11
 
@@ -0,0 +1,94 @@
1
+ require 'em-http'
2
+ require 'oauth/helper'
3
+ require 'oauth/client/helper'
4
+ require 'oauth/request_proxy/em_http_request'
5
+
6
+ # Extensions for em-http so that we can use consumer.sign! with an EventMachine::HttpClient
7
+ # instance. This is purely syntactic sugar.
8
+ class EventMachine::HttpClient
9
+
10
+ attr_reader :oauth_helper
11
+
12
+ # Add the OAuth information to an HTTP request. Depending on the <tt>options[:scheme]</tt> setting
13
+ # this may add a header, additional query string parameters, or additional POST body parameters.
14
+ # The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
15
+ # header.
16
+ #
17
+ # * http - Configured Net::HTTP instance, ignored in this scenario except for getting host.
18
+ # * consumer - OAuth::Consumer instance
19
+ # * token - OAuth::Token instance
20
+ # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
21
+ # +signature_method+, +nonce+, +timestamp+)
22
+ #
23
+ # This method also modifies the <tt>User-Agent</tt> header to add the OAuth gem version.
24
+ #
25
+ # See Also: {OAuth core spec version 1.0, section 5.4.1}[http://oauth.net/core/1.0#rfc.section.5.4.1]
26
+ def oauth!(http, consumer = nil, token = nil, options = {})
27
+ options = { :request_uri => normalized_oauth_uri(http),
28
+ :consumer => consumer,
29
+ :token => token,
30
+ :scheme => 'header',
31
+ :signature_method => nil,
32
+ :nonce => nil,
33
+ :timestamp => nil }.merge(options)
34
+
35
+ @oauth_helper = OAuth::Client::Helper.new(self, options)
36
+ self.__send__(:"set_oauth_#{options[:scheme]}")
37
+ end
38
+
39
+ # Create a string suitable for signing for an HTTP request. This process involves parameter
40
+ # normalization as specified in the OAuth specification. The exact normalization also depends
41
+ # on the <tt>options[:scheme]</tt> being used so this must match what will be used for the request
42
+ # itself. The default scheme is +header+, in which the OAuth parameters as put into the +Authorization+
43
+ # header.
44
+ #
45
+ # * http - Configured Net::HTTP instance
46
+ # * consumer - OAuth::Consumer instance
47
+ # * token - OAuth::Token instance
48
+ # * options - Request-specific options (e.g. +request_uri+, +consumer+, +token+, +scheme+,
49
+ # +signature_method+, +nonce+, +timestamp+)
50
+ #
51
+ # See Also: {OAuth core spec version 1.0, section 9.1.1}[http://oauth.net/core/1.0#rfc.section.9.1.1]
52
+ def signature_base_string(http, consumer = nil, token = nil, options = {})
53
+ options = { :request_uri => normalized_oauth_uri(http),
54
+ :consumer => consumer,
55
+ :token => token,
56
+ :scheme => 'header',
57
+ :signature_method => nil,
58
+ :nonce => nil,
59
+ :timestamp => nil }.merge(options)
60
+
61
+ OAuth::Client::Helper.new(self, options).signature_base_string
62
+ end
63
+
64
+ protected
65
+
66
+ # Since we expect to get the host etc details from the http instance (...),
67
+ # we create a fake url here. Surely this is a horrible, horrible idea?
68
+ def normalized_oauth_uri(http)
69
+ uri = URI.parse(normalize_uri.path)
70
+ uri.host = http.address
71
+ uri.port = http.port
72
+
73
+ if http.respond_to?(:use_ssl?) && http.use_ssl?
74
+ uri.scheme = "https"
75
+ else
76
+ uri.scheme = "http"
77
+ end
78
+ uri.to_s
79
+ end
80
+
81
+ def set_oauth_header
82
+ headers = (self.options[:head] ||= {})
83
+ headers['Authorization'] = @oauth_helper.header
84
+ end
85
+
86
+ def set_oauth_body
87
+ raise NotImplementedError, 'please use the set_oauth_header method instead'
88
+ end
89
+
90
+ def set_oauth_query_string
91
+ raise NotImplementedError, 'please use the set_oauth_header method instead'
92
+ end
93
+
94
+ end
@@ -36,14 +36,16 @@ module OAuth::Client
36
36
  'oauth_timestamp' => timestamp,
37
37
  'oauth_nonce' => nonce,
38
38
  'oauth_verifier' => options[:oauth_verifier],
39
- 'oauth_version' => '1.0'
39
+ 'oauth_version' => (options[:oauth_version] || '1.0')
40
40
  }.reject { |k,v| v.to_s == "" }
41
41
  end
42
42
 
43
43
  def signature(extra_options = {})
44
44
  OAuth::Signature.sign(@request, { :uri => options[:request_uri],
45
45
  :consumer => options[:consumer],
46
- :token => options[:token] }.merge(extra_options) )
46
+ :token => options[:token],
47
+ :unsigned_parameters => options[:unsigned_parameters]
48
+ }.merge(extra_options) )
47
49
  end
48
50
 
49
51
  def signature_base_string(extra_options = {})
@@ -55,7 +57,8 @@ module OAuth::Client
55
57
 
56
58
  def amend_user_agent_header(headers)
57
59
  @oauth_ua_string ||= "OAuth gem v#{OAuth::VERSION}"
58
- if headers['User-Agent']
60
+ # Net::HTTP in 1.9 appends Ruby
61
+ if headers['User-Agent'] && headers['User-Agent'] != 'Ruby'
59
62
  headers['User-Agent'] += " (#{@oauth_ua_string})"
60
63
  else
61
64
  headers['User-Agent'] = @oauth_ua_string
@@ -66,7 +69,7 @@ module OAuth::Client
66
69
  parameters = oauth_parameters
67
70
  parameters.merge!('oauth_signature' => signature(options.merge(:parameters => parameters)))
68
71
 
69
- header_params_str = parameters.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ')
72
+ header_params_str = parameters.sort.map { |k,v| "#{k}=\"#{escape(v)}\"" }.join(', ')
70
73
 
71
74
  realm = "realm=\"#{options[:realm]}\", " if options[:realm]
72
75
  "OAuth #{realm}#{header_params_str}"
@@ -80,18 +80,21 @@ private
80
80
  self['Authorization'] = @oauth_helper.header
81
81
  end
82
82
 
83
- # FIXME: if you're using a POST body and query string parameters, using this
84
- # method will convert those parameters on the query string into parameters in
85
- # the body. this is broken, and should be fixed.
83
+ # FIXME: if you're using a POST body and query string parameters, this method
84
+ # will move query string parameters into the body unexpectedly. This may
85
+ # cause problems with non-x-www-form-urlencoded bodies submitted to URLs
86
+ # containing query string params. If duplicate parameters are present in both
87
+ # places, all instances should be included when calculating the signature
88
+ # base string.
89
+
86
90
  def set_oauth_body
87
- self.set_form_data(@oauth_helper.parameters_with_oauth)
91
+ self.set_form_data(@oauth_helper.stringify_keys(@oauth_helper.parameters_with_oauth))
88
92
  params_with_sig = @oauth_helper.parameters.merge(:oauth_signature => @oauth_helper.signature)
89
- self.set_form_data(params_with_sig)
93
+ self.set_form_data(@oauth_helper.stringify_keys(params_with_sig))
90
94
  end
91
95
 
92
96
  def set_oauth_query_string
93
97
  oauth_params_str = @oauth_helper.oauth_parameters.map { |k,v| [escape(k), escape(v)] * "=" }.join("&")
94
-
95
98
  uri = URI.parse(path)
96
99
  if uri.query.to_s == ""
97
100
  uri.query = oauth_params_str
@@ -3,6 +3,7 @@ require 'net/https'
3
3
  require 'oauth/oauth'
4
4
  require 'oauth/client/net_http'
5
5
  require 'oauth/errors'
6
+ require 'cgi'
6
7
 
7
8
  module OAuth
8
9
  class Consumer
@@ -75,9 +76,9 @@ module OAuth
75
76
  @secret = consumer_secret
76
77
 
77
78
  # ensure that keys are symbols
78
- @options = @@default_options.merge(options.inject({}) { |options, (key, value)|
79
- options[key.to_sym] = value
80
- options
79
+ @options = @@default_options.merge(options.inject({}) { |opts, (key, value)|
80
+ opts[key.to_sym] = value
81
+ opts
81
82
  })
82
83
  end
83
84
 
@@ -101,8 +102,8 @@ module OAuth
101
102
  end
102
103
  end
103
104
 
104
- def get_access_token(request_token, request_options = {}, *arguments)
105
- response = token_request(http_method, (access_token_url? ? access_token_url : access_token_path), request_token, request_options, *arguments)
105
+ def get_access_token(request_token, request_options = {}, *arguments, &block)
106
+ response = token_request(http_method, (access_token_url? ? access_token_url : access_token_path), request_token, request_options, *arguments, &block)
106
107
  OAuth::AccessToken.from_hash(self, response)
107
108
  end
108
109
 
@@ -120,12 +121,20 @@ module OAuth
120
121
  # @request_token = @consumer.get_request_token({}, :foo => "bar")
121
122
  #
122
123
  # TODO oauth_callback should be a mandatory parameter
123
- def get_request_token(request_options = {}, *arguments)
124
+ def get_request_token(request_options = {}, *arguments, &block)
124
125
  # if oauth_callback wasn't provided, it is assumed that oauth_verifiers
125
126
  # will be exchanged out of band
126
- request_options[:oauth_callback] ||= OAuth::OUT_OF_BAND
127
-
128
- response = token_request(http_method, (request_token_url? ? request_token_url : request_token_path), nil, request_options, *arguments)
127
+ request_options[:oauth_callback] ||= OAuth::OUT_OF_BAND unless request_options[:exclude_callback]
128
+
129
+ if block_given?
130
+ response = token_request(http_method,
131
+ (request_token_url? ? request_token_url : request_token_path),
132
+ nil,
133
+ request_options,
134
+ *arguments, &block)
135
+ else
136
+ response = token_request(http_method, (request_token_url? ? request_token_url : request_token_path), nil, request_options, *arguments)
137
+ end
129
138
  OAuth::RequestToken.from_hash(self, response)
130
139
  end
131
140
 
@@ -146,13 +155,16 @@ module OAuth
146
155
  path = "#{_uri.path}#{_uri.query ? "?#{_uri.query}" : ""}"
147
156
  end
148
157
 
149
- rsp = http.request(create_signed_request(http_method, path, token, request_options, *arguments))
158
+ # override the request with your own, this is useful for file uploads which Net::HTTP does not do
159
+ req = create_signed_request(http_method, path, token, request_options, *arguments)
160
+ return nil if block_given? and yield(req) == :done
161
+ rsp = http.request(req)
150
162
 
151
163
  # check for an error reported by the Problem Reporting extension
152
164
  # (http://wiki.oauth.net/ProblemReporting)
153
165
  # note: a 200 may actually be an error; check for an oauth_problem key to be sure
154
166
  if !(headers = rsp.to_hash["www-authenticate"]).nil? &&
155
- (h = headers.select { |h| h =~ /^OAuth / }).any? &&
167
+ (h = headers.select { |hdr| hdr =~ /^OAuth / }).any? &&
156
168
  h.first =~ /oauth_problem/
157
169
 
158
170
  # puts "Header: #{h.first}"
@@ -181,17 +193,20 @@ module OAuth
181
193
  # Creates a request and parses the result as url_encoded. This is used internally for the RequestToken and AccessToken requests.
182
194
  def token_request(http_method, path, token = nil, request_options = {}, *arguments)
183
195
  response = request(http_method, path, token, request_options, *arguments)
184
-
185
196
  case response.code.to_i
186
197
 
187
198
  when (200..299)
188
- # symbolize keys
189
- # TODO this could be considered unexpected behavior; symbols or not?
190
- # TODO this also drops subsequent values from multi-valued keys
191
- CGI.parse(response.body).inject({}) do |h,(k,v)|
192
- h[k.to_sym] = v.first
193
- h[k] = v.first
194
- h
199
+ if block_given?
200
+ yield response.body
201
+ else
202
+ # symbolize keys
203
+ # TODO this could be considered unexpected behavior; symbols or not?
204
+ # TODO this also drops subsequent values from multi-valued keys
205
+ CGI.parse(response.body).inject({}) do |h,(k,v)|
206
+ h[k.strip.to_sym] = v.first
207
+ h[k.strip] = v.first
208
+ h
209
+ end
195
210
  end
196
211
  when (300..399)
197
212
  # this is a redirect
@@ -298,7 +313,6 @@ module OAuth
298
313
 
299
314
  if [:post, :put].include?(http_method)
300
315
  data = arguments.shift
301
- data.reject! { |k,v| v.nil? } if data.is_a?(Hash)
302
316
  end
303
317
 
304
318
  headers = arguments.first.is_a?(Hash) ? arguments.shift : {}
@@ -321,7 +335,9 @@ module OAuth
321
335
  end
322
336
 
323
337
  if data.is_a?(Hash)
324
- request.set_form_data(data)
338
+ form_data = {}
339
+ data.each {|k,v| form_data[k.to_s] = v if !v.nil?}
340
+ request.set_form_data(form_data)
325
341
  elsif data
326
342
  if data.respond_to?(:read)
327
343
  request.body_stream = data
@@ -341,11 +357,15 @@ module OAuth
341
357
  request
342
358
  end
343
359
 
344
- # Unset cached http instance because it cannot be marshalled when
345
- # it has already been used and use_ssl is set to true
346
360
  def marshal_dump(*args)
347
- @http = nil
348
- self
361
+ {:key => @key,
362
+ :secret => @secret,
363
+ :options => @options}
364
+ end
365
+
366
+ def marshal_load(data)
367
+ initialize(data[:key], data[:secret], data[:options])
349
368
  end
369
+
350
370
  end
351
371
  end