dropbox-sdk-forked_v2 1.0.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG +77 -0
- data/LICENSE +20 -0
- data/README.md +71 -0
- data/Rakefile +21 -0
- data/examples/chunked_upload.rb +71 -0
- data/examples/cli_example.rb +213 -0
- data/examples/copy_between_accounts.rb +148 -0
- data/examples/dropbox_controller.rb +113 -0
- data/examples/oauth1_upgrade.rb +40 -0
- data/examples/search_cache.rb +322 -0
- data/examples/web_file_browser.rb +193 -0
- data/lib/dropbox_sdk.rb +1484 -0
- data/lib/trusted-certs.crt +1396 -0
- metadata +115 -0
@@ -0,0 +1,113 @@
|
|
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 set up:
|
7
|
+
# 1. Create a Dropbox App key and secret to use the API. https://www.dropbox.com/developers
|
8
|
+
# 2. Add http://localhost:3000/dropbox/auth_finish as a Redirect URI for your Dropbox app.
|
9
|
+
# 3. Copy your App key and App secret into APP_KEY and APP_SECRET below.
|
10
|
+
#
|
11
|
+
# To run:
|
12
|
+
# 1. You need a Rails 3 project (to create one, run: rails new <folder-name>)
|
13
|
+
# 2. Copy dropbox_sdk.rb into <folder-name>/vendor
|
14
|
+
# 3. Copy this file into <folder-name>/app/controllers/
|
15
|
+
# 4. Add the following lines to <folder-name>/config/routes.rb
|
16
|
+
# get "dropbox/main"
|
17
|
+
# post "dropbox/upload"
|
18
|
+
# get "dropbox/auth_start"
|
19
|
+
# get "dropbox/auth_finish"
|
20
|
+
# 5. Run: rails server
|
21
|
+
# 6. Point your browser at: https://localhost:3000/dropbox/main
|
22
|
+
|
23
|
+
require 'dropbox_sdk'
|
24
|
+
require 'securerandom'
|
25
|
+
|
26
|
+
APP_KEY = ""
|
27
|
+
APP_SECRET = ""
|
28
|
+
|
29
|
+
class DropboxController < ApplicationController
|
30
|
+
|
31
|
+
def main
|
32
|
+
client = get_dropbox_client
|
33
|
+
unless client
|
34
|
+
redirect_to(:action => 'auth_start') and return
|
35
|
+
end
|
36
|
+
|
37
|
+
account_info = client.account_info
|
38
|
+
|
39
|
+
# Show a file upload page
|
40
|
+
render :inline =>
|
41
|
+
"#{account_info['email']} <br/><%= form_tag({:action => :upload}, :multipart => true) do %><%= file_field_tag 'file' %><%= submit_tag 'Upload' %><% end %>"
|
42
|
+
end
|
43
|
+
|
44
|
+
def upload
|
45
|
+
client = get_dropbox_client
|
46
|
+
unless client
|
47
|
+
redirect_to(:action => 'auth_start') and return
|
48
|
+
end
|
49
|
+
|
50
|
+
begin
|
51
|
+
# Upload the POST'd file to Dropbox, keeping the same name
|
52
|
+
resp = client.put_file(params[:file].original_filename, params[:file].read)
|
53
|
+
render :text => "Upload successful. File now at #{resp['path']}"
|
54
|
+
rescue DropboxAuthError => e
|
55
|
+
session.delete(:access_token) # An auth error means the access token is probably bad
|
56
|
+
logger.info "Dropbox auth error: #{e}"
|
57
|
+
render :text => "Dropbox auth error"
|
58
|
+
rescue DropboxError => e
|
59
|
+
logger.info "Dropbox API error: #{e}"
|
60
|
+
render :text => "Dropbox API error"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_dropbox_client
|
65
|
+
if session[:access_token]
|
66
|
+
begin
|
67
|
+
access_token = session[:access_token]
|
68
|
+
DropboxClient.new(access_token)
|
69
|
+
rescue
|
70
|
+
# Maybe something's wrong with the access token?
|
71
|
+
session.delete(:access_token)
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def get_web_auth()
|
78
|
+
redirect_uri = url_for(:action => 'auth_finish')
|
79
|
+
DropboxOAuth2Flow.new(APP_KEY, APP_SECRET, redirect_uri, session, :dropbox_auth_csrf_token)
|
80
|
+
end
|
81
|
+
|
82
|
+
def auth_start
|
83
|
+
authorize_url = get_web_auth().start()
|
84
|
+
|
85
|
+
# Send the user to the Dropbox website so they can authorize our app. After the user
|
86
|
+
# authorizes our app, Dropbox will redirect them here with a 'code' parameter.
|
87
|
+
redirect_to authorize_url
|
88
|
+
end
|
89
|
+
|
90
|
+
def auth_finish
|
91
|
+
begin
|
92
|
+
access_token, user_id, url_state = get_web_auth.finish(params)
|
93
|
+
session[:access_token] = access_token
|
94
|
+
redirect_to :action => 'main'
|
95
|
+
rescue DropboxOAuth2Flow::BadRequestError => e
|
96
|
+
render :text => "Error in OAuth 2 flow: Bad request: #{e}"
|
97
|
+
rescue DropboxOAuth2Flow::BadStateError => e
|
98
|
+
logger.info("Error in OAuth 2 flow: No CSRF token in session: #{e}")
|
99
|
+
redirect_to(:action => 'auth_start')
|
100
|
+
rescue DropboxOAuth2Flow::CsrfError => e
|
101
|
+
logger.info("Error in OAuth 2 flow: CSRF mismatch: #{e}")
|
102
|
+
render :text => "CSRF error"
|
103
|
+
rescue DropboxOAuth2Flow::NotApprovedError => e
|
104
|
+
render :text => "Not approved? Why not, bro?"
|
105
|
+
rescue DropboxOAuth2Flow::ProviderError => e
|
106
|
+
logger.info "Error in OAuth 2 flow: Error redirect from Dropbox: #{e}"
|
107
|
+
render :text => "Strange error."
|
108
|
+
rescue DropboxError => e
|
109
|
+
logger.info "Error getting OAuth 2 access token: #{e}"
|
110
|
+
render :text => "Error communicating with Dropbox servers."
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.expand_path('../../lib/dropbox_sdk', __FILE__)
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
# You must use your Dropbox App key and secret to use the API.
|
5
|
+
# Find this at https://www.dropbox.com/developers
|
6
|
+
APP_KEY = ''
|
7
|
+
APP_SECRET = ''
|
8
|
+
|
9
|
+
def main
|
10
|
+
if APP_KEY == '' or APP_SECRET == ''
|
11
|
+
warn "ERROR: Set your APP_KEY and APP_SECRET at the top of search_cache.rb"
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
|
15
|
+
prog_name = __FILE__
|
16
|
+
args = ARGV
|
17
|
+
if args.size != 2
|
18
|
+
warn "Usage: #{prog_name} <oauth1-access-token-key> <oauth1-access-token-secret>"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
access_token_key = args[0]
|
23
|
+
access_token_secret = args[1]
|
24
|
+
|
25
|
+
sess = DropboxSession.new(APP_KEY, APP_SECRET)
|
26
|
+
sess.set_access_token(access_token_key, access_token_secret)
|
27
|
+
c = DropboxClient.new(sess)
|
28
|
+
|
29
|
+
print "Creating OAuth 2 access token...\n"
|
30
|
+
oauth2_access_token = c.create_oauth2_access_token
|
31
|
+
|
32
|
+
print "Using OAuth 2 access token to get account info...\n"
|
33
|
+
c2 = DropboxClient.new(oauth2_access_token)
|
34
|
+
pp c2.account_info
|
35
|
+
|
36
|
+
print "Disabling OAuth 1 access token...\n"
|
37
|
+
c.disable_access_token
|
38
|
+
end
|
39
|
+
|
40
|
+
main()
|
@@ -0,0 +1,322 @@
|
|
1
|
+
# An example use of the /delta API call. Maintains a local cache of
|
2
|
+
# the App Folder's contents. Use the 'update' sub-command to update
|
3
|
+
# the local cache. Use the 'find' sub-command to search the local
|
4
|
+
# cache.
|
5
|
+
#
|
6
|
+
# Example usage:
|
7
|
+
#
|
8
|
+
# 1. Link to your Dropbox account
|
9
|
+
# > ruby search_cache.rb link
|
10
|
+
#
|
11
|
+
# 2. Go to Dropbox and make changes to the contents.
|
12
|
+
#
|
13
|
+
# 3. Update the local cache to match what's on Dropbox.
|
14
|
+
# > ruby search_cache.rb update
|
15
|
+
#
|
16
|
+
# 4. Search the local cache.
|
17
|
+
# > ruby search_cache.rb find 'txt'
|
18
|
+
|
19
|
+
# Repeat steps 2-4 any number of times.
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
require File.expand_path('../../lib/dropbox_sdk', __FILE__)
|
24
|
+
require 'json'
|
25
|
+
|
26
|
+
# You must use your Dropbox App key and secret to use the API.
|
27
|
+
# Find this at https://www.dropbox.com/developers
|
28
|
+
APP_KEY = ''
|
29
|
+
APP_SECRET = ''
|
30
|
+
|
31
|
+
STATE_FILE = 'search_cache.json'
|
32
|
+
|
33
|
+
def main()
|
34
|
+
if APP_KEY == '' or APP_SECRET == ''
|
35
|
+
warn "ERROR: Set your APP_KEY and APP_SECRET at the top of search_cache.rb"
|
36
|
+
exit
|
37
|
+
end
|
38
|
+
prog_name = __FILE__
|
39
|
+
args = ARGV
|
40
|
+
if args.size == 0
|
41
|
+
warn("Usage:\n")
|
42
|
+
warn(" #{prog_name} link Link to a user's account.")
|
43
|
+
warn(" #{prog_name} update Update cache to the latest on Dropbox.")
|
44
|
+
warn(" #{prog_name} update <num> Update cache, limit to <num> pages of /delta.")
|
45
|
+
warn(" #{prog_name} find <term> Search cache for <term>.")
|
46
|
+
warn(" #{prog_name} find Display entire cache contents")
|
47
|
+
warn(" #{prog_name} reset Delete the cache.")
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
command = args[0]
|
52
|
+
if command == 'link'
|
53
|
+
command_link(args)
|
54
|
+
elsif command == 'update'
|
55
|
+
command_update(args)
|
56
|
+
elsif command == 'find'
|
57
|
+
command_find(args)
|
58
|
+
elsif command == 'reset'
|
59
|
+
command_reset(args)
|
60
|
+
else
|
61
|
+
warn "ERROR: Unknown command: #{command}"
|
62
|
+
warn "Run with no arguments for help."
|
63
|
+
exit(1)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def command_link(args)
|
69
|
+
if args.size != 1
|
70
|
+
warn "ERROR: \"link\" doesn't take any arguments"
|
71
|
+
exit
|
72
|
+
end
|
73
|
+
|
74
|
+
web_auth = DropboxOAuth2FlowNoRedirect.new(APP_KEY, APP_SECRET)
|
75
|
+
authorize_url = web_auth.start()
|
76
|
+
puts "1. Go to: #{authorize_url}"
|
77
|
+
puts "2. Click \"Allow\" (you might have to log in first)."
|
78
|
+
puts "3. Copy the authorization code."
|
79
|
+
|
80
|
+
print "Enter the authorization code here: "
|
81
|
+
STDOUT.flush
|
82
|
+
auth_code = STDIN.gets.strip
|
83
|
+
|
84
|
+
access_token, user_id = web_auth.finish(auth_code)
|
85
|
+
puts "Link successful."
|
86
|
+
|
87
|
+
save_state({
|
88
|
+
'access_token' => access_token,
|
89
|
+
'tree' => {}
|
90
|
+
})
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def command_update(args)
|
95
|
+
if args.size == 1
|
96
|
+
page_limit = nil
|
97
|
+
elsif args.size == 2
|
98
|
+
page_limit = Integer(args[1])
|
99
|
+
else
|
100
|
+
warn "ERROR: \"update\" takes either zero or one argument."
|
101
|
+
exit
|
102
|
+
end
|
103
|
+
|
104
|
+
# Load state
|
105
|
+
state = load_state()
|
106
|
+
access_token = state['access_token']
|
107
|
+
cursor = state['cursor']
|
108
|
+
tree = state['tree']
|
109
|
+
|
110
|
+
# Connect to Dropbox
|
111
|
+
c = DropboxClient.new(access_token)
|
112
|
+
|
113
|
+
page = 0
|
114
|
+
changed = false
|
115
|
+
while (page_limit == nil) or (page < page_limit)
|
116
|
+
# Get /delta results from Dropbox
|
117
|
+
result = c.delta(cursor)
|
118
|
+
page += 1
|
119
|
+
if result['reset'] == true
|
120
|
+
puts 'reset'
|
121
|
+
changed = true
|
122
|
+
tree = {}
|
123
|
+
end
|
124
|
+
cursor = result['cursor']
|
125
|
+
# Apply the entries one by one to our cached tree.
|
126
|
+
for delta_entry in result['entries']
|
127
|
+
changed = true
|
128
|
+
apply_delta(tree, delta_entry)
|
129
|
+
end
|
130
|
+
cursor = result['cursor']
|
131
|
+
if not result['has_more']
|
132
|
+
break
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# Save state
|
137
|
+
if changed
|
138
|
+
state['cursor'] = cursor
|
139
|
+
state['tree'] = tree
|
140
|
+
save_state(state)
|
141
|
+
else
|
142
|
+
puts "No updates."
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
# We track folder state as a tree of Node objects.
|
148
|
+
class Node
|
149
|
+
attr_accessor :path, :content
|
150
|
+
def initialize(path, content)
|
151
|
+
# The "original" page (i.e. not the lower-case path)
|
152
|
+
@path = path
|
153
|
+
# For files, content is a pair (size, modified)
|
154
|
+
# For folders, content is a hash of children Nodes, keyed by lower-case file names.
|
155
|
+
@content = content
|
156
|
+
end
|
157
|
+
def folder?()
|
158
|
+
@content.is_a? Hash
|
159
|
+
end
|
160
|
+
def to_json()
|
161
|
+
[@path, Node.to_json_content(@content)]
|
162
|
+
end
|
163
|
+
def self.from_json(jnode)
|
164
|
+
path, jcontent = jnode
|
165
|
+
Node.new(path, Node.from_json_content(jcontent))
|
166
|
+
end
|
167
|
+
def self.to_json_content(content)
|
168
|
+
if content.is_a? Hash
|
169
|
+
map_hash_values(content) { |child| child.to_json }
|
170
|
+
else
|
171
|
+
content
|
172
|
+
end
|
173
|
+
end
|
174
|
+
def self.from_json_content(jcontent)
|
175
|
+
if jcontent.is_a? Hash
|
176
|
+
map_hash_values(jcontent) { |jchild| Node.from_json jchild }
|
177
|
+
else
|
178
|
+
jcontent
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Run a mapping function over every value in a Hash, returning a new Hash.
|
184
|
+
def map_hash_values(h)
|
185
|
+
new = {}
|
186
|
+
h.each { |k,v| new[k] = yield v }
|
187
|
+
new
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
def apply_delta(root, e)
|
192
|
+
path, metadata = e
|
193
|
+
branch, leaf = split_path(path)
|
194
|
+
|
195
|
+
if metadata != nil
|
196
|
+
puts "+ #{path}"
|
197
|
+
# Traverse down the tree until we find the parent folder of the entry
|
198
|
+
# we want to add. Create any missing folders along the way.
|
199
|
+
children = root
|
200
|
+
branch.each do |part|
|
201
|
+
node = get_or_create_child(children, part)
|
202
|
+
# If there's no folder here, make an empty one.
|
203
|
+
if not node.folder?
|
204
|
+
node.content = {}
|
205
|
+
end
|
206
|
+
children = node.content
|
207
|
+
end
|
208
|
+
|
209
|
+
# Create the file/folder.
|
210
|
+
node = get_or_create_child(children, leaf)
|
211
|
+
node.path = metadata['path'] # Save the un-lower-cased path.
|
212
|
+
if metadata['is_dir']
|
213
|
+
# Only create a folder if there isn't one there already.
|
214
|
+
node.content = {} if not node.folder?
|
215
|
+
else
|
216
|
+
node.content = metadata['size'], metadata['modified']
|
217
|
+
end
|
218
|
+
else
|
219
|
+
puts "- #{path}"
|
220
|
+
# Traverse down the tree until we find the parent of the entry we
|
221
|
+
# want to delete.
|
222
|
+
children = root
|
223
|
+
missing_parent = false
|
224
|
+
branch.each do |part|
|
225
|
+
node = children[part]
|
226
|
+
# If one of the parent folders is missing, then we're done.
|
227
|
+
if node == nil or not node.folder?
|
228
|
+
missing_parent = true
|
229
|
+
break
|
230
|
+
end
|
231
|
+
children = node.content
|
232
|
+
end
|
233
|
+
# If we made it all the way, delete the file/folder.
|
234
|
+
if not missing_parent
|
235
|
+
children.delete(leaf)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def get_or_create_child(children, name)
|
241
|
+
child = children[name]
|
242
|
+
if child == nil
|
243
|
+
children[name] = child = Node.new(nil, nil)
|
244
|
+
end
|
245
|
+
child
|
246
|
+
end
|
247
|
+
|
248
|
+
def split_path(path)
|
249
|
+
bad, *parts = path.split '/'
|
250
|
+
[parts, parts.pop]
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
def command_find(args)
|
255
|
+
if args.size == 1
|
256
|
+
term = ''
|
257
|
+
elsif args.size == 2
|
258
|
+
term = args[1]
|
259
|
+
else
|
260
|
+
warn("ERROR: \"find\" takes either zero or one arguments.")
|
261
|
+
exit
|
262
|
+
end
|
263
|
+
|
264
|
+
state = load_state()
|
265
|
+
results = []
|
266
|
+
search_tree(results, state['tree'], term)
|
267
|
+
for r in results
|
268
|
+
puts("#{r}")
|
269
|
+
end
|
270
|
+
puts("[Matches: #{results.size}]")
|
271
|
+
end
|
272
|
+
|
273
|
+
|
274
|
+
def command_reset(args)
|
275
|
+
if args.size != 1
|
276
|
+
warn("ERROR: \"reset\" takes no arguments.")
|
277
|
+
exit
|
278
|
+
end
|
279
|
+
|
280
|
+
# Delete cursor, empty tree.
|
281
|
+
state = load_state()
|
282
|
+
if state.has_key?('cursor')
|
283
|
+
state.delete('cursor')
|
284
|
+
end
|
285
|
+
state['tree'] = {}
|
286
|
+
save_state(state)
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
# Recursively search 'tree' for files that contain the string in 'term'.
|
291
|
+
# Print out any matches.
|
292
|
+
def search_tree(results, tree, term)
|
293
|
+
tree.each do |name_lc, node|
|
294
|
+
path = node.path
|
295
|
+
if (path != nil) and path.include?(term)
|
296
|
+
if node.folder?
|
297
|
+
results.push("#{path}")
|
298
|
+
else
|
299
|
+
size, modified = node.content
|
300
|
+
results.push("#{path} (#{size}, #{modified})")
|
301
|
+
end
|
302
|
+
end
|
303
|
+
if node.folder?
|
304
|
+
search_tree(results, node.content, term)
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def save_state(state)
|
310
|
+
state['tree'] = Node.to_json_content(state['tree'])
|
311
|
+
File.open(STATE_FILE,"w") do |f|
|
312
|
+
f.write(JSON.pretty_generate(state, :max_nesting => false))
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def load_state()
|
317
|
+
state = JSON.parse(File.read(STATE_FILE), :max_nesting => false)
|
318
|
+
state['tree'] = Node.from_json_content(state['tree'])
|
319
|
+
state
|
320
|
+
end
|
321
|
+
|
322
|
+
main()
|
@@ -0,0 +1,193 @@
|
|
1
|
+
# -------------------------------------------------------------------
|
2
|
+
# An example webapp that lets you browse and upload files to Dropbox.
|
3
|
+
# Demonstrates:
|
4
|
+
# - The webapp OAuth process.
|
5
|
+
# - The metadata() and put_file() calls.
|
6
|
+
#
|
7
|
+
# To set up:
|
8
|
+
# 1. Create a Dropbox App key and secret to use the API. https://www.dropbox.com/developers
|
9
|
+
# 2. Add http://localhost:5000/dropbox-auth-finish as a Redirect URI for your Dropbox app.
|
10
|
+
# 3. Copy your App key and App secret into APP_KEY and APP_SECRET below.
|
11
|
+
#
|
12
|
+
# To run:
|
13
|
+
# 1. Install Sinatra $ gem install sinatra
|
14
|
+
# 2. Launch server $ ruby web_file_browser.rb
|
15
|
+
# 3. Browse to http://localhost:5000/
|
16
|
+
# -------------------------------------------------------------------
|
17
|
+
|
18
|
+
require 'rubygems'
|
19
|
+
require 'sinatra'
|
20
|
+
require 'pp'
|
21
|
+
require 'securerandom'
|
22
|
+
require File.expand_path('../../lib/dropbox_sdk', __FILE__)
|
23
|
+
|
24
|
+
# Get your app's key and secret from https://www.dropbox.com/developers/
|
25
|
+
APP_KEY = ''
|
26
|
+
APP_SECRET = ''
|
27
|
+
|
28
|
+
# -------------------------------------------------------------------
|
29
|
+
# OAuth stuff
|
30
|
+
|
31
|
+
def get_web_auth()
|
32
|
+
return DropboxOAuth2Flow.new(APP_KEY, APP_SECRET, url('/dropbox-auth-finish'),
|
33
|
+
session, :dropbox_auth_csrf_token)
|
34
|
+
end
|
35
|
+
|
36
|
+
get '/dropbox-auth-start' do
|
37
|
+
authorize_url = get_web_auth().start()
|
38
|
+
|
39
|
+
# Send the user to the Dropbox website so they can authorize our app. After the user
|
40
|
+
# authorizes our app, Dropbox will redirect them to our '/dropbox-auth-finish' endpoint.
|
41
|
+
redirect authorize_url
|
42
|
+
end
|
43
|
+
|
44
|
+
get '/dropbox-auth-finish' do
|
45
|
+
begin
|
46
|
+
access_token, user_id, url_state = get_web_auth.finish(params)
|
47
|
+
rescue DropboxOAuth2Flow::BadRequestError => e
|
48
|
+
return html_page "Error in OAuth 2 flow", "<p>Bad request to /dropbox-auth-finish: #{e}</p>"
|
49
|
+
rescue DropboxOAuth2Flow::BadStateError => e
|
50
|
+
return html_page "Error in OAuth 2 flow", "<p>Auth session expired: #{e}</p>"
|
51
|
+
rescue DropboxOAuth2Flow::CsrfError => e
|
52
|
+
logger.info("/dropbox-auth-finish: CSRF mismatch: #{e}")
|
53
|
+
return html_page "Error in OAuth 2 flow", "<p>CSRF mismatch</p>"
|
54
|
+
rescue DropboxOAuth2Flow::NotApprovedError => e
|
55
|
+
return html_page "Not Approved?", "<p>Why not, bro?</p>"
|
56
|
+
rescue DropboxOAuth2Flow::ProviderError => e
|
57
|
+
return html_page "Error in OAuth 2 flow", "Error redirect from Dropbox: #{e}"
|
58
|
+
rescue DropboxError => e
|
59
|
+
logger.info "Error getting OAuth 2 access token: #{e}"
|
60
|
+
return html_page "Error in OAuth 2 flow", "<p>Error getting access token</p>"
|
61
|
+
end
|
62
|
+
|
63
|
+
# In this simple example, we store the authorized DropboxSession in the session.
|
64
|
+
# A real webapp might store it somewhere more persistent.
|
65
|
+
session[:access_token] = access_token
|
66
|
+
redirect url('/')
|
67
|
+
end
|
68
|
+
|
69
|
+
get '/dropbox-unlink' do
|
70
|
+
session.delete(:access_token)
|
71
|
+
nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# If we already have an authorized DropboxSession, returns a DropboxClient.
|
75
|
+
def get_dropbox_client
|
76
|
+
if session[:access_token]
|
77
|
+
return DropboxClient.new(session[:access_token])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# -------------------------------------------------------------------
|
82
|
+
# File/folder display stuff
|
83
|
+
|
84
|
+
get '/' do
|
85
|
+
# Get the DropboxClient object. Redirect to OAuth flow if necessary.
|
86
|
+
client = get_dropbox_client
|
87
|
+
unless client
|
88
|
+
redirect url("/dropbox-auth-start")
|
89
|
+
end
|
90
|
+
|
91
|
+
# Call DropboxClient.metadata
|
92
|
+
path = params[:path] || '/'
|
93
|
+
begin
|
94
|
+
entry = client.metadata(path)
|
95
|
+
rescue DropboxAuthError => e
|
96
|
+
session.delete(:access_token) # An auth error means the access token is probably bad
|
97
|
+
logger.info "Dropbox auth error: #{e}"
|
98
|
+
return html_page "Dropbox auth error"
|
99
|
+
rescue DropboxError => e
|
100
|
+
if e.http_response.code == '404'
|
101
|
+
return html_page "Path not found: #{h path}"
|
102
|
+
else
|
103
|
+
logger.info "Dropbox API error: #{e}"
|
104
|
+
return html_page "Dropbox API error"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
if entry['is_dir']
|
109
|
+
render_folder(client, entry)
|
110
|
+
else
|
111
|
+
render_file(client, entry)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def render_folder(client, entry)
|
116
|
+
# Provide an upload form (so the user can add files to this folder)
|
117
|
+
out = "<form action='/upload' method='post' enctype='multipart/form-data'>"
|
118
|
+
out += "<label for='file'>Upload file:</label> <input name='file' type='file'/>"
|
119
|
+
out += "<input type='submit' value='Upload'/>"
|
120
|
+
out += "<input name='folder' type='hidden' value='#{h entry['path']}'/>"
|
121
|
+
out += "</form>" # TODO: Add a token to counter CSRF attacks.
|
122
|
+
# List of folder contents
|
123
|
+
entry['contents'].each do |child|
|
124
|
+
cp = child['path'] # child path
|
125
|
+
cn = File.basename(cp) # child name
|
126
|
+
if (child['is_dir']) then cn += '/' end
|
127
|
+
out += "<div><a style='text-decoration: none' href='/?path=#{h cp}'>#{h cn}</a></div>"
|
128
|
+
end
|
129
|
+
|
130
|
+
html_page "Folder: #{entry['path']}", out
|
131
|
+
end
|
132
|
+
|
133
|
+
def render_file(client, entry)
|
134
|
+
# Just dump out metadata hash
|
135
|
+
html_page "File: #{entry['path']}", "<pre>#{h entry.pretty_inspect}</pre>"
|
136
|
+
end
|
137
|
+
|
138
|
+
# -------------------------------------------------------------------
|
139
|
+
# File upload handler
|
140
|
+
|
141
|
+
post '/upload' do
|
142
|
+
# Check POST parameter.
|
143
|
+
file = params[:file]
|
144
|
+
unless file && (temp_file = file[:tempfile]) && (name = file[:filename])
|
145
|
+
return html_page "Upload error", "<p>No file selected.</p>"
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get the DropboxClient object.
|
149
|
+
client = get_dropbox_client
|
150
|
+
unless client
|
151
|
+
return html_page "Upload error", "<p>Not linked with a Dropbox account.</p>"
|
152
|
+
end
|
153
|
+
|
154
|
+
# Call DropboxClient.put_file
|
155
|
+
begin
|
156
|
+
entry = client.put_file("#{params[:folder]}/#{name}", temp_file.read)
|
157
|
+
rescue DropboxAuthError => e
|
158
|
+
session.delete(:access_token) # An auth error means the access token is probably bad
|
159
|
+
logger.info "Dropbox auth error: #{e}"
|
160
|
+
return html_page "Dropbox auth error"
|
161
|
+
rescue DropboxError => e
|
162
|
+
logger.info "Dropbox API error: #{e}"
|
163
|
+
return html_page "Dropbox API error"
|
164
|
+
end
|
165
|
+
|
166
|
+
html_page "Upload complete", "<pre>#{h entry.pretty_inspect}</pre>"
|
167
|
+
end
|
168
|
+
|
169
|
+
# -------------------------------------------------------------------
|
170
|
+
|
171
|
+
def html_page(title, body='')
|
172
|
+
"<html>" +
|
173
|
+
"<head><title>#{h title}</title></head>" +
|
174
|
+
"<body><h1>#{h title}</h1>#{body}</body>" +
|
175
|
+
"</html>"
|
176
|
+
end
|
177
|
+
|
178
|
+
# Rack will issue a warning if no session secret key is set. A real web app would not have
|
179
|
+
# a hard-coded secret in the code but would load it from a config file.
|
180
|
+
use Rack::Session::Cookie, :secret => 'dummy_secret'
|
181
|
+
|
182
|
+
set :port, 5000
|
183
|
+
enable :sessions
|
184
|
+
|
185
|
+
helpers do
|
186
|
+
include Rack::Utils
|
187
|
+
alias_method :h, :escape_html
|
188
|
+
end
|
189
|
+
|
190
|
+
if APP_KEY == '' or APP_SECRET == ''
|
191
|
+
puts "You must set APP_KEY and APP_SECRET at the top of \"#{__FILE__}\"!"
|
192
|
+
exit 1
|
193
|
+
end
|