tor-browser

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

HttpPostFile.cpp (9332B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 // To explain some of the oddities:
      6 // This plugin avoids linking against a runtime that might not be present, thus
      7 // it avoids standard library functions.
      8 // NSIS requires GlobalAlloc/GlobalFree for its interfaces, and I use them for
      9 // other allocations (vs e.g. HeapAlloc) for the sake of consistency.
     10 
     11 #include <Windows.h>
     12 #include <Wininet.h>
     13 
     14 #define AGENT_NAME L"HttpPostFile plugin"
     15 
     16 PBYTE LoadFileData(LPWSTR fileName, DWORD& cbData);
     17 bool HttpPost(LPURL_COMPONENTS pUrl, LPWSTR contentTypeHeader, PBYTE data,
     18              DWORD cbData);
     19 
     20 // NSIS API
     21 typedef struct _stack_t {
     22  struct _stack_t* next;
     23  WCHAR text[1];
     24 } stack_t;
     25 
     26 // Unlink and return the topmost element of the stack, if any.
     27 static stack_t* popstack(stack_t** stacktop) {
     28  if (!stacktop || !*stacktop) return nullptr;
     29  stack_t* element = *stacktop;
     30  *stacktop = element->next;
     31  element->next = nullptr;
     32  return element;
     33 }
     34 
     35 // Allocate a new stack element (with space for `stringsize`), copy the string,
     36 // add to the top of the stack.
     37 static void pushstring(LPCWSTR str, stack_t** stacktop,
     38                       unsigned int stringsize) {
     39  stack_t* element;
     40  if (!stacktop) return;
     41 
     42  // The allocation here has space for stringsize+1 WCHARs, because stack_t.text
     43  // is 1 element long. This is consistent with the NSIS ExDLL example, though
     44  // inconsistent with the comment that says the array "should be the length of
     45  // g_stringsize when allocating". I'm sticking to consistency with
     46  // the code, and erring towards having a larger buffer than necessary.
     47 
     48  element = (stack_t*)GlobalAlloc(
     49      GPTR, (sizeof(stack_t) + stringsize * sizeof(*str)));
     50  lstrcpynW(element->text, str, stringsize);
     51  element->next = *stacktop;
     52  *stacktop = element;
     53 }
     54 
     55 BOOL APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID) {
     56  // No initialization or cleanup is needed.
     57  return TRUE;
     58 }
     59 
     60 extern "C" {
     61 
     62 // HttpPostFile::Post <File> <Content-Type header with \r\n> <URL>
     63 //
     64 // e.g. HttpPostFile "C:\blah.json" "Content-Type: application/json$\r$\n"
     65 // "https://example.com"
     66 //
     67 // Leaves a result string on the stack, "success" if the POST was successful, an
     68 // error message otherwise.
     69 // The status code from the server is not checked, as long as we got some
     70 // response the result will be "success". The response is read, but discarded.
     71 void __declspec(dllexport)
     72    Post(HWND hwndParent, int string_size, char* /* variables */,
     73         stack_t** stacktop, void* /* extra_parameters */) {
     74  static const URL_COMPONENTS kZeroComponents = {0};
     75  const WCHAR* errorMsg = L"error";
     76 
     77  DWORD cbData = INVALID_FILE_SIZE;
     78  PBYTE data = nullptr;
     79 
     80  // Copy a constant, because initializing an automatic variable with {0} ends
     81  // up linking to memset, which isn't available.
     82  URL_COMPONENTS components = kZeroComponents;
     83 
     84  // Get args, taking ownership of the strings from the stack, to avoid
     85  // allocating and copying strings.
     86  stack_t* postFileName = popstack(stacktop);
     87  stack_t* contentTypeHeader = popstack(stacktop);
     88  stack_t* url = popstack(stacktop);
     89 
     90  if (!postFileName || !contentTypeHeader || !url) {
     91    errorMsg = L"error getting arguments";
     92    goto finish;
     93  }
     94 
     95  data = LoadFileData(postFileName->text, cbData);
     96  if (!data || cbData == INVALID_FILE_SIZE) {
     97    errorMsg = L"error reading file";
     98    goto finish;
     99  }
    100 
    101  {
    102    // This length is used to allocate for the host name and path components,
    103    // which should be no longer than the source URL.
    104    int urlBufLen = lstrlenW(url->text) + 1;
    105 
    106    components.dwStructSize = sizeof(components);
    107    components.dwHostNameLength = urlBufLen;
    108    components.dwUrlPathLength = urlBufLen;
    109    components.lpszHostName =
    110        (LPWSTR)GlobalAlloc(GPTR, urlBufLen * sizeof(WCHAR));
    111    components.lpszUrlPath =
    112        (LPWSTR)GlobalAlloc(GPTR, urlBufLen * sizeof(WCHAR));
    113  }
    114 
    115  errorMsg = L"error parsing URL";
    116  if (components.lpszHostName && components.lpszUrlPath &&
    117      InternetCrackUrl(url->text, 0, 0, &components) &&
    118      (components.nScheme == INTERNET_SCHEME_HTTP ||
    119       components.nScheme == INTERNET_SCHEME_HTTPS)) {
    120    errorMsg = L"error sending HTTP request";
    121    if (HttpPost(&components, contentTypeHeader->text, data, cbData)) {
    122      // success!
    123      errorMsg = nullptr;
    124    }
    125  }
    126 
    127 finish:
    128  if (components.lpszUrlPath) {
    129    GlobalFree(components.lpszUrlPath);
    130  }
    131  if (components.lpszHostName) {
    132    GlobalFree(components.lpszHostName);
    133  }
    134  if (data) {
    135    GlobalFree(data);
    136  }
    137 
    138  // Free args taken from the NSIS stack
    139  if (url) {
    140    GlobalFree(url);
    141  }
    142  if (contentTypeHeader) {
    143    GlobalFree(contentTypeHeader);
    144  }
    145  if (postFileName) {
    146    GlobalFree(postFileName);
    147  }
    148 
    149  if (errorMsg) {
    150    pushstring(errorMsg, stacktop, string_size);
    151  } else {
    152    pushstring(L"success", stacktop, string_size);
    153  }
    154 }
    155 }
    156 
    157 // Returns buffer with file contents on success, placing the size in cbData.
    158 // Returns nullptr on failure.
    159 // Caller must use GlobalFree() on the returned buffer if non-null.
    160 PBYTE LoadFileData(LPWSTR fileName, DWORD& cbData) {
    161  bool success = false;
    162 
    163  HANDLE hPostFile = INVALID_HANDLE_VALUE;
    164 
    165  PBYTE data = nullptr;
    166 
    167  DWORD bytesRead;
    168  DWORD bytesReadTotal;
    169 
    170  hPostFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr,
    171                         OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
    172  if (hPostFile == INVALID_HANDLE_VALUE) {
    173    goto finish;
    174  }
    175 
    176  cbData = GetFileSize(hPostFile, NULL);
    177  if (cbData == INVALID_FILE_SIZE) {
    178    goto finish;
    179  }
    180 
    181  data = (PBYTE)GlobalAlloc(GPTR, cbData);
    182  if (!data) {
    183    goto finish;
    184  }
    185 
    186  bytesReadTotal = 0;
    187  do {
    188    if (!ReadFile(hPostFile, data + bytesReadTotal, cbData - bytesReadTotal,
    189                  &bytesRead, nullptr /* overlapped */)) {
    190      goto finish;
    191    }
    192    bytesReadTotal += bytesRead;
    193  } while (bytesReadTotal < cbData && bytesRead > 0);
    194 
    195  if (bytesReadTotal == cbData) {
    196    success = true;
    197  }
    198 
    199 finish:
    200  if (!success) {
    201    if (data) {
    202      GlobalFree(data);
    203      data = nullptr;
    204    }
    205    cbData = INVALID_FILE_SIZE;
    206  }
    207  if (hPostFile != INVALID_HANDLE_VALUE) {
    208    CloseHandle(hPostFile);
    209    hPostFile = INVALID_HANDLE_VALUE;
    210  }
    211 
    212  return data;
    213 }
    214 
    215 // Returns true on success
    216 bool HttpPost(LPURL_COMPONENTS pUrl, LPWSTR contentTypeHeader, PBYTE data,
    217              DWORD cbData) {
    218  bool success = false;
    219 
    220  HINTERNET hInternet = nullptr;
    221  HINTERNET hConnect = nullptr;
    222  HINTERNET hRequest = nullptr;
    223 
    224  hInternet = InternetOpen(AGENT_NAME, INTERNET_OPEN_TYPE_PRECONFIG,
    225                           nullptr,  // proxy
    226                           nullptr,  // proxy bypass
    227                           0         // flags
    228  );
    229  if (!hInternet) {
    230    goto finish;
    231  }
    232 
    233  hConnect = InternetConnect(hInternet, pUrl->lpszHostName, pUrl->nPort,
    234                             nullptr,  // userName,
    235                             nullptr,  // password
    236                             INTERNET_SERVICE_HTTP,
    237                             0,  // flags
    238                             0   // context
    239  );
    240  if (!hConnect) {
    241    goto finish;
    242  }
    243 
    244  {
    245    // NOTE: Some of these settings are perhaps unnecessary for a POST.
    246    DWORD httpFlags = INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES |
    247                      INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD;
    248    if (pUrl->nScheme == INTERNET_SCHEME_HTTPS) {
    249      // NOTE: nsJSON sets flags to allow redirecting HTTPS to HTTP, or HTTP to
    250      // HTTPS I left those out because it seemed undesirable for our use case.
    251      httpFlags |= INTERNET_FLAG_SECURE;
    252    }
    253    hRequest = HttpOpenRequest(hConnect, L"POST", pUrl->lpszUrlPath,
    254                               nullptr,  // version,
    255                               nullptr,  // referrer
    256                               nullptr,  // accept types
    257                               httpFlags,
    258                               0  // context
    259    );
    260    if (!hRequest) {
    261      goto finish;
    262    }
    263  }
    264 
    265  if (contentTypeHeader) {
    266    if (!HttpAddRequestHeaders(hRequest, contentTypeHeader,
    267                               -1L,  // headers length (count string length)
    268                               HTTP_ADDREQ_FLAG_ADD)) {
    269      goto finish;
    270    }
    271  }
    272 
    273  if (!HttpSendRequestW(hRequest,
    274                        nullptr,  // additional headers
    275                        0,        // headers length
    276                        data, cbData)) {
    277    goto finish;
    278  }
    279 
    280  BYTE readBuffer[1024];
    281  DWORD bytesRead;
    282  do {
    283    if (!InternetReadFile(hRequest, readBuffer, sizeof(readBuffer),
    284                          &bytesRead)) {
    285      goto finish;
    286    }
    287    // read data is thrown away
    288  } while (bytesRead > 0);
    289 
    290  success = true;
    291 
    292 finish:
    293  if (hRequest) {
    294    InternetCloseHandle(hRequest);
    295  }
    296  if (hConnect) {
    297    InternetCloseHandle(hConnect);
    298  }
    299  if (hInternet) {
    300    InternetCloseHandle(hInternet);
    301  }
    302 
    303  return success;
    304 }