summaryrefslogtreecommitdiffstats
path: root/other-licenses/nsis/Contrib/HttpPostFile/HttpPostFile.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'other-licenses/nsis/Contrib/HttpPostFile/HttpPostFile.cpp')
-rw-r--r--other-licenses/nsis/Contrib/HttpPostFile/HttpPostFile.cpp304
1 files changed, 304 insertions, 0 deletions
diff --git a/other-licenses/nsis/Contrib/HttpPostFile/HttpPostFile.cpp b/other-licenses/nsis/Contrib/HttpPostFile/HttpPostFile.cpp
new file mode 100644
index 0000000000..7b55493829
--- /dev/null
+++ b/other-licenses/nsis/Contrib/HttpPostFile/HttpPostFile.cpp
@@ -0,0 +1,304 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// To explain some of the oddities:
+// This plugin avoids linking against a runtime that might not be present, thus
+// it avoids standard library functions.
+// NSIS requires GlobalAlloc/GlobalFree for its interfaces, and I use them for
+// other allocations (vs e.g. HeapAlloc) for the sake of consistency.
+
+#include <Windows.h>
+#include <Wininet.h>
+
+#define AGENT_NAME L"HttpPostFile plugin"
+
+PBYTE LoadFileData(LPWSTR fileName, DWORD& cbData);
+bool HttpPost(LPURL_COMPONENTS pUrl, LPWSTR contentTypeHeader, PBYTE data,
+ DWORD cbData);
+
+// NSIS API
+typedef struct _stack_t {
+ struct _stack_t* next;
+ WCHAR text[1];
+} stack_t;
+
+// Unlink and return the topmost element of the stack, if any.
+static stack_t* popstack(stack_t** stacktop) {
+ if (!stacktop || !*stacktop) return nullptr;
+ stack_t* element = *stacktop;
+ *stacktop = element->next;
+ element->next = nullptr;
+ return element;
+}
+
+// Allocate a new stack element (with space for `stringsize`), copy the string,
+// add to the top of the stack.
+static void pushstring(LPCWSTR str, stack_t** stacktop,
+ unsigned int stringsize) {
+ stack_t* element;
+ if (!stacktop) return;
+
+ // The allocation here has space for stringsize+1 WCHARs, because stack_t.text
+ // is 1 element long. This is consistent with the NSIS ExDLL example, though
+ // inconsistent with the comment that says the array "should be the length of
+ // g_stringsize when allocating". I'm sticking to consistency with
+ // the code, and erring towards having a larger buffer than necessary.
+
+ element = (stack_t*)GlobalAlloc(
+ GPTR, (sizeof(stack_t) + stringsize * sizeof(*str)));
+ lstrcpynW(element->text, str, stringsize);
+ element->next = *stacktop;
+ *stacktop = element;
+}
+
+BOOL APIENTRY DllMain(HINSTANCE instance, DWORD reason, LPVOID) {
+ // No initialization or cleanup is needed.
+ return TRUE;
+}
+
+extern "C" {
+
+// HttpPostFile::Post <File> <Content-Type header with \r\n> <URL>
+//
+// e.g. HttpPostFile "C:\blah.json" "Content-Type: application/json$\r$\n"
+// "https://example.com"
+//
+// Leaves a result string on the stack, "success" if the POST was successful, an
+// error message otherwise.
+// The status code from the server is not checked, as long as we got some
+// response the result will be "success". The response is read, but discarded.
+void __declspec(dllexport)
+ Post(HWND hwndParent, int string_size, char* /* variables */,
+ stack_t** stacktop, void* /* extra_parameters */) {
+ static const URL_COMPONENTS kZeroComponents = {0};
+ const WCHAR* errorMsg = L"error";
+
+ DWORD cbData = INVALID_FILE_SIZE;
+ PBYTE data = nullptr;
+
+ // Copy a constant, because initializing an automatic variable with {0} ends
+ // up linking to memset, which isn't available.
+ URL_COMPONENTS components = kZeroComponents;
+
+ // Get args, taking ownership of the strings from the stack, to avoid
+ // allocating and copying strings.
+ stack_t* postFileName = popstack(stacktop);
+ stack_t* contentTypeHeader = popstack(stacktop);
+ stack_t* url = popstack(stacktop);
+
+ if (!postFileName || !contentTypeHeader || !url) {
+ errorMsg = L"error getting arguments";
+ goto finish;
+ }
+
+ data = LoadFileData(postFileName->text, cbData);
+ if (!data || cbData == INVALID_FILE_SIZE) {
+ errorMsg = L"error reading file";
+ goto finish;
+ }
+
+ {
+ // This length is used to allocate for the host name and path components,
+ // which should be no longer than the source URL.
+ int urlBufLen = lstrlenW(url->text) + 1;
+
+ components.dwStructSize = sizeof(components);
+ components.dwHostNameLength = urlBufLen;
+ components.dwUrlPathLength = urlBufLen;
+ components.lpszHostName =
+ (LPWSTR)GlobalAlloc(GPTR, urlBufLen * sizeof(WCHAR));
+ components.lpszUrlPath =
+ (LPWSTR)GlobalAlloc(GPTR, urlBufLen * sizeof(WCHAR));
+ }
+
+ errorMsg = L"error parsing URL";
+ if (components.lpszHostName && components.lpszUrlPath &&
+ InternetCrackUrl(url->text, 0, 0, &components) &&
+ (components.nScheme == INTERNET_SCHEME_HTTP ||
+ components.nScheme == INTERNET_SCHEME_HTTPS)) {
+ errorMsg = L"error sending HTTP request";
+ if (HttpPost(&components, contentTypeHeader->text, data, cbData)) {
+ // success!
+ errorMsg = nullptr;
+ }
+ }
+
+finish:
+ if (components.lpszUrlPath) {
+ GlobalFree(components.lpszUrlPath);
+ }
+ if (components.lpszHostName) {
+ GlobalFree(components.lpszHostName);
+ }
+ if (data) {
+ GlobalFree(data);
+ }
+
+ // Free args taken from the NSIS stack
+ if (url) {
+ GlobalFree(url);
+ }
+ if (contentTypeHeader) {
+ GlobalFree(contentTypeHeader);
+ }
+ if (postFileName) {
+ GlobalFree(postFileName);
+ }
+
+ if (errorMsg) {
+ pushstring(errorMsg, stacktop, string_size);
+ } else {
+ pushstring(L"success", stacktop, string_size);
+ }
+}
+}
+
+// Returns buffer with file contents on success, placing the size in cbData.
+// Returns nullptr on failure.
+// Caller must use GlobalFree() on the returned buffer if non-null.
+PBYTE LoadFileData(LPWSTR fileName, DWORD& cbData) {
+ bool success = false;
+
+ HANDLE hPostFile = INVALID_HANDLE_VALUE;
+
+ PBYTE data = nullptr;
+
+ DWORD bytesRead;
+ DWORD bytesReadTotal;
+
+ hPostFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (hPostFile == INVALID_HANDLE_VALUE) {
+ goto finish;
+ }
+
+ cbData = GetFileSize(hPostFile, NULL);
+ if (cbData == INVALID_FILE_SIZE) {
+ goto finish;
+ }
+
+ data = (PBYTE)GlobalAlloc(GPTR, cbData);
+ if (!data) {
+ goto finish;
+ }
+
+ bytesReadTotal = 0;
+ do {
+ if (!ReadFile(hPostFile, data + bytesReadTotal, cbData - bytesReadTotal,
+ &bytesRead, nullptr /* overlapped */)) {
+ goto finish;
+ }
+ bytesReadTotal += bytesRead;
+ } while (bytesReadTotal < cbData && bytesRead > 0);
+
+ if (bytesReadTotal == cbData) {
+ success = true;
+ }
+
+finish:
+ if (!success) {
+ if (data) {
+ GlobalFree(data);
+ data = nullptr;
+ }
+ cbData = INVALID_FILE_SIZE;
+ }
+ if (hPostFile != INVALID_HANDLE_VALUE) {
+ CloseHandle(hPostFile);
+ hPostFile = INVALID_HANDLE_VALUE;
+ }
+
+ return data;
+}
+
+// Returns true on success
+bool HttpPost(LPURL_COMPONENTS pUrl, LPWSTR contentTypeHeader, PBYTE data,
+ DWORD cbData) {
+ bool success = false;
+
+ HINTERNET hInternet = nullptr;
+ HINTERNET hConnect = nullptr;
+ HINTERNET hRequest = nullptr;
+
+ hInternet = InternetOpen(AGENT_NAME, INTERNET_OPEN_TYPE_PRECONFIG,
+ nullptr, // proxy
+ nullptr, // proxy bypass
+ 0 // flags
+ );
+ if (!hInternet) {
+ goto finish;
+ }
+
+ hConnect = InternetConnect(hInternet, pUrl->lpszHostName, pUrl->nPort,
+ nullptr, // userName,
+ nullptr, // password
+ INTERNET_SERVICE_HTTP,
+ 0, // flags
+ 0 // context
+ );
+ if (!hConnect) {
+ goto finish;
+ }
+
+ {
+ // NOTE: Some of these settings are perhaps unnecessary for a POST.
+ DWORD httpFlags = INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_COOKIES |
+ INTERNET_FLAG_NO_UI | INTERNET_FLAG_RELOAD;
+ if (pUrl->nScheme == INTERNET_SCHEME_HTTPS) {
+ // NOTE: nsJSON sets flags to allow redirecting HTTPS to HTTP, or HTTP to
+ // HTTPS I left those out because it seemed undesirable for our use case.
+ httpFlags |= INTERNET_FLAG_SECURE;
+ }
+ hRequest = HttpOpenRequest(hConnect, L"POST", pUrl->lpszUrlPath,
+ nullptr, // version,
+ nullptr, // referrer
+ nullptr, // accept types
+ httpFlags,
+ 0 // context
+ );
+ if (!hRequest) {
+ goto finish;
+ }
+ }
+
+ if (contentTypeHeader) {
+ if (!HttpAddRequestHeaders(hRequest, contentTypeHeader,
+ -1L, // headers length (count string length)
+ HTTP_ADDREQ_FLAG_ADD)) {
+ goto finish;
+ }
+ }
+
+ if (!HttpSendRequestW(hRequest,
+ nullptr, // additional headers
+ 0, // headers length
+ data, cbData)) {
+ goto finish;
+ }
+
+ BYTE readBuffer[1024];
+ DWORD bytesRead;
+ do {
+ if (!InternetReadFile(hRequest, readBuffer, sizeof(readBuffer),
+ &bytesRead)) {
+ goto finish;
+ }
+ // read data is thrown away
+ } while (bytesRead > 0);
+
+ success = true;
+
+finish:
+ if (hRequest) {
+ InternetCloseHandle(hRequest);
+ }
+ if (hConnect) {
+ InternetCloseHandle(hConnect);
+ }
+ if (hInternet) {
+ InternetCloseHandle(hInternet);
+ }
+
+ return success;
+} \ No newline at end of file