mirror of
https://github.com/openmoh/openmohaa.git
synced 2025-04-28 21:57:57 +03:00
1644 lines
39 KiB
C
1644 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;
|
||
|
}
|