openmohaa/code/gamespy/ghttp/ghttpPost.c
2023-02-04 21:00:01 +01:00

1643 lines
39 KiB
C

/*
GameSpy GHTTP SDK
Dan "Mr. Pants" Schoenblum
dan@gamespy.com
Copyright 1999-2007 GameSpy Industries, Inc
devsupport@gamespy.com
*/
#include "ghttpPost.h"
#include "ghttpMain.h"
#include "ghttpConnection.h"
#include "ghttpCommon.h"
#include "../common/gsCrypt.h"
#include "../common/gsSSL.h"
#include "../common/gsXML.h"
// The border between parts in a file send.
///////////////////////////////////////////
#define GHI_MULTIPART_BOUNDARY "Qr4G823s23d---<<><><<<>--7d118e0536"
#define GHI_MULTIPART_BOUNDARY_BASE "--" GHI_MULTIPART_BOUNDARY
#define GHI_MULTIPART_BOUNDARY_FIRST GHI_MULTIPART_BOUNDARY_BASE CRLF
#define GHI_MULTIPART_BOUNDARY_NORMAL CRLF GHI_MULTIPART_BOUNDARY_BASE CRLF
#define GHI_MULTIPART_BOUNDARY_END CRLF GHI_MULTIPART_BOUNDARY_BASE "--" CRLF
#define GHI_LEGAL_URLENCODED_CHARS "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@-.*"
#define GHI_DIGITS "0123456789ABCDEF"
// DIME header settings
// first byte is a combination of VERSION + first/last/chunked
#define GHI_DIME_VERSION (0x1<<3) // 5th bit (from the left)
#define GHI_DIMEFLAG_FIRSTRECORD (1<<2)
#define GHI_DIMEFLAG_LASTRECORD (1<<1)
#define GHI_DIMEFLAG_CHUNKED (1<<0)
// second byte is combination of TYPE_T and reserved (4bits = 0)
#define GHI_DIMETYPE_T_UNCHANGED (0x0 << 4)
#define GHI_DIMETYPE_T_MEDIA (0x1 << 4)
#define GHI_DIMETYPE_T_URI (0x2 << 4)
#define GHI_DIMETYPE_T_UNKNOWN (0x3 << 4)
#define GHI_DIMETYPE_T_EMPTY (0x4 << 4) // lengths must be set to 0
//#define GHI_DIME_SOAPID "gsi:soap"
#define GHI_DIME_SOAPID "cid:id0"
#define GHI_DIME_SOAPTYPE "http://schemas.xmlsoap.org/soap/envelope/"
typedef struct GSIDimeHeader
{
gsi_u8 mVersionAndFlags;
gsi_u8 mTypeT;
gsi_u16 mOptionsLength;
gsi_u16 mIdLength;
gsi_u16 mTypeLength;
gsi_u32 mDataLength;
// gsi_u8 mOptions[mOptionsLength];
// gsi_u8 mId[mIdLength];
// gsi_u8 mType[mTypeLength];
// gsi_u8 mData[mDataLength];
} GHIDimeHeader;
// POST TYPES.
//////////////
typedef enum
{
GHIString, // A regular string.
GHIFileDisk, // A file from disk.
GHIFileMemory, // A file from memory.
GHIXmlData // XML Soap. (long string)
} GHIPostDataType;
// POST OBJECT.
///////////////
typedef struct GHIPost
{
DArray data;
ghttpPostCallback callback;
void * param;
GHTTPBool hasFiles;
GHTTPBool hasSoap;
GHTTPBool useDIME;
GHTTPBool autoFree;
} GHIPost;
// POST DATA.
/////////////
typedef struct GHIPostStringData
{
char * string;
int len;
GHTTPBool invalidChars;
int extendedChars;
} GHIPostStringData;
typedef struct GHIPostFileDiskData
{
char * filename;
char * reportFilename;
char * contentType;
} GHIPostFileDiskData;
typedef struct GHIPostFileMemoryData
{
const char * buffer;
int len;
char * reportFilename;
char * contentType;
} GHIPostFileMemoryData;
typedef struct GHIPostXmlData
{
GSXmlStreamWriter xml;
} GHIPostXmlData;
typedef struct GHIPostData
{
GHIPostDataType type;
char * name;
union
{
GHIPostStringData string;
GHIPostFileDiskData fileDisk;
GHIPostFileMemoryData fileMemory;
GHIPostXmlData xml;
} data;
} GHIPostData;
// POST STATE.
//////////////
//typedef struct GHIPostStringState
//{
//} GHIPostStringState;
typedef struct GHIPostFileDiskState
{
FILE * file;
long len;
} GHIPostFileDiskState;
//typedef struct GHIPostFileMemoryState
//{
//} GHIPostFileMemoryState;
//typedef struct GHIPostSoapState
//{
//} GHIPostSoapState;
typedef struct GHIPostState
{
GHIPostData * data;
int pos;
union
{
//GHIPostStringState string;
GHIPostFileDiskState fileDisk;
//GHIPostFileMemoryState fileMemory;
//GHIPostSoapState soap;
} state;
} GHIPostState;
// FUNCTIONS.
/////////////
static void ghiPostDataFree
(
void * elem
)
{
GHIPostData * data = (GHIPostData *)elem;
// Free the name.
/////////////////
if (data->type != GHIXmlData)
gsifree(data->name);
// Free based on type.
//////////////////////
if(data->type == GHIString)
{
// Free the string string.
/////////////////////////
gsifree(data->data.string.string);
}
else if(data->type == GHIFileDisk)
{
// Free the strings.
////////////////////
gsifree(data->data.fileDisk.filename);
gsifree(data->data.fileDisk.reportFilename);
gsifree(data->data.fileDisk.contentType);
}
else if(data->type == GHIFileMemory)
{
// Free the strings.
////////////////////
gsifree(data->data.fileMemory.reportFilename);
gsifree(data->data.fileMemory.contentType);
}
else if(data->type == GHIXmlData)
{
gsXmlFreeWriter(data->data.xml.xml);
}
else
{
// The type didn't match any known types.
/////////////////////////////////////////
assert(0);
}
}
GHTTPPost ghiNewPost
(
void
)
{
GHIPost * post;
// Allocate the post object.
////////////////////////////
post = (GHIPost *)gsimalloc(sizeof(GHIPost));
if(!post)
return NULL;
// Initialize it.
/////////////////
memset(post, 0, sizeof(GHIPost));
post->autoFree = GHTTPTrue;
// Create the array of data objects.
////////////////////////////////////
post->data = ArrayNew(sizeof(GHIPostData), 0, ghiPostDataFree);
if(!post->data)
{
gsifree(post);
return NULL;
}
return (GHTTPPost)post;
}
void ghiPostSetAutoFree
(
GHTTPPost _post,
GHTTPBool autoFree
)
{
GHIPost * post = (GHIPost *)_post;
post->autoFree = autoFree;
}
GHTTPBool ghiIsPostAutoFree
(
GHTTPPost _post
)
{
GHIPost * post = (GHIPost *)_post;
return post->autoFree;
}
void ghiFreePost
(
GHTTPPost _post
)
{
GHIPost * post = (GHIPost *)_post;
// Free the array of data objects.
//////////////////////////////////
ArrayFree(post->data);
// Free the post object.
////////////////////////
gsifree(post);
}
GHTTPBool ghiPostAddString
(
GHTTPPost _post,
const char * name,
const char * string
)
{
GHIPost * post = (GHIPost *)_post;
GHIPostData data;
int len;
int rcode;
// Copy the strings.
////////////////////
name = goastrdup(name);
string = goastrdup(string);
if(!name || !string)
{
gsifree((char *)name);
gsifree((char *)string);
return GHTTPFalse;
}
// Set the data.
////////////////
memset(&data, 0, sizeof(GHIPostData));
data.type = GHIString;
data.name = (char *)name;
data.data.string.string = (char *)string;
len = (int)strlen(string);
data.data.string.len = len;
data.data.string.invalidChars = GHTTPFalse;
// Are there any invalid characters?
////////////////////////////////////
rcode = (int)strspn(string, GHI_LEGAL_URLENCODED_CHARS);
if(rcode != len)
{
int i;
int count = 0;
data.data.string.invalidChars = GHTTPTrue;
// Count the number, not including spaces.
//////////////////////////////////////////
for(i = 0 ; string[i] ; i++)
if(!strchr(GHI_LEGAL_URLENCODED_CHARS, string[i]) && (string[i] != ' '))
count++;
data.data.string.extendedChars = count;
}
// Add it.
//////////
ArrayAppend(post->data, &data);
return GHTTPTrue;
}
GHTTPBool ghiPostAddFileFromDisk
(
GHTTPPost _post,
const char * name,
const char * filename,
const char * reportFilename,
const char * contentType
)
{
GHIPost * post = (GHIPost *)_post;
GHIPostData data;
// Copy the strings.
////////////////////
name = goastrdup(name);
filename = goastrdup(filename);
reportFilename = goastrdup(reportFilename);
contentType = goastrdup(contentType);
if(!name || !filename || !reportFilename || !contentType)
{
gsifree((char *)name);
gsifree((char *)filename);
gsifree((char *)reportFilename);
gsifree((char *)contentType);
return GHTTPFalse;
}
// Set the data.
////////////////
memset(&data, 0, sizeof(GHIPostData));
data.type = GHIFileDisk;
data.name = (char *)name;
data.data.fileDisk.filename = (char *)filename;
data.data.fileDisk.reportFilename = (char *)reportFilename;
data.data.fileDisk.contentType = (char *)contentType;
// Add it.
//////////
ArrayAppend(post->data, &data);
// We have files.
/////////////////
post->hasFiles = GHTTPTrue;
// if we have both soap and a file we MUST use DIME
if (post->hasSoap == GHTTPTrue)
post->useDIME = GHTTPTrue;
return GHTTPTrue;
}
GHTTPBool ghiPostAddFileFromMemory
(
GHTTPPost _post,
const char * name,
const char * buffer,
int bufferLen,
const char * reportFilename,
const char * contentType
)
{
GHIPost * post = (GHIPost *)_post;
GHIPostData data;
// Copy the strings.
////////////////////
name = goastrdup(name);
reportFilename = goastrdup(reportFilename);
contentType = goastrdup(contentType);
if(!name || !reportFilename || !contentType)
{
gsifree((char *)name);
gsifree((char *)reportFilename);
gsifree((char *)contentType);
return GHTTPFalse;
}
// Set it.
//////////
memset(&data, 0, sizeof(GHIPostData));
data.type = GHIFileMemory;
data.name = (char *)name;
data.data.fileMemory.buffer = (char *)buffer;
data.data.fileMemory.len = bufferLen;
data.data.fileMemory.reportFilename = (char *)reportFilename;
data.data.fileMemory.contentType = (char *)contentType;
// Add it.
//////////
ArrayAppend(post->data, &data);
// We have a file.
//////////////////
post->hasFiles = GHTTPTrue;
// if we have both soap and a file we MUST use DIME
if (post->hasSoap == GHTTPTrue)
post->useDIME = GHTTPTrue;
return GHTTPTrue;
}
GHTTPBool ghiPostAddXml
(
GHTTPPost _post,
GSXmlStreamWriter xml
)
{
GHIPostData data;
//unsigned int rcode = 0;
GHIPost * post = (GHIPost *)_post;
data.type = GHIXmlData;
data.data.xml.xml = xml;
ArrayAppend(post->data, &data);
post->hasSoap = GHTTPTrue;
// if we have both soap and a file we MUST use DIME
if (post->hasFiles == GHTTPTrue)
post->useDIME = GHTTPTrue;
return GHTTPTrue;
}
void ghiPostSetCallback
(
GHTTPPost _post,
ghttpPostCallback callback,
void * param
)
{
GHIPost * post = (GHIPost *)_post;
// Set the callback and param.
//////////////////////////////
post->callback = callback;
post->param = param;
}
const char * ghiPostGetContentType
(
struct GHIConnection * connection
)
{
GHIPost * post = connection->post;
assert(post);
if(!post)
return "";
// Report content-type based on if we are sending files or not.
///////////////////////////////////////////////////////////////
if(post->useDIME)
return ("application/dime");
else if (post->hasFiles)
{
GS_ASSERT(!post->hasSoap);
return ("multipart/form-data; boundary=" GHI_MULTIPART_BOUNDARY);
}
else if (post->hasSoap)
{
GS_ASSERT(!post->hasFiles);
return ("text/xml");
}
else
{
GS_ASSERT(!post->hasSoap);
GS_ASSERT(!post->hasFiles);
return "application/x-www-form-urlencoded";
}
}
static int ghiPostGetNoFilesContentLength
(
struct GHIConnection * connection
)
{
GHIPost * post = connection->post;
GHIPostData * data;
int i;
int num;
int total = 0;
int foundSoapAlready = 0;
num = ArrayLength(post->data);
if(!num)
return 0;
for(i = 0 ; i < num ; i++)
{
data = (GHIPostData *)ArrayNth(post->data, i);
GS_ASSERT(data->type == GHIString || data->type == GHIXmlData);
if (data->type == GHIString)
{
total += (int)strlen(data->name);
total += data->data.string.len;
total += (data->data.string.extendedChars * 2);
total++; // '='
}
else if (data->type == GHIXmlData)
{
GS_ASSERT(foundSoapAlready == 0); // only support one soap object per request
foundSoapAlready = 1;
total += gsXmlWriterGetDataLength(data->data.xml.xml);
}
}
total += (num - 1); // '&'
GSI_UNUSED(foundSoapAlready);
return total;
}
static int ghiPostGetHasFilesContentLength
(
struct GHIConnection * connection
)
{
GHIPost * post = connection->post;
GHIPostData * data;
int i;
int num;
int total = 0;
int foundSoapAlready = 0;
static int boundaryLen;
static int stringBaseLen;
static int fileBaseLen;
static int endLen;
static int xmlBaseLen;
if(!boundaryLen)
{
if (post->useDIME)
{
GS_ASSERT(post->hasSoap);
GS_ASSERT(post->hasFiles);
boundaryLen = sizeof(GHIDimeHeader);
stringBaseLen = boundaryLen;
fileBaseLen = boundaryLen;
xmlBaseLen = boundaryLen;
endLen = 0;
}
else
{
GS_ASSERT(!post->hasSoap);
boundaryLen = (int)strlen(GHI_MULTIPART_BOUNDARY_BASE);
stringBaseLen = (boundaryLen + 47); // + name + string
fileBaseLen = (boundaryLen + 76); // + name + filename + content-type + file
xmlBaseLen = 0; // no boundaries for text/xml type soap
endLen = (boundaryLen + 4);
}
}
num = ArrayLength(post->data);
for(i = 0 ; i < num ; i++)
{
data = (GHIPostData *)ArrayNth(post->data, i);
if(data->type == GHIString)
{
total += stringBaseLen;
total += (int)strlen(data->name);
total += data->data.string.len;
}
else if(data->type == GHIFileDisk)
{
GHIPostState * state;
total += fileBaseLen;
total += (int)strlen(data->name);
total += (int)strlen(data->data.fileDisk.contentType);
state = (GHIPostState *)ArrayNth(connection->postingState.states, i);
assert(state);
total += (int)state->state.fileDisk.len;
if (!post->useDIME)
total += (int)strlen(data->data.fileDisk.reportFilename);
if (post->useDIME)
{
// have to include padding bytes!
int padBytes = 0;
padBytes = 4-(int)strlen(data->name)%4;
if (padBytes != 4)
total += padBytes;
padBytes = 4-(int)strlen(data->data.fileDisk.contentType)%4;
if (padBytes != 4)
total += padBytes;
padBytes = 4-(int)state->state.fileDisk.len%4;
if (padBytes != 4)
total += padBytes;
}
}
else if(data->type == GHIFileMemory)
{
total += fileBaseLen;
total += (int)strlen(data->name);
total += (int)strlen(data->data.fileMemory.contentType);
total += data->data.fileMemory.len;
if (!post->useDIME)
total += (int)strlen(data->data.fileMemory.reportFilename);
if (post->useDIME)
{
// have to include padding bytes!
int padBytes = 0;
padBytes = 4-(int)strlen(data->name)%4;
if (padBytes != 4)
total += padBytes;
padBytes = 4-(int)strlen(data->data.fileMemory.contentType)%4;
if (padBytes != 4)
total += padBytes;
padBytes = 4-(int)data->data.fileMemory.len%4;
if (padBytes != 4)
total += padBytes;
}
}
else if(data->type == GHIXmlData)
{
int padBytes = 0;
GS_ASSERT(foundSoapAlready == 0); // only one soap envelope per request
GS_ASSERT(post->useDIME); // soap+file = use DIME
foundSoapAlready = 1;
total += xmlBaseLen;
total += gsXmlWriterGetDataLength(data->data.xml.xml);
// have to include padding bytes!
padBytes = 4-(int)gsXmlWriterGetDataLength(data->data.xml.xml)%4;
if (padBytes != 4)
total += padBytes;
total += (int)strlen(GHI_DIME_SOAPID);
padBytes = 4-(int)strlen(GHI_DIME_SOAPID)%4;
if (padBytes != 4)
total += padBytes;
total += (int)strlen(GHI_DIME_SOAPTYPE);
padBytes = 4-(int)strlen(GHI_DIME_SOAPTYPE)%4;
if (padBytes != 4)
total += padBytes;
}
else
{
assert(0);
return 0;
}
}
// Add the end.
///////////////
total += endLen;
GSI_UNUSED(foundSoapAlready);
return total;
}
static int ghiPostGetContentLength
(
struct GHIConnection * connection
)
{
GHIPost * post = connection->post;
assert(post);
if(!post)
return 0;
if(post->hasFiles)
return ghiPostGetHasFilesContentLength(connection);
return ghiPostGetNoFilesContentLength(connection);
}
static GHTTPBool ghiPostStateInit
(
GHIPostState * state
)
{
GHIPostDataType type;
// The type.
////////////
type = state->data->type;
// Set the position to sending header.
//////////////////////////////////////
state->pos = -1;
// Init based on type.
//////////////////////
if(type == GHIString)
{
}
else if(type == GHIFileDisk)
{
// Open the file.
/////////////////
#ifndef NOFILE
state->state.fileDisk.file = fopen(state->data->data.fileDisk.filename, "rb");
#endif
if(!state->state.fileDisk.file)
return GHTTPFalse;
// Get the file length.
///////////////////////
if(fseek(state->state.fileDisk.file, 0, SEEK_END) != 0)
return GHTTPFalse;
state->state.fileDisk.len = ftell(state->state.fileDisk.file);
if(state->state.fileDisk.len == EOF)
return GHTTPFalse;
rewind(state->state.fileDisk.file);
}
else if(type == GHIFileMemory)
{
}
else if(type == GHIXmlData)
{
}
else
{
// The type didn't match any known types.
/////////////////////////////////////////
assert(0);
return GHTTPFalse;
}
return GHTTPTrue;
}
static void ghiPostStateCleanup
(
GHIPostState * state
)
{
GHIPostDataType type;
// The type.
////////////
type = state->data->type;
// Init based on type.
//////////////////////
if(type == GHIString)
{
}
else if(type == GHIFileDisk)
{
if(state->state.fileDisk.file)
fclose(state->state.fileDisk.file);
state->state.fileDisk.file = NULL;
}
else if(type == GHIFileMemory)
{
}
else if(type == GHIXmlData)
{
}
else
{
// The type didn't match any known types.
/////////////////////////////////////////
assert(0);
}
}
GHTTPBool ghiPostInitState
(
struct GHIConnection * connection
)
{
int i;
int len;
GHIPostData * data;
GHIPostState state;
GHIPostState * pState;
assert(connection->post);
if(!connection->post)
return GHTTPFalse;
// Create an array for the states.
//////////////////////////////////
connection->postingState.index = 0;
connection->postingState.bytesPosted = 0;
connection->postingState.totalBytes = 0;
connection->postingState.completed = GHTTPFalse;
connection->postingState.callback = connection->post->callback;
connection->postingState.param = connection->post->param;
len = ArrayLength(connection->post->data);
connection->postingState.states = ArrayNew(sizeof(GHIPostState), len, NULL);
if(!connection->postingState.states)
return GHTTPFalse;
// Setup all the states.
////////////////////////
for(i = 0 ; i < len ; i++)
{
// Get the data object for this index.
//////////////////////////////////////
data = (GHIPostData *)ArrayNth(connection->post->data, i);
// Initialize the state's members.
//////////////////////////////////
memset(&state, 0, sizeof(GHIPostState));
state.data = data;
// Call the init function.
//////////////////////////
if(!ghiPostStateInit(&state))
{
// We need to cleanup everything we just initialized.
/////////////////////////////////////////////////////
for(i-- ; i >= 0 ; i--)
{
pState = (GHIPostState *)ArrayNth(connection->postingState.states, i);
ghiPostStateCleanup(pState);
}
// Free the array.
//////////////////
ArrayFree(connection->postingState.states);
connection->postingState.states = NULL;
return GHTTPFalse;
}
// Add it to the array.
///////////////////////
ArrayAppend(connection->postingState.states, &state);
}
// If this asserts, there aren't the same number of state objects as data objects.
// There should be a 1-to-1 mapping between data and states.
//////////////////////////////////////////////////////////////////////////////////
assert(ArrayLength(connection->post->data) == ArrayLength(connection->postingState.states));
// Get the total number of bytes.
/////////////////////////////////
connection->postingState.totalBytes = ghiPostGetContentLength(connection);
// Wait for continue before posting.
// -- Enabled for Soap messages only
// -- Disabled for all other content because many web servers do not support it
//////////////////////////////////////////////////////
if (connection->post->hasSoap == GHTTPTrue)
connection->postingState.waitPostContinue = GHTTPTrue;
else
connection->postingState.waitPostContinue = GHTTPFalse;
return GHTTPTrue;
}
void ghiPostCleanupState
(
struct GHIConnection * connection
)
{
int i;
int len;
GHIPostState * state;
// Loop through and call the cleanup function.
//////////////////////////////////////////////
if(connection->postingState.states)
{
len = ArrayLength(connection->postingState.states);
for(i = 0 ; i < len ; i++)
{
state = (GHIPostState *)ArrayNth(connection->postingState.states, i);
ghiPostStateCleanup(state);
}
// Free the array.
//////////////////
ArrayFree(connection->postingState.states);
connection->postingState.states = NULL;
}
// Free the post.
/////////////////
if(connection->post && connection->post->autoFree)
{
ghiFreePost(connection->post);
connection->post = NULL;
}
}
static GHIPostingResult ghiPostStringStateDoPosting
(
GHIPostState * state,
GHIConnection * connection
)
{
//GHTTPBool result;
assert(state->pos >= 0);
// Is this an empty string?
///////////////////////////
if(state->data->data.string.len == 0)
return GHIPostingDone;
assert(state->pos < state->data->data.string.len);
// If we're doing a simple post, we need to fix invalid characters.
// - only applies to simple posts
///////////////////////////////////////////////////////////////////
if(!connection->post->hasFiles && !connection->post->hasSoap && state->data->data.string.invalidChars)
{
int i;
int c;
const char * string = state->data->data.string.string;
char hex[4] = "%00";
GHIBuffer *writeBuffer;
// When encrypting, we need space for two copies
if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None)
writeBuffer = &connection->sendBuffer;
else
writeBuffer = &connection->encodeBuffer;
// This could probably be done a lot better.
////////////////////////////////////////////
for(i = 0 ; (c = string[i]) != 0 ; i++)
{
if(strchr(GHI_LEGAL_URLENCODED_CHARS, c))
{
// Legal.
/////////
//result = ghiAppendCharToBuffer(writeBuffer, c);
ghiAppendCharToBuffer(writeBuffer, c);
}
else if(c == ' ')
{
// Space.
/////////
//result = ghiAppendCharToBuffer(writeBuffer, '+');
ghiAppendCharToBuffer(writeBuffer, '+');
}
else
{
// To hex.
//////////
assert((c / 16) < 16);
hex[1] = GHI_DIGITS[c / 16];
hex[2] = GHI_DIGITS[c % 16];
//result = ghiAppendDataToBuffer(writeBuffer, hex, 3);
ghiAppendDataToBuffer(writeBuffer, hex, 3);
}
}
}
else
{
// copy the string as-is, encrypting if necessary
GHITrySendResult result = ghiTrySendThenBuffer(connection,
state->data->data.string.string, state->data->data.string.len);
if (result == GHITrySendError)
return GHIPostingError;
else
return GHIPostingDone;
}
// Send the URL fixed string
////////////////////////////
if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None)
{
// The URL fixed string was written to the send buffer, so send it!
if (!ghiSendBufferedData(connection))
return GHIPostingError;
if (connection->sendBuffer.pos == connection->sendBuffer.len)
ghiResetBuffer(&connection->sendBuffer);
return GHIPostingDone;
}
else
{
// SSL data is in the "to be encrypted" buffer, so wait until
// we have the full MIME form before encrypting (for efficiency)
return GHIPostingDone;
}
}
static GHIPostingResult ghiPostXmlStateDoPosting
(
GHIPostState * state,
GHIConnection * connection
)
{
GSXmlStreamWriter xml = state->data->data.xml.xml;
char pad[3] = { '\0', '\0', '\0' };
int padlen = 0;
// make sure state is valid
GS_ASSERT(state->pos >= 0);
GS_ASSERT(connection->post != NULL);
// when using a DIME, we have to pad to multiple of 4
if (connection->post->useDIME)
{
padlen = 4-(gsXmlWriterGetDataLength(xml)%4);
if (padlen == 4)
padlen = 0;
}
if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None &&
connection->encryptor.mEncryptOnBuffer == GHTTPTrue)
{
// Copy to encode buffer before encrypting
GS_ASSERT(connection->encodeBuffer.len >= 0); // there must be a header for this soap data!
if (!ghiAppendDataToBuffer(&connection->encodeBuffer, gsXmlWriterGetData(xml), gsXmlWriterGetDataLength(xml)) ||
!ghiAppendDataToBuffer(&connection->encodeBuffer, pad, padlen) ||
!ghiEncryptDataToBuffer(&connection->sendBuffer, connection->encodeBuffer.data, connection->encodeBuffer.len)
)
{
return GHIPostingError;
}
// Clear out our temporary buffer
ghiResetBuffer(&connection->encodeBuffer);
// Send what we can now
if (GHTTPFalse == ghiSendBufferedData(connection))
return GHIPostingError;
// is there more to send?
if (connection->sendBuffer.pos == connection->sendBuffer.len)
ghiResetBuffer(&connection->sendBuffer);
return GHIPostingDone;
}
else
{
GHITrySendResult result;
// plain text - send immediately
result = ghiTrySendThenBuffer(connection, gsXmlWriterGetData(xml), gsXmlWriterGetDataLength(xml));
if (result == GHITrySendError)
return GHIPostingError;
result = ghiTrySendThenBuffer(connection, pad, padlen);
if (result == GHITrySendError)
return GHIPostingError;
return GHIPostingDone;
}
}
static GHIPostingResult ghiPostFileDiskStateDoPosting
(
GHIPostState * state,
GHIConnection * connection
)
{
char buffer[4096];
int len;
GHITrySendResult result;
assert(state->pos >= 0);
assert(state->pos < state->state.fileDisk.len);
assert(state->pos == (int)ftell(state->state.fileDisk.file));
// Loop while data is being sent.
/////////////////////////////////
do
{
// Read some data from the file.
////////////////////////////////
len = (int)fread(buffer, 1, sizeof(buffer), state->state.fileDisk.file);
if(len <= 0)
{
connection->completed = GHTTPTrue;
connection->result = GHTTPFileReadFailed;
return GHIPostingError;
}
// Update our position.
///////////////////////
state->pos += len;
// Check for too much.
//////////////////////
if(state->pos > state->state.fileDisk.len)
{
connection->completed = GHTTPTrue;
connection->result = GHTTPFileReadFailed;
return GHIPostingError;
}
// Send.
////////
result = ghiTrySendThenBuffer(connection, buffer, len);
if(result == GHITrySendError)
return GHIPostingError;
// Check if we've handled everything.
/////////////////////////////////////
if(state->pos == state->state.fileDisk.len)
{
// when using a DIME, we have to pad to multiple of 4
if (connection->post->useDIME)
{
char pad[3] = { '\0', '\0', '\0' };
int padlen = 4-state->state.fileDisk.len%4;
if (padlen != 4 && padlen > 0)
{
if (GHITrySendError == ghiTrySendThenBuffer(connection, pad, padlen))
return GHIPostingError;
}
}
return GHIPostingDone;
}
}
while(result == GHITrySendSent);
return GHIPostingPosting;
}
static GHIPostingResult ghiPostFileMemoryStateDoPosting
(
GHIPostState * state,
GHIConnection * connection
)
{
int rcode;
int len;
assert(state->pos >= 0);
// Is this an empty file?
/////////////////////////
if(state->data->data.fileMemory.len == 0)
return GHIPostingDone;
assert(state->pos < state->data->data.fileMemory.len);
// Send what we can.
////////////////////
if (connection->encryptor.mEngine == GHTTPEncryptionEngine_None)
{
// Plain text: Send directly from memory
do
{
len = (state->data->data.fileMemory.len - state->pos);
rcode = ghiDoSend(connection, state->data->data.fileMemory.buffer + state->pos, len);
if(gsiSocketIsError(rcode))
return GHIPostingError;
// Update the pos.
//////////////////
state->pos += rcode;
// Did we send it all?
//////////////////////
if(state->data->data.fileMemory.len == state->pos)
{
// when using a DIME, we have to pad to multiple of 4
if (connection->post->useDIME)
{
char pad[3] = { '\0', '\0', '\0' };
int padlen = 4-state->data->data.fileMemory.len%4;
if (padlen != 4 && padlen > 0)
{
if (GHITrySendError == ghiTrySendThenBuffer(connection, pad, padlen))
return GHIPostingError;
}
}
return GHIPostingDone;
}
}
while(rcode);
return GHIPostingPosting; // (rcode == 0) ?
}
else
{
// Encrypted: can't avoid the copy due to encryption+MAC
GHITrySendResult result;
do
{
len = (state->data->data.fileMemory.len - state->pos);
len = min(len, GS_SSL_MAX_CONTENTLENGTH);
result = ghiTrySendThenBuffer(connection, state->data->data.fileMemory.buffer + state->pos, len);
if (result == GHITrySendError)
return GHIPostingError;
// Update the pos.
//////////////////
state->pos += len;
// Did we send it all?
//////////////////////
if(state->data->data.fileMemory.len == state->pos)
{
// when using a DIME, we have to pad to multiple of 4
if (connection->post->useDIME)
{
char pad[3] = { '\0', '\0', '\0' };
int padlen = 4-state->data->data.fileMemory.len%4;
if (padlen != 4 && padlen > 0)
{
if (GHITrySendError == ghiTrySendThenBuffer(connection, pad, padlen))
return GHIPostingError;
}
}
return GHIPostingDone;
}
}
while(result == GHITrySendSent);
return GHIPostingPosting;
}
}
static GHIPostingResult ghiPostStateDoPosting
(
GHIPostState * state,
GHIConnection * connection,
GHTTPBool first,
GHTTPBool last
)
{
int len = 0;
GHITrySendResult result;
// Check for sending the header.
////////////////////////////////
if(state->pos == -1)
{
char buffer[2048];
// Bump up the position so we only send the header once.
////////////////////////////////////////////////////////
state->pos = 0;
// Check if this is a simple post.
//////////////////////////////////
if(!connection->post->hasFiles && !connection->post->hasSoap)
{
// Simple post only supports strings.
/////////////////////////////////////
assert(state->data->type == GHIString);
// Format the header.
/////////////////////
if(first)
sprintf(buffer, "%s=", state->data->name);
else
sprintf(buffer, "&%s=", state->data->name);
}
else
{
// Format the header based on string or file.
/////////////////////////////////////////////
if(state->data->type == GHIString)
{
sprintf(buffer,
"%s"
"Content-Disposition: form-data; "
"name=\"%s\"" CRLF
CRLF,
first?GHI_MULTIPART_BOUNDARY_FIRST:GHI_MULTIPART_BOUNDARY_NORMAL,
state->data->name);
}
else if(state->data->type == GHIXmlData)
{
if (connection->post->useDIME)
{
// use DIME header
// Copy from a temp struct to circumvent alignment issues
int writePos = 0;
int padBytes = 0;
GHIDimeHeader header;
header.mVersionAndFlags = GHI_DIME_VERSION;
if (first)
header.mVersionAndFlags |= GHI_DIMEFLAG_FIRSTRECORD;
if (last)
header.mVersionAndFlags |= GHI_DIMEFLAG_LASTRECORD;
header.mTypeT = GHI_DIMETYPE_T_URI;
header.mOptionsLength = 0;
header.mIdLength = htons((short)strlen(GHI_DIME_SOAPID));
header.mTypeLength = htons((short)strlen(GHI_DIME_SOAPTYPE));
header.mDataLength = htonl(gsXmlWriterGetDataLength(state->data->data.xml.xml));
memcpy(&buffer[writePos], &header, sizeof(GHIDimeHeader));
writePos += sizeof(GHIDimeHeader);
// id
strcpy(&buffer[writePos], GHI_DIME_SOAPID);
writePos += strlen(GHI_DIME_SOAPID);
padBytes = (int)(4-strlen(GHI_DIME_SOAPID)%4);
if (padBytes != 4)
{
while(padBytes-- > 0)
buffer[writePos++] = '\0';
}
// type
strcpy(&buffer[writePos], GHI_DIME_SOAPTYPE);
writePos += strlen(GHI_DIME_SOAPTYPE);
padBytes = (int)(4-strlen(GHI_DIME_SOAPTYPE)%4);
if (padBytes != 4)
{
while(padBytes-- > 0)
buffer[writePos++] = '\0';
}
len = writePos;
}
else
buffer[0] = '\0';
}
else if((state->data->type == GHIFileDisk) || (state->data->type == GHIFileMemory))
{
const char * filename;
const char * contentType;
int filelen;
if(state->data->type == GHIFileDisk)
{
filelen = state->state.fileDisk.len;
filename = state->data->data.fileDisk.reportFilename;
contentType = state->data->data.fileDisk.contentType;
}
else
{
filelen = state->data->data.fileMemory.len;
filename = state->data->data.fileMemory.reportFilename;
contentType = state->data->data.fileMemory.contentType;
}
if (connection->post->useDIME)
{
// use DIME header
// Copy from a temp struct to circumvent alignment issues
int writePos = 0;
int padBytes = 0;
GHIDimeHeader header;
header.mVersionAndFlags = GHI_DIME_VERSION;
if (first)
header.mVersionAndFlags |= GHI_DIMEFLAG_FIRSTRECORD;
if (last)
header.mVersionAndFlags |= GHI_DIMEFLAG_LASTRECORD;
header.mTypeT = GHI_DIMETYPE_T_MEDIA;
header.mOptionsLength = 0;
header.mIdLength = htons((short)strlen(state->data->name));
header.mTypeLength = htons((short)strlen(contentType));
header.mDataLength = htonl(filelen);
memcpy(&buffer[writePos], &header, sizeof(GHIDimeHeader));
writePos += sizeof(GHIDimeHeader);
// id
strcpy(&buffer[writePos], state->data->name);
writePos += strlen(state->data->name);
padBytes = (int)(4-strlen(state->data->name)%4);
if (padBytes != 4)
{
while(padBytes-- > 0)
buffer[writePos++] = '\0';
}
// type
strcpy(&buffer[writePos], contentType);
writePos += strlen(contentType);
padBytes = (int)(4-strlen(contentType)%4);
if (padBytes != 4)
{
while(padBytes-- > 0)
buffer[writePos++] = '\0';
}
len = writePos;
}
else
{
// use MIME header
sprintf(buffer,
"%s"
"Content-Disposition: form-data; "
"name=\"%s\"; "
"filename=\"%s\"" CRLF
"Content-Type: %s" CRLF CRLF,
first?GHI_MULTIPART_BOUNDARY_FIRST:GHI_MULTIPART_BOUNDARY_NORMAL,
state->data->name,
filename,
contentType);
}
}
else
{
assert(0);
}
}
// SSL: encrypt and send
if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None &&
connection->encryptor.mEncryptOnBuffer == GHTTPTrue)
{
if (len == 0)
len = (int)strlen(buffer);
if (GHTTPFalse == ghiEncryptDataToBuffer(&connection->sendBuffer, buffer, len))
return GHIPostingError;
if (GHTTPFalse == ghiSendBufferedData(connection))
return GHIPostingError;
// any data remaining?
if (connection->sendBuffer.pos < connection->sendBuffer.len)
return GHIPostingPosting;
// We sent everything, reset the send buffer to conserve space
ghiResetBuffer(&connection->sendBuffer);
}
// If sending plain text, send right away
else
{
// Try sending. (the one-time header)
/////////////////////////////////////
if (len == 0)
len = (int)strlen(buffer);
result = ghiTrySendThenBuffer(connection, buffer, len);
if(result == GHITrySendError)
return GHIPostingError;
// If it was buffered, don't try anymore.
/////////////////////////////////////////
if(result == GHITrySendBuffered)
return GHIPostingPosting;
// We sent everything, reset the send buffer to conserve space
ghiResetBuffer(&connection->sendBuffer);
}
}
// Post based on type.
//////////////////////
if(state->data->type == GHIString)
return ghiPostStringStateDoPosting(state, connection);
if(state->data->type == GHIXmlData)
return ghiPostXmlStateDoPosting(state, connection);
if(state->data->type == GHIFileDisk)
return ghiPostFileDiskStateDoPosting(state, connection);
assert(state->data->type == GHIFileMemory);
return ghiPostFileMemoryStateDoPosting(state, connection);
}
GHIPostingResult ghiPostDoPosting
(
struct GHIConnection * connection
)
{
GHIPostingResult postingResult;
GHITrySendResult trySendResult;
GHIPostingState * postingState;
GHIPostState * postState;
int len;
assert(connection);
assert(connection->post);
assert(connection->postingState.states);
assert(ArrayLength(connection->post->data) == ArrayLength(connection->postingState.states));
assert(connection->postingState.index >= 0);
assert(connection->postingState.index <= ArrayLength(connection->postingState.states));
// Cache some stuff.
////////////////////
postingState = &connection->postingState;
len = ArrayLength(postingState->states);
// Check for buffered data.
///////////////////////////
if(connection->sendBuffer.pos < connection->sendBuffer.len)
{
// Send the buffered data.
//////////////////////////
if(!ghiSendBufferedData(connection))
return GHIPostingError;
// Check if we couldn't send it all.
////////////////////////////////////
if(connection->sendBuffer.pos < connection->sendBuffer.len)
return GHIPostingPosting;
// We sent it all, so reset the buffer.
///////////////////////////////////////
ghiResetBuffer(&connection->sendBuffer);
// If uploading a DIME attachment, wait for HTTP continue.
//////////////////////////////////////////////////////////
if (connection->postingState.waitPostContinue)
return GHIPostingWaitForContinue;
// Was that all that's left?
////////////////////////////
if(connection->postingState.index == len)
return GHIPostingDone;
}
// When posting soap and DIME attachments, we should terminate the
// header and wait for a response. This will either be a continue or
// a server error.
if (connection->postingState.waitPostContinue)
{
if (connection->post->hasFiles || connection->post->hasSoap)
{
// terminate the header and wait for a response
GS_ASSERT(connection->encodeBuffer.len == 0);
trySendResult = ghiTrySendThenBuffer(connection, CRLF, (int)strlen(CRLF));
if(trySendResult == GHITrySendError)
return GHIPostingError;
else if (trySendResult == GHITrySendBuffered)
return GHIPostingPosting;
else
{
if (connection->postingState.waitPostContinue == GHTTPTrue)
return GHIPostingWaitForContinue;
//else
// fall through
}
}
else
{
// simple posts don't have to wait
connection->postingState.waitPostContinue = GHTTPFalse;
// fall through
}
}
// Loop while there's data to upload.
/////////////////////////////////////
while(postingState->index < len)
{
// Get the current data state.
//////////////////////////////
postState = (GHIPostState *)ArrayNth(postingState->states, postingState->index);
assert(postState);
// Upload the current data.
///////////////////////////
postingResult = ghiPostStateDoPosting(postState, connection,
(postingState->index == 0)?GHTTPTrue:GHTTPFalse,
(postingState->index == (ArrayLength(postingState->states)-1))?GHTTPTrue:GHTTPFalse);
// Check for error.
///////////////////
if(postingResult == GHIPostingError)
{
// Make sure we already set the error stuff.
////////////////////////////////////////////
assert(connection->completed && connection->result);
return GHIPostingError;
}
// Check for still posting.
///////////////////////////
if(postingResult == GHIPostingPosting)
return GHIPostingPosting;
// One more done.
/////////////////
postingState->index++;
}
// Encrypt and send anything left in the encode buffer
// -- for example, when posting string data we don't encrypt until we have the entire string (for efficiency only)
if (connection->encryptor.mEngine != GHTTPEncryptionEngine_None)
{
if (connection->encodeBuffer.len > 0)
{
GS_ASSERT(connection->encodeBuffer.pos == 0); // if you hit this, it means you forgot the clear the buffer
if (GHTTPFalse == ghiEncryptDataToBuffer(&connection->sendBuffer,
connection->encodeBuffer.data, connection->encodeBuffer.len))
{
return GHIPostingError;
}
ghiResetBuffer(&connection->encodeBuffer);
}
}
// Send or buffer the end marker.
/////////////////////////////////
if(connection->post->hasFiles && !connection->post->useDIME)
{
GS_ASSERT(!connection->post->hasSoap);
// send MIME boundary end
trySendResult = ghiTrySendThenBuffer(connection, GHI_MULTIPART_BOUNDARY_END, (int)strlen(GHI_MULTIPART_BOUNDARY_END));
if(trySendResult == GHITrySendError)
return GHIPostingError;
}
// We're not done if there's stuff in the buffer.
/////////////////////////////////////////////////
if(connection->sendBuffer.pos < connection->sendBuffer.len)
return GHIPostingPosting;
return GHIPostingDone;
}