tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

server-stream-download.sjs (3959B)


      1 /* Any copyright is dedicated to the Public Domain.
      2  * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 const { setInterval, clearInterval } = ChromeUtils.importESModule(
      5   "resource://gre/modules/Timer.sys.mjs"
      6 );
      7 
      8 // stolen from file_blocked_script.sjs
      9 function setGlobalState(data, key) {
     10   x = {
     11     data,
     12     QueryInterface(iid) {
     13       return this;
     14     },
     15   };
     16   x.wrappedJSObject = x;
     17   setObjectState(key, x);
     18 }
     19 
     20 function getGlobalState(key) {
     21   var data;
     22   getObjectState(key, function (x) {
     23     data = x && x.wrappedJSObject.data;
     24   });
     25   return data;
     26 }
     27 
     28 /*
     29  * We want to let the sw_download_canceled.js service worker know when the
     30  * stream was canceled.  To this end, we let it issue a monitor request which we
     31  * fulfill when the stream has been canceled.  In order to coordinate between
     32  * multiple requests, we use the getObjectState/setObjectState mechanism that
     33  * httpd.js exposes to let data be shared and/or persist between requests.  We
     34  * handle both possible orderings of the requests because we currently don't
     35  * try and impose an ordering between the two requests as issued by the SW, and
     36  * file_blocked_script.sjs encourages us to do this, but we probably could order
     37  * them.
     38  */
     39 const MONITOR_KEY = "stream-monitor";
     40 function completeMonitorResponse(response, data) {
     41   response.write(JSON.stringify(data));
     42   response.finish();
     43 }
     44 function handleMonitorRequest(request, response) {
     45   response.setHeader("Content-Type", "application/json");
     46   response.setStatusLine(request.httpVersion, 200, "Found");
     47 
     48   response.processAsync();
     49   // Necessary to cause the headers to be flushed; that or touching the
     50   // bodyOutputStream getter.
     51   response.write("");
     52   dump("server-stream-download.js: monitor headers issued\n");
     53 
     54   const alreadyCompleted = getGlobalState(MONITOR_KEY);
     55   if (alreadyCompleted) {
     56     completeMonitorResponse(response, alreadyCompleted);
     57     setGlobalState(null, MONITOR_KEY);
     58   } else {
     59     setGlobalState(response, MONITOR_KEY);
     60   }
     61 }
     62 
     63 const MAX_TICK_COUNT = 3000;
     64 const TICK_INTERVAL = 2;
     65 function handleStreamRequest(request, response) {
     66   const name = "server-stream-download";
     67 
     68   // Create some payload to send.
     69   let strChunk =
     70     "Static routes are the future of ServiceWorkers! So say we all!\n";
     71   while (strChunk.length < 1024) {
     72     strChunk += strChunk;
     73   }
     74 
     75   response.setHeader("Content-Disposition", `attachment; filename="${name}"`);
     76   response.setHeader(
     77     "Content-Type",
     78     `application/octet-stream; name="${name}"`
     79   );
     80   response.setHeader("Content-Length", `${strChunk.length * MAX_TICK_COUNT}`);
     81   response.setStatusLine(request.httpVersion, 200, "Found");
     82 
     83   response.processAsync();
     84   response.write(strChunk);
     85   dump("server-stream-download.js: stream headers + first payload issued\n");
     86 
     87   let count = 0;
     88   let intervalId;
     89   function closeStream(why, message) {
     90     dump("server-stream-download.js: closing stream: " + why + "\n");
     91     clearInterval(intervalId);
     92     response.finish();
     93 
     94     const data = { why, message };
     95     const monitorResponse = getGlobalState(MONITOR_KEY);
     96     if (monitorResponse) {
     97       completeMonitorResponse(monitorResponse, data);
     98       setGlobalState(null, MONITOR_KEY);
     99     } else {
    100       setGlobalState(data, MONITOR_KEY);
    101     }
    102   }
    103   function tick() {
    104     try {
    105       // bound worst-case behavior.
    106       if (count++ > MAX_TICK_COUNT) {
    107         closeStream("timeout", "timeout");
    108         return;
    109       }
    110       response.write(strChunk);
    111     } catch (e) {
    112       closeStream("canceled", e.message);
    113     }
    114   }
    115   intervalId = setInterval(tick, TICK_INTERVAL);
    116 }
    117 
    118 function handleRequest(request, response) {
    119   dump(
    120     "server-stream-download.js: processing request for " +
    121       request.path +
    122       "?" +
    123       request.queryString +
    124       "\n"
    125   );
    126   const query = new URLSearchParams(request.queryString);
    127   if (query.has("monitor")) {
    128     handleMonitorRequest(request, response);
    129   } else {
    130     handleStreamRequest(request, response);
    131   }
    132 }