Dropbox
Dropbox is a file syncing app like Google Drive and One Drive with the easiest,
bug free setup of the three. Since there is an app component and a web component, this post will be divided into two parts: dropbox.com
and
Dropbox.app
. Additionally, Dropbox has a collaborative doc editor, similar to Google Docs, called Dropbox paper, which will be left for another post.
- Dropbox.com
- CSP headers? Yup
- File uploads
- File Search
- File Downloads
- What about deleting files?
- What about restoring files?
- Notifications
- Taking a look at the endpoints
- Refetching Data
- Open
- Settings
- Login & Logout
- Dropbox.app
- ~/.dropbox
- App contents
- What does file syncing look like?
Dropbox.com
The Dropbox website is a React.js app and judging by their CSS naming, it’s
internally called Maestro. Some things to note is that the app is not server
side render. Additionally, if you are logged in, you will automatically be directed to
https://dropbox.com/h
instead of https://dropbox.com
.
Note: basically every XHR request on the website includes is_xhr
and a
request tracking id t
, so we will omit those for conciseness.
so we will omit these
Packages
Some packages used include:
- jquery
- bloodhound
- flash-detect
- react-transition-group
- plupload which uses moxie
- reflux
- web-socket-js which uses swfobject
- tslib – TypeScript runtime helpers
- lodash
- classnames
- react
- react-dom
- prop-types
- keymaster
- react-dom-factories
- create-react-class
- react-addons-linked-state-mixin
- redux
- redux-thunk
- alameda
Lazy Loading
Dropbox uses lazy loading for their components, which means that components are fetched when they are needed instead of having one large JS bundle.
Their setup involves a wrapper component <LoadableComponent/>
and each
component is fetched in a small bundle.
Ideally, each page would only require a few chunks but the Dropbox’s setup involves around 90, which adds up to a little under a MB. As a consequence of number of bundles, it takes about 2 seconds for all the chunks to be processed and the page to be fully loaded.
CSP headers? Yup
Dropbox.com has their CSP headers setup.
# expanded CSP header
Content-Security-Policy:
script-src 'unsafe-eval' https://www.dropbox.com/static/compiled/js/ https://www.dropbox.com/static/javascript/ https://www.dropbox.com/static/api/ https://www.dropbox.com/page_success/ https://cfl.dropboxstatic.com/static/compiled/js/ https://www.dropboxstatic.com/static/compiled/js/ https://cfl.dropboxstatic.com/static/js/ https://www.dropboxstatic.com/static/js/ https://cfl.dropboxstatic.com/static/previews/ https://www.dropboxstatic.com/static/previews/ https://cfl.dropboxstatic.com/static/api/ https://www.dropboxstatic.com/static/api/ https://cfl.dropboxstatic.com/static/cms/ https://www.dropboxstatic.com/static/cms/ https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/ 'unsafe-inline' ;
img-src https://* data: blob: ;
child-src https://www.dropbox.com/static/serviceworker/ blob: ;
default-src 'none' ;
frame-src https://* carousel://* dbapi-6://* dbapi-7://* dbapi-8://* itms-apps://* itms-appss://* ;
worker-src https://www.dropbox.com/static/serviceworker/ blob: ;
style-src https://* 'unsafe-inline' 'unsafe-eval' ;
connect-src https://* ws://127.0.0.1:*/ws ;
object-src 'self' https://cfl.dropboxstatic.com/static/ https://www.dropboxstatic.com/static/ https://flash.dropboxstatic.com https://swf.dropboxstatic.com https://dbxlocal.dropboxstatic.com ;
media-src https://* blob: ;
font-src https://* data: ;
form-action 'self' https://www.dropbox.com/ https://dl-web.dropbox.com/ https://photos.dropbox.com/ https://paper.dropbox.com/ https://showcase.dropbox.com/ https://accounts.google.com/ https://api.login.yahoo.com/ https://login.yahoo.com/ ;
base-uri 'self' ;
report-uri https://www.dropbox.com/csp_log
Some interesting features are carousel://
, which was Dropbox’s great, but now
discontinued
photos app.
Additionally, there is a localhost
setup for websockets ws://127.0.0.1:*/ws
,
which dropbox tries and fails to connect to on page load. Odd, or maybe not.
File uploads
Dropbox’s file uploads use a multi-part setup similar to Twitch’s.
Dropbox has a drag and drop file upload which can be initiated by dragging a file anywhere on the page as well as a traditional file upload button.
The process works like follows:
-
On drag, Dropbox has a little notification popup that I believe is made with JQuery and a plain ol’
<div>
.Once we drop our file to upload, Dropbox then lazy loads the necessary chunks for the folder selection modal:
pkg-mover.js
pkg-selectable-list.js
fileops_modals.js
Which on load, makes some tracking HTTP requests along with a
POST
tohttps://www.dropbox.com/browse_util/tree_view_folders
with the folder as
/
, which returns the top-level folder structure of yourDropbox/
folder.When we expand a given folder, another request is made with the folder argument adjusted to
/apps
in our case.the responses have the following structure:
Array<{ read_only: boolean is_shared: boolean has_subdirs: boolean contained_ns: number path: string icon: string fileid: string }>
-
After selecting our desired folder and hitting upload,
An analytics request is made to
https://www.dropbox.com/log/web_upload_action
with the following form data about file size, extension, and time.
Then Dropbox sends another
POST
request tohttps://www.dropbox.com/cmd/upload_precheck
with the file upload path which gets response
{ "status": "success", "changesets": null, "failure_details": null, "rollback_hints": null, "new_browse_files": null }
-
Then Dropbox starts uploading the file chunks by first making an
OPTIONS
and then aPOST
request.Note: this
OPTIONS
request is due to CORS and the required preflight request.https://dl-web.dropbox.com/upload_web_file_block
with the number of blocks, block size, and an ID as query string params and the file chunk in the body of the request. In the case of this file, it was small enough to only need one chunk.
-
After uploading each chunk, Dropbox then commits the upload by first making an
OPTIONS
and thenPOST
request tohttps://dl-web.dropbox.com/commit_web_upload_file
with the destination path, the filename, file size, and whether or not to overwrite files and a unique identifier in the request body, getting a response of
{"status": "complete"}
-
Finally, Dropbox makes an analytics request to
https://www.dropbox.com/log/web_upload_action
logging the number of files upload, the file size, the file extension, and the time.
File Search
In the upper right corner of the dropbox home page is a search box for searching across your files.
On clicking into the search, Dropbox makes some HTTP requests associated with analytics. This is a theme across the site.
Once you start typing, a POST
request is made to
https://www.dropbox.com/search/dropdown_search
with the query
, folder path (fq_path
), and settings on showing Dropbox
paper docs, system links, and sugessions. There are some additional ids related
to analytics.
getting a response of type
{
search_results: Array<{
bytes: number
type: number
fq_path: string
has_mount_access_perms: boolean
icon: string
is_dir: boolean
is_in_team_folder_tree: boolean
is_symlink: boolean
is_unmounted: boolean
mount_access_perms: null
ns_id: number
ns_path: string
server_timestamp: number
sjid: number
ts: number
highlight_spans: Array<{
string: string
is_highlighted: boolean
}>
match_type: "FILENAME_ONLY"
search_result_type: "FILE"
}]
}
The search box makes a new request with each letter you type. Each of which take around 300-500ms. There is also an analytics request made with each keypress as well to
https://www.dropbox.com/searchclientlogger
with similar data to the actual search request along with the response time of the search.
Note that when a new key is pressed, the previous request to
/search/dropdown_search
is canceled, but the analytics request is not.
File Downloads
File downloads are staightfoward
For ~/Dropbox/work/site/index.html
Dropbox sends a GET
request to
https://dl-web.dropbox.com/get/work/site/index.html
with query params
_download_id: 8802024142707665304351494771994008944470470176929744910009710868181
_notify_domain: www.dropbox.com
_subject_uid: 17362572
dl: 1
revision_id: phBhfwFlVtjjVwOrFAotSiSOwoSmnlhobBWzNYrigSuDQyEVpsntgJuPiwNZgJOmPmAfTNMsHRdSTDsqgwexMtFCIMZslSCqugqoM-GbAjTmjHTtNCEOhCXJaXNzPAgauSJ
w: zJIVXjyUaUdyzZylQMKzXPFuhVcRoYHhkvfMdGHiELdO_fJ-w
Side note:
- Dropbox uses NGINX to proxy their requests and also has request tracking via
a
x-dropbox-request-id
header. - For static assets, like images, Dropbox uses Cloudflare as a CDN
What about deleting files?
Deleting index.html
only requires a POST
request to
https://www.dropbox.com/cmd/delete
with the file name and some tracking info, along with some additional analytics requests.
What about restoring files?
First you select a file and hit the restore button. Then Dropbox sends a POST
request to
https://www.dropbox.com/trash_details/ajax_by_sjids_batch
with form data
_subject_uid: 17362572
ns_sj_ids: {"96225992":[1973459]}
getting a response of
[{
"event_details": [{
"name": "index.html",
"src_path": "",
"can_display_content": true,
"size": "25.13 KB",
"fq_path": "/index.html",
"icon": "page_white_code_gray_32"
}],
"has_acl_restore": false,
"ns_id": 96225992
}]
Note: I’m not sure what ns
stands for but it’s likely related to the
identification of files and it will come up again later.
Then Dropbox makes another POST
request to
https://www.dropbox.com/2/restorations/changeset/restore_true_deletions_batch
with a payload of
{
"ns_cs_ids": [{
"ns_id": 96225992,
"cs_id": 437234937
}],
"initiation_location": {
".tag": "deleted_files"
}
}
with a response of
{
".tag": "complete",
"quota_status": {
".tag": "not_overquota"
}
}
Along with, you guessed it, analytics requests.
The restoring of files page also has a filtering mechanism for restoring files via a POST
to
https://www.dropbox.com/deleted_files_with_mixed_cs/ajax
with form data
ns_ids: 8917152990,9548858169,1477914422,7200743354,1071936718,32131521
ns_id_to_cursor: {
"32131521": 0,
"8917152990": 0,
"9548858169": 0,
"1477914422": 0,
"7200743354": 0,
"1071936718": 0,
}
start_ts: 1539057600
end_ts: 1540531298
actor_email:
host_ids:
fq_path: /books
_subject_uid: 13275036
where
start_ts
→ from dateend_ts
→ to dateactor_email
→ deleted byfq_path
→ in folder
getting response of type
{
ns_id_to_cursor: {
[key: string]: number
},
ns_ids: Array<number>
events: Array<{
pkey: string
is_dup: boolean
file_name: string
src_path: string
user_type: "non_team"
has_shared_folders: number
is_folder: boolean
host_id: number
reference_file_name: string
sf_with_access_count: number
icon: "page_white_gray_32"
ago: string
name: null
timestamp: number
browse_uri: string
actor_email: string
sf_without_access_count: number
owned_sf_count: number
file_count: number
is_user_owned_ns_id: boolean
cs_info: {
user_id: number
can_rollback: boolean
cs_id: number
is_read_only: boolean
sjids_for_display: Array<number>
can_purge: boolean
}
ns_id: number
containing_deletion_path: string
}>
}
Something interesting to note is that when multiple files are grouped together,
the browse_uri
field on the response is set to the following style:
https://www.dropbox.com/home/work/docs?select_multi=["notes.txt", "report.docx"]
Neat way to select multiple files.
Notifications
For notifications for things like file sharing, file comments, and Dropbox Paper, Dropbox has a bell icon and a drop down.
To fetch the notifications, Dropbox sends a POST
request to
https://www.dropbox.com/web/notifications/retrieve_user
with a specified limit of 100, getting a response of type
{
notifications: Array<{
status: number
seen_state: number
type_id: number
nid: string
bluenote_object: {
name: string
url: string
ignore: boolean
thumbnail_url: string
type: string
id: string
}
notification_div: null
role_label: string
bluenote_notification_type_id: "DROPBOX_SHARED_CONTENT" | "PAPER_COMMENT" | "NONE"
bluenote_actor: {
actor_display_name: string
actor_account_id: string
actor_initials: string
actor_avatar_url: string
}
user_id: number
bluenote_payload: {
notification_preferences_bucket: "shared_content" | "always_show" | "comment" | "task"
home_params: {
message: string
surface_action: {
ignore: boolean
params: {
url_path: string
}
action_name: "open_url"
}
button_actions: []
}
campaign_info: {
contentId: number
versionId: string
categoryId: number
campaignId: number
}
image: {
avatar_account_id: string
type: "avatar"
ignore: boolean
avatar_initials: string
avatar_url: string
system_local_icon: string
}
template_version: number
template_type: "generic"
ignore: boolean
preview: {
show_thumbnail: boolean
}
}
target_object_key: string
feed_time: number
bluenote_typed_data: {
server_path: string
file_obj_id: string
sharer_name: string
folder_name_on_web: null
backup_url_path: string
share_text: null
folder_name_on_desktop: null
ns_id: number
}
}>
latest_nid: {
[key: string]: string
}
bolt_token: {
[key: string]: string
}
}
Now, to fetch new notifications, Dropbox uses long polling.
In particular, Dropbox sends an OPTIONS
and then a POST
to
https://bolt.dropbox.com/2/notify/subscribe
with a payload of
{
"channel_states": [{
"channel_id": {
"app_id": "user_notification",
"unique_id": "17362572"
},
"revision": "01540347804794524440",
"token": "BhTEa4VCDClurY3BrWftM5nX/hDy+MIwHx8L28ZTeDHk5651f/6sFePs10MCcAMr2ws/jZa7Ba2CJ5cbZt02DbCVjwYAIw008rmkNOzM2ZsNoAWlxXywjO/69iO3hNm8n9vLIqe6EwXxVad73J7IK2tR"
}]
}
taking between 30 and 90 seconds before completing and creating a new request.
Taking a look at the endpoints
First we navigate around for a while with dev tools open and recording network requests.
Then we save our requests to a .har
file and with the help of jq
,
jq '.log.entries |
map({
"url": (
.request.url |
split("?") |
.[0] |
select(
(contains("dropboxstatic") or
contains("dl-") or
contains("png") or
contains(".js") or
contains(".css") or
contains("fonts") or
contains(".html") or
contains("jpeg")) | not
)
),
"method": .request.method
}) | .[] | "\(.method) \(.url)"' *.har |
sort |
uniq |
sed -e 's/"//g' -e "s/^ *//g" |
pbcopy
we have a list of endpoints
ws://127.0.0.1:17601/ws
ws://127.0.0.1:17602/ws
GET https://dropbox.com/hstsping
GET https://marketing.dropbox.com/login
GET https://www.dropbox.com/
GET https://www.dropbox.com/deleted_files
GET https://www.dropbox.com/get_pass_receiver_token
GET https://www.dropbox.com/get_pass_transmitter_token
GET https://www.dropbox.com/h
GET https://www.dropbox.com/home
GET https://www.dropbox.com/login
GET https://www.dropbox.com/logout
GET https://www.dropbox.com/nav_menu
GET https://www.dropbox.com/page_success/end
GET https://www.dropbox.com/page_success/head
GET https://www.dropbox.com/page_success/start
GET https://www.dropbox.com/photos
GET https://www.dropbox.com/security_checkup
GET https://www.google.com/recaptcha/api2/anchor
GET https://www.google.com/recaptcha/api2/bframe
GET ws://127.0.0.1:17600/ws
OPTIONS https://beacon.dropbox.com/1/update
OPTIONS https://bolt.dropbox.com/2/notify/subscribe
OPTIONS https://thunder.dropbox.com/2/payloads/subscribe
POST https://beacon.dropbox.com/1/update
POST https://bolt.dropbox.com/2/notify/subscribe
POST https://thunder.dropbox.com/2/payloads/subscribe
POST https://www.dropbox.com/2/app_actions/get_available_actions_for_user
POST https://www.dropbox.com/2/auth_logger/log_auth_event
POST https://www.dropbox.com/2/security_settings/get_active_web_sessions
POST https://www.dropbox.com/2/security_settings/get_linked_apps
POST https://www.dropbox.com/2/security_settings/get_linked_hosts
POST https://www.dropbox.com/2/security_settings/get_mobile_devices
POST https://www.dropbox.com/2/security_settings/get_twofactor_info
POST https://www.dropbox.com/2/security_settings/mark_sct_completed
POST https://www.dropbox.com/2/seen_state/get_coachmark_info
POST https://www.dropbox.com/2/seen_state/get_seen_state_info
POST https://www.dropbox.com/2/seen_state/get_seen_state_users
POST https://www.dropbox.com/2/seen_state/update_timestamps_and_mark_offline
POST https://www.dropbox.com/2/sharing/alpha/create_shared_link_with_settings
POST https://www.dropbox.com/2/sharing/alpha/get_file_metadata
POST https://www.dropbox.com/2/sharing/alpha/list_file_members
POST https://www.dropbox.com/2/sharing/alpha/list_shared_links
POST https://www.dropbox.com/2/sharing/alpha/update_file_policy
POST https://www.dropbox.com/2/sharing/get_file_member_counts
POST https://www.dropbox.com/2/users/get_account_batch
POST https://www.dropbox.com/2/users/get_current_account
POST https://www.dropbox.com/2/users/get_sharing_prefs
POST https://www.dropbox.com/2/users/get_space_usage
POST https://www.dropbox.com/activity/generate/json
POST https://www.dropbox.com/ajax_login
POST https://www.dropbox.com/ajax_verify_code
POST https://www.dropbox.com/alternate_wtl
POST https://www.dropbox.com/alternate_wtl_browser_performance_info
POST https://www.dropbox.com/browse_get_next
POST https://www.dropbox.com/browse_photos_events_data
POST https://www.dropbox.com/browse_photos_events_outline
POST https://www.dropbox.com/browse_photos_missing_date_data
POST https://www.dropbox.com/browse_util/tree_view_folders
POST https://www.dropbox.com/cmd/delete
POST https://www.dropbox.com/cmd/upload_precheck
POST https://www.dropbox.com/contact_search_log
POST https://www.dropbox.com/contacts/get
POST https://www.dropbox.com/deleted_files_with_mixed_cs/ajax
POST https://www.dropbox.com/get_browse_bolt_data
POST https://www.dropbox.com/get_growth_tci_home_plan_description_variant
POST https://www.dropbox.com/get_info_for_quota_upsell
POST https://www.dropbox.com/get_subgrowth_pro_oq_modal_recurring_variant
POST https://www.dropbox.com/growth/gating_check
POST https://www.dropbox.com/home_feed/get_file_metadata
POST https://www.dropbox.com/home_feed/log_activities
POST https://www.dropbox.com/home_feed/retrieve_paper_recents
POST https://www.dropbox.com/home_feed/retrieve_starred
POST https://www.dropbox.com/jse
POST https://www.dropbox.com/log/file_preview
POST https://www.dropbox.com/log/file_view
POST https://www.dropbox.com/log/home_availability
POST https://www.dropbox.com/log/notif_events
POST https://www.dropbox.com/log/pass_facepile
POST https://www.dropbox.com/log/security_checkup_log
POST https://www.dropbox.com/log/telemetry
POST https://www.dropbox.com/log/ux_analytics
POST https://www.dropbox.com/log/web_upload_action
POST https://www.dropbox.com/log/web_user_action
POST https://www.dropbox.com/log_js_sw_data
POST https://www.dropbox.com/portkey_chat_discoverability_variant
POST https://www.dropbox.com/preview_activity_log
POST https://www.dropbox.com/profile_services/connected_services
POST https://www.dropbox.com/prompt/ha
POST https://www.dropbox.com/prompt/log_impression
POST https://www.dropbox.com/prompt/main_campaign
POST https://www.dropbox.com/recents/check_files_in_root_collection
POST https://www.dropbox.com/recents/json
POST https://www.dropbox.com/recents/logger
POST https://www.dropbox.com/retrieve_unity_nonce
POST https://www.dropbox.com/share_tib_log
POST https://www.dropbox.com/should_show_file_preview_upsell
POST https://www.dropbox.com/should_show_uj_popup
POST https://www.dropbox.com/sso_state
POST https://www.dropbox.com/starred/get_status
POST https://www.dropbox.com/trash/namespace_list
POST https://www.dropbox.com/unity_connection_log
POST https://www.dropbox.com/unity_open_log
POST https://www.dropbox.com/web/notifications/retrieve_user
POST https://www.google.com/recaptcha/api2/reload
Although this list is incomplete, we can see how Dropbox generally constructs
their endpoints – with a purposeful, RPC style, mostly using POST
requests for
every task, including CRUD. Another thing to note is that the long poll
subscribe requests are on seperate sub-domains.
Refetching Data
In order to keep the homepage up to date, in addition to the long polling for notifications, dropbox uses a simple refetch when the page regains visibility.
When the page regains visibility, Dropbox fetches data from the following urls:
POST https://www.dropbox.com/recents/json
POST https://www.dropbox.com/home_feed/retrieve_paper_recents
POST https://www.dropbox.com/activity/generate/json
POST https://www.dropbox.com/starred/get_status
POST https://www.dropbox.com/home_feed/retrieve_starred
POST https://www.dropbox.com/recents/check_files_in_root_collection
POST https://www.dropbox.com/unity_connection_log
POST https://www.dropbox.com/recents/logger
POST https://www.dropbox.com/home_feed/get_file_metadata
POST https://www.dropbox.com/home_feed/retrieve_starred
POST https://www.dropbox.com/home_feed/get_file_metadata
POST https://bolt.dropbox.com/2/notify/subscribe
As we can see, Dropbox refetches almost all the data from its initial page load.
Open
When previewing a document, and not using safari, there is an additional button in the nav bar, Open.
Depending on the document, there will either be a button Open
or a button
dropdown with options for opening in a text editor (seems like the one that is
system default) or opening in finder.
Judging by the network requests and CSS on the page, this feature is called unity.
The way it works is like follows.
-
on page load
fetch the unity chunk
https://cfl.dropboxstatic.com/static/compiled/js/packaged/pkg-unity.min-vflNioxCV.js
Additionally, it will make an analytics associated
POST
request tohttps://www.dropbox.com/unity_connection_log
for every open button on the page. So if you are on the homepage and the 10 recent files are shown, then Dropbox makes an HTTP request for each of them. This stalls other requests which results in the page taking longer to load.
Also fetch the nonce for the unity feature via
https://www.dropbox.com/retrieve_unity_nonce
getting a response of
{ "status": "OK", "message": { "nonce": "0232c91bd3d6809c7853424ceb2c160964a91d849f458c31b38bccff6d2ceaab", "client_has_auth": true, "client_port": 17600 }, "html_response": false }
The
client_port
will come in handy later. -
When using the open button, Dropbox sends of some analytics requests to
https://www.dropbox.com/unity_connection_log https://www.dropbox.com/unity_open_log
But how does it open the local text editor or file browser?
Well, the previous outlined web socket connection to
localhost
at the begining of this post gives us a clue as does theclient_port
key of the nonce request.If we look at the help docs for the open button and in particular the firewall setup, we see that Dropbox needs ports
17600
and17603
for the open button.https://www.dropbox.com/help/desktop-web/open-button https://www.dropbox.com/help/desktop-web/configuring-firewall
If we look in the network tab on Safari and Firefox, we will see the websockets fail, but on Chrome, we see a websocket succesfully setup. But why do the websockets work in Chrome and not other browsers?
This is because Chrome is the only browser that has Flash setup by default.
But why does Dropbox need Flash to communicate over websockets? because
dropbox.com
uses TLS, while the local websocket connection doesn’t. Browsers only support secure websocketswss
when the page is secure to prevent mixed content.So Dropbox uses the Flash based
web-socket-js
library, mentioned earlier, to by pass this restriction and create insecure websocket requests to the local Dropbox client.In the
unity
file noted above, there is the following check to see if insecure, non-flash websockets can be used.e.canBrowserSupportNonSecureWebsocket = function() { var e = parseInt(i.version, 10); return !!(i.safari && e < 9) || !!(i.chrome && e >= 53) }
The websocket setup works as follows.
Note:
↓
means the web client received and↑
means the client sent the packet.namespace dropbox { interface Packet<F extends string, D, L extends object> { func: F header: { message_id: number should_respond: boolean log_data: L } data: D } type WebPacket<F extends string, D extends object> = Packet<F, D, { web_unity_version: number user_agent: string }> type AppPacket<F extends string, D> = Packet<F, D, { os_version: "mac" | "windows" | "linux" buildno: string client_unity_version: number unity_session_id: string }> type AppResponse<D> = AppPacket<"response", D> export type InitiateHandshake = WebPacket<"initiate_handshake", { secure_web: boolean }> export type CheckUnityVersion = AppPacket<"check_unity_version", { client_version: number }> export type FetchUnityNonce = AppPacket<"fetch_unity_nonce", { nonce_key: string }> export type VerifyUnityNonce = WebPacket<"verify_unity_nonce", { nonce: string }> export type CompleteHandshake = AppPacket<"complete_handshake", { is_success: true }> export type CheckFileBatch = WebPacket<"check_file_batch", { server_paths: string[] user_id: 17362572 }> export type CheckFileBatchResponse = AppResponse<{ [key: string]: { can_open_directly: boolean open_application_identifier: string // e.g. "com.apple.Preview" path_is_dir: boolean is_infinite_placeholder: boolean open_application_name: string // e.g. "Preview" is_locally_available: true } }> export type OpenFile = WebPacket<"open_file", { server_path: string user_id: number in_file_browser: boolean }> export type OpenFileResponse = AppResponse<boolean> }
For the handshake
// ↓ CheckUnityVersion // ↑ InitiateHandshake // ↓ FetchUnityNonce // ↑ VerifyUnityNonce // ↓ CompleteHandshake // ↑ CheckFileBatch // ↓ CheckFileBatchResponse
and then opening a file has the following structure
// ↑ OpenFile // ↓ OpenFileResponse
We can confirm both the handshake and open request via our friendly Wireshark.
Which matches the websocket packets.
And then then we can see the open request
which for every push of the button, results in 4 packets being exchanged.
If we examine a packet
we can see the response exchanged from the localhost to the browser which matches
OpenFileResponse
Which is then ferried to the analytics endpoint
https://www.dropbox.com/unity_open_log
Overall, quite a bit of work to open a file locally.
If you’d like to get into the nitty gritty details of the connection setup, you can take a peek at the js file referenced in step 1.
Settings
Settings page is a pretty common tab setup.
Editing works using modals like Trello.
Login & Logout
How does their auth system work?
Login
When using the email and password login, once the email field is determined to be valid, Dropbox will send a request to
https://www.dropbox.com/sso_state
with the email as form data.
So when typing in j.shmoe@example.com
, Dropbox will send a request for
j.shmoe@example.co
as well as j.shmoe@example.com
. Additionally, Dropbox
won’t make an extra request if you retype the same email.
Dropbox then logins in via a POST
to
https://www.dropbox.com/ajax_login
with form data
login_email: j.shmoe@example.com
login_password: password123
cont: /
remember_me: true
g-recaptcha-response-v3: 03AMGVjXiny8WAxrC6wSJjufd-eXIJGMShSWhyS1L5Sh-3VlzaagL9c4bsjqSZJ7sSt9xZBZqcbl1_ebYrkEm4cQQMuEPub2G_SW5Nms4u0MkKlxGK9EniRY20pZVDViGkAZKFch4H0dKLF_2DXKOAWVFX4Mlvxh853HVhxq_8V58-IQ6cY9utUIpf_jCc-7aon4KghU8AHvWhTk3KMdBIzm28cHOk492DhwLF4z1-C1WLncliWbdDLm_O6M9_hKPT7TAPZkcuUoC_SIS-ztu6ad5lhBOirW5jJQ
signup_data:
third_party_auth_experiment: EXPERIMENT
require_role:
signup_tag:
getting a response of
{
status: "TWOFACTOR"
remember_me: 1,
u2f_challenge: {
"u2f_js1.1": {
challenge: string
registeredKeys: [{
publicKey: string
keyHandle: string
version: "U2F_V2"
transports: null
appId: "https://www.dropbox.com/u2f-app-id.json"
}]
appId: "https://www.dropbox.com/u2f-app-id.json"
}
webauthn_wd07: {
rpId: "www.dropbox.com"
challenge: string
extensions: {
appid: "https://www.dropbox.com/u2f-app-id.json"
}
allowCredentials: [{
transports: []
key_format: 1
type: "public-key"
id: string
public_key: string
}]
}
},
use_email_2fa: null
last_two_digits: null
email: string
}
Note: the u2f_challenge
is actually a string of JSON.
After sending the email
and password
, if 2-factor is enabled then Dropbox
will prompt for either a
but if you are on a browser which supports hardware keys, like Chrome, you will be asked to insert your key
The Yubikey page will send a POST
request to
https://www.dropbox.com/ajax_verify_code
with form data
remember_me: true
cont: /
code: {"protocol":"u2f_js1.1","response":{"errorCode":5}}
trusted: false
signup_data:
third_party_auth_experiment: EXPERIMENT
require_role:
signup_tag:
which is also the same URL for verifying 2-factor codes
remember_me: true
cont: /
code: 566285
trusted: false
signup_data:
third_party_auth_experiment: EXPERIMENT
require_role:
signup_tag:
Interestingly enough, when the 2-factor code is false, the server responds with a 200 request
and an a invalid JSON request of
err:{"code": {"message_text": "Invalid code"}}
Note, there are some additional requests to recaptcha.
After entering a correct 2-factor code, then Dropbox will redirect to
dropbox.com
with a new page that has the React bundle. This page also has
server rendered state that is preloaded into the React app. In particular, it has data for:
- unread notifications
- recent files
- recent Dropbox Paper files
- starred files
- ab testing (shown below)
{
tasks: {
forOthers: [],
completed: [],
forMe: []
},
calendarProtoData: {
shouldShow: false,
outlookEnabled: false,
isAuthed: false,
onboardingProps: {
attachmentOnboardingVariant: null,
showAttachDialog: false,
showHomeSurvey: false,
showJewelOnboarding: false,
showSectionOnboarding: false,
showDragOnboarding: false
},
devFeaturesEnabled: false,
userRole: 'personal',
suggestionsEnabled: false
},
teamInsights: {
variant: 'OFF'
},
searchBarExperiments: {
expPaperFolders: false,
expDeletedFilterRightRail: 'OFF',
expQuerySuggestions: 'OFF',
expSearchSuccessBanner: false,
expSearchDropdownUpsell: 'OFF',
expSinglePageSearch: false,
expDesktopTraySearchNotification: false,
expFileTypeFilter: 'OFF',
expUseFileInfoEndpoint: true,
expShowSymlinkIcon: false,
expUseNewFolderPicker: false
},
maybeRenderTeamAdminModals: false,
onboardingData: {
onboardingComplete: false,
showUnreadOnboarding: true
},
bannerProps: {
showMigrateBanner: false,
isPaperEnabledForUser: true,
isTeam: false
},
renderSnapEngage: false,
uploaderPostTTIExperiments: {},
expEsignatureEnabled: false,
homeGrowthExperiments: {
numRemainingLicenses: null,
expSubgrowthBizTeamsSuggestionsOnHome: 'OFF',
recommendedMembers: [],
expSubgrowthBizTeamsRecMembersHome: 'OFF'
},
cloudDocsExperiments: {
expGddCreate: false,
expFileCreatePrimaryButton: false,
expPaperCreate: false
}
}
And now that all the cookies are set, auth is complete.
Logout
In a faux pas, Dropbox uses a GET
request to
https://www.dropbox.com/logout
which logs out the user out and then redirects to the login page.
Dropbox.app
How does opening the website from the app work?
Clicking on the globe in the app will open the dropbox website, automatically logging in.
Essentially, the Dropbox app creates a temp .html
file (see below), which contains a
nonce that is used for login and then open
s it.
<!DOCTYPE html>
<!-- saved from url=(0023)https://www.dropbox.com -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript">
(function() {
"use strict";
function join_nonce(b, c) {
if (b == undefined || c == undefined || c.length !== b.length || (c.length % 2) !== 0) {
// Invalid inputs; Return empty string.
return null;
}
// hex-decode and xor
var a = '';
for (var i = 0; i !== c.length; i += 2) {
a += String.fromCharCode(parseInt(c.slice(i, i + 2), 16) ^ parseInt(b.slice(i, i + 2), 16));
}
return a;
}
function go() {
var b_parts = window.location.hash.substr(1).split('-');
// Clear the URI fragment, so that we don't try to re-use the
// nonce if the user presses 'Back'.
window.location.hash = '';
var inputs = document.getElementsByName('n');
var broken = false;
for (var i = 0; i < inputs.length; i++) {
var input = inputs.item(i);
var b = b_parts[i];
var c = input.getAttribute('data-nonce-c');
var n = join_nonce(b, c);
if (n === null) {
broken = true;
n = '';
}
input.setAttribute('value', n);
}
if (broken) {
// If any of the nonces are broken, don't send any.
for (var i = 0; i < inputs.length; i++) {
inputs.item(i).setAttribute('value', '');
}
}
document.desktop_login.submit();
setTimeout(function() {
window.location = unescape("%68%74%74%70%73%3A%2F%2F%77%77%77%2E%64%72%6F%70%62%6F%78%2E%63%6F%6D%2F%68%6F%6D%65");
}, 30000);
}
window.onload = go;
})();
</script>
<style type="text/css">
body {
text-align: center;
}
html,
body {
height: 100%;
}
body:before {
content: '';
display: inline-block;
height: 100%;
vertical-align: middle;
margin-right: -0.25em;
/* Adjusts for spacing */
}
.container {
display: inline-block;
}
</style>
<title>Dropbox</title>
</head>
<body>
<div class="container">
<img width="40" height="40" src="--snip--">
</div>
<form name="desktop_login" action="https://www.dropbox.com/desktop_login" method="post">
<input type="hidden" name="buildno" value="Dropbox-mac-61.3.90">
<input type="hidden" name="u" value="h">
<input type="hidden" name="c" value="">
<input type="hidden" name="i" value="4737312849">
<input type="hidden" name="n" value="" data-nonce-c="7AA8002DC9152CD7D2D90D14025F0D1907B669A69F7A8F4D9F886B39F3B81FA381134C42430FC8C507821A">
<noscript>
<div class="center">
<meta id="meta-refresh" http-equiv="refresh" content="2;URL=https://www.dropbox.com/login?cont=%2F%68">
<p>Dropbox can't log you in automatically because your browser has scripts disabled.</p>
<a href="https://www.dropbox.com/login?cont=%2F%68">Sign in manually</a>
</div>
</noscript>
</form>
</body>
</html>
It uses a nonce, so how does it generate new ones?
Not sure. Dropbox doesn’t make any network requests on the button press. One way to generate the nonces would be through a shared secret, where Dropbox just generates a new nonce each time the globe is pressed.
~/.dropbox
.
├── Crashpad
│ ├── completed
│ │ └── 806c3981-e568-4461-805b-96caa2a4ba45.dmp
│ ├── new
│ ├── pending
│ └── settings.dat
├── QuitReports
│ └── 573a220f-edb7-4102-8658-9edbf78616ff.dbt
├── dropbox.pid
├── events
│ ├── 1-5bd5510d
│ ├── 1-5bd5510f
│ ├── 1-5bd55111
│ ├── 1-5bd55113
│ ├── 1-5bd55116
│ └── 1-5bd55119
├── host.db
├── info.json
├── instance1
│ ├── TO_HASH_byihg40j
│ ├── aggregation.dbx
│ ├── checker.dbx
│ ├── config.db
│ ├── config.dbx
│ ├── deleted.dbx
│ ├── filecache.dbx
│ ├── filecache.dbx-shm
│ ├── filecache.dbx-wal
│ ├── hostkeys
│ ├── local_view_manager
│ │ └── saved_session.db
│ ├── notifications.dbx
│ ├── photo.dbx
│ ├── resync.dbx
│ ├── s58f54491
│ ├── sigstore.dbx
│ ├── th
│ └── unlink.db
├── instance_db
│ ├── hostkeys
│ └── instance.dbx
├── logs
│ ├── 0
│ └── 1
│ ├── 1-5bd550eb
│ ├── 1-5bd550f9
│ └── 1-5bd55108
├── machine_storage
│ ├── file_icon
│ │ └── a8564bf4da2eebbc212ceef93d588891.png
│ ├── icon.db
│ ├── local_view_manager
│ │ └── saved_session.db
│ └── tray-thumbnails.db
└── unlink.db
Basically, any .db
you see is an sqlite database, and a .dbx
is an
encrypted sqlite database.
Nothing too interesting, except for Crashpad/
.
Crashpad is the error reporting for Chromium.
Although the .dmp
file in Crashpad/completed/
is mostly binary, we can
decode it with
minidump_stackwalk
.
However, I am much too lazy to download Chromium and build minidump_stackwalk
so we’ll just use strings
instead.
strings ~/.dropbox/Crashpad/completed/*.dmp | sort -u | pbcopy
The resulting output can be divided into a few categories as it is quite large ~1800 lines.
Importantly, we can see that the Dropbox app is written mainly in Python with some libraries to talk to Cocoa. As we have already seen from the Crashpad format, Chromium is used, in particular, Chromium Embedded Framework
-
Environment
This includes environmental variables, Dropbox versions, USB devices, recent folders and files, username, and email.
# the environmental vars executable_path=/Applications/Dropbox.app/Contents/MacOS/Dropbox TMPDIR=/var/folders/7b/44cxjcbn5q3c9chky5q9h_f00000gn/T/ __CF_USER_TEXT_ENCODING=0x1F5:0x0:0x0 SHELL=/usr/local/bin/bash HOME=/Users/jshmoe Apple_PubSub_Socket_Render=/private/tmp/com.apple.launchd.9HELC1g2ly/Render SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.nyOexY3g8a/Listeners DBX_PREINSTALL_OUT= LOGNAME=jshmoe PATH=/usr/bin:/bin:/usr/sbin:/sbin DISPLAY=/private/tmp/com.apple.launchd.KDq2MnQbrr/org.macosforge.xquartz:0 XPC_SERVICE_NAME=com.apple.xpc.launchd.oneshot.0x10000051.Dropbox COMMAND_MODE=unix2003 USER=jshmoe PATH=/usr/bin:/bin:/usr/sbin:/sbin DU_INSTALL_PATH=/Applications/Dropbox.app SSH_AUTH_SOCK=/private/tmp/com.apple.launchd.nyOexY3g8a/Listeners XPC_FLAGS=0x1 XPC_SERVICE_NAME=com.apple.xpc.launchd.oneshot.0x10000051.Dropbox
-
ObjC libraries
All the native Apple libraries including those related to Webkit and Quartz.
-
Python Libraries
We also see that the Dropbox client is using Python 3.5.
-
Chromium Embedded Libraries
-
Python stack trace
Which references
PyObjCTools/AppHelper.py
contextlib.py
dropbox/client/bolt/base.py
dropbox/client/bolt/signalling.py
dropbox/client/camera_upload/photouploader.py
dropbox/client/desktop_login/__init__.py
dropbox/client/high_trace.py
dropbox/client/idlehands/__init__.py
dropbox/client/main.py
dropbox/client/message_queue.py
dropbox/client/periodic.py
dropbox/client/presence/beacon.py
dropbox/client/presence/controller.py
dropbox/client/presence/threads.py
dropbox/client/reporting/event.py
dropbox/client/shell/mac.py
dropbox/client/unity/connection.py
dropbox/client/view_aggregator.py
dropbox/client/watchdog.py
dropbox/client_api/cancellable_select.py
dropbox/client_api/connection_hub.py
dropbox/client_api/dropbox_connection.py
dropbox/client_api/kv_connection.py
dropbox/collaboration/threading.py
dropbox/db_thread.py
dropbox/dispatch.py
dropbox/fileevents/__init__.py
dropbox/fileevents/dbfsevents.py
dropbox/foundation/actors/_impl.py
dropbox/foundation/futures/sleep/__init__.py
dropbox/foundation/futures/thread.py
dropbox/foundation/runtime/__init__.py
dropbox/foundation/transitional/connection_pool.py
dropbox/gui.py
dropbox/list_legacy.py
dropbox/native_event.py
dropbox/native_queue.py
dropbox/native_threading/python3.py
dropbox/sqlite3_helpers.py
dropbox/sync_engine/download_block.py
dropbox/sync_engine/download_meta.py
dropbox/sync_engine/file_cache/file_cache.py
dropbox/sync_engine/file_cache/sqlite_backend.py
dropbox/sync_engine/file_cache/sync_logic/local_events.py
dropbox/sync_engine/file_cache/transactions.py
dropbox/sync_engine/file_cache/util.py
dropbox/sync_engine/hashing.py
dropbox/sync_engine/infinite/download/dispatcher_thread.py
dropbox/sync_engine/infinite/download/metadata_fetcher.py
dropbox/sync_engine/p2p/discovery.py
dropbox/sync_engine/p2p/server.py
dropbox/sync_engine/prehashing.py
dropbox/sync_engine/reconstruct.py
dropbox/sync_engine/reindex.py
dropbox/sync_engine/sfj_consistency_checker.py
dropbox/sync_engine/sync_engine.py
dropbox/sync_engine/threads.py
dropbox/sync_engine/upload_block.py
dropbox/sync_engine/upload_meta.py
dropbox/threadutils.py
dropbox/trace.py
queue.py
threading.py
tornado/ioloop.py
tornado/platform/kqueue.py
ui/cocoa/uikit.py
-
Misc. Symbols
Which include some familar faces like Unity as well as some new ones like Harmony, which we will explore later.
Some other interesting symbols include the p2p threadpools, which I guess are for lan sync, as well as screenshots processing for the auto uploading of screenshots to Dropbox.
Additionally there is INFINITE, which is probably related to Project Infinite which allows Dropbox to sync files on demand when you click on them.
_db_thread__ACTIVITY_PROVIDER _db_thread__AUTHENTICATE _db_thread__BACKGROUNDWORKER _db_thread__BEACON _db_thread__CLIENT_SHARING _db_thread__CLIENT_SHARING_NETWORK _db_thread__CONNECTIVITY_WATCH _db_thread__DBFSEventThread _db_thread__DOWNLOAD_BLOCK_REQUEST _db_thread__DOWNLOAD_BLOCK_RESPONSE _db_thread__EVENTREPORTER _db_thread__FILEEVENTS _db_thread__FILE_VIEW_WATCHER_PROCESSING _db_thread__FINDER_SERVER _db_thread__FINDER_SERVER_REPORTER _db_thread__GLOBAL_POOL_0 _db_thread__GLOBAL_POOL_1 _db_thread__GLOBAL_POOL_2 _db_thread__GLOBAL_POOL_3 _db_thread__HARMONY _db_thread__HASH _db_thread__HOURLY_REFRESH _db_thread__IDLEHANDLER _db_thread__INFINITE_CONFIGURATION _db_thread__INFINITE_DOWNLOAD _db_thread__INFINITE_DOWNLOAD_BLOCK_REQUEST _db_thread__INFINITE_METADATA _db_thread__INFINITE_PAGING_WORKER _db_thread__LIST _db_thread__LIST_LEGACY _db_thread__NAVIGATIONSERVICEWORKER _db_thread__NOTIFICATION_RETRIEVAL _db_thread__P2P_DISCOVER _db_thread__P2P_REQUEST _db_thread__P2P_SERVER _db_thread__PERIODIC _db_thread__PERSISTENT_EVENT_REPORTER _db_thread__PHOTOUPLOADER _db_thread__PREHASH _db_thread__PRESENCE _db_thread__RECONSTRUCT _db_thread__REINDEX _db_thread__RTRACE _db_thread__SCREENSHOTS_PROCESSING _db_thread__SEARCHTHREAD _db_thread__SFJ_CONSISTENCY_CHECKER _db_thread__SUPPORTACTIONUPDATETHREAD _db_thread__SUPPORT_ACTION_MANAGER _db_thread__SYNC_UI_UPDATE _db_thread__SubscribeThread _db_thread__ThunderThread _db_thread__UNITYSTARTSTOP _db_thread__UNITYWORKER _db_thread__UPLOAD_BLOCK_REQUEST _db_thread__UPLOAD_BLOCK_RESPONSE _db_thread__UPLOAD_META _db_thread__USERNOTIFICATION _db_thread__VIEW_AGGREGATOR_PRIMARY _db_thread__WATCHDOG _x__db_thread__ActorThread_1 _x__db_thread__ActorThread_2 _x__db_thread__ActorThread_3 _x__db_thread__ActorThread_4 _x__db_thread__ActorThread_5 _x__db_thread__BEACON_CONNECTION _x__db_thread__BEACON_HEARTBEAT_LOOP _x__db_thread__BEACON_UPDATE_LOOP _x__db_thread__DESKTOP_LOGIN_PREFETCH_PRIMARY _x__db_thread__HARMONY_BACKGROUND _x__db_thread__HARMONY_PLUGIN _x__db_thread__HARMONY_REFRESH_UI_LOOP _x__db_thread__INFINITE_WRITER_worker_0 _x__db_thread__PRESENCE_GET_CONTEXT _x__db_thread__SYNC_CALLBACKS_worker_0 _x__db_thread__SYNC_TRANSITION_worker_0 _x__db_thread__SYNC_TYPE_PREFETCH_worker_0 _x__db_thread__SYNC_WORKER_worker_0 _x__db_thread__USER_CACHE _x__db_thread__default_request_pool_worker_0 _x__db_thread__default_request_pool_worker_1 _x__db_thread__p2p_client_threadpool_worker_0 _x__db_thread__p2p_client_threadpool_worker_1 _x__db_thread__p2p_request_threadpool_get_worker_0 _x__db_thread__p2p_request_threadpool_head_worker_0 _x__db_thread__p2p_server_handler_pool_get_worker_0 _x__db_thread__p2p_server_handler_pool_head_worker_0 _x__db_thread__retrieve_batch_request_pool_worker_0 _x__db_thread__retrieve_batch_request_pool_worker_1 _x__db_thread__retrieve_batch_request_pool_worker_2 _x__db_thread__retrieve_batch_request_pool_worker_3 _x__db_thread__sleep_thread _x__db_thread__upload_request_pool_worker_0 _x__db_thread__upload_request_pool_worker_1 _x__db_thread__upload_request_pool_worker_2 _x__db_thread__upload_request_pool_worker_3 _x__db_thread__xtl_netreq_0 _x__db_thread__xtl_netreq_1 _x__db_thread__xtl_netreq_2 _x__db_thread__xtl_netreq_3 _x__db_thread__xtl_netreq_4
App contents
If we take a look at /Applications/Dropbox.app/
, we can get an idea of its
general structure.
In particular, we see Harmony and Infinite again along with all the Python dependencies, some of which we saw earlier.
There is also rsync
, a library for syncing files.
Queue the infamous HN comment.
We can see an additional app, Dropbox Web Helper.app which I’m not sure of its purpose.
Overall, the app is mostly images and localization, running with the help of a fair bit of python.
.
└── Contents
├── Frameworks
│ ├── Tungsten.framework
│ │ ├── Resources -> Versions/Current/Resources
│ │ ├── Tungsten -> Versions/Current/Tungsten
│ │ └── Versions
│ │ ├── A
│ │ │ ├── Frameworks
│ │ │ │ ├── Chromium\ Embedded\ Framework.framework
│ │ │ │ │ ├── Chromium\ Embedded\ Framework
│ │ │ │ │ ├── Resources
│ │ │ │ │ │ ├── Info.plist
│ │ │ │ │ │ ├── am.lproj
│ │ │ │ │ │ │ └── locale.pak
│ │ │ │ │ │ ├── ar.lproj
│ │ │ │ │ │ │ └── locale.pak
│ │ │ │ │ │ ├── bg.lproj
│ │ │ │ │ │ │ └── locale.pak
│ │ │ │ │ │ ├── bn.lproj
│ │ │ │ │ │ │ └── locale.pak
│ │ │ │ │ │ │ -- snipped --
│ │ │ │ │ │ └── zh_TW.lproj
│ │ │ │ │ │ └── locale.pak
│ │ │ │ │ └── _CodeSignature
│ │ │ │ │ └── CodeResources
│ │ │ │ └── Dropbox\ Web\ Helper.app
│ │ │ │ └── Contents
│ │ │ │ ├── Info.plist
│ │ │ │ ├── MacOS
│ │ │ │ │ └── Dropbox\ Web\ Helper
│ │ │ │ ├── PkgInfo
│ │ │ │ └── _CodeSignature
│ │ │ │ └── CodeResources
│ │ │ ├── Headers
│ │ │ │ ├── TNApplication.h
│ │ │ │ ├── TNFrameInfo.h
│ │ │ │ ├── TNNavigationAction.h
│ │ │ │ ├── TNPreferences.h
│ │ │ │ ├── TNScriptMessage.h
│ │ │ │ ├── TNUserContentController.h
│ │ │ │ ├── TNUserScript.h
│ │ │ │ ├── TNWebView.h
│ │ │ │ ├── TNWebViewConfiguration.h
│ │ │ │ └── tungsten.h
│ │ │ ├── Resources
│ │ │ │ └── Info.plist
│ │ │ ├── Tungsten
│ │ │ └── _CodeSignature
│ │ │ └── CodeResources
│ │ └── Current -> A
│ ├── libdropbox_bootstrap.dylib
│ ├── libdropbox_crashpad.dylib
│ ├── libdropbox_python.3.5.dylib
│ ├── libdropbox_sqlite_ext.dylib
│ ├── libdropbox_watchdog.dylib
│ ├── libffi.6.dylib
│ ├── libffi.dylib -> libffi.6.dylib
│ ├── librsync.1.0.2.dylib
│ ├── librsync.1.dylib -> librsync.1.0.2.dylib
│ └── librsync.dylib -> librsync.1.0.2.dylib
├── Info.plist
├── MacOS
│ └── Dropbox
├── PkgInfo
├── PlugIns
│ └── garcon.appex
│ └── Contents
│ ├── Info.plist
│ ├── MacOS
│ │ └── garcon
│ ├── Resources
│ │ ├── Assets.car
│ │ ├── context-menu-icon.icns
│ │ ├── da_DK.lproj
│ │ │ ├── InfoPlist.strings
│ │ │ └── garcon.strings
│ │ ├── de.lproj
│ │ │ ├── InfoPlist.strings
│ │ │ └── garcon.strings
│ │ ├── en.lproj
│ │ │ -- snipped --
│ │ ├── nl_NL.lproj
│ │ │ ├── InfoPlist.strings
│ │ │ └── garcon.strings
│ │ ├── overlay-error.icns
│ │ ├── overlay-ignored.icns
│ │ ├── overlay-infinite.icns
│ │ ├── overlay-mixedstate.icns
│ │ ├── overlay-syncing.icns
│ │ ├── overlay-uptodate.icns
│ │ └── zh_TW.lproj
│ │ ├── InfoPlist.strings
│ │ └── garcon.strings
│ └── _CodeSignature
│ └── CodeResources
├── Resources
│ ├── CameraQuotaSplash.tiff
│ ├── CameraSplashDrawing.tiff
│ ├── CameraUploadsMigration.tiff
│ ├── DropboxAppFolderIcon.icns
│ ├── DropboxAppFolderIconYosemite.icns
│ ├── DropboxAppFolderIconYosemite.icns.rsrc
│ ├── DropboxBundle.bundle.tgz
│ ├── DropboxCameraUploadsFolderIcon.icns
│ ├── DropboxCameraUploadsFolderIconYosemite.icns
│ ├── DropboxCameraUploadsFolderIconYosemite.icns.rsrc
│ ├── DropboxFolderIcon.icns
│ ├── DropboxFolderIconYosemite.icns
│ ├── DropboxFolderIconYosemite.icns.rsrc
│ ├── DropboxHelperInstaller.tgz
│ ├── DropboxKext-canary.kext.tgz
│ ├── DropboxKext-dev.kext.tgz
│ ├── DropboxKext-stable.kext.tgz
│ ├── DropboxMacUpdate.app
│ │ └── Contents
│ │ ├── Frameworks
│ │ │ └── CrashReporter.framework
│ │ │ ├── CrashReporter -> Versions/Current/CrashReporter
│ │ │ ├── Resources -> Versions/Current/Resources
│ │ │ └── Versions
│ │ │ ├── A
│ │ │ │ ├── CrashReporter
│ │ │ │ ├── Resources
│ │ │ │ │ └── Info.plist
│ │ │ │ └── _CodeSignature
│ │ │ │ └── CodeResources
│ │ │ └── Current -> A
│ │ ├── Info.plist
│ │ ├── MacOS
│ │ │ └── DropboxMacUpdate
│ │ ├── PkgInfo
│ │ └── _CodeSignature
│ │ └── CodeResources
│ ├── DropboxQL.qlgenerator
│ │ └── Contents
│ │ ├── Info.plist
│ │ ├── MacOS
│ │ │ └── DropboxQL
│ │ └── _CodeSignature
│ │ └── CodeResources
│ ├── DropboxReadOnlySharedFolderIcon.icns
│ ├── DropboxReadOnlySharedFolderIconYosemite.icns
│ ├── DropboxReadOnlySharedFolderIconYosemite.icns.rsrc
│ ├── DropboxReadOnlyTeamFolderIcon.icns
│ ├── DropboxReadOnlyTeamFolderIconYosemite.icns
│ ├── DropboxReadOnlyTeamFolderIconYosemite.icns.rsrc
│ ├── DropboxSharedFolderIcon.icns
│ ├── DropboxSharedFolderIconYosemite.icns
│ ├── DropboxSharedFolderIconYosemite.icns.rsrc
│ ├── DropboxTeamFolderIcon.icns
│ ├── DropboxTeamFolderIconYosemite.icns
│ ├── DropboxTeamFolderIconYosemite.icns.rsrc
│ ├── DropboxTeamMemberFolderIcon.icns
│ ├── DropboxTeamMemberFolderIconYosemite.icns
│ ├── DropboxTeamMemberFolderIconYosemite.icns.rsrc
│ ├── DropboxViewNameOnlySharedFolderIcon.icns
│ ├── DropboxViewNameOnlySharedFolderIconYosemite.icns
│ ├── DropboxViewNameOnlySharedFolderIconYosemite.icns.rsrc
│ ├── FinderLoadBundle.tgz
│ ├── HarmonyOnboarding_1.tiff
│ ├── HarmonyOnboarding_2.tiff
│ ├── HarmonyOnboarding_3.tiff
│ ├── PrefsImport.tiff
│ ├── PrefsLinkSecond.tiff
│ ├── PrefsLinkSecondYosemite.tiff
│ ├── PrefsLinkingDisabled.tiff
│ ├── PrefsNotifications.tiff
│ ├── PrefsSelSyncWarning.tiff
│ ├── ScreenshotsBox.tiff
│ ├── ServerViewsErrorGeneric.tiff
│ ├── ServerViewsErrorNetwork.tiff
│ ├── SharedFolderAlert.tiff
│ ├── SharingErrorCir.tiff
│ ├── SharingWarnCir.tiff
│ ├── account-popup-arrows.png
│ ├── account-popup-arrows@2x.png
│ ├── box_32.png
│ ├── box_36.png
│ ├── box_40.png
│ ├── box_64.png
│ ├── box_white_background_32.tiff
│ ├── camera-import.png
│ ├── camera-import@2x.png
│ ├── collaboration-bang.png
│ ├── collaboration-bang@2x.png
│ ├── collaboration-blueConflict.png
│ ├── collaboration-blueConflict@2x.png
│ ├── collaboration-blueLink.png
│ ├── collaboration-blueLink@2x.png
│ ├── collaboration-blueLogo.png
│ ├── collaboration-blueLogo@2x.png
│ ├── collaboration-comment.png
│ ├── collaboration-comment@2x.png
│ ├── collaboration-conflicted.png
│ ├── collaboration-conflicted@2x.png
│ ├── collaboration-email.png
│ ├── collaboration-email@2x.png
│ ├── collaboration-history.png
│ ├── collaboration-history@2x.png
│ ├── collaboration-lock.png
│ ├── collaboration-lock@2x.png
│ ├── collaboration-logo.png
│ ├── collaboration-logo@2x.png
│ ├── collaboration-logoOutsideDropbox.png
│ ├── collaboration-logoOutsideDropbox@2x.png
│ ├── collaboration-share.png
│ ├── collaboration-share@2x.png
│ ├── collaboration-shared-folder.png
│ ├── collaboration-shared-folder@2x.png
│ ├── collaboration-update.png
│ ├── collaboration-update@2x.png
│ ├── copy-link-icon.png
│ ├── copy-link-icon@2x.png
│ ├── da_DK.lproj
│ │ └── InfoPlist.strings
│ ├── dbaccessperm.tgz
│ ├── dbfseventsd.tgz
│ ├── dbkextd.tgz
│ ├── dbxlink.icns
│ ├── de.lproj
│ │ └── InfoPlist.strings
│ ├── dropbox-business.png
│ ├── dropbox-business@2x.png
│ ├── dropbox-folder.png
│ ├── dropbox-folder@2x.png
│ ├── dropbox-personal.png
│ ├── dropbox-personal@2x.png
│ ├── dropbox-team.png
│ ├── dropbox-team@2x.png
│ ├── dropbox-upgrade.png
│ ├── dropbox-upgrade@2x.png
│ ├── dropbox-website.png
│ ├── dropbox-website@2x.png
│ ├── dropboxstatus-broken.tiff
│ ├── dropboxstatus-busy.tiff
│ ├── dropboxstatus-cam.tiff
│ ├── dropboxstatus-connecting.tiff
│ ├── dropboxstatus-idle.tiff
│ ├── dropboxstatus-notification_alt.tiff
│ ├── dropboxstatus-notification_bell.tiff
│ ├── dropboxstatus-notification_cutout.tiff
│ ├── dropboxstatus-notification_cutout_darkmode.tiff
│ ├── dropboxstatus-notification_reddot.tiff
│ ├── dropboxstatus-notification_reddot_darkmode.tiff
│ ├── dropboxstatus-paused.tiff
│ ├── dropboxstatus-snooze.tiff
│ ├── emblem-dropbox-infinite.icns
│ ├── emblem-dropbox-mixedstate.icns
│ ├── emblem-dropbox-selsync.icns
│ ├── emblem-dropbox-syncing.icns
│ ├── emblem-dropbox-unsyncable.icns
│ ├── emblem-dropbox-uptodate.icns
│ ├── empty.docx
│ ├── empty.pptx
│ ├── empty.xlsx
│ ├── en.lproj
│ │ └── InfoPlist.strings
│ ├── error-v2.png
│ ├── error-v2@2x.png
│ ├── error.png
│ ├── error@2x.png
│ ├── es.lproj
│ │ └── InfoPlist.strings
│ ├── es_ES.lproj
│ │ └── InfoPlist.strings
│ ├── fonts
│ │ └── GothamNarrow-Medium.otf
│ ├── fr.lproj
│ │ └── InfoPlist.strings
│ ├── gdoc.icns
│ ├── gsheet.icns
│ ├── gslides.icns
│ ├── icon.icns
│ ├── overlay-infinite.icns
│ ├── overlay-mixedstate.icns
│ ├── overlay-uptodate.icns
│ ├── paper.icns
│ ├── paper.png
│ ├── paper@2x.png
│ ├── pause-v2.png
│ ├── pause-v2@2x.png
│ ├── pause.png
│ ├── pause@2x.png
│ ├── pl.lproj
│ │ └── InfoPlist.strings
│ ├── pt_BR.lproj
│ │ └── InfoPlist.strings
│ ├── python-resources
│ │ ├── AppKit._AppKit.cpython-35m-darwin.so
│ │ ├── AppKit._inlines.cpython-35m-darwin.so
│ │ ├── CFNetwork._manual.cpython-35m-darwin.so
│ │ ├── CoreFoundation._CoreFoundation.cpython-35m-darwin.so
│ │ ├── CoreFoundation._inlines.cpython-35m-darwin.so
│ │ ├── CoreServices._inlines.cpython-35m-darwin.so
│ │ ├── FSEvents._callbacks.cpython-35m-darwin.so
│ │ ├── Foundation._Foundation.cpython-35m-darwin.so
│ │ ├── Foundation._inlines.cpython-35m-darwin.so
│ │ ├── ImageCaptureCore._ImageCaptureCore.cpython-35m-darwin.so
│ │ ├── Quartz.CoreGraphics._callbacks.cpython-35m-darwin.so
│ │ ├── Quartz.CoreGraphics._coregraphics.cpython-35m-darwin.so
│ │ ├── Quartz.CoreGraphics._doubleindirect.cpython-35m-darwin.so
│ │ ├── Quartz.CoreGraphics._inlines.cpython-35m-darwin.so
│ │ ├── Quartz.CoreGraphics._sortandmap.cpython-35m-darwin.so
│ │ ├── Quartz.CoreVideo._CVPixelBuffer.cpython-35m-darwin.so
│ │ ├── Quartz.ImageKit._imagekit.cpython-35m-darwin.so
│ │ ├── Quartz.PDFKit._PDFKit.cpython-35m-darwin.so
│ │ ├── Quartz.QuartzCore._quartzcore.cpython-35m-darwin.so
│ │ ├── Quartz.QuickLookUI._QuickLookUI.cpython-35m-darwin.so
│ │ ├── ScriptingBridge._ScriptingBridge.cpython-35m-darwin.so
│ │ ├── SystemConfiguration._manual.cpython-35m-darwin.so
│ │ ├── WebKit._WebKit.cpython-35m-darwin.so
│ │ ├── _bisect.cpython-35m-darwin.so
│ │ ├── _bz2.cpython-35m-darwin.so
│ │ ├── _cffi_backend.cpython-35m-darwin.so
│ │ ├── _codecs_cn.cpython-35m-darwin.so
│ │ ├── _codecs_hk.cpython-35m-darwin.so
│ │ ├── _codecs_iso2022.cpython-35m-darwin.so
│ │ ├── _codecs_jp.cpython-35m-darwin.so
│ │ ├── _codecs_kr.cpython-35m-darwin.so
│ │ ├── _codecs_tw.cpython-35m-darwin.so
│ │ ├── _csv.cpython-35m-darwin.so
│ │ ├── _ctypes.cpython-35m-darwin.so
│ │ ├── _datetime.cpython-35m-darwin.so
│ │ ├── _decimal.cpython-35m-darwin.so
│ │ ├── _elementtree.cpython-35m-darwin.so
│ │ ├── _heapq.cpython-35m-darwin.so
│ │ ├── _json.cpython-35m-darwin.so
│ │ ├── _md5.cpython-35m-darwin.so
│ │ ├── _multibytecodec.cpython-35m-darwin.so
│ │ ├── _multiprocessing.cpython-35m-darwin.so
│ │ ├── _pickle.cpython-35m-darwin.so
│ │ ├── _posixsubprocess.cpython-35m-darwin.so
│ │ ├── _random.cpython-35m-darwin.so
│ │ ├── _scproxy.cpython-35m-darwin.so
│ │ ├── _sha1.cpython-35m-darwin.so
│ │ ├── _sha256.cpython-35m-darwin.so
│ │ ├── _sha512.cpython-35m-darwin.so
│ │ ├── _struct.cpython-35m-darwin.so
│ │ ├── _yappi.cpython-35m-darwin.so
│ │ ├── array.cpython-35m-darwin.so
│ │ ├── cpuid.compiled._cpuid.cpython-35m-darwin.so
│ │ ├── crashpad.compiled._Crashpad.cpython-35m-darwin.so
│ │ ├── cryptography.hazmat.bindings._constant_time.cpython-35m-darwin.so
│ │ ├── cryptography.hazmat.bindings._openssl.cpython-35m-darwin.so
│ │ ├── cryptography.hazmat.bindings._padding.cpython-35m-darwin.so
│ │ ├── dropbox.collaboration.mac.compiled._scriptingbridge_shim.cpython-35m-darwin.so
│ │ ├── dropbox.dirtraverse.mac.high_sierra.compiled._dirtraverse_high_sierra.cpython-35m-darwin.so
│ │ ├── dropbox.dirtraverse.mac.legacy.compiled._dirtraverse_mac_legacy.cpython-35m-darwin.so
│ │ ├── fastpath.cpython-35m-darwin.so
│ │ ├── fcntl.cpython-35m-darwin.so
│ │ ├── grp.cpython-35m-darwin.so
│ │ ├── librsyncffi.compiled._librsyncffi.cpython-35m-darwin.so
│ │ ├── mac_thread_times.cpython-35m-darwin.so
│ │ ├── macffi.ApplicationServices._macffi_ApplicationServices.cpython-35m-darwin.so
│ │ ├── macffi.CoreFoundation._macffi_CoreFoundation.cpython-35m-darwin.so
│ │ ├── macffi.CoreGraphics._macffi_CoreGraphics.cpython-35m-darwin.so
│ │ ├── macffi.CoreGraphicsServices._macffi_CoreGraphicsServices.cpython-35m-darwin.so
│ │ ├── macffi.CoreProcess._macffi_CoreProcess.cpython-35m-darwin.so
│ │ ├── macffi.CoreServices._macffi_CoreServices.cpython-35m-darwin.so
│ │ ├── macffi.DiskArbitration._macffi_DiskArbitration.cpython-35m-darwin.so
│ │ ├── macffi.FSEvents._macffi_FSEvents.cpython-35m-darwin.so
│ │ ├── macffi.QuickLook._macffi_QuickLook.cpython-35m-darwin.so
│ │ ├── macffi.Security.compiled._macffi_Security.cpython-35m-darwin.so
│ │ ├── macffi.ServiceManagement._macffi_ServiceManagement.cpython-35m-darwin.so
│ │ ├── macffi.System._macffi_System.cpython-35m-darwin.so
│ │ ├── macffi.pthread._macffi_pthread.cpython-35m-darwin.so
│ │ ├── macffi.pthread._macffi_qos.cpython-35m-darwin.so
│ │ ├── macffi.xpc._macffi_xpc.cpython-35m-darwin.so
│ │ ├── macinfinite.compiled._infinite.cpython-35m-darwin.so
│ │ ├── math.cpython-35m-darwin.so
│ │ ├── mmap.cpython-35m-darwin.so
│ │ ├── nucleus_python.cpython-35m-darwin.so
│ │ ├── objc._machsignals.cpython-35m-darwin.so
│ │ ├── objc._objc.cpython-35m-darwin.so
│ │ ├── parser.cpython-35m-darwin.so
│ │ ├── posixffi.libc._posixffi_libc.cpython-35m-darwin.so
│ │ ├── psutil._psutil_osx.cpython-35m-darwin.so
│ │ ├── psutil._psutil_posix.cpython-35m-darwin.so
│ │ ├── pyexpat.cpython-35m-darwin.so
│ │ ├── python-packages-35.zip
│ │ ├── resource.cpython-35m-darwin.so
│ │ ├── select.cpython-35m-darwin.so
│ │ ├── termios.cpython-35m-darwin.so
│ │ ├── tornado.speedups.cpython-35m-darwin.so
│ │ ├── ui.cocoa.xui.compiled._javascriptcore.cpython-35m-darwin.so
│ │ └── unicodedata.cpython-35m-darwin.so
│ ├── resume.png
│ ├── resume@2x.png
│ ├── ru.lproj
│ │ └── InfoPlist.strings
│ ├── search-back.png
│ ├── search-back@2x.png
│ ├── settings.png
│ ├── settings@2x.png
│ ├── solid_background.png
│ ├── status-edited.png
│ ├── status-edited@2x.png
│ ├── status-private.png
│ ├── status-private@2x.png
│ ├── status-shared-folder.png
│ ├── status-shared-folder@2x.png
│ ├── status-shared-link.png
│ ├── status-shared-link@2x.png
│ ├── sv_SE.lproj
│ │ └── InfoPlist.strings
│ ├── sync.icns
│ ├── syncing-v2.png
│ ├── syncing-v2@2x.png
│ ├── syncing.png
│ ├── syncing@2x.png
│ ├── th_TH.lproj
│ │ └── InfoPlist.strings
│ ├── uk_UA.lproj
│ │ └── InfoPlist.strings
│ ├── up-to-date-v2.png
│ ├── up-to-date-v2@2x.png
│ ├── up-to-date.png
│ ├── up-to-date@2x.png
│ ├── viewer-editing.png
│ ├── viewer-nosync.png
│ ├── viewer-selsync.png
│ ├── viewer-synced.png
│ ├── viewer-synced@2x.png
│ ├── viewer-syncing.png
│ ├── viewer-viewing.png
│ ├── xui_resources.zip
│ ├── zh_CN.lproj
│ │ └── InfoPlist.strings
│ └── zh_TW.lproj
│ └── InfoPlist.strings
├── XPCServices
│ ├── DropboxActivityProvider.xpc
│ │ └── Contents
│ │ ├── Info.plist
│ │ ├── MacOS
│ │ │ └── DropboxActivityProvider
│ │ ├── Resources
│ │ │ ├── EFActivityProviderMain.nib
│ │ │ └── README.md
│ │ └── _CodeSignature
│ │ └── CodeResources
│ ├── DropboxFolderTagger.xpc
│ │ └── Contents
│ │ ├── Info.plist
│ │ ├── MacOS
│ │ │ └── DropboxFolderTagger
│ │ └── _CodeSignature
│ │ └── CodeResources
│ └── DropboxNotificationService.xpc
│ └── Contents
│ ├── Info.plist
│ ├── MacOS
│ │ └── DropboxNotificationService
│ └── _CodeSignature
│ └── CodeResources
└── _CodeSignature
└── CodeResources
What does file syncing look like?
There are two types of syncing: syncing to Dropbox, and Lan sync.
For lan sync we could open Wireshark and take a peek, especiially since it supports the protocol by default, but there is a blog post that outlines how it works by Dropbox so its best to just read that.
Some key take aways:
- Dropbox splits files into 4MB blocks, addressed by the block’s hash.
- LAN Sync only syncs the file blocks. Metadata is fetched from Dropbox servers.
- Discovery uses UDP with port 17500. Blocks fetching uses HTTPS.
- Dropbox client runs an HTTP block server.
Now for traditional syncing with Dropbox.
First, we need to install mitmproxy
.
Then we run mitmproxy
and add the generated cert to keychain.
open ~/.mitmproxy/mitmproxy-ca-cert.pem
We also need to update the cert to Always Trust.
Then we head over to System Preferences and setup the SOCKS proxy.
now we just need to run mitmproxy
and we should be good to go.
mitmproxy --showhost --mode socks5
But we aren’t getting the traffic, instead we get the following warning
warn: 127.0.0.1:50544: Client Handshake failed. The client may not trust the proxy's certificate for client-web.dropbox.com
Well, we tried, looks like Dropbox uses pinned certificates. Good on them.