413 lines
12 KiB
C++
413 lines
12 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved
|
|
|
|
// This file contains the server service specific code.
|
|
|
|
#include "Service.h"
|
|
#include "assert.h"
|
|
|
|
// The server version of ProcessMessage. This is the entry point for the application-specific code.
|
|
HRESULT CFileRepServer::ProcessMessage(
|
|
_In_ CRequest* request,
|
|
_In_ const WS_XML_STRING* receivedAction)
|
|
{
|
|
PrintVerbose(L"Entering CFileRepServer::ProcessMessage");
|
|
|
|
HRESULT hr = S_OK;
|
|
FileRequest* fileRequest = NULL;
|
|
WS_MESSAGE* requestMessage = request->GetRequestMessage();
|
|
WS_CHANNEL* channel = request->GetChannel();
|
|
WS_ERROR* error = request->GetError();
|
|
|
|
// Make sure action is what we expect
|
|
if (WsXmlStringEquals(receivedAction, &fileRequestAction, error) != S_OK)
|
|
{
|
|
PrintInfo(L"Illegal action");
|
|
|
|
hr = WS_E_ENDPOINT_ACTION_NOT_SUPPORTED;
|
|
}
|
|
else
|
|
{
|
|
// Read file request
|
|
|
|
WS_HEAP* heap;
|
|
IfFailedExit(WsGetMessageProperty(requestMessage, WS_MESSAGE_PROPERTY_HEAP, &heap, sizeof(heap), error));
|
|
|
|
IfFailedExit(WsReadBody(requestMessage, &fileRequestElement, WS_READ_REQUIRED_POINTER,
|
|
heap, &fileRequest, sizeof(fileRequest), error));
|
|
IfFailedExit(WsReadMessageEnd(channel, requestMessage, NULL, error));
|
|
|
|
IfFailedExit(ReadAndSendFile(request, fileRequest->fileName, fileRequest->filePosition, error));
|
|
}
|
|
|
|
EXIT
|
|
|
|
// We do not print error messages here. That is handled in the caller.
|
|
PrintVerbose(L"Leaving CFileRepServer::ProcessMessage");
|
|
return hr;
|
|
}
|
|
|
|
// Performs the actual file transfer.
|
|
// This function will fail to produce a valid file on the client if the file was changed in between requests for chunks.
|
|
// There are ways to work around that, but doing so is beyond the scope of this version of the sample. A simple fix would be
|
|
// to keep the file open between requests and prevent writing, but in the spirit of web services this app does not maintain
|
|
// state between requests.
|
|
HRESULT CFileRepServer::ReadAndSendFile(
|
|
_In_ CRequest* request,
|
|
_In_ const LPWSTR fileName,
|
|
_In_ LONGLONG chunkPosition,
|
|
_In_opt_ WS_ERROR* error)
|
|
{
|
|
PrintVerbose(L"Entering CFileRepServer::ReadAndSendFile");
|
|
|
|
HANDLE file = NULL;
|
|
HRESULT hr = S_OK;
|
|
|
|
file = CreateFileW(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
|
|
if (INVALID_HANDLE_VALUE == file)
|
|
{
|
|
PrintInfo(L"Invalid file name");
|
|
if (-1 != chunkPosition)
|
|
{
|
|
hr = SendError(request, GlobalStrings::invalidFileName);
|
|
}
|
|
else
|
|
{
|
|
hr = SendFileInfo(request, fileName, -1, chunkSize);
|
|
}
|
|
|
|
PrintVerbose(L"Leaving CFileRepServer::ReadAndSendFile");
|
|
return hr;
|
|
}
|
|
|
|
LARGE_INTEGER len;
|
|
|
|
if (!GetFileSizeEx(file, &len))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
PrintError(L"Unable to determine file length", true);
|
|
if (FAILED(SendError(request, GlobalStrings::unableToDetermineFileLength)))
|
|
{
|
|
PrintError(L"Unable to send failure back", true);
|
|
}
|
|
|
|
if (!CloseHandle(file))
|
|
{
|
|
PrintError(L"Unable to close file handle", true);
|
|
}
|
|
|
|
// This has its own return path to ensure that the right error info is returned.
|
|
// The main error path would overwrite it.
|
|
PrintVerbose(L"Leaving CFileRepServer::ReadAndSendFile");
|
|
return hr;
|
|
}
|
|
|
|
LONGLONG fileLength = len.QuadPart;
|
|
|
|
if (chunkPosition == DISCOVERY_REQUEST)
|
|
{
|
|
PrintInfo(L"Processing discovery message");
|
|
hr = SendFileInfo(request, fileName, fileLength, chunkSize);
|
|
}
|
|
else if (chunkPosition < -1)
|
|
{
|
|
PrintInfo(L"Invalid request");
|
|
hr = SendError(request, GlobalStrings::invalidRequest);
|
|
}
|
|
else if (chunkPosition >= fileLength)
|
|
{
|
|
PrintInfo(L"Request out of range of the file");
|
|
hr = SendError(request, GlobalStrings::outOfRange);
|
|
}
|
|
else
|
|
{
|
|
long chunkSize = this->chunkSize;
|
|
if (fileLength - chunkPosition < chunkSize)
|
|
{
|
|
chunkSize = (DWORD)(fileLength - chunkPosition);
|
|
}
|
|
|
|
LARGE_INTEGER pos;
|
|
pos.QuadPart = chunkPosition;
|
|
|
|
if (!SetFilePointerEx(file, pos, NULL, FILE_BEGIN))
|
|
{
|
|
PrintError(L"Unable to set file pointer", true);
|
|
hr = E_FAIL;
|
|
|
|
// Ignore return value as we already have a failure.
|
|
SendError(request, GlobalStrings::unableToSetFilePointer);
|
|
|
|
}
|
|
else
|
|
{
|
|
hr = ReadAndSendChunk(request, chunkSize, chunkPosition, file);
|
|
}
|
|
}
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
PrintError(L"CFileRepServer::ReadAndSendFile\n", true);
|
|
PrintError(hr, error, true);
|
|
}
|
|
|
|
if (!CloseHandle(file))
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
PrintError(L"Unable to close file handle", true);
|
|
}
|
|
|
|
PrintVerbose(L"Leaving CFileRepServer::ReadAndSendFile");
|
|
return hr;
|
|
}
|
|
|
|
// The first message of a file transfer is a discovery request byt the client. This function handles such requests.
|
|
HRESULT CFileRepServer::SendFileInfo(
|
|
_In_ CRequest* request,
|
|
_In_z_ const LPWSTR fileName,
|
|
_In_ LONGLONG fileLength,
|
|
_In_ DWORD chunkSize)
|
|
{
|
|
PrintVerbose(L"Entering CFileRepServer::SendFileInfo");
|
|
|
|
HRESULT hr = S_OK;
|
|
WS_ERROR* error = request->GetError();
|
|
WS_MESSAGE* replyMessage = request->GetReplyMessage();
|
|
WS_MESSAGE* requestMessage = request->GetRequestMessage();
|
|
WS_CHANNEL* channel = request->GetChannel();
|
|
|
|
FileInfo fileInfo;
|
|
fileInfo.fileName = fileName;
|
|
fileInfo.fileLength = fileLength;
|
|
fileInfo.chunkSize = chunkSize;
|
|
|
|
WS_MESSAGE_DESCRIPTION fileInfoMessageDescription;
|
|
fileInfoMessageDescription.action = &fileInfoAction;
|
|
fileInfoMessageDescription.bodyElementDescription = &fileInfoElement;
|
|
|
|
hr = WsSendReplyMessage(
|
|
channel,
|
|
replyMessage,
|
|
&fileInfoMessageDescription,
|
|
WS_WRITE_REQUIRED_VALUE,
|
|
&fileInfo,
|
|
sizeof(fileInfo),
|
|
requestMessage,
|
|
NULL,
|
|
error);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
PrintError(L"CFileRepServer::SendFileInfo", true);
|
|
PrintError(hr, error, true);
|
|
}
|
|
|
|
WsResetMessage(replyMessage, NULL);
|
|
|
|
PrintVerbose(L"Leaving CFileRepServer::SendFileInfo");
|
|
return hr;
|
|
}
|
|
|
|
// Reads the data and serializes it into the message. This function does custom serialization for the same
|
|
// reason and with the same alrogithm as DeserializeAndWriteMessage.
|
|
HRESULT CFileRepServer::ReadAndSendChunk(
|
|
_In_ CRequest* request,
|
|
_In_ long chunkSize,
|
|
_In_ LONGLONG chunkPosition,
|
|
_In_ HANDLE file)
|
|
{
|
|
PrintVerbose(L"Entering CFileRepServer::ReadAndSendChunk");
|
|
|
|
if (chunkSize < 0 || chunkPosition < 0)
|
|
{
|
|
PrintVerbose(L"Leaving CFileRepServer::ReadAndSendChunk");
|
|
return E_INVALIDARG;
|
|
}
|
|
|
|
HRESULT hr = S_OK;
|
|
WS_XML_WRITER* writer = NULL;
|
|
WS_MESSAGE* replyMessage = request->GetReplyMessage();
|
|
WS_MESSAGE* requestMessage = request->GetRequestMessage();
|
|
WS_ERROR* error = request->GetError();
|
|
WS_CHANNEL* channel = request->GetChannel();
|
|
|
|
BYTE* buf = NULL;
|
|
LONG length = 0;
|
|
|
|
// To avoid using too much memory we read and write the message in chunks.
|
|
LONG bytesToRead = FILE_CHUNK;
|
|
if (bytesToRead > chunkSize)
|
|
{
|
|
bytesToRead = chunkSize;
|
|
}
|
|
|
|
buf = (BYTE*)HeapAlloc(GetProcessHeap(), 0, bytesToRead);
|
|
IfNullExit(buf);
|
|
|
|
IfFailedExit(WsInitializeMessage(replyMessage, WS_BLANK_MESSAGE, requestMessage, error));
|
|
|
|
// Add the action header
|
|
IfFailedExit(WsSetHeader(
|
|
replyMessage,
|
|
WS_ACTION_HEADER,
|
|
WS_XML_STRING_TYPE,
|
|
WS_WRITE_REQUIRED_VALUE,
|
|
&fileReplyAction,
|
|
sizeof(fileReplyAction),
|
|
error));
|
|
|
|
// Send the message headers
|
|
IfFailedExit(WsWriteMessageStart(channel, replyMessage, NULL, error));
|
|
|
|
// Get writer to serialize message body
|
|
IfFailedExit(WsGetMessageProperty(replyMessage, WS_MESSAGE_PROPERTY_BODY_WRITER, &writer, sizeof(writer), error));
|
|
|
|
// Write FileChunk start element.
|
|
// This whole code block is the serialization equivalent of the desiralization code.
|
|
IfFailedExit(WsWriteStartElement(writer, NULL, &fileChunkLocalName, &fileChunkNamespace, error));
|
|
|
|
// Write chunkPosition element
|
|
IfFailedExit(WsWriteStartElement(writer, NULL, &chunkPositionLocalName, &fileChunkNamespace, error));
|
|
IfFailedExit(WsWriteValue(writer, WS_INT64_VALUE_TYPE, &chunkPosition, sizeof(chunkPosition), error));
|
|
IfFailedExit(WsWriteEndElement(writer, error));
|
|
|
|
// Write fileContent start element
|
|
IfFailedExit(WsWriteStartElement(writer, NULL, &fileContentLocalName, &fileChunkNamespace, error));
|
|
|
|
// Like in the deserialization code, we read the file in multiple steps to avoid
|
|
// having to have everything in memory at once. The message could potentially be
|
|
// big so this is more efficient.
|
|
for (;;)
|
|
{
|
|
ULONG bytesRead = 0;
|
|
|
|
if (length + bytesToRead > chunkSize)
|
|
{
|
|
bytesToRead = chunkSize - length;
|
|
}
|
|
|
|
if (!ReadFile(file, buf, bytesToRead, &bytesRead, NULL))
|
|
{
|
|
|
|
PrintError(L"File read error.", true);
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
|
|
EXIT_FUNCTION
|
|
}
|
|
|
|
if (0 == bytesRead)
|
|
{
|
|
// We reched the end of the file before filling the chunk. Send a partial chunk.
|
|
break;
|
|
}
|
|
|
|
IfFailedExit(WsWriteBytes(writer, buf, bytesRead, error));
|
|
|
|
length += bytesRead;
|
|
|
|
if (length == chunkSize)
|
|
{
|
|
// We filled the message
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Write fileContent end element
|
|
IfFailedExit(WsWriteEndElement(writer, error));
|
|
|
|
// Write error element
|
|
IfFailedExit(WsWriteStartElement(writer, NULL, &errorLocalName, &fileChunkNamespace, error));
|
|
const WCHAR* noError = GlobalStrings::noError;
|
|
IfFailedExit(WsWriteType(
|
|
writer,
|
|
WS_ELEMENT_TYPE_MAPPING,
|
|
WS_WSZ_TYPE,
|
|
NULL,
|
|
WS_WRITE_REQUIRED_POINTER,
|
|
&noError,
|
|
sizeof(noError),
|
|
error));
|
|
|
|
// Closing elements;
|
|
IfFailedExit(WsWriteEndElement(writer, error));
|
|
IfFailedExit(WsWriteEndElement(writer, error));
|
|
IfFailedExit(WsWriteMessageEnd(channel, replyMessage, NULL, error));
|
|
|
|
hr = WsResetMessage(replyMessage, NULL);
|
|
HeapFree(GetProcessHeap(), 0, buf);
|
|
|
|
PrintVerbose(L"Leaving CFileRepServer::ReadAndSendChunk");
|
|
return hr;
|
|
|
|
|
|
ERROR_EXIT
|
|
|
|
PrintError(L"CFileRepServer::ReadAndSendChunk", true);
|
|
PrintError(hr, error, true);
|
|
|
|
WsResetMessage(replyMessage, NULL);
|
|
if (NULL != buf)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, buf);
|
|
}
|
|
|
|
PrintVerbose(L"Leaving CFileRepServer::ReadAndSendChunk");
|
|
return hr;
|
|
}
|
|
|
|
// Construct an error message containing no data except the error string.
|
|
HRESULT CFileRepServer::SendError(
|
|
_In_ CRequest* request,
|
|
_In_z_ const WCHAR errorMessage[])
|
|
{
|
|
PrintVerbose(L"Entering CFileRepServer::SendError");
|
|
|
|
HRESULT hr = S_OK;
|
|
WS_ERROR* error = request->GetError();
|
|
WS_MESSAGE* replyMessage = request->GetReplyMessage();
|
|
WS_MESSAGE* requestMessage = request->GetRequestMessage();
|
|
WS_CHANNEL* channel = request->GetChannel();
|
|
|
|
FileChunk fileChunk;
|
|
fileChunk.fileContent.bytes = NULL;
|
|
fileChunk.fileContent.length = 0;
|
|
fileChunk.chunkPosition = -1;
|
|
fileChunk.error = (LPWSTR)errorMessage;
|
|
|
|
WS_MESSAGE_DESCRIPTION fileReplyMessageDescription;
|
|
fileReplyMessageDescription.action = &fileReplyAction;
|
|
fileReplyMessageDescription.bodyElementDescription = &fileChunkElement;
|
|
|
|
// As there is no large payload we use the serializer here.
|
|
hr = WsSendReplyMessage(
|
|
channel,
|
|
replyMessage,
|
|
&fileReplyMessageDescription,
|
|
WS_WRITE_REQUIRED_VALUE,
|
|
&fileChunk,
|
|
sizeof(fileChunk),
|
|
requestMessage,
|
|
NULL,
|
|
error);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
PrintError(L"CFileRepServer::SendError\n", true);
|
|
PrintError(hr, error, true);
|
|
}
|
|
|
|
WsResetMessage(replyMessage, NULL);
|
|
|
|
PrintVerbose(L"Leaving CFileRepServer::SendError");
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
|