dropbox-sdk 1.5.1 → 1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +4 -0
- data/README +52 -7
- data/cli_example.rb +14 -28
- data/dropbox_controller.rb +91 -41
- data/lib/dropbox_sdk.rb +468 -135
- data/web_file_browser.rb +65 -61
- metadata +50 -36
data/CHANGELOG
CHANGED
data/README
CHANGED
@@ -1,7 +1,52 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
46
|
-
|
39
|
+
print "Enter the authorization code here: "
|
40
|
+
STDOUT.flush
|
41
|
+
auth_code = STDIN.gets.strip
|
47
42
|
|
48
|
-
|
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)
|
data/dropbox_controller.rb
CHANGED
@@ -1,57 +1,107 @@
|
|
1
|
-
|
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
|
-
|
4
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
25
|
+
def main
|
26
|
+
client = get_dropbox_client
|
27
|
+
unless client
|
28
|
+
redirect_to(:action => 'auth_start') and return
|
29
|
+
end
|
18
30
|
|
19
|
-
|
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
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
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
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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.
|
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,
|
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
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
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,
|
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
|
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
|
-
#
|
293
|
-
#
|
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
|
-
#
|
297
|
-
#
|
298
|
-
|
299
|
-
|
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
|
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
|
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
|
-
#
|
359
|
-
# * to_path
|
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
|
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
|
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
|
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
|
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(
|
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
|
408
|
-
# * total_size
|
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
|
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 =
|
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
|
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
|
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
|
-
|
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
|
509
|
-
# * rev
|
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
|
522
|
-
# * rev
|
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
|
538
|
-
# * rev
|
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
|
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
|
-
#
|
573
|
-
# * from_path
|
574
|
-
# * to_path
|
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
|
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
|
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
|
635
|
-
# * to_path
|
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
|
886
|
-
# * size
|
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
|
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
|
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
|
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(/\/+/,"/")
|