dropbox home page

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

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:

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:

  1. 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 to

    https://www.dropbox.com/browse_util/tree_view_folders
    

    with the folder as /, which returns the top-level folder structure of your Dropbox/ 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
    }>
    
    file upload modal
  2. 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 to

    https://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
    }
    
  3. Then Dropbox starts uploading the file chunks by first making an OPTIONS and then a POST 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.

    uploading file

  4. After uploading each chunk, Dropbox then commits the upload by first making an OPTIONS and then POST request to

    https://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"}
    

    uploaded file success

  5. 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.

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
delete files filter

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 date
  • end_ts → to date
  • actor_email → deleted by
  • fq_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.

  1. 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 to

    https://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.

  2. 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 the client_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 and 17603 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 websockets wss 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.

    open button handshake

    Which matches the websocket packets.

    And then then we can see the open request

    open button wireshark

    which for every push of the button, results in 4 packets being exchanged.

    If we examine a packet

    open button packet detail

    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

Settings page is a pretty common tab setup.

Editing works using modals like Trello.

Login & Logout

How does their auth system work?

Login

login page

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

login 2-factor

but if you are on a browser which supports hardware keys, like Chrome, you will be asked to insert your key

login with yubikey

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

file upload modal

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 opens 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

    • tornado – async network library
    • hazmat – cryptography library
      • constant_time
      • openssl
      • padding

    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.

mitmproxy cert setup

Then we head over to System Preferences and setup the SOCKS proxy.

setup 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.