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