2025-11-28 00:35:46 +09:00

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;
}