Skip to content

Instantly share code, notes, and snippets.

@nikolaifedorov
Last active December 21, 2015 11:18
Show Gist options
  • Save nikolaifedorov/6297871 to your computer and use it in GitHub Desktop.
Save nikolaifedorov/6297871 to your computer and use it in GitHub Desktop.
Ruby script that implements a readonly FUSE FS. It uses the web interface for it's data. more see: https://forums.dropbox.com/topic.php?id=4164 http://dl.dropboxusercontent.com/u/263514/dropboxfs.rb
#! /usr/bin/env ruby
#
# dropboxfs.rb
#
# Copyright (c) 2008 Raymond Sneekes (raymond@sneek.es)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
require 'net/http'
require 'net/https'
require 'rubygems'
require 'hpricot'
require 'highline/import'
require 'chronic'
require 'fusefs'
include FuseFS
PATH_LOGIN = "/login"
PATH_BROWSE = "/browse2"
class Entry
attr_accessor :name, :url, :date
attr_reader :size
def initialize(name, url)
@name = name
@url = url
end
def size=(size)
@size=size unless size == '&nbsp;'
end
def directory?
!@name.nil? && @size.nil?
end
def file?
!@name.nil? && !@size.nil?
end
end
class DropBoxFS < FuseFS::FuseDir
def initialize(email_address, password)
@http = setup_http('www.getdropbox.com')
@cache = {}
init_cache(email_address)
login email_address, password
end
def contents(path)
entries = ls(path)
entries.keys
end
def directory?(path)
e = get_entry(path)
e.directory? unless e.nil?
end
def file?(path)
e = get_entry(path)
e.file? unless e.nil?
end
def executable?(path)
false
end
def size(path)
e = get_entry(path)
e.size unless e.nil?
end
def read_file(path)
e = get_entry(path)
get_file( e.url ) unless e.nil?
end
private
def setup_http(hostname)
http = Net::HTTP.new(hostname, 443)
http.use_ssl = true
http
end
def login(email_address, password)
# get login page
resp, data = @http.get(PATH_LOGIN, nil)
# extract hidden t value
d = Hpricot(data)
t = d.at("form").search("input[@name='t']")[0]['value']
# create postdata & headers
data = "t=#{t}&email=#{email_address}&password=#{password}"
headers = {
'Referer' => 'https://www.getdropbox.com/login',
'Content-Type' => 'application/x-www-form-urlencoded'
}
# post login-details
resp, data = @http.post(PATH_LOGIN, data, headers)
# get cookie
@cookie = resp.response['set-cookie']
return true
end
def ls(path)
path = URI.escape(path)
if @cache.key? path
puts "From cache: #{path}"
return @cache[path]
end
puts "Retrieving: #{path}"
# create header
headers = {
'Cookie' => @cookie,
'Referer' => 'https://www.getdropbox.com/home',
'Content-Type' => 'application/x-www-form-urlencoded'
}
resp, data = @http.get("#{PATH_BROWSE}/#{path}?ajax=no", headers)
doc = Hpricot(data)
entries = {}
# extract file deteails
doc.search("//div.browse-file-box-details") do |div|
# first link is filename/url
a = div.search("div.details-filename").at("a")
e = Entry.new( a.inner_html, a['href'].sub(/#{PATH_BROWSE}/, "") )
e.size = convert_size( div.at("div.details-size").inner_html )
e.date = Chronic.parse( div.at("div.details-modified").inner_html )
entries[e.name] = e unless e.url == '/' || e.name == "Parent directory"
end
@cache[path] = entries unless @cache.key? path
entries
end
def get_entry(request_path)
path, item = get_parts(request_path)
path = "/" + path if path == ""
ls path unless @cache.key? URI.encode(path)
path = URI.encode(path)
if @cache.key? path
return @cache[path][item]
else
return nil
end
end
def get_file(url)
u = URI.parse(url)
if file_cached?(u)
puts "From cache: #{url}"
return read_cached_file(u)
end
puts "Downloading: #{url}"
@file_http = setup_http(u.host) if @file_http.nil?
headers = {
'Cookie' => @cookie,
'Referer' => 'https://www.getdropbox.com/home',
'Content-Type' => 'application/x-www-form-urlencoded'
}
File.open(get_cache_filename(u), "w") do |f|
@file_http.get("#{u.path}?#{u.query}", headers) { |data| f.write data }
end
return read_cached_file(u)
end
def init_cache(email_address)
@cache_base = "#{ENV['HOME']}/.dropboxfs/#{email_address}"
create_path(@cache_base)
end
def file_cached?(uri)
File.exists?("#{@cache_base}/#{uri.path}")
end
def get_cache_filename(uri)
path, filename = get_parts(uri.path)
path = "#{@cache_base}/#{path}"
create_path(path)
"#{path}/#{filename}"
end
def read_cached_file(uri)
return File.read("#{@cache_base}/#{uri.path}")
end
def create_path(path)
return if File.exists?(path)
tmp = "/"
path.split("/").each do |dir|
next if dir.size == 0
tmp += "/#{dir}"
Dir.mkdir(tmp) unless File.exists?(tmp)
end
end
def get_parts(path)
pattern = /(.*)\/(.*)/
pattern =~ path
data = Regexp.last_match
# path, item
return data[1], data[2]
end
def convert_size(size)
pattern = /([0-9\.]+)([\D]+)/
pattern =~ size
match = Regexp.last_match
return if match.nil? || match.size != 3
size = match[1].to_f
size = case
when match[2] == " bytes" then size
when match[2] == "KB" then size * 1024
when match[2] == "MB" then size * 1024 * 1024
when match[2] == "GB" then size * 1024 * 1024 * 1024
else 0
end
size.to_i
end
end
if (ARGV.size < 2)
puts "Usage: #{$0} <directory> <emailaddress> [<password>]"
exit
else
dirname = ARGV[0]
end
if ARGV.size == 2
emailaddress = ARGV[1]
end
if ARGV.size == 3
password = ARGV[2]
else
password = ask("Enter your password: ") { |q| q.echo = "*" }
password.sub!(/\n/, "")
end
root = DropBoxFS.new(emailaddress, password)
FuseFS.set_root root
FuseFS.mount_under dirname
FuseFS.run # returns when unmounted
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment