зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1461690
Part 3: HttpPostFile plugin for uploading uninstall ping via POST. r=mhowell
Differential Revision: https://phabricator.services.mozilla.com/D93717
This commit is contained in:
Родитель
d8b64a9146
Коммит
23d03c7659
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30128.74
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "HttpPostFile", "HttpPostFile.vcxproj", "{A8BF99FD-8603-4137-862A-1D14268D7812}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A8BF99FD-8603-4137-862A-1D14268D7812}.Release|x86.ActiveCfg = Release|Win32
|
||||
{A8BF99FD-8603-4137-862A-1D14268D7812}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {5EF33D14-5BB9-4E44-A347-9FF33E86D9DC}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{a8bf99fd-8603-4137-862a-1d14268d7812}</ProjectGuid>
|
||||
<RootNamespace>HttpPostFile</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WINVER=0x601;_WIN32_WINNT=0x601;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<OmitDefaultLibName>true</OmitDefaultLibName>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<SDLCheck>false</SDLCheck>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>wininet.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<EntryPointSymbol>DllMain</EntryPointSymbol>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="HttpPostFile.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
|
@ -0,0 +1,63 @@
|
|||
; Any copyright is dedicated to the Public Domain.
|
||||
; http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
; Simple driver for HttpPostFile, passes command line args to HttpPostFile::Post and
|
||||
; writes the result string to a file for automated checking.
|
||||
; Always specifies Content-Type: application/json
|
||||
;
|
||||
; Usage: posttest /postfile=postfile.json /url=http://example.com /resultfile=result.txt
|
||||
|
||||
!include LogicLib.nsh
|
||||
!include FileFunc.nsh
|
||||
|
||||
OutFile "postdriver.exe"
|
||||
RequestExecutionLevel user
|
||||
ShowInstDetails show
|
||||
Unicode true
|
||||
|
||||
!addplugindir ..\..\..\Plugins
|
||||
|
||||
Var PostFileArg
|
||||
Var UrlArg
|
||||
Var ResultFileArg
|
||||
Var ResultString
|
||||
|
||||
Section
|
||||
|
||||
StrCpy $ResultString "error getting command line arguments"
|
||||
|
||||
ClearErrors
|
||||
${GetParameters} $0
|
||||
IfErrors done
|
||||
|
||||
ClearErrors
|
||||
${GetOptions} " $0" " /postfile=" $PostFileArg
|
||||
IfErrors done
|
||||
|
||||
${GetOptions} " $0" " /url=" $UrlArg
|
||||
IfErrors done
|
||||
|
||||
${GetOptions} " $0" " /resultfile=" $ResultFileArg
|
||||
IfErrors done
|
||||
|
||||
DetailPrint "POST File = $PostFileArg"
|
||||
DetailPrint "URL = $UrlArg"
|
||||
DetailPrint "Result File = $ResultFileArg"
|
||||
|
||||
StrCpy $ResultString "error running plugin"
|
||||
HttpPostFile::Post $PostFileArg "Content-Type: application/json$\r$\n" $UrlArg
|
||||
Pop $ResultString
|
||||
|
||||
done:
|
||||
${If} $ResultString != "success"
|
||||
DetailPrint $ResultString
|
||||
${EndIf}
|
||||
|
||||
ClearErrors
|
||||
FileOpen $0 $ResultFileArg "w"
|
||||
${Unless} ${Errors}
|
||||
FileWrite $0 $ResultString
|
||||
FileClose $0
|
||||
${EndUnless}
|
||||
|
||||
SectionEnd
|
|
@ -0,0 +1,247 @@
|
|||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
# Unit test for the HttpPostFile plugin, using a server on localhost.
|
||||
#
|
||||
# This test has not been set up to run in continuous integration. It is
|
||||
# intended to be run manually, and only on Windows.
|
||||
#
|
||||
# Requires postdriver.exe, which can be built from postdriver.nsi with makensis
|
||||
# from MozillaBuild:
|
||||
#
|
||||
# makensis-3.01.exe postdriver.nsi
|
||||
#
|
||||
# It can then be run from this directory as:
|
||||
#
|
||||
# python3 test.py
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import http.server
|
||||
import socketserver
|
||||
import threading
|
||||
|
||||
DRIVER_EXE_FILE_NAME = "postdriver.exe"
|
||||
JSON_FILE_NAME = "test1.json"
|
||||
RESULT_FILE_NAME = "result.txt"
|
||||
BIND_HOST = "127.0.0.1"
|
||||
BIND_PORT = 8080
|
||||
COMMON_URL = f"http://{BIND_HOST}:{BIND_PORT}/submit"
|
||||
COMMON_JSON_BYTES = '{"yes": "indeed",\n"and": "ij"}'.encode('utf-8')
|
||||
|
||||
DRIVER_TIMEOUT_SECS = 60
|
||||
SERVER_TIMEOUT_SECS = 120
|
||||
|
||||
|
||||
class PostHandler(http.server.BaseHTTPRequestHandler):
|
||||
"""BaseHTTPRequestHandler, basically just here to have a configurable do_POST handler"""
|
||||
|
||||
|
||||
last_submission = None
|
||||
last_content_type = None
|
||||
server_response = 'Hello, plugin'.encode('utf-8')
|
||||
|
||||
|
||||
def server_accept_submit(handler):
|
||||
"""Plugs into PostHandler.do_POST, accepts a POST on /submit and saves it into
|
||||
the globals"""
|
||||
|
||||
global last_submission
|
||||
global last_content_type
|
||||
global server_response
|
||||
|
||||
last_submission = None
|
||||
last_content_type = None
|
||||
|
||||
if handler.path == "/submit":
|
||||
handler.send_response(200, 'Ok')
|
||||
content_length = int(handler.headers['Content-Length'])
|
||||
last_submission = handler.rfile.read(content_length)
|
||||
last_content_type = handler.headers['Content-Type']
|
||||
else:
|
||||
handler.send_response(404, 'Not found')
|
||||
handler.end_headers()
|
||||
|
||||
handler.wfile.write(server_response)
|
||||
handler.wfile.flush()
|
||||
|
||||
handler.log_message("sent response")
|
||||
|
||||
|
||||
server_hang_event = None
|
||||
|
||||
|
||||
def server_hang(handler):
|
||||
"""Plugs into PostHandler.do_POST, waits on server_hang_event or until timeout"""
|
||||
server_hang_event.wait(SERVER_TIMEOUT_SECS)
|
||||
|
||||
|
||||
def run_and_assert_result(handle_request, post_file, url, expected_result):
|
||||
"""Sets up the server on another thread, runs the NSIS driver, and checks the result"""
|
||||
global last_submission
|
||||
global server_hang_event
|
||||
|
||||
try:
|
||||
os.remove(RESULT_FILE_NAME)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
PostHandler.do_POST = handle_request
|
||||
last_submission = None
|
||||
|
||||
def handler_thread():
|
||||
with socketserver.TCPServer((BIND_HOST, BIND_PORT), PostHandler) as httpd:
|
||||
httpd.timeout = SERVER_TIMEOUT_SECS
|
||||
httpd.handle_request()
|
||||
|
||||
if handle_request:
|
||||
server_thread = threading.Thread(target=handler_thread)
|
||||
server_thread.start()
|
||||
|
||||
try:
|
||||
subprocess.call([DRIVER_EXE_FILE_NAME, f'/postfile={post_file}', f'/url={url}',
|
||||
f'/resultfile={RESULT_FILE_NAME}', '/S'], timeout=DRIVER_TIMEOUT_SECS)
|
||||
|
||||
with open(RESULT_FILE_NAME, "r") as result_file:
|
||||
result = result_file.read()
|
||||
|
||||
if result != expected_result:
|
||||
raise AssertionError(f'{result} != {expected_result}')
|
||||
|
||||
finally:
|
||||
if server_hang_event:
|
||||
server_hang_event.set()
|
||||
|
||||
if handle_request:
|
||||
server_thread.join()
|
||||
os.remove(RESULT_FILE_NAME)
|
||||
|
||||
|
||||
def create_json_file(json_bytes=COMMON_JSON_BYTES):
|
||||
with open(JSON_FILE_NAME, "wb") as outfile:
|
||||
outfile.write(json_bytes)
|
||||
|
||||
|
||||
def check_submission(json_bytes=COMMON_JSON_BYTES):
|
||||
if last_submission != json_bytes:
|
||||
raise AssertionError(f'{last_submission.hex()} != {COMMON_JSON_BYTES}')
|
||||
|
||||
|
||||
def cleanup_json_file():
|
||||
os.remove(JSON_FILE_NAME)
|
||||
|
||||
|
||||
# Tests begin here
|
||||
|
||||
try:
|
||||
cleanup_json_file()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
# Basic test
|
||||
|
||||
create_json_file()
|
||||
run_and_assert_result(server_accept_submit, JSON_FILE_NAME, COMMON_URL, "success")
|
||||
check_submission()
|
||||
assert last_content_type == 'application/json'
|
||||
cleanup_json_file()
|
||||
|
||||
print("Basic test OK\n")
|
||||
|
||||
# Test with missing file
|
||||
|
||||
try:
|
||||
cleanup_json_file()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
run_and_assert_result(None, JSON_FILE_NAME, COMMON_URL, "error reading file")
|
||||
|
||||
print("Missing file test OK\n")
|
||||
|
||||
# Test with empty file
|
||||
|
||||
create_json_file(bytes())
|
||||
run_and_assert_result(server_accept_submit, JSON_FILE_NAME, COMMON_URL, "success")
|
||||
check_submission(bytes())
|
||||
cleanup_json_file()
|
||||
|
||||
print("Empty file test OK\n")
|
||||
|
||||
# Test with large file
|
||||
|
||||
# NOTE: Not actually JSON, but nothing here should care
|
||||
four_mbytes = bytes([x & 255 for x in range(4*1024*1024)])
|
||||
create_json_file(four_mbytes)
|
||||
run_and_assert_result(server_accept_submit, JSON_FILE_NAME, COMMON_URL, "success")
|
||||
if last_submission != four_mbytes:
|
||||
raise AssertionError("large file mismatch")
|
||||
cleanup_json_file()
|
||||
|
||||
print("Large file test OK\n")
|
||||
|
||||
# Test with long file name
|
||||
|
||||
# Test with bad URL
|
||||
|
||||
bogus_url = "notAUrl"
|
||||
create_json_file()
|
||||
run_and_assert_result(None, JSON_FILE_NAME, bogus_url, "error parsing URL")
|
||||
cleanup_json_file()
|
||||
|
||||
print("Bad URL test OK\n")
|
||||
|
||||
# Test with empty response
|
||||
|
||||
server_response = bytes()
|
||||
create_json_file()
|
||||
run_and_assert_result(server_accept_submit, JSON_FILE_NAME, COMMON_URL, "success")
|
||||
check_submission()
|
||||
cleanup_json_file()
|
||||
|
||||
print("Empty response test OK\n")
|
||||
|
||||
# Test with large response
|
||||
|
||||
server_response = four_mbytes
|
||||
create_json_file()
|
||||
run_and_assert_result(server_accept_submit, JSON_FILE_NAME, COMMON_URL, "success")
|
||||
check_submission()
|
||||
cleanup_json_file()
|
||||
|
||||
print("Large response test OK\n")
|
||||
|
||||
# Test with 404
|
||||
# NOTE: This succeeds since the client doesn't check the status code
|
||||
|
||||
create_json_file()
|
||||
nonexistent_url = f"http://{BIND_HOST}:{BIND_PORT}/bad"
|
||||
run_and_assert_result(server_accept_submit, JSON_FILE_NAME, nonexistent_url, "success")
|
||||
cleanup_json_file()
|
||||
|
||||
print("404 response test OK\n")
|
||||
|
||||
# Test with no server on the port
|
||||
# NOTE: I'm assuming nothing else has been able to bind to the port
|
||||
|
||||
print("Running no server test, this will take a few seconds...")
|
||||
|
||||
create_json_file()
|
||||
run_and_assert_result(None, JSON_FILE_NAME, COMMON_URL, "error sending HTTP request")
|
||||
cleanup_json_file()
|
||||
|
||||
print("No server test OK\n")
|
||||
|
||||
# Test with server that hangs on response
|
||||
# NOTE: HttpPostFile doesn't currently set the timeouts. Defaults seem to be around 30 seconds,
|
||||
# but if they end up being longer than the 60 second driver timeout then this will fail.
|
||||
|
||||
print("Running server hang test, this will take up to a minute...")
|
||||
|
||||
server_hang_event = threading.Event()
|
||||
create_json_file()
|
||||
run_and_assert_result(server_hang, JSON_FILE_NAME, COMMON_URL, "error sending HTTP request")
|
||||
cleanup_json_file()
|
||||
server_hang_event = None
|
||||
|
||||
print("Server hang test OK\n")
|
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче