lib/dropbox_sdk.rb in dropbox-sdk-1.5.1 vs lib/dropbox_sdk.rb in dropbox-sdk-1.6
- old
+ new
@@ -2,86 +2,101 @@
require 'uri'
require 'net/https'
require 'cgi'
require 'json'
require 'yaml'
+require 'base64'
+require 'securerandom'
+require 'pp'
module Dropbox # :nodoc:
API_SERVER = "api.dropbox.com"
API_CONTENT_SERVER = "api-content.dropbox.com"
WEB_SERVER = "www.dropbox.com"
API_VERSION = 1
- SDK_VERSION = "1.5.1"
+ SDK_VERSION = "1.6"
TRUSTED_CERT_FILE = File.join(File.dirname(__FILE__), 'trusted-certs.crt')
-end
-# DropboxSession is responsible for holding OAuth information. It knows how to take your consumer key and secret
-# and request an access token, an authorize url, and get an access token. You just need to pass it to
-# DropboxClient after its been authorized.
-class DropboxSession
-
- # * consumer_key - Your Dropbox application's "app key".
- # * consumer_secret - Your Dropbox application's "app secret".
- def initialize(consumer_key, consumer_secret)
- @consumer_key = consumer_key
- @consumer_secret = consumer_secret
- @request_token = nil
- @access_token = nil
- end
-
- private
-
- def do_http(uri, auth_token, request) # :nodoc:
+ def self.do_http(uri, request) # :nodoc:
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
- enable_cert_checking(http)
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = Dropbox::TRUSTED_CERT_FILE
- request.add_field('Authorization', build_auth_header(auth_token))
-
#We use this to better understand how developers are using our SDKs.
request['User-Agent'] = "OfficialDropboxRubySDK/#{Dropbox::SDK_VERSION}"
begin
http.request(request)
rescue OpenSSL::SSL::SSLError => e
raise DropboxError.new("SSL error connecting to Dropbox. " +
- "There may be a problem with the set of certificates in \"#{Dropbox::TRUSTED_CERT_FILE}\". " +
- e)
+ "There may be a problem with the set of certificates in \"#{Dropbox::TRUSTED_CERT_FILE}\". #{e}")
end
end
- def enable_cert_checking(http)
- http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ # Parse response. You probably shouldn't be calling this directly. This takes responses from the server
+ # and parses them. It also checks for errors and raises exceptions with the appropriate messages.
+ def self.parse_response(response, raw=false) # :nodoc:
+ if response.kind_of?(Net::HTTPServerError)
+ raise DropboxError.new("Dropbox Server Error: #{response} - #{response.body}", response)
+ elsif response.kind_of?(Net::HTTPUnauthorized)
+ raise DropboxAuthError.new("User is not authenticated.", response)
+ elsif not response.kind_of?(Net::HTTPSuccess)
+ begin
+ d = JSON.parse(response.body)
+ rescue
+ raise DropboxError.new("Dropbox Server Error: body=#{response.body}", response)
+ end
+ if d['user_error'] and d['error']
+ raise DropboxError.new(d['error'], response, d['user_error']) #user_error is translated
+ elsif d['error']
+ raise DropboxError.new(d['error'], response)
+ else
+ raise DropboxError.new(response.body, response)
+ end
+ end
+
+ return response.body if raw
+
+ begin
+ return JSON.parse(response.body)
+ rescue JSON::ParserError
+ raise DropboxError.new("Unable to parse JSON response: #{response.body}", response)
+ end
end
- def build_auth_header(token) # :nodoc:
- header = "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", " +
- "oauth_consumer_key=\"#{URI.escape(@consumer_key)}\", "
- if token
- key = URI.escape(token.key)
- secret = URI.escape(token.secret)
- header += "oauth_token=\"#{key}\", oauth_signature=\"#{URI.escape(@consumer_secret)}&#{secret}\""
+ # A string comparison function that is resistant to timing attacks. If you're comparing a
+ # string you got from the outside world with a string that is supposed to be a secret, use
+ # this function to check equality.
+ def self.safe_string_equals(a, b)
+ if a.length != b.length
+ false
else
- header += "oauth_signature=\"#{URI.escape(@consumer_secret)}&\""
+ a.chars.zip(b.chars).map {|ac,bc| ac == bc}.all?
end
- header
end
+end
- def do_get_with_token(url, token, headers=nil) # :nodoc:
- uri = URI.parse(url)
- do_http(uri, token, Net::HTTP::Get.new(uri.request_uri))
+class DropboxSessionBase # :nodoc:
+
+ protected
+
+ def do_http(uri, request) # :nodoc:
+ sign_request(request)
+ Dropbox::do_http(uri, request)
end
public
def do_get(url, headers=nil) # :nodoc:
assert_authorized
- do_get_with_token(url, @access_token)
+ uri = URI.parse(url)
+ request = Net::HTTP::Get.new(uri.request_uri)
+ do_http(uri, request)
end
def do_http_with_body(uri, request, body)
if body != nil
if body.is_a?(Hash)
@@ -101,11 +116,11 @@
s = body.to_s
request["Content-Length"] = s.length
request.body = s
end
end
- do_http(uri, @access_token, request)
+ do_http(uri, request)
end
def do_post(url, headers=nil, body=nil) # :nodoc:
assert_authorized
uri = URI.parse(url)
@@ -115,12 +130,56 @@
def do_put(url, headers=nil, body=nil) # :nodoc:
assert_authorized
uri = URI.parse(url)
do_http_with_body(uri, Net::HTTP::Put.new(uri.request_uri, headers), body)
end
+end
+# DropboxSession is responsible for holding OAuth 1 information. It knows how to take your consumer key and secret
+# and request an access token, an authorize url, and get an access token. You just need to pass it to
+# DropboxClient after its been authorized.
+class DropboxSession < DropboxSessionBase # :nodoc:
+ # * consumer_key - Your Dropbox application's "app key".
+ # * consumer_secret - Your Dropbox application's "app secret".
+ def initialize(consumer_key, consumer_secret)
+ @consumer_key = consumer_key
+ @consumer_secret = consumer_secret
+ @request_token = nil
+ @access_token = nil
+ end
+
+ private
+
+ def build_auth_header(token) # :nodoc:
+ header = "OAuth oauth_version=\"1.0\", oauth_signature_method=\"PLAINTEXT\", " +
+ "oauth_consumer_key=\"#{URI.escape(@consumer_key)}\", "
+ if token
+ key = URI.escape(token.key)
+ secret = URI.escape(token.secret)
+ header += "oauth_token=\"#{key}\", oauth_signature=\"#{URI.escape(@consumer_secret)}&#{secret}\""
+ else
+ header += "oauth_signature=\"#{URI.escape(@consumer_secret)}&\""
+ end
+ header
+ end
+
+ def do_get_with_token(url, token, headers=nil) # :nodoc:
+ uri = URI.parse(url)
+ request = Net::HTTP::Get.new(uri.request_uri)
+ request.add_field('Authorization', build_auth_header(token))
+ Dropbox::do_http(uri, request)
+ end
+
+ protected
+
+ def sign_request(request) # :nodoc:
+ request.add_field('Authorization', build_auth_header(@access_token))
+ end
+
+ public
+
def get_token(url_end, input_token, error_message_prefix) #: nodoc:
response = do_get_with_token("https://#{Dropbox::API_SERVER}:443/#{Dropbox::API_VERSION}/oauth#{url_end}", input_token)
if not response.kind_of?(Net::HTTPSuccess) # it must be a 200
raise DropboxAuthError.new("#{error_message_prefix} Server returned #{response.code}: #{response.message}.", response)
end
@@ -191,11 +250,11 @@
# your user has gone to the authorize_url and approved your request
def get_access_token
return @access_token if authorized?
if @request_token.nil?
- raise DropboxAuthError.new("No request token. You must set this or get an authorize url first.")
+ raise RuntimeError.new("No request token. You must set this or get an authorize url first.")
end
@access_token = get_token("/access_token", @request_token, "Couldn't get access token.")
end
@@ -242,10 +301,306 @@
session
end
end
+class DropboxOAuth2Session < DropboxSessionBase # :nodoc:
+ def initialize(oauth2_access_token)
+ if not oauth2_access_token.is_a?(String)
+ raise "bad type for oauth2_access_token (expecting String)"
+ end
+ @access_token = oauth2_access_token
+ end
+
+ def assert_authorized
+ true
+ end
+
+ protected
+
+ def sign_request(request) # :nodoc:
+ request.add_field('Authorization', 'Bearer ' + @access_token)
+ end
+end
+
+# Base class for the two OAuth 2 authorization helpers.
+class DropboxOAuth2FlowBase # :nodoc:
+ def initialize(consumer_key, consumer_secret, locale=nil)
+ if not consumer_key.is_a?(String)
+ raise ArgumentError, "consumer_key must be a String, got #{consumer_key.inspect}"
+ end
+ if not consumer_secret.is_a?(String)
+ raise ArgumentError, "consumer_secret must be a String, got #{consumer_secret.inspect}"
+ end
+ if not (locale.nil? or locale.is_a?(String))
+ raise ArgumentError, "locale must be a String or nil, got #{locale.inspect}"
+ end
+ @consumer_key = consumer_key
+ @consumer_secret = consumer_secret
+ @locale = locale
+ end
+
+ def _get_authorize_url(redirect_uri, state)
+ params = {"client_id" => @consumer_key, "response_type" => "code"}
+ if not redirect_uri.nil?
+ params["redirect_uri"] = redirect_uri
+ end
+ if not state.nil?
+ params["state"] = state
+ end
+ if not @locale.nil?
+ params['locale'] = @locale
+ end
+
+ host = Dropbox::WEB_SERVER
+ path = "/#{Dropbox::API_VERSION}/oauth2/authorize"
+
+ target = URI::Generic.new("https", nil, host, nil, nil, path, nil, nil, nil)
+
+ target.query = params.collect {|k,v|
+ CGI.escape(k) + "=" + CGI.escape(v)
+ }.join("&")
+
+ target.to_s
+ end
+
+ # Finish the OAuth 2 authorization process. If you used a redirect_uri, pass that in.
+ # Will return an access token string that you can use with DropboxClient.
+ def _finish(code, original_redirect_uri)
+ if not code.is_a?(String)
+ raise ArgumentError, "code must be a String"
+ end
+
+ uri = URI.parse("https://#{Dropbox::API_SERVER}/1/oauth2/token")
+ request = Net::HTTP::Post.new(uri.request_uri)
+ client_credentials = @consumer_key + ':' + @consumer_secret
+ request.add_field('Authorization', 'Basic ' + Base64.encode64(client_credentials).chomp("\n"))
+
+ params = {
+ "grant_type" => "authorization_code",
+ "code" => code,
+ }
+
+ if not @locale.nil?
+ params['locale'] = @locale
+ end
+ if not original_redirect_uri.nil?
+ params['redirect_uri'] = original_redirect_uri
+ end
+
+ form_data = {}
+ params.each {|k,v| form_data[k.to_s] = v if !v.nil?}
+ request.set_form_data(form_data)
+
+ response = Dropbox::do_http(uri, request)
+
+ j = Dropbox::parse_response(response)
+ ["token_type", "access_token", "uid"].each { |k|
+ if not j.has_key?(k)
+ raise DropboxError.new("Bad response from /token: missing \"#{k}\".")
+ end
+ if not j[k].is_a?(String)
+ raise DropboxError.new("Bad response from /token: field \"#{k}\" is not a string.")
+ end
+ }
+ if j["token_type"] != "bearer" and j["token_type"] != "Bearer":
+ raise DropboxError.new("Bad response from /token: \"token_type\" is \"#{token_type}\".")
+ end
+
+ return j['access_token'], j['uid']
+ end
+end
+
+# OAuth 2 authorization helper for apps that can't provide a redirect URI
+# (such as the command line example apps).
+class DropboxOAuth2FlowNoRedirect < DropboxOAuth2FlowBase
+
+ # * consumer_key: Your Dropbox API app's "app key"
+ # * consumer_secret: Your Dropbox API app's "app secret"
+ # * locale: The locale of the user currently using your app.
+ def initialize(consumer_key, consumer_secret, locale=nil)
+ super(consumer_key, consumer_secret, locale)
+ end
+
+ # Returns a authorization_url, which is a page on Dropbox's website. Have the user
+ # visit this URL and approve your app.
+ def start()
+ _get_authorize_url(nil, nil)
+ end
+
+ # If the user approves your app, they will be presented with an "authorization code".
+ # Have the user copy/paste that authorization code into your app and then call this
+ # method to get an access token.
+ #
+ # Returns a two-entry list (access_token, user_id)
+ # * access_token is an access token string that can be passed to DropboxClient.
+ # * user_id is the Dropbox user ID of the user that just approved your app.
+ def finish(code)
+ _finish(code, nil)
+ end
+end
+
+# The standard OAuth 2 authorization helper. Use this if you're writing a web app.
+class DropboxOAuth2Flow < DropboxOAuth2FlowBase
+
+ # * consumer_key: Your Dropbox API app's "app key"
+ # * consumer_secret: Your Dropbox API app's "app secret"
+ # * redirect_uri: The URI that the Dropbox server will redirect the user to after the user
+ # finishes authorizing your app. This URI must be HTTPs-based and pre-registered with
+ # the Dropbox servers, though localhost URIs are allowed without pre-registration and can
+ # be either HTTP or HTTPS.
+ # * session: A hash that represents the current web app session (will be used to save the CSRF
+ # token)
+ # * csrf_token_key: The key to use when storing the CSRF token in the session (for example,
+ # :dropbox_auth_csrf_token)
+ # * locale: The locale of the user currently using your app (ex: "en" or "en_US").
+ def initialize(consumer_key, consumer_secret, redirect_uri, session, csrf_token_session_key, locale=nil)
+ super(consumer_key, consumer_secret, locale)
+ if not redirect_uri.is_a?(String)
+ raise ArgumentError, "redirect_uri must be a String, got #{consumer_secret.inspect}"
+ end
+ @redirect_uri = redirect_uri
+ @session = session
+ @csrf_token_session_key = csrf_token_session_key
+ end
+
+ # Starts the OAuth 2 authorizaton process, which involves redirecting the user to
+ # the returned "authorization URL" (a URL on the Dropbox website). When the user then
+ # either approves or denies your app access, Dropbox will redirect them to the
+ # redirect_uri you provided to the constructor, at which point you should call finish()
+ # to complete the process.
+ #
+ # This function will also save a CSRF token to the session and csrf_token_session_key
+ # you provided to the constructor. This CSRF token will be checked on finish() to prevent
+ # request forgery.
+ #
+ # * url_state: Any data you would like to keep in the URL through the authorization
+ # process. This exact value will be returned to you by finish().
+ #
+ # Returns the URL to redirect the user to.
+ def start(url_state=nil)
+ unless url_state.nil? or url_state.is_a?(String)
+ raise ArgumentError, "url_state must be a String"
+ end
+
+ csrf_token = SecureRandom.base64(16)
+ state = csrf_token
+ unless url_state.nil?
+ state += "|" + url_state
+ end
+ @session[@csrf_token_session_key] = csrf_token
+
+ return _get_authorize_url(@redirect_uri, state)
+ end
+
+ # Call this after the user has visited the authorize URL (see: start()), approved your app,
+ # and was redirected to your redirect URI.
+ #
+ # * query_params: The query params on the GET request to your redirect URI.
+ #
+ # Returns a tuple of (access_token, user_id, url_state). access_token can be used to
+ # construct a DropboxClient. user_id is the Dropbox user ID of the user that jsut approved
+ # your app. url_state is the value you originally passed in to start().
+ #
+ # Can throw BadRequestError, BadStateError, CsrfError, NotApprovedError,
+ # ProviderError, and the standard DropboxError.
+ def finish(query_params)
+ csrf_token_from_session = @session[@csrf_token_session_key]
+
+ # Check well-formedness of request.
+
+ state = query_params['state']
+ if state.nil?
+ raise BadRequestError.new("Missing query parameter 'state'.")
+ end
+
+ error = query_params['error']
+ error_description = query_params['error_description']
+ code = query_params['code']
+
+ if not error.nil? and not code.nil?
+ raise BadRequestError.new("Query parameters 'code' and 'error' are both set;" +
+ " only one must be set.")
+ end
+ if error.nil? and code.nil?
+ raise BadRequestError.new("Neither query parameter 'code' or 'error' is set.")
+ end
+
+ # Check CSRF token
+
+ if csrf_token_from_session.nil?
+ raise BadStateError.new("Missing CSRF token in session.");
+ end
+ unless csrf_token_from_session.length > 20
+ raise RuntimeError.new("CSRF token unexpectedly short: #{csrf_token_from_session.inspect}")
+ end
+
+ split_pos = state.index('|')
+ if split_pos.nil?
+ given_csrf_token = state
+ url_state = nil
+ else
+ given_csrf_token, url_state = state.split('|', 2)
+ end
+ if not Dropbox::safe_string_equals(csrf_token_from_session, given_csrf_token)
+ raise CsrfError.new("Expected #{csrf_token_from_session.inspect}, " +
+ "got #{given_csrf_token.inspect}.")
+ end
+ @session.delete(@csrf_token_session_key)
+
+ # Check for error identifier
+
+ if not error.nil?
+ if error == 'access_denied'
+ # The user clicked "Deny"
+ if error_description.nil?
+ raise NotApprovedError.new("No additional description from Dropbox.")
+ else
+ raise NotApprovedError.new("Additional description from Dropbox: #{error_description}")
+ end
+ else
+ # All other errors.
+ full_message = error
+ if not error_description.nil?
+ full_message += ": " + error_description
+ end
+ raise ProviderError.new(full_message)
+ end
+ end
+
+ # If everything went ok, make the network call to get an access token.
+
+ access_token, user_id = _finish(code, @redirect_uri)
+ return access_token, user_id, url_state
+ end
+
+ # Thrown if the redirect URL was missing parameters or if the given parameters were not valid.
+ #
+ # The recommended action is to show an HTTP 400 error page.
+ class BadRequestError < Exception; end
+
+ # Thrown if all the parameters are correct, but there's no CSRF token in the session. This
+ # probably means that the session expired.
+ #
+ # The recommended action is to redirect the user's browser to try the approval process again.
+ class BadStateError < Exception; end
+
+ # Thrown if the given 'state' parameter doesn't contain the CSRF token from the user's session.
+ # This is blocked to prevent CSRF attacks.
+ #
+ # The recommended action is to respond with an HTTP 403 error page.
+ class CsrfError < Exception; end
+
+ # The user chose not to approve your app.
+ class NotApprovedError < Exception; end
+
+ # Dropbox redirected to your redirect URI with some unexpected error identifier and error
+ # message.
+ class ProviderError < Exception; end
+end
+
+
# A class that represents either an OAuth request token or an OAuth access token.
class OAuthToken # :nodoc:
def initialize(key, secret)
@key = key
@secret = secret
@@ -287,101 +642,79 @@
# This is raised when you call metadata with a hash and that hash matches
# See documentation in metadata function
class DropboxNotModified < DropboxError
end
-# This is the Dropbox Client API you'll be working with most often. You need to give it
-# a DropboxSession which has already been authorized, or which it can authorize.
+# Use this class to make Dropbox API calls. You'll need to obtain an OAuth 2 access token
+# first; you can get one using either DropboxOAuth2Flow or DropboxOAuth2FlowNoRedirect.
class DropboxClient
- # Initialize a new DropboxClient. You need to give it a session which has been authorized. See
- # documentation on DropboxSession for how to authorize it.
- def initialize(session, root="app_folder", locale=nil)
- session.get_access_token
+ # Args:
+ # * +oauth2_access_token+: Obtained via DropboxOAuth2Flow or DropboxOAuth2FlowNoRedirect.
+ # * +locale+: The user's current locale (used to localize error messages).
+ def initialize(oauth2_access_token, root="auto", locale=nil)
+ if oauth2_access_token.is_a?(String)
+ @session = DropboxOAuth2Session.new(oauth2_access_token)
+ elsif oauth2_access_token.is_a?(DropboxSession)
+ @session = oauth2_access_token
+ @session.get_access_token
+ else
+ raise ArgumentError.new("oauth2_access_token doesn't have a valid type")
+ end
@root = root.to_s # If they passed in a symbol, make it a string
- if not ["dropbox","app_folder"].include?(@root)
- raise DropboxError.new("root must be :dropbox or :app_folder")
+ if not ["dropbox","app_folder","auto"].include?(@root)
+ raise ArgumentError.new("root must be :dropbox, :app_folder, or :auto")
end
if @root == "app_folder"
#App Folder is the name of the access type, but for historical reasons
#sandbox is the URL root component that indicates this
@root = "sandbox"
end
@locale = locale
- @session = session
end
- # Parse response. You probably shouldn't be calling this directly. This takes responses from the server
- # and parses them. It also checks for errors and raises exceptions with the appropriate messages.
- def parse_response(response, raw=false) # :nodoc:
- if response.kind_of?(Net::HTTPServerError)
- raise DropboxError.new("Dropbox Server Error: #{response} - #{response.body}", response)
- elsif response.kind_of?(Net::HTTPUnauthorized)
- raise DropboxAuthError.new(response, "User is not authenticated.")
- elsif not response.kind_of?(Net::HTTPSuccess)
- begin
- d = JSON.parse(response.body)
- rescue
- raise DropboxError.new("Dropbox Server Error: body=#{response.body}", response)
- end
- if d['user_error'] and d['error']
- raise DropboxError.new(d['error'], response, d['user_error']) #user_error is translated
- elsif d['error']
- raise DropboxError.new(d['error'], response)
- else
- raise DropboxError.new(response.body, response)
- end
- end
-
- return response.body if raw
-
- begin
- return JSON.parse(response.body)
- rescue JSON::ParserError
- raise DropboxError.new("Unable to parse JSON response: #{response.body}", response)
- end
- end
-
- # Returns account info in a Hash object
+ # Returns some information about the current user's Dropbox account (the "current user"
+ # is the user associated with the access token you're using).
#
# For a detailed description of what this call returns, visit:
# https://www.dropbox.com/developers/reference/api#account-info
def account_info()
response = @session.do_get build_url("/account/info")
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Uploads a file to a server. This uses the HTTP PUT upload method for simplicity
#
- # Arguments:
- # * to_path: The directory path to upload the file to. If the destination
+ # Args:
+ # * +to_path+: The directory path to upload the file to. If the destination
# directory does not yet exist, it will be created.
- # * file_obj: A file-like object to upload. If you would like, you can
+ # * +file_obj+: A file-like object to upload. If you would like, you can
# pass a string as file_obj.
- # * overwrite: Whether to overwrite an existing file at the given path. [default is False]
+ # * +overwrite+: Whether to overwrite an existing file at the given path. [default is False]
# If overwrite is False and a file already exists there, Dropbox
# will rename the upload to make sure it doesn't overwrite anything.
# You must check the returned metadata to know what this new name is.
# This field should only be True if your intent is to potentially
# clobber changes to a file that you don't know about.
- # * parent_rev: The rev field from the 'parent' of this upload. [optional]
+ # * +parent_rev+: The rev field from the 'parent' of this upload. [optional]
# If your intent is to update the file at the given path, you should
# pass the parent_rev parameter set to the rev value from the most recent
# metadata you have of the existing file at that path. If the server
# has a more recent version of the file at the specified path, it will
# automatically rename your uploaded file, spinning off a conflict.
# Using this parameter effectively causes the overwrite parameter to be ignored.
# The file will always be overwritten if you send the most-recent parent_rev,
# and it will never be overwritten you send a less-recent one.
# Returns:
- # * a Hash containing the metadata of the newly uploaded file. The file may have a different name if it conflicted.
+ # * a Hash containing the metadata of the newly uploaded file. The file may have a different
+ # name if it conflicted.
#
# Simple Example
- # client = DropboxClient(session, :app_folder)
+ # client = DropboxClient(oauth2_access_token)
# #session is a DropboxSession I've already authorized
# client.put_file('/test_file_on_dropbox', open('/tmp/test_file'))
# This will upload the "/tmp/test_file" from my computer into the root of my App's app folder
# and call it "test_file_on_dropbox".
# The file will not overwrite any pre-existing file.
@@ -396,18 +729,18 @@
response = @session.do_put(build_url(path, params, content_server=true),
{"Content-Type" => "application/octet-stream"},
file_obj)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Returns a ChunkedUploader object.
#
# Args:
- # * file_obj: The file-like object to be uploaded. Must support .read()
- # * total_size: The total size of file_obj
+ # * +file_obj+: The file-like object to be uploaded. Must support .read()
+ # * +total_size+: The total size of file_obj
def get_chunked_uploader(file_obj, total_size)
ChunkedUploader.new(self, file_obj, total_size)
end
# ChunkedUploader is responsible for uploading a large file to Dropbox in smaller chunks.
@@ -426,22 +759,22 @@
# Uploads data from this ChunkedUploader's file_obj in chunks, until
# an error occurs. Throws an exception when an error occurs, and can
# be called again to resume the upload.
#
# Args:
- # * chunk_size: The chunk size for each individual upload. Defaults to 4MB.
+ # * +chunk_size+: The chunk size for each individual upload. Defaults to 4MB.
def upload(chunk_size=4*1024*1024)
last_chunk = nil
while @offset < @total_size
if not last_chunk
last_chunk = @file_obj.read(chunk_size)
end
resp = {}
begin
- resp = @client.parse_response(@client.partial_chunked_upload(last_chunk, @upload_id, @offset))
+ resp = Dropbox::parse_response(@client.partial_chunked_upload(last_chunk, @upload_id, @offset))
last_chunk = nil
rescue DropboxError => e
resp = JSON.parse(e.http_response.body)
raise unless resp.has_key? 'offset'
end
@@ -455,13 +788,13 @@
end
# Completes a file upload
#
# Args:
- # * to_path: The directory path to upload the file to. If the destination
+ # * +to_path+: The directory path to upload the file to. If the destination
# directory does not yet exist, it will be created.
- # * overwrite: Whether to overwrite an existing file at the given path. [default is False]
+ # * +overwrite+: Whether to overwrite an existing file at the given path. [default is False]
# If overwrite is False and a file already exists there, Dropbox
# will rename the upload to make sure it doesn't overwrite anything.
# You must check the returned metadata to know what this new name is.
# This field should only be True if your intent is to potentially
# clobber changes to a file that you don't know about.
@@ -479,11 +812,11 @@
# * A Hash with the metadata of file just uploaded.
# For a detailed description of what this call returns, visit:
# https://www.dropbox.com/developers/reference/api#metadata
def finish(to_path, overwrite=false, parent_rev=nil)
response = @client.commit_chunked_upload(to_path, @upload_id, overwrite, parent_rev)
- @client.parse_response(response)
+ Dropbox::parse_response(response)
end
end
def commit_chunked_upload(to_path, upload_id, overwrite=false, parent_rev=nil) #:nodoc
params = {'overwrite' => overwrite.to_s,
@@ -503,41 +836,41 @@
end
# Download a file
#
# Args:
- # * from_path: The path to the file to be downloaded
- # * rev: A previous revision value of the file to be downloaded
+ # * +from_path+: The path to the file to be downloaded
+ # * +rev+: A previous revision value of the file to be downloaded
#
# Returns:
# * The file contents.
def get_file(from_path, rev=nil)
response = get_file_impl(from_path, rev)
- parse_response(response, raw=true)
+ Dropbox::parse_response(response, raw=true)
end
# Download a file and get its metadata.
#
# Args:
- # * from_path: The path to the file to be downloaded
- # * rev: A previous revision value of the file to be downloaded
+ # * +from_path+: The path to the file to be downloaded
+ # * +rev+: A previous revision value of the file to be downloaded
#
# Returns:
# * The file contents.
# * The file metadata as a hash.
def get_file_and_metadata(from_path, rev=nil)
response = get_file_impl(from_path, rev)
- parsed_response = parse_response(response, raw=true)
+ parsed_response = Dropbox::parse_response(response, raw=true)
metadata = parse_metadata(response)
return parsed_response, metadata
end
# Download a file (helper method - don't call this directly).
#
# Args:
- # * from_path: The path to the file to be downloaded
- # * rev: A previous revision value of the file to be downloaded
+ # * +from_path+: The path to the file to be downloaded
+ # * +rev+: A previous revision value of the file to be downloaded
#
# Returns:
# * The HTTPResponse for the file download request.
def get_file_impl(from_path, rev=nil) # :nodoc:
params = {}
@@ -549,11 +882,11 @@
private :get_file_impl
# Parses out file metadata from a raw dropbox HTTP response.
#
# Args:
- # * dropbox_raw_response: The raw, unparsed HTTPResponse from Dropbox.
+ # * +dropbox_raw_response+: The raw, unparsed HTTPResponse from Dropbox.
#
# Returns:
# * The metadata of the file as a hash.
def parse_metadata(dropbox_raw_response) # :nodoc:
begin
@@ -567,13 +900,13 @@
end
private :parse_metadata
# Copy a file or folder to a new location.
#
- # Arguments:
- # * from_path: The path to the file or folder to be copied.
- # * to_path: The destination path of the file or folder to be copied.
+ # Args:
+ # * +from_path+: The path to the file or folder to be copied.
+ # * +to_path+: The destination path of the file or folder to be copied.
# This parameter should include the destination filename (e.g.
# from_path: '/test.txt', to_path: '/dir/test.txt'). If there's
# already a file at the to_path, this copy will be renamed to
# be unique.
#
@@ -586,17 +919,17 @@
"root" => @root,
"from_path" => format_path(from_path, false),
"to_path" => format_path(to_path, false),
}
response = @session.do_post build_url("/fileops/copy", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Create a folder.
#
# Arguments:
- # * path: The path of the new folder.
+ # * +path+: The path of the new folder.
#
# Returns:
# * A hash with the metadata of the newly created folder.
# For a detailed description of what this call returns, visit:
# https://www.dropbox.com/developers/reference/api#fileops-create-folder
@@ -605,17 +938,17 @@
"root" => @root,
"path" => format_path(path, false),
}
response = @session.do_post build_url("/fileops/create_folder", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Deletes a file
#
# Arguments:
- # * path: The path of the file to delete
+ # * +path+: The path of the file to delete
#
# Returns:
# * A Hash with the metadata of file just deleted.
# For a detailed description of what this call returns, visit:
# https://www.dropbox.com/developers/reference/api#fileops-delete
@@ -623,18 +956,18 @@
params = {
"root" => @root,
"path" => format_path(path, false),
}
response = @session.do_post build_url("/fileops/delete", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Moves a file
#
# Arguments:
- # * from_path: The path of the file to be moved
- # * to_path: The destination path of the file or folder to be moved
+ # * +from_path+: The path of the file to be moved
+ # * +to_path+: The destination path of the file or folder to be moved
# If the file or folder already exists, it will be renamed to be unique.
#
# Returns:
# * A Hash with the metadata of file or folder just moved.
# For a detailed description of what this call returns, visit:
@@ -644,11 +977,11 @@
"root" => @root,
"from_path" => format_path(from_path, false),
"to_path" => format_path(to_path, false),
}
response = @session.do_post build_url("/fileops/move", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Retrives metadata for a file or folder
#
# Arguments:
@@ -684,11 +1017,11 @@
response = @session.do_get build_url("/metadata/#{@root}#{format_path(path)}", params=params)
if response.kind_of? Net::HTTPRedirection
raise DropboxNotModified.new("metadata not modified")
end
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Search directory for filenames matching query
#
# Arguments:
@@ -709,11 +1042,11 @@
'file_limit' => file_limit.to_s,
'include_deleted' => include_deleted.to_s
}
response = @session.do_get build_url("/search/#{@root}#{format_path(path)}", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Retrive revisions of a file
#
# Arguments:
@@ -732,11 +1065,11 @@
params = {
'rev_limit' => rev_limit.to_s
}
response = @session.do_get build_url("/revisions/#{@root}#{format_path(path)}", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Restore a file to a previous revision.
#
@@ -752,11 +1085,11 @@
params = {
'rev' => rev.to_s
}
response = @session.do_post build_url("/restore/#{@root}#{format_path(path)}", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Returns a direct link to a media file
# All of Dropbox's API methods require OAuth, which may cause problems in
# situations where an application expects to be able to hit a URL multiple times
@@ -769,11 +1102,11 @@
# Returns:
# * A Hash object that looks like the following:
# {'url': 'https://dl.dropbox.com/0/view/wvxv1fw6on24qw7/file.mov', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
def media(path)
response = @session.do_get build_url("/media/#{@root}#{format_path(path)}")
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Get a URL to share a media file
# Shareable links created on Dropbox are time-limited, but don't require any
# authentication, so they can be given out freely. The time limit should allow
@@ -788,11 +1121,11 @@
# {'url': 'http://www.dropbox.com/s/m/a2mbDa2', 'expires': 'Thu, 16 Sep 2011 01:01:25 +0000'}
# For a detailed description of what this call returns, visit:
# https://www.dropbox.com/developers/reference/api#shares
def shares(path)
response = @session.do_get build_url("/shares/#{@root}#{format_path(path)}")
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Download a thumbnail for an image.
#
# Arguments:
@@ -804,11 +1137,11 @@
# for more details. [defaults to large]
# Returns:
# * The thumbnail data
def thumbnail(from_path, size='large')
response = thumbnail_impl(from_path, size)
- parse_response(response, raw=true)
+ Dropbox::parse_response(response, raw=true)
end
# Download a thumbnail for an image along with the image's metadata.
#
# Arguments:
@@ -818,11 +1151,11 @@
# Returns:
# * The thumbnail data
# * The metadata for the image as a hash
def thumbnail_and_metadata(from_path, size='large')
response = thumbnail_impl(from_path, size)
- parsed_response = parse_response(response, raw=true)
+ parsed_response = Dropbox::parse_response(response, raw=true)
metadata = parse_metadata(response)
return parsed_response, metadata
end
# A way of letting you keep a local representation of the Dropbox folder
@@ -874,18 +1207,18 @@
if cursor
params['cursor'] = cursor
end
response = @session.do_post build_url("/delta", params)
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Download a thumbnail (helper method - don't call this directly).
#
# Args:
- # * from_path: The path to the file to be thumbnailed.
- # * size: A string describing the desired thumbnail size. See thumbnail()
+ # * +from_path+: The path to the file to be thumbnailed.
+ # * +size+: A string describing the desired thumbnail size. See thumbnail()
# for details.
#
# Returns:
# * The HTTPResponse for the thumbnail request.
def thumbnail_impl(from_path, size='large') # :nodoc:
@@ -904,29 +1237,29 @@
# Creates and returns a copy ref for a specific file. The copy ref can be
# used to instantly copy that file to the Dropbox of another account.
#
# Args:
- # * path: The path to the file for a copy ref to be created on.
+ # * +path+: The path to the file for a copy ref to be created on.
#
# Returns:
# * A Hash object that looks like the following example:
# {"expires"=>"Fri, 31 Jan 2042 21:01:05 +0000", "copy_ref"=>"z1X6ATl6aWtzOGq0c3g5Ng"}
def create_copy_ref(path)
path = "/copy_ref/#{@root}#{format_path(path)}"
response = @session.do_get(build_url(path, {}))
- parse_response(response)
+ Dropbox::parse_response(response)
end
# Adds the file referenced by the copy ref to the specified path
#
# Args:
- # * copy_ref: A copy ref string that was returned from a create_copy_ref call.
+ # * +copy_ref+: A copy ref string that was returned from a create_copy_ref call.
# The copy_ref can be created from any other Dropbox account, or from the same account.
- # * to_path: The path to where the file will be created.
+ # * +to_path+: The path to where the file will be created.
#
# Returns:
# * A hash with the metadata of the new file.
def add_copy_ref(to_path, copy_ref)
path = "/fileops/copy"
@@ -935,11 +1268,11 @@
'to_path' => "#{to_path}",
'root' => @root}
response = @session.do_post(build_url(path, params))
- parse_response(response)
+ Dropbox::parse_response(response)
end
def build_url(url, params=nil, content_server=false) # :nodoc:
port = 443
host = content_server ? Dropbox::API_CONTENT_SERVER : Dropbox::API_SERVER
@@ -961,10 +1294,10 @@
target.to_s
end
#From the oauth spec plus "/". Slash should not be ecsaped
- RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~\/]/
+ RESERVED_CHARACTERS = /[^a-zA-Z0-9\-\.\_\~\/]/ # :nodoc:
def format_path(path, escape=true) # :nodoc:
path = path.gsub(/\/+/,"/")
# replace multiple slashes with a single one