Last active
December 21, 2015 11:18
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env ruby | |
# | |
# dropboxfs.rb | |
# | |
# Copyright (c) 2008 Raymond Sneekes ([email protected]) | |
# | |
# 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 == ' ' | |
end | |
def directory? | |
[email protected]? && @size.nil? | |
end | |
def file? | |
[email protected]? && [email protected]? | |
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