dropbox-sdk 1.5.1 → 1.6

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,7 @@
1
+ 1.6 (2013-07-07)
2
+ * Added OAuth 2 support (DropboxOAuth2Flow). OAuth 1 still works.
3
+ * Fixed many minor bugs.
4
+
1
5
  1.5.1 (2012-8-20)
2
6
  * Fixed packaging.
3
7
 
data/README CHANGED
@@ -1,7 +1,52 @@
1
- Getting started with the Dropbox Ruby SDK:
2
- 1. Install json and oauth ruby gems with:
3
- gem install json oauth
4
- 2. Edit cli_example.rb to to include your APP_KEY and APP_SECRET
5
- 3. Try running the example app: 'ruby cli_example.rb'.
6
- 4. See dropbox_controller.rb for an example Ruby on Rails controller. It needs an APP_KEY and APP_SECRET set too.
7
- 5. Check out the dropbox website for reference and help! http://dropbox.com/developers_beta
1
+ Dropbox Core SDK for Ruby
2
+
3
+ A Ruby library that for Dropbox's HTTP-based Core API.
4
+
5
+ https://www.dropbox.com/developers/core/docs
6
+
7
+ ----------------------------------
8
+ Setup
9
+
10
+ You can install this package using 'pip':
11
+
12
+ # pip install dropbox
13
+
14
+ ----------------------------------
15
+ Getting a Dropbox API key
16
+
17
+ You need a Dropbox API key to make API requests.
18
+ - Go to: https://dropbox.com/developers/apps
19
+ - If you've already registered an app, click on the "Options" link to see the
20
+ app's API key and secret.
21
+ - Otherwise, click "Create an app" to register an app. Choose "Full Dropbox" or
22
+ "App Folder" depending on your needs.
23
+ See: https://www.dropbox.com/developers/reference#permissions
24
+
25
+ ----------------------------------
26
+ Using the Dropbox API
27
+
28
+ Full documentation: https://www.dropbox.com/developers/core/
29
+
30
+ Before your app can access a Dropbox user's files, the user must authorize your
31
+ application using OAuth 2. Successfully completing this authorization flow
32
+ gives you an "access token" for the user's Dropbox account, which grants you the
33
+ ability to make Dropbox API calls to access their files.
34
+
35
+ - Authorization example for a web app: web_file_browser.rb
36
+ - Authorization example for a command-line tool:
37
+ https://www.dropbox.com/developers/core/start/ruby
38
+
39
+ Once you have an access token, create a DropboxClient instance and start making
40
+ API calls.
41
+
42
+ You only need to perform the authorization process once per user. Once you have
43
+ an access token for a user, save it somewhere persistent, like in a database.
44
+ The next time that user visits your app, you can skip the authorization process
45
+ and go straight to making API calls.
46
+
47
+ ----------------------------------
48
+ Running the Examples
49
+
50
+ There are example programs included in the tarball. Before you can run an
51
+ example, you need to edit the ".rb" file and put your Dropbox API app key and
52
+ secret in the "APP_KEY" and "APP_SECRET" constants.
data/cli_example.rb CHANGED
@@ -12,10 +12,6 @@ require 'pp'
12
12
  # Find this at https://www.dropbox.com/developers
13
13
  APP_KEY = ''
14
14
  APP_SECRET = ''
15
- ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
16
- #The default is :app_folder, but your application might be
17
- #set to have full :dropbox access. Check your app at
18
- #https://www.dropbox.com/developers/apps
19
15
 
20
16
  class DropboxCLI
21
17
  LOGIN_REQUIRED = %w{put get cp mv rm ls mkdir info logout search thumbnail}
@@ -27,37 +23,28 @@ class DropboxCLI
27
23
  exit
28
24
  end
29
25
 
30
- @session = DropboxSession.new(APP_KEY, APP_SECRET)
31
26
  @client = nil
32
27
  end
33
28
 
34
29
  def login
35
- ########
36
- # Instead of going to a authorize URL, you can set a access token key and secret
37
- # from a previous session
38
- ########
39
- # @session.set_access_token('key', 'secret')
40
-
41
- if @session.authorized?
42
- puts "already logged in!"
30
+ if not @client.nil?
31
+ puts "already logged in!"
43
32
  else
33
+ web_auth = DropboxOAuth2FlowNoRedirect.new(APP_KEY, APP_SECRET)
34
+ authorize_url = web_auth.start()
35
+ puts "1. Go to: #{authorize_url}"
36
+ puts "2. Click \"Allow\" (you might have to log in first)."
37
+ puts "3. Copy the authorization code."
44
38
 
45
- # grab the request token for session
46
- @session.get_request_token
39
+ print "Enter the authorization code here: "
40
+ STDOUT.flush
41
+ auth_code = STDIN.gets.strip
47
42
 
48
- authorize_url = @session.get_authorize_url
49
- puts "Got a request token. Your request token key is #{@session.request_token.key} and your token secret is #{@session.request_token.secret}"
50
-
51
- # make the user log in and authorize this token
52
- puts "AUTHORIZING", authorize_url, "Please visit that web page and hit 'Allow', then hit Enter here."
53
- gets
54
-
55
- # get the access token from the server. Its then stored in the session.
56
- @session.get_access_token
43
+ access_token, user_id = web_auth.finish(auth_code)
57
44
 
45
+ @client = DropboxClient.new(access_token)
46
+ puts "You are logged in. Your access token is #{access_token}."
58
47
  end
59
- puts "You are logged in. Your access token key is #{@session.access_token.key} your secret is #{@session.access_token.secret}"
60
- @client = DropboxClient.new(@session, ACCESS_TYPE)
61
48
  end
62
49
 
63
50
  def command_loop
@@ -96,9 +83,8 @@ class DropboxCLI
96
83
  end
97
84
 
98
85
  def logout(command)
99
- @session.clear_access_token
100
- puts "You are logged out."
101
86
  @client = nil
87
+ puts "You are logged out."
102
88
  end
103
89
 
104
90
  def put(command)
@@ -1,57 +1,107 @@
1
- require 'dropbox_sdk'
1
+ # ---------------------------------------------------------------------------------------
2
+ # A Rails 3 controller that:
3
+ # - Runs the through Dropbox's OAuth 2 flow, yielding a Dropbox API access token.
4
+ # - Makes a Dropbox API call to upload a file.
5
+ #
6
+ # To run:
7
+ # 1. You need a Rails 3 project (to create one, run: rails new <folder-name>)
8
+ # 2. Copy this file into <folder-name>/app/controllers/
9
+ # 3. Add the following lines to <folder-name>/config/routes.rb
10
+ # get "dropbox/main"
11
+ # post "dropbox/upload"
12
+ # get "dropbox/auth_start"
13
+ # get "dropbox/auth_finish"
14
+ # 4. Run: rails server
15
+ # 5. Point your browser at: https://localhost:3000/dropbox/main
2
16
 
3
- # This is an example of a Rails 3 controller that authorizes an application
4
- # and then uploads a file to the user's Dropbox.
17
+ require '../lib/dropbox_sdk'
18
+ require 'securerandom'
5
19
 
6
- # You must set these
7
20
  APP_KEY = ""
8
21
  APP_SECRET = ""
9
- ACCESS_TYPE = :app_folder #The two valid values here are :app_folder and :dropbox
10
- #The default is :app_folder, but your application might be
11
- #set to have full :dropbox access. Check your app at
12
- #https://www.dropbox.com/developers/apps
13
22
 
23
+ class DropboxController < ApplicationController
14
24
 
15
- # Examples routes for config/routes.rb (Rails 3)
16
- #match 'db/authorize', :controller => 'db', :action => 'authorize'
17
- #match 'db/upload', :controller => 'db', :action => 'upload'
25
+ def main
26
+ client = get_dropbox_client
27
+ unless client
28
+ redirect_to(:action => 'auth_start') and return
29
+ end
18
30
 
19
- class DbController < ApplicationController
20
- def authorize
21
- if not params[:oauth_token] then
22
- dbsession = DropboxSession.new(APP_KEY, APP_SECRET)
31
+ account_info = client.account_info
23
32
 
24
- session[:dropbox_session] = dbsession.serialize #serialize and save this DropboxSession
33
+ # Show a file upload page
34
+ render :inline =>
35
+ "#{account_info['email']} <br/><%= form_tag({:action => :upload}, :multipart => true) do %><%= file_field_tag 'file' %><%= submit_tag 'Upload' %><% end %>"
36
+ end
25
37
 
26
- #pass to get_authorize_url a callback url that will return the user here
27
- redirect_to dbsession.get_authorize_url url_for(:action => 'authorize')
28
- else
29
- # the user has returned from Dropbox
30
- dbsession = DropboxSession.deserialize(session[:dropbox_session])
31
- dbsession.get_access_token #we've been authorized, so now request an access_token
32
- session[:dropbox_session] = dbsession.serialize
38
+ def upload
39
+ client = get_dropbox_client
40
+ unless client
41
+ redirect_to(:action => 'auth_start') and return
42
+ end
33
43
 
34
- redirect_to :action => 'upload'
44
+ begin
45
+ # Upload the POST'd file to Dropbox, keeping the same name
46
+ resp = client.put_file(params[:file].original_filename, params[:file].read)
47
+ render :text => "Upload successful. File now at #{resp['path']}"
48
+ rescue DropboxAuthError => e
49
+ session.delete(:access_token) # An auth error means the access token is probably bad
50
+ logger.info "Dropbox auth error: #{e}"
51
+ render :text => "Dropbox auth error"
52
+ rescue DropboxError => e
53
+ logger.info "Dropbox API error: #{e}"
54
+ render :text => "Dropbox API error"
35
55
  end
36
56
  end
37
57
 
38
- def upload
39
- # Check if user has no dropbox session...re-direct them to authorize
40
- return redirect_to(:action => 'authorize') unless session[:dropbox_session]
41
-
42
- dbsession = DropboxSession.deserialize(session[:dropbox_session])
43
- client = DropboxClient.new(dbsession, ACCESS_TYPE) #raise an exception if session not authorized
44
- info = client.account_info # look up account information
45
-
46
- if request.method != "POST"
47
- # show a file upload page
48
- render :inline =>
49
- "#{info['email']} <br/><%= form_tag({:action => :upload}, :multipart => true) do %><%= file_field_tag 'file' %><%= submit_tag %><% end %>"
50
- return
51
- else
52
- # upload the posted file to dropbox keeping the same name
53
- resp = client.put_file(params[:file].original_filename, params[:file].read)
54
- render :text => "Upload successful! File now at #{resp['path']}"
58
+ def get_dropbox_client
59
+ if session[:access_token]
60
+ begin
61
+ access_token = session[:access_token]
62
+ DropboxClient.new(access_token)
63
+ rescue
64
+ # Maybe something's wrong with the access token?
65
+ session.delete(:access_token)
66
+ raise
67
+ end
68
+ end
69
+ end
70
+
71
+ def get_web_auth()
72
+ redirect_uri = url_for(:action => 'auth_finish')
73
+ DropboxOAuth2Flow.new(APP_KEY, APP_SECRET, redirect_uri, session, :dropbox_auth_csrf_token)
74
+ end
75
+
76
+ def auth_start
77
+ authorize_url = get_web_auth().start()
78
+
79
+ # Send the user to the Dropbox website so they can authorize our app. After the user
80
+ # authorizes our app, Dropbox will redirect them here with a 'code' parameter.
81
+ redirect_to authorize_url
82
+ end
83
+
84
+ def auth_finish
85
+ begin
86
+ access_token, user_id, url_state = get_web_auth.finish(params)
87
+ session[:access_token] = access_token
88
+ redirect_to :action => 'main'
89
+ rescue DropboxOAuth2Flow::BadRequestError => e
90
+ render :text => "Error in OAuth 2 flow: Bad request: #{e}"
91
+ rescue DropboxOAuth2Flow::BadStateError => e
92
+ logger.info("Error in OAuth 2 flow: No CSRF token in session: #{e}")
93
+ redirect_to(:action => 'auth_start')
94
+ rescue DropboxOAuth2Flow::CsrfError => e
95
+ logger.info("Error in OAuth 2 flow: CSRF mismatch: #{e}")
96
+ render :text => "CSRF error"
97
+ rescue DropboxOAuth2Flow::NotApprovedError => e
98
+ render :text => "Not approved? Why not, bro?"
99
+ rescue DropboxOAuth2Flow::ProviderError => e
100
+ logger.info "Error in OAuth 2 flow: Error redirect from Dropbox: #{e}"
101
+ render :text => "Strange error."
102
+ rescue DropboxError => e
103
+ logger.info "Error getting OAuth 2 access token: #{e}"
104
+ render :text => "Error communicating with Dropbox servers."
55
105
  end
56
106
  end
57
107
  end
data/lib/dropbox_sdk.rb CHANGED
@@ -4,6 +4,9 @@ require 'net/https'
4
4
  require 'cgi'
5
5
  require 'json'
6
6
  require 'yaml'
7
+ require 'base64'
8
+ require 'securerandom'
9
+ require 'pp'
7
10
 
8
11
  module Dropbox # :nodoc:
9
12
  API_SERVER = "api.dropbox.com"
@@ -11,36 +14,17 @@ module Dropbox # :nodoc:
11
14
  WEB_SERVER = "www.dropbox.com"
12
15
 
13
16
  API_VERSION = 1
14
- SDK_VERSION = "1.5.1"
17
+ SDK_VERSION = "1.6"
15
18
 
16
19
  TRUSTED_CERT_FILE = File.join(File.dirname(__FILE__), 'trusted-certs.crt')
17
- end
18
-
19
- # DropboxSession is responsible for holding OAuth information. It knows how to take your consumer key and secret
20
- # and request an access token, an authorize url, and get an access token. You just need to pass it to
21
- # DropboxClient after its been authorized.
22
- class DropboxSession
23
-
24
- # * consumer_key - Your Dropbox application's "app key".
25
- # * consumer_secret - Your Dropbox application's "app secret".
26
- def initialize(consumer_key, consumer_secret)
27
- @consumer_key = consumer_key
28
- @consumer_secret = consumer_secret
29
- @request_token = nil
30
- @access_token = nil
31
- end
32
-
33
- private
34
20
 
35
- def do_http(uri, auth_token, request) # :nodoc:
21
+ def self.do_http(uri, request) # :nodoc:
36
22
  http = Net::HTTP.new(uri.host, uri.port)
37
23
 
38
24
  http.use_ssl = true
39
- enable_cert_checking(http)
25
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
40
26
  http.ca_file = Dropbox::TRUSTED_CERT_FILE
41
27
 
42
- request.add_field('Authorization', build_auth_header(auth_token))
43
-
44
28
  #We use this to better understand how developers are using our SDKs.
45
29
  request['User-Agent'] = "OfficialDropboxRubySDK/#{Dropbox::SDK_VERSION}"
46
30
 
@@ -48,38 +32,69 @@ class DropboxSession
48
32
  http.request(request)
49
33
  rescue OpenSSL::SSL::SSLError => e
50
34
  raise DropboxError.new("SSL error connecting to Dropbox. " +
51
- "There may be a problem with the set of certificates in \"#{Dropbox::TRUSTED_CERT_FILE}\". " +
52
- e)
35
+ "There may be a problem with the set of certificates in \"#{Dropbox::TRUSTED_CERT_FILE}\". #{e}")
53
36
  end
54
37
  end
55
38
 
56
- def enable_cert_checking(http)
57
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
39
+ # Parse response. You probably shouldn't be calling this directly. This takes responses from the server
40
+ # and parses them. It also checks for errors and raises exceptions with the appropriate messages.
41
+ def self.parse_response(response, raw=false) # :nodoc:
42
+ if response.kind_of?(Net::HTTPServerError)
43
+ raise DropboxError.new("Dropbox Server Error: #{response} - #{response.body}", response)
44
+ elsif response.kind_of?(Net::HTTPUnauthorized)
45
+ raise DropboxAuthError.new("User is not authenticated.", response)
46
+ elsif not response.kind_of?(Net::HTTPSuccess)
47
+ begin
48
+ d = JSON.parse(response.body)
49
+ rescue
50
+ raise DropboxError.new("Dropbox Server Error: body=#{response.body}", response)
51
+ end
52
+ if d['user_error'] and d['error']
53
+ raise DropboxError.new(d['error'], response, d['user_error']) #user_error is translated
54
+ elsif d['error']
55
+ raise DropboxError.new(d['error'], response)
56
+ else
57
+ raise DropboxError.new(response.body, response)
58
+ end
59
+ end
60
+
61
+ return response.body if raw
62
+
63
+ begin
64
+ return JSON.parse(response.body)
65
+ rescue JSON::ParserError
66
+ raise DropboxError.new("Unable to parse JSON response: #{response.body}", response)
67
+ end
58
68
  end
59
69
 
60
- def build_auth_header(token) # :nodoc:
61
- header = "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", " +
62
- "oauth_consumer_key=\"#{URI.escape(@consumer_key)}\", "
63
- if token
64
- key = URI.escape(token.key)
65
- secret = URI.escape(token.secret)
66
- header += "oauth_token=\"#{key}\", oauth_signature=\"#{URI.escape(@consumer_secret)}&#{secret}\""
70
+ # A string comparison function that is resistant to timing attacks. If you're comparing a
71
+ # string you got from the outside world with a string that is supposed to be a secret, use
72
+ # this function to check equality.
73
+ def self.safe_string_equals(a, b)
74
+ if a.length != b.length
75
+ false
67
76
  else
68
- header += "oauth_signature=\"#{URI.escape(@consumer_secret)}&\""
77
+ a.chars.zip(b.chars).map {|ac,bc| ac == bc}.all?
69
78
  end
70
- header
71
79
  end
80
+ end
72
81
 
73
- def do_get_with_token(url, token, headers=nil) # :nodoc:
74
- uri = URI.parse(url)
75
- do_http(uri, token, Net::HTTP::Get.new(uri.request_uri))
82
+ class DropboxSessionBase # :nodoc:
83
+
84
+ protected
85
+
86
+ def do_http(uri, request) # :nodoc:
87
+ sign_request(request)
88
+ Dropbox::do_http(uri, request)
76
89
  end
77
90
 
78
91
  public
79
92
 
80
93
  def do_get(url, headers=nil) # :nodoc:
81
94
  assert_authorized
82
- do_get_with_token(url, @access_token)
95
+ uri = URI.parse(url)
96
+ request = Net::HTTP::Get.new(uri.request_uri)
97
+ do_http(uri, request)
83
98
  end
84
99
 
85
100
  def do_http_with_body(uri, request, body)
@@ -103,7 +118,7 @@ class DropboxSession
103
118
  request.body = s
104
119
  end
105
120
  end
106
- do_http(uri, @access_token, request)
121
+ do_http(uri, request)
107
122
  end
108
123
 
109
124
  def do_post(url, headers=nil, body=nil) # :nodoc:
@@ -117,7 +132,51 @@ class DropboxSession
117
132
  uri = URI.parse(url)
118
133
  do_http_with_body(uri, Net::HTTP::Put.new(uri.request_uri, headers), body)
119
134
  end
135
+ end
120
136
 
137
+ # DropboxSession is responsible for holding OAuth 1 information. It knows how to take your consumer key and secret
138
+ # and request an access token, an authorize url, and get an access token. You just need to pass it to
139
+ # DropboxClient after its been authorized.
140
+ class DropboxSession < DropboxSessionBase # :nodoc:
141
+
142
+ # * consumer_key - Your Dropbox application's "app key".
143
+ # * consumer_secret - Your Dropbox application's "app secret".
144
+ def initialize(consumer_key, consumer_secret)
145
+ @consumer_key = consumer_key
146
+ @consumer_secret = consumer_secret
147
+ @request_token = nil
148
+ @access_token = nil
149
+ end
150
+
151
+ private
152
+
153
+ def build_auth_header(token) # :nodoc:
154
+ header = "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", " +
155
+ "oauth_consumer_key=\"#{URI.escape(@consumer_key)}\", "
156
+ if token
157
+ key = URI.escape(token.key)
158
+ secret = URI.escape(token.secret)
159
+ header += "oauth_token=\"#{key}\", oauth_signature=\"#{URI.escape(@consumer_secret)}&#{secret}\""
160
+ else
161
+ header += "oauth_signature=\"#{URI.escape(@consumer_secret)}&\""
162
+ end
163
+ header
164
+ end
165
+
166
+ def do_get_with_token(url, token, headers=nil) # :nodoc:
167
+ uri = URI.parse(url)
168
+ request = Net::HTTP::Get.new(uri.request_uri)
169
+ request.add_field('Authorization', build_auth_header(token))
170
+ Dropbox::do_http(uri, request)
171
+ end
172
+
173
+ protected
174
+
175
+ def sign_request(request) # :nodoc:
176
+ request.add_field('Authorization', build_auth_header(@access_token))
177
+ end
178
+
179
+ public
121
180
 
122
181
  def get_token(url_end, input_token, error_message_prefix) #: nodoc:
123
182
  response = do_get_with_token("https://#{Dropbox::API_SERVER}:443/#{Dropbox::API_VERSION}/oauth#{url_end}", input_token)
@@ -193,7 +252,7 @@ class DropboxSession
193
252
  return @access_token if authorized?
194
253
 
195
254
  if @request_token.nil?
196
- raise DropboxAuthError.new("No request token. You must set this or get an authorize url first.")
255
+ raise RuntimeError.new("No request token. You must set this or get an authorize url first.")
197
256
  end
198
257
 
199
258
  @access_token = get_token("/access_token", @request_token, "Couldn't get access token.")
@@ -244,6 +303,302 @@ class DropboxSession
244
303
  end
245
304
 
246
305
 
306
+ class DropboxOAuth2Session < DropboxSessionBase # :nodoc:
307
+ def initialize(oauth2_access_token)
308
+ if not oauth2_access_token.is_a?(String)
309
+ raise "bad type for oauth2_access_token (expecting String)"
310
+ end
311
+ @access_token = oauth2_access_token
312
+ end
313
+
314
+ def assert_authorized
315
+ true
316
+ end
317
+
318
+ protected
319
+
320
+ def sign_request(request) # :nodoc:
321
+ request.add_field('Authorization', 'Bearer ' + @access_token)
322
+ end
323
+ end
324
+
325
+ # Base class for the two OAuth 2 authorization helpers.
326
+ class DropboxOAuth2FlowBase # :nodoc:
327
+ def initialize(consumer_key, consumer_secret, locale=nil)
328
+ if not consumer_key.is_a?(String)
329
+ raise ArgumentError, "consumer_key must be a String, got #{consumer_key.inspect}"
330
+ end
331
+ if not consumer_secret.is_a?(String)
332
+ raise ArgumentError, "consumer_secret must be a String, got #{consumer_secret.inspect}"
333
+ end
334
+ if not (locale.nil? or locale.is_a?(String))
335
+ raise ArgumentError, "locale must be a String or nil, got #{locale.inspect}"
336
+ end
337
+ @consumer_key = consumer_key
338
+ @consumer_secret = consumer_secret
339
+ @locale = locale
340
+ end
341
+
342
+ def _get_authorize_url(redirect_uri, state)
343
+ params = {"client_id" => @consumer_key, "response_type" => "code"}
344
+ if not redirect_uri.nil?
345
+ params["redirect_uri"] = redirect_uri
346
+ end
347
+ if not state.nil?
348
+ params["state"] = state
349
+ end
350
+ if not @locale.nil?
351
+ params['locale'] = @locale
352
+ end
353
+
354
+ host = Dropbox::WEB_SERVER
355
+ path = "/#{Dropbox::API_VERSION}/oauth2/authorize"
356
+
357
+ target = URI::Generic.new("https", nil, host, nil, nil, path, nil, nil, nil)
358
+
359
+ target.query = params.collect {|k,v|
360
+ CGI.escape(k) + "=" + CGI.escape(v)
361
+ }.join("&")
362
+
363
+ target.to_s
364
+ end
365
+
366
+ # Finish the OAuth 2 authorization process. If you used a redirect_uri, pass that in.
367
+ # Will return an access token string that you can use with DropboxClient.
368
+ def _finish(code, original_redirect_uri)
369
+ if not code.is_a?(String)
370
+ raise ArgumentError, "code must be a String"
371
+ end
372
+
373
+ uri = URI.parse("https://#{Dropbox::API_SERVER}/1/oauth2/token")
374
+ request = Net::HTTP::Post.new(uri.request_uri)
375
+ client_credentials = @consumer_key + ':' + @consumer_secret
376
+ request.add_field('Authorization', 'Basic ' + Base64.encode64(client_credentials).chomp("\n"))
377
+
378
+ params = {
379
+ "grant_type" => "authorization_code",
380
+ "code" => code,
381
+ }
382
+
383
+ if not @locale.nil?
384
+ params['locale'] = @locale
385
+ end
386
+ if not original_redirect_uri.nil?
387
+ params['redirect_uri'] = original_redirect_uri
388
+ end
389
+
390
+ form_data = {}
391
+ params.each {|k,v| form_data[k.to_s] = v if !v.nil?}
392
+ request.set_form_data(form_data)
393
+
394
+ response = Dropbox::do_http(uri, request)
395
+
396
+ j = Dropbox::parse_response(response)
397
+ ["token_type", "access_token", "uid"].each { |k|
398
+ if not j.has_key?(k)
399
+ raise DropboxError.new("Bad response from /token: missing \"#{k}\".")
400
+ end
401
+ if not j[k].is_a?(String)
402
+ raise DropboxError.new("Bad response from /token: field \"#{k}\" is not a string.")
403
+ end
404
+ }
405
+ if j["token_type"] != "bearer" and j["token_type"] != "Bearer":
406
+ raise DropboxError.new("Bad response from /token: \"token_type\" is \"#{token_type}\".")
407
+ end
408
+
409
+ return j['access_token'], j['uid']
410
+ end
411
+ end
412
+
413
+ # OAuth 2 authorization helper for apps that can't provide a redirect URI
414
+ # (such as the command line example apps).
415
+ class DropboxOAuth2FlowNoRedirect < DropboxOAuth2FlowBase
416
+
417
+ # * consumer_key: Your Dropbox API app's "app key"
418
+ # * consumer_secret: Your Dropbox API app's "app secret"
419
+ # * locale: The locale of the user currently using your app.
420
+ def initialize(consumer_key, consumer_secret, locale=nil)
421
+ super(consumer_key, consumer_secret, locale)
422
+ end
423
+
424
+ # Returns a authorization_url, which is a page on Dropbox's website. Have the user
425
+ # visit this URL and approve your app.
426
+ def start()
427
+ _get_authorize_url(nil, nil)
428
+ end
429
+
430
+ # If the user approves your app, they will be presented with an "authorization code".
431
+ # Have the user copy/paste that authorization code into your app and then call this
432
+ # method to get an access token.
433
+ #
434
+ # Returns a two-entry list (access_token, user_id)
435
+ # * access_token is an access token string that can be passed to DropboxClient.
436
+ # * user_id is the Dropbox user ID of the user that just approved your app.
437
+ def finish(code)
438
+ _finish(code, nil)
439
+ end
440
+ end
441
+
442
+ # The standard OAuth 2 authorization helper. Use this if you're writing a web app.
443
+ class DropboxOAuth2Flow < DropboxOAuth2FlowBase
444
+
445
+ # * consumer_key: Your Dropbox API app's "app key"
446
+ # * consumer_secret: Your Dropbox API app's "app secret"
447
+ # * redirect_uri: The URI that the Dropbox server will redirect the user to after the user
448
+ # finishes authorizing your app. This URI must be HTTPs-based and pre-registered with
449
+ # the Dropbox servers, though localhost URIs are allowed without pre-registration and can
450
+ # be either HTTP or HTTPS.
451
+ # * session: A hash that represents the current web app session (will be used to save the CSRF
452
+ # token)
453
+ # * csrf_token_key: The key to use when storing the CSRF token in the session (for example,
454
+ # :dropbox_auth_csrf_token)
455
+ # * locale: The locale of the user currently using your app (ex: "en" or "en_US").
456
+ def initialize(consumer_key, consumer_secret, redirect_uri, session, csrf_token_session_key, locale=nil)
457
+ super(consumer_key, consumer_secret, locale)
458
+ if not redirect_uri.is_a?(String)
459
+ raise ArgumentError, "redirect_uri must be a String, got #{consumer_secret.inspect}"
460
+ end
461
+ @redirect_uri = redirect_uri
462
+ @session = session
463
+ @csrf_token_session_key = csrf_token_session_key
464
+ end
465
+
466
+ # Starts the OAuth 2 authorizaton process, which involves redirecting the user to
467
+ # the returned "authorization URL" (a URL on the Dropbox website). When the user then
468
+ # either approves or denies your app access, Dropbox will redirect them to the
469
+ # redirect_uri you provided to the constructor, at which point you should call finish()
470
+ # to complete the process.
471
+ #
472
+ # This function will also save a CSRF token to the session and csrf_token_session_key
473
+ # you provided to the constructor. This CSRF token will be checked on finish() to prevent
474
+ # request forgery.
475
+ #
476
+ # * url_state: Any data you would like to keep in the URL through the authorization
477
+ # process. This exact value will be returned to you by finish().
478
+ #
479
+ # Returns the URL to redirect the user to.
480
+ def start(url_state=nil)
481
+ unless url_state.nil? or url_state.is_a?(String)
482
+ raise ArgumentError, "url_state must be a String"
483
+ end
484
+
485
+ csrf_token = SecureRandom.base64(16)
486
+ state = csrf_token
487
+ unless url_state.nil?
488
+ state += "|" + url_state
489
+ end
490
+ @session[@csrf_token_session_key] = csrf_token
491
+
492
+ return _get_authorize_url(@redirect_uri, state)
493
+ end
494
+
495
+ # Call this after the user has visited the authorize URL (see: start()), approved your app,
496
+ # and was redirected to your redirect URI.
497
+ #
498
+ # * query_params: The query params on the GET request to your redirect URI.
499
+ #
500
+ # Returns a tuple of (access_token, user_id, url_state). access_token can be used to
501
+ # construct a DropboxClient. user_id is the Dropbox user ID of the user that jsut approved
502
+ # your app. url_state is the value you originally passed in to start().
503
+ #
504
+ # Can throw BadRequestError, BadStateError, CsrfError, NotApprovedError,
505
+ # ProviderError, and the standard DropboxError.
506
+ def finish(query_params)
507
+ csrf_token_from_session = @session[@csrf_token_session_key]
508
+
509
+ # Check well-formedness of request.
510
+
511
+ state = query_params['state']
512
+ if state.nil?
513
+ raise BadRequestError.new("Missing query parameter 'state'.")
514
+ end
515
+
516
+ error = query_params['error']
517
+ error_description = query_params['error_description']
518
+ code = query_params['code']
519
+
520
+ if not error.nil? and not code.nil?
521
+ raise BadRequestError.new("Query parameters 'code' and 'error' are both set;" +
522
+ " only one must be set.")
523
+ end
524
+ if error.nil? and code.nil?
525
+ raise BadRequestError.new("Neither query parameter 'code' or 'error' is set.")
526
+ end
527
+
528
+ # Check CSRF token
529
+
530
+ if csrf_token_from_session.nil?
531
+ raise BadStateError.new("Missing CSRF token in session.");
532
+ end
533
+ unless csrf_token_from_session.length > 20
534
+ raise RuntimeError.new("CSRF token unexpectedly short: #{csrf_token_from_session.inspect}")
535
+ end
536
+
537
+ split_pos = state.index('|')
538
+ if split_pos.nil?
539
+ given_csrf_token = state
540
+ url_state = nil
541
+ else
542
+ given_csrf_token, url_state = state.split('|', 2)
543
+ end
544
+ if not Dropbox::safe_string_equals(csrf_token_from_session, given_csrf_token)
545
+ raise CsrfError.new("Expected #{csrf_token_from_session.inspect}, " +
546
+ "got #{given_csrf_token.inspect}.")
547
+ end
548
+ @session.delete(@csrf_token_session_key)
549
+
550
+ # Check for error identifier
551
+
552
+ if not error.nil?
553
+ if error == 'access_denied'
554
+ # The user clicked "Deny"
555
+ if error_description.nil?
556
+ raise NotApprovedError.new("No additional description from Dropbox.")
557
+ else
558
+ raise NotApprovedError.new("Additional description from Dropbox: #{error_description}")
559
+ end
560
+ else
561
+ # All other errors.
562
+ full_message = error
563
+ if not error_description.nil?
564
+ full_message += ": " + error_description
565
+ end
566
+ raise ProviderError.new(full_message)
567
+ end
568
+ end
569
+
570
+ # If everything went ok, make the network call to get an access token.
571
+
572
+ access_token, user_id = _finish(code, @redirect_uri)
573
+ return access_token, user_id, url_state
574
+ end
575
+
576
+ # Thrown if the redirect URL was missing parameters or if the given parameters were not valid.
577
+ #
578
+ # The recommended action is to show an HTTP 400 error page.
579
+ class BadRequestError < Exception; end
580
+
581
+ # Thrown if all the parameters are correct, but there's no CSRF token in the session. This
582
+ # probably means that the session expired.
583
+ #
584
+ # The recommended action is to redirect the user's browser to try the approval process again.
585
+ class BadStateError < Exception; end
586
+
587
+ # Thrown if the given 'state' parameter doesn't contain the CSRF token from the user's session.
588
+ # This is blocked to prevent CSRF attacks.
589
+ #
590
+ # The recommended action is to respond with an HTTP 403 error page.
591
+ class CsrfError < Exception; end
592
+
593
+ # The user chose not to approve your app.
594
+ class NotApprovedError < Exception; end
595
+
596
+ # Dropbox redirected to your redirect URI with some unexpected error identifier and error
597
+ # message.
598
+ class ProviderError < Exception; end
599
+ end
600
+
601
+
247
602
  # A class that represents either an OAuth request token or an OAuth access token.
248
603
  class OAuthToken # :nodoc:
249
604
  def initialize(key, secret)
@@ -289,19 +644,27 @@ end
289
644
  class DropboxNotModified < DropboxError
290
645
  end
291
646
 
292
- # This is the Dropbox Client API you'll be working with most often. You need to give it
293
- # a DropboxSession which has already been authorized, or which it can authorize.
647
+ # Use this class to make Dropbox API calls. You'll need to obtain an OAuth 2 access token
648
+ # first; you can get one using either DropboxOAuth2Flow or DropboxOAuth2FlowNoRedirect.
294
649
  class DropboxClient
295
650
 
296
- # Initialize a new DropboxClient. You need to give it a session which has been authorized. See
297
- # documentation on DropboxSession for how to authorize it.
298
- def initialize(session, root="app_folder", locale=nil)
299
- session.get_access_token
651
+ # Args:
652
+ # * +oauth2_access_token+: Obtained via DropboxOAuth2Flow or DropboxOAuth2FlowNoRedirect.
653
+ # * +locale+: The user's current locale (used to localize error messages).
654
+ def initialize(oauth2_access_token, root="auto", locale=nil)
655
+ if oauth2_access_token.is_a?(String)
656
+ @session = DropboxOAuth2Session.new(oauth2_access_token)
657
+ elsif oauth2_access_token.is_a?(DropboxSession)
658
+ @session = oauth2_access_token
659
+ @session.get_access_token
660
+ else
661
+ raise ArgumentError.new("oauth2_access_token doesn't have a valid type")
662
+ end
300
663
 
301
664
  @root = root.to_s # If they passed in a symbol, make it a string
302
665
 
303
- if not ["dropbox","app_folder"].include?(@root)
304
- raise DropboxError.new("root must be :dropbox or :app_folder")
666
+ if not ["dropbox","app_folder","auto"].include?(@root)
667
+ raise ArgumentError.new("root must be :dropbox, :app_folder, or :auto")
305
668
  end
306
669
  if @root == "app_folder"
307
670
  #App Folder is the name of the access type, but for historical reasons
@@ -310,63 +673,32 @@ class DropboxClient
310
673
  end
311
674
 
312
675
  @locale = locale
313
- @session = session
314
- end
315
-
316
- # Parse response. You probably shouldn't be calling this directly. This takes responses from the server
317
- # and parses them. It also checks for errors and raises exceptions with the appropriate messages.
318
- def parse_response(response, raw=false) # :nodoc:
319
- if response.kind_of?(Net::HTTPServerError)
320
- raise DropboxError.new("Dropbox Server Error: #{response} - #{response.body}", response)
321
- elsif response.kind_of?(Net::HTTPUnauthorized)
322
- raise DropboxAuthError.new(response, "User is not authenticated.")
323
- elsif not response.kind_of?(Net::HTTPSuccess)
324
- begin
325
- d = JSON.parse(response.body)
326
- rescue
327
- raise DropboxError.new("Dropbox Server Error: body=#{response.body}", response)
328
- end
329
- if d['user_error'] and d['error']
330
- raise DropboxError.new(d['error'], response, d['user_error']) #user_error is translated
331
- elsif d['error']
332
- raise DropboxError.new(d['error'], response)
333
- else
334
- raise DropboxError.new(response.body, response)
335
- end
336
- end
337
-
338
- return response.body if raw
339
-
340
- begin
341
- return JSON.parse(response.body)
342
- rescue JSON::ParserError
343
- raise DropboxError.new("Unable to parse JSON response: #{response.body}", response)
344
- end
345
676
  end
346
677
 
347
- # Returns account info in a Hash object
678
+ # Returns some information about the current user's Dropbox account (the "current user"
679
+ # is the user associated with the access token you're using).
348
680
  #
349
681
  # For a detailed description of what this call returns, visit:
350
682
  # https://www.dropbox.com/developers/reference/api#account-info
351
683
  def account_info()
352
684
  response = @session.do_get build_url("/account/info")
353
- parse_response(response)
685
+ Dropbox::parse_response(response)
354
686
  end
355
687
 
356
688
  # Uploads a file to a server. This uses the HTTP PUT upload method for simplicity
357
689
  #
358
- # Arguments:
359
- # * to_path: The directory path to upload the file to. If the destination
690
+ # Args:
691
+ # * +to_path+: The directory path to upload the file to. If the destination
360
692
  # directory does not yet exist, it will be created.
361
- # * file_obj: A file-like object to upload. If you would like, you can
693
+ # * +file_obj+: A file-like object to upload. If you would like, you can
362
694
  # pass a string as file_obj.
363
- # * overwrite: Whether to overwrite an existing file at the given path. [default is False]
695
+ # * +overwrite+: Whether to overwrite an existing file at the given path. [default is False]
364
696
  # If overwrite is False and a file already exists there, Dropbox
365
697
  # will rename the upload to make sure it doesn't overwrite anything.
366
698
  # You must check the returned metadata to know what this new name is.
367
699
  # This field should only be True if your intent is to potentially
368
700
  # clobber changes to a file that you don't know about.
369
- # * parent_rev: The rev field from the 'parent' of this upload. [optional]
701
+ # * +parent_rev+: The rev field from the 'parent' of this upload. [optional]
370
702
  # If your intent is to update the file at the given path, you should
371
703
  # pass the parent_rev parameter set to the rev value from the most recent
372
704
  # metadata you have of the existing file at that path. If the server
@@ -376,10 +708,11 @@ class DropboxClient
376
708
  # The file will always be overwritten if you send the most-recent parent_rev,
377
709
  # and it will never be overwritten you send a less-recent one.
378
710
  # Returns:
379
- # * a Hash containing the metadata of the newly uploaded file. The file may have a different name if it conflicted.
711
+ # * a Hash containing the metadata of the newly uploaded file. The file may have a different
712
+ # name if it conflicted.
380
713
  #
381
714
  # Simple Example
382
- # client = DropboxClient(session, :app_folder)
715
+ # client = DropboxClient(oauth2_access_token)
383
716
  # #session is a DropboxSession I've already authorized
384
717
  # client.put_file('/test_file_on_dropbox', open('/tmp/test_file'))
385
718
  # This will upload the "/tmp/test_file" from my computer into the root of my App's app folder
@@ -398,14 +731,14 @@ class DropboxClient
398
731
  {"Content-Type" => "application/octet-stream"},
399
732
  file_obj)
400
733
 
401
- parse_response(response)
734
+ Dropbox::parse_response(response)
402
735
  end
403
736
 
404
737
  # Returns a ChunkedUploader object.
405
738
  #
406
739
  # Args:
407
- # * file_obj: The file-like object to be uploaded. Must support .read()
408
- # * total_size: The total size of file_obj
740
+ # * +file_obj+: The file-like object to be uploaded. Must support .read()
741
+ # * +total_size+: The total size of file_obj
409
742
  def get_chunked_uploader(file_obj, total_size)
410
743
  ChunkedUploader.new(self, file_obj, total_size)
411
744
  end
@@ -428,7 +761,7 @@ class DropboxClient
428
761
  # be called again to resume the upload.
429
762
  #
430
763
  # Args:
431
- # * chunk_size: The chunk size for each individual upload. Defaults to 4MB.
764
+ # * +chunk_size+: The chunk size for each individual upload. Defaults to 4MB.
432
765
  def upload(chunk_size=4*1024*1024)
433
766
  last_chunk = nil
434
767
 
@@ -439,7 +772,7 @@ class DropboxClient
439
772
 
440
773
  resp = {}
441
774
  begin
442
- resp = @client.parse_response(@client.partial_chunked_upload(last_chunk, @upload_id, @offset))
775
+ resp = Dropbox::parse_response(@client.partial_chunked_upload(last_chunk, @upload_id, @offset))
443
776
  last_chunk = nil
444
777
  rescue DropboxError => e
445
778
  resp = JSON.parse(e.http_response.body)
@@ -457,9 +790,9 @@ class DropboxClient
457
790
  # Completes a file upload
458
791
  #
459
792
  # Args:
460
- # * to_path: The directory path to upload the file to. If the destination
793
+ # * +to_path+: The directory path to upload the file to. If the destination
461
794
  # directory does not yet exist, it will be created.
462
- # * overwrite: Whether to overwrite an existing file at the given path. [default is False]
795
+ # * +overwrite+: Whether to overwrite an existing file at the given path. [default is False]
463
796
  # If overwrite is False and a file already exists there, Dropbox
464
797
  # will rename the upload to make sure it doesn't overwrite anything.
465
798
  # You must check the returned metadata to know what this new name is.
@@ -481,7 +814,7 @@ class DropboxClient
481
814
  # https://www.dropbox.com/developers/reference/api#metadata
482
815
  def finish(to_path, overwrite=false, parent_rev=nil)
483
816
  response = @client.commit_chunked_upload(to_path, @upload_id, overwrite, parent_rev)
484
- @client.parse_response(response)
817
+ Dropbox::parse_response(response)
485
818
  end
486
819
  end
487
820
 
@@ -505,28 +838,28 @@ class DropboxClient
505
838
  # Download a file
506
839
  #
507
840
  # Args:
508
- # * from_path: The path to the file to be downloaded
509
- # * rev: A previous revision value of the file to be downloaded
841
+ # * +from_path+: The path to the file to be downloaded
842
+ # * +rev+: A previous revision value of the file to be downloaded
510
843
  #
511
844
  # Returns:
512
845
  # * The file contents.
513
846
  def get_file(from_path, rev=nil)
514
847
  response = get_file_impl(from_path, rev)
515
- parse_response(response, raw=true)
848
+ Dropbox::parse_response(response, raw=true)
516
849
  end
517
850
 
518
851
  # Download a file and get its metadata.
519
852
  #
520
853
  # Args:
521
- # * from_path: The path to the file to be downloaded
522
- # * rev: A previous revision value of the file to be downloaded
854
+ # * +from_path+: The path to the file to be downloaded
855
+ # * +rev+: A previous revision value of the file to be downloaded
523
856
  #
524
857
  # Returns:
525
858
  # * The file contents.
526
859
  # * The file metadata as a hash.
527
860
  def get_file_and_metadata(from_path, rev=nil)
528
861
  response = get_file_impl(from_path, rev)
529
- parsed_response = parse_response(response, raw=true)
862
+ parsed_response = Dropbox::parse_response(response, raw=true)
530
863
  metadata = parse_metadata(response)
531
864
  return parsed_response, metadata
532
865
  end
@@ -534,8 +867,8 @@ class DropboxClient
534
867
  # Download a file (helper method - don't call this directly).
535
868
  #
536
869
  # Args:
537
- # * from_path: The path to the file to be downloaded
538
- # * rev: A previous revision value of the file to be downloaded
870
+ # * +from_path+: The path to the file to be downloaded
871
+ # * +rev+: A previous revision value of the file to be downloaded
539
872
  #
540
873
  # Returns:
541
874
  # * The HTTPResponse for the file download request.
@@ -551,7 +884,7 @@ class DropboxClient
551
884
  # Parses out file metadata from a raw dropbox HTTP response.
552
885
  #
553
886
  # Args:
554
- # * dropbox_raw_response: The raw, unparsed HTTPResponse from Dropbox.
887
+ # * +dropbox_raw_response+: The raw, unparsed HTTPResponse from Dropbox.
555
888
  #
556
889
  # Returns:
557
890
  # * The metadata of the file as a hash.
@@ -569,9 +902,9 @@ class DropboxClient
569
902
 
570
903
  # Copy a file or folder to a new location.
571
904
  #
572
- # Arguments:
573
- # * from_path: The path to the file or folder to be copied.
574
- # * to_path: The destination path of the file or folder to be copied.
905
+ # Args:
906
+ # * +from_path+: The path to the file or folder to be copied.
907
+ # * +to_path+: The destination path of the file or folder to be copied.
575
908
  # This parameter should include the destination filename (e.g.
576
909
  # from_path: '/test.txt', to_path: '/dir/test.txt'). If there's
577
910
  # already a file at the to_path, this copy will be renamed to
@@ -588,13 +921,13 @@ class DropboxClient
588
921
  "to_path" => format_path(to_path, false),
589
922
  }
590
923
  response = @session.do_post build_url("/fileops/copy", params)
591
- parse_response(response)
924
+ Dropbox::parse_response(response)
592
925
  end
593
926
 
594
927
  # Create a folder.
595
928
  #
596
929
  # Arguments:
597
- # * path: The path of the new folder.
930
+ # * +path+: The path of the new folder.
598
931
  #
599
932
  # Returns:
600
933
  # * A hash with the metadata of the newly created folder.
@@ -607,13 +940,13 @@ class DropboxClient
607
940
  }
608
941
  response = @session.do_post build_url("/fileops/create_folder", params)
609
942
 
610
- parse_response(response)
943
+ Dropbox::parse_response(response)
611
944
  end
612
945
 
613
946
  # Deletes a file
614
947
  #
615
948
  # Arguments:
616
- # * path: The path of the file to delete
949
+ # * +path+: The path of the file to delete
617
950
  #
618
951
  # Returns:
619
952
  # * A Hash with the metadata of file just deleted.
@@ -625,14 +958,14 @@ class DropboxClient
625
958
  "path" => format_path(path, false),
626
959
  }
627
960
  response = @session.do_post build_url("/fileops/delete", params)
628
- parse_response(response)
961
+ Dropbox::parse_response(response)
629
962
  end
630
963
 
631
964
  # Moves a file
632
965
  #
633
966
  # Arguments:
634
- # * from_path: The path of the file to be moved
635
- # * to_path: The destination path of the file or folder to be moved
967
+ # * +from_path+: The path of the file to be moved
968
+ # * +to_path+: The destination path of the file or folder to be moved
636
969
  # If the file or folder already exists, it will be renamed to be unique.
637
970
  #
638
971
  # Returns:
@@ -646,7 +979,7 @@ class DropboxClient
646
979
  "to_path" => format_path(to_path, false),
647
980
  }
648
981
  response = @session.do_post build_url("/fileops/move", params)
649
- parse_response(response)
982
+ Dropbox::parse_response(response)
650
983
  end
651
984
 
652
985
  # Retrives metadata for a file or folder
@@ -686,7 +1019,7 @@ class DropboxClient
686
1019
  if response.kind_of? Net::HTTPRedirection
687
1020
  raise DropboxNotModified.new("metadata not modified")
688
1021
  end
689
- parse_response(response)
1022
+ Dropbox::parse_response(response)
690
1023
  end
691
1024
 
692
1025
  # Search directory for filenames matching query
@@ -711,7 +1044,7 @@ class DropboxClient
711
1044
  }
712
1045
 
713
1046
  response = @session.do_get build_url("/search/#{@root}#{format_path(path)}", params)
714
- parse_response(response)
1047
+ Dropbox::parse_response(response)
715
1048
  end
716
1049
 
717
1050
  # Retrive revisions of a file
@@ -734,7 +1067,7 @@ class DropboxClient
734
1067
  }
735
1068
 
736
1069
  response = @session.do_get build_url("/revisions/#{@root}#{format_path(path)}", params)
737
- parse_response(response)
1070
+ Dropbox::parse_response(response)
738
1071
 
739
1072
  end
740
1073
 
@@ -754,7 +1087,7 @@ class DropboxClient
754
1087
  }
755
1088
 
756
1089
  response = @session.do_post build_url("/restore/#{@root}#{format_path(path)}", params)
757
- parse_response(response)
1090
+ Dropbox::parse_response(response)
758
1091
  end
759
1092
 
760
1093
  # Returns a direct link to a media file
@@ -771,7 +1104,7 @@ class DropboxClient
771
1104
  # {'url': 'https://dl.dropbox.com/0/view/wvxv1fw6on24qw7/file.mov', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
772
1105
  def media(path)
773
1106
  response = @session.do_get build_url("/media/#{@root}#{format_path(path)}")
774
- parse_response(response)
1107
+ Dropbox::parse_response(response)
775
1108
  end
776
1109
 
777
1110
  # Get a URL to share a media file
@@ -790,7 +1123,7 @@ class DropboxClient
790
1123
  # https://www.dropbox.com/developers/reference/api#shares
791
1124
  def shares(path)
792
1125
  response = @session.do_get build_url("/shares/#{@root}#{format_path(path)}")
793
- parse_response(response)
1126
+ Dropbox::parse_response(response)
794
1127
  end
795
1128
 
796
1129
  # Download a thumbnail for an image.
@@ -806,7 +1139,7 @@ class DropboxClient
806
1139
  # * The thumbnail data
807
1140
  def thumbnail(from_path, size='large')
808
1141
  response = thumbnail_impl(from_path, size)
809
- parse_response(response, raw=true)
1142
+ Dropbox::parse_response(response, raw=true)
810
1143
  end
811
1144
 
812
1145
  # Download a thumbnail for an image along with the image's metadata.
@@ -820,7 +1153,7 @@ class DropboxClient
820
1153
  # * The metadata for the image as a hash
821
1154
  def thumbnail_and_metadata(from_path, size='large')
822
1155
  response = thumbnail_impl(from_path, size)
823
- parsed_response = parse_response(response, raw=true)
1156
+ parsed_response = Dropbox::parse_response(response, raw=true)
824
1157
  metadata = parse_metadata(response)
825
1158
  return parsed_response, metadata
826
1159
  end
@@ -876,14 +1209,14 @@ class DropboxClient
876
1209
  end
877
1210
 
878
1211
  response = @session.do_post build_url("/delta", params)
879
- parse_response(response)
1212
+ Dropbox::parse_response(response)
880
1213
  end
881
1214
 
882
1215
  # Download a thumbnail (helper method - don't call this directly).
883
1216
  #
884
1217
  # Args:
885
- # * from_path: The path to the file to be thumbnailed.
886
- # * size: A string describing the desired thumbnail size. See thumbnail()
1218
+ # * +from_path+: The path to the file to be thumbnailed.
1219
+ # * +size+: A string describing the desired thumbnail size. See thumbnail()
887
1220
  # for details.
888
1221
  #
889
1222
  # Returns:
@@ -906,7 +1239,7 @@ class DropboxClient
906
1239
  # used to instantly copy that file to the Dropbox of another account.
907
1240
  #
908
1241
  # Args:
909
- # * path: The path to the file for a copy ref to be created on.
1242
+ # * +path+: The path to the file for a copy ref to be created on.
910
1243
  #
911
1244
  # Returns:
912
1245
  # * A Hash object that looks like the following example:
@@ -916,15 +1249,15 @@ class DropboxClient
916
1249
 
917
1250
  response = @session.do_get(build_url(path, {}))
918
1251
 
919
- parse_response(response)
1252
+ Dropbox::parse_response(response)
920
1253
  end
921
1254
 
922
1255
  # Adds the file referenced by the copy ref to the specified path
923
1256
  #
924
1257
  # Args:
925
- # * copy_ref: A copy ref string that was returned from a create_copy_ref call.
1258
+ # * +copy_ref+: A copy ref string that was returned from a create_copy_ref call.
926
1259
  # The copy_ref can be created from any other Dropbox account, or from the same account.
927
- # * to_path: The path to where the file will be created.
1260
+ # * +to_path+: The path to where the file will be created.
928
1261
  #
929
1262
  # Returns:
930
1263
  # * A hash with the metadata of the new file.
@@ -937,7 +1270,7 @@ class DropboxClient
937
1270
 
938
1271
  response = @session.do_post(build_url(path, params))
939
1272
 
940
- parse_response(response)
1273
+ Dropbox::parse_response(response)
941
1274
  end
942
1275
 
943
1276
  def build_url(url, params=nil, content_server=false) # :nodoc:
@@ -963,7 +1296,7 @@ class DropboxClient
963
1296
  end
964
1297
 
965
1298
  #From the oauth spec plus "/". Slash should not be ecsaped
966
- RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~\/]/
1299
+ RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~\/]/ # :nodoc:
967
1300
 
968
1301
  def format_path(path, escape=true) # :nodoc:
969
1302
  path = path.gsub(/\/+/,"/")