mirror of
https://github.com/Ylianst/MeshAgent
synced 2025-12-06 00:13:33 +00:00
3846 lines
123 KiB
C
3846 lines
123 KiB
C
/*
|
|
Copyright 2006 - 2018 Intel Corporation
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
#ifdef MEMORY_CHECK
|
|
#include <assert.h>
|
|
#define MEMCHECK(x) x
|
|
#else
|
|
#define MEMCHECK(x)
|
|
#endif
|
|
|
|
#if defined(WIN32) && !defined(_WIN32_WCE)
|
|
|
|
#define _CRTDBG_MAP_ALLOC
|
|
#include <crtdbg.h>
|
|
#endif
|
|
|
|
#if defined(WINSOCK2)
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#elif defined(WINSOCK1)
|
|
#include <winsock.h>
|
|
#include <wininet.h>
|
|
#endif
|
|
|
|
#ifdef SEMAPHORE_TRACKING
|
|
#define SEM_TRACK(x) x
|
|
void WebClient_TrackLock(const char* MethodName, int Occurance, void *data)
|
|
{
|
|
char v[100];
|
|
wchar_t wv[100];
|
|
size_t l;
|
|
|
|
sprintf_s(v, 100, " LOCK[%s, %d] (%x)\r\n",MethodName,Occurance,data);
|
|
|
|
#ifdef WIN32
|
|
mbstowcs_s(&l, wv, 100, v, 100);
|
|
OutputDebugString(wv);
|
|
#else
|
|
printf(v);
|
|
#endif
|
|
};
|
|
void WebClient_TrackUnLock(const char* MethodName, int Occurance, void *data)
|
|
{
|
|
char v[100];
|
|
wchar_t wv[100];
|
|
size_t l;
|
|
|
|
sprintf_s(v, 100, "UNLOCK[%s, %d] (%x)\r\n",MethodName,Occurance,data);
|
|
#ifdef WIN32
|
|
mbstowcs_s(&l, wv, 100, v, 100);
|
|
OutputDebugString(wv);
|
|
#else
|
|
printf(v);
|
|
#endif
|
|
};
|
|
#else
|
|
#define SEM_TRACK(x)
|
|
#endif
|
|
|
|
|
|
#include "ILibParsers.h"
|
|
#include "ILibWebClient.h"
|
|
#include "ILibWebServer.h"
|
|
#include "ILibAsyncSocket.h"
|
|
#include "ILibRemoteLogging.h"
|
|
#include "ILibCrypto.h"
|
|
|
|
extern sem_t *ILibAsyncSocket_GetSendLock(ILibAsyncSocket_SocketModule socketModule);
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
int ILibWebClientDataObjectIndex = -1;
|
|
#endif
|
|
|
|
#ifdef ILibWebClient_SESSION_TRACKING
|
|
void ILibWebClient_SessionTrack(void *RequestToken, void *Session, char *msg)
|
|
{
|
|
#if defined(WIN32) || defined(_WIN32_WCE)
|
|
char tempMsg[4096];
|
|
sprintf_s(tempMsg, 4096, "%s >> Request: %x , Session: %x\r\n",msg,RequestToken,Session);
|
|
OutputDebugString(tempMsg);
|
|
#else
|
|
printf("%s >> Request: %x , Session: %x\r\n",msg,RequestToken,Session);
|
|
#endif
|
|
}
|
|
#define SESSION_TRACK(RequestToken,Session,msg) ILibWebClient_SessionTrack(RequestToken,Session,msg)
|
|
#else
|
|
#define SESSION_TRACK(RequestToken,Session,msg)
|
|
#endif
|
|
|
|
#define INET_SOCKADDR_LENGTH(x) ((x==AF_INET6?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in)))
|
|
|
|
//
|
|
// We keep a table of all the connections. This is the maximum number allowed to be
|
|
// idle. Since we have in the constructor a pool size, this feature may be depracted.
|
|
// ToDo: Look into depracating this
|
|
//
|
|
#define MAX_IDLE_SESSIONS 20
|
|
|
|
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
//
|
|
// This means the module doesn't know yet if the server supports persistent connections
|
|
//
|
|
#define PIPELINE_UNKNOWN 0
|
|
//
|
|
// The server does indeed support persistent connections
|
|
//
|
|
#define PIPELINE_YES 1
|
|
//
|
|
// The server does not support persistent connections
|
|
//
|
|
#define PIPELINE_NO 2
|
|
//
|
|
// Chunk processing flags
|
|
//
|
|
#define STARTCHUNK 0
|
|
#define ENDCHUNK 1
|
|
#define DATACHUNK 2
|
|
#define FOOTERCHUNK 3
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
|
|
|
|
struct ILibWebClient_StreamedRequestBuffer
|
|
{
|
|
char *buffer;
|
|
enum ILibAsyncSocket_MemoryOwnership MemoryOwnership;
|
|
int length;
|
|
int done;
|
|
};
|
|
|
|
struct ILibWebClient_StreamedRequestState
|
|
{
|
|
ILibWebClient_OnSendOK OnSendOK;
|
|
void *BufferQueue;
|
|
int done;
|
|
int canceled;
|
|
int doNotSendRightAway;
|
|
int idleTimeout;
|
|
ILibAsyncSocket_TimeoutHandler idleTimeoutHandler;
|
|
};
|
|
|
|
struct ILibWebClientManager
|
|
{
|
|
ILibChain_Link ChainLink;
|
|
|
|
void **socks;
|
|
int socksLength;
|
|
|
|
void *WebSocketTable;
|
|
void *DataTable;
|
|
void *idleTable;
|
|
void *backlogQueue;
|
|
|
|
int MaxConnectionsToSameServer;
|
|
|
|
void *timer;
|
|
int idleCount;
|
|
|
|
sem_t QLock;
|
|
|
|
void *user;
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
SSL_CTX *ssl_ctx;
|
|
ILibWebClient_OnSslConnection OnSslConnection;
|
|
ILibWebClient_OnHttpsConnection OnHttpsConnection;
|
|
int EnableHTTPS_Called;
|
|
#endif
|
|
|
|
//typedef void(*ILibWebClient_OnSslConnection)(ILibWebClient_StateObject sender, X509 *x509, void *user1, void *user2);
|
|
//typedef void(*ILibAsyncSocket_OnSslConnection)(ILibAsyncSocket_SocketModule AsyncSocketToken, X509 *x509, void *user);
|
|
};
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
struct ILibWebClient_ChunkData
|
|
{
|
|
int Flag;
|
|
char *buffer;
|
|
int offset;
|
|
int mallocSize;
|
|
|
|
int read_BeginPointer;
|
|
int read_EndPointer;
|
|
|
|
int bytesLeft;
|
|
int complete;
|
|
};
|
|
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
typedef struct ILibWebClientDataObject
|
|
{
|
|
int IsWebSocket;
|
|
int IsOrphan;
|
|
int PipelineFlag;
|
|
int ActivityCounter;
|
|
int NC;
|
|
char CNONCE[17];
|
|
struct sockaddr_in6 remote;
|
|
struct sockaddr_in6 proxy;
|
|
struct ILibWebClientManager *Parent;
|
|
char* DigestData;
|
|
int webSocketMaskOverride;
|
|
|
|
int PendingConnectionIndex;
|
|
|
|
int DeferDestruction;
|
|
int CancelRequest;
|
|
int FinHeader;
|
|
int NeedFlush;
|
|
int Chunked;
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
struct ILibWebClient_ChunkData *chunk;
|
|
int ConnectionCloseSpecified;
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
int BytesLeft;
|
|
int WaitForClose;
|
|
int Closing;
|
|
int Server; // 0 = Client, 1 = Server, 2 = Server, but ignore header checks
|
|
int DisconnectSent;
|
|
|
|
int HeaderLength;
|
|
|
|
struct packetheader *header;
|
|
struct sockaddr_in6 source;
|
|
|
|
int InitialRequestAnswered;
|
|
void* RequestQueue;
|
|
void* SOCK;
|
|
struct sockaddr_in6 LocalIP;
|
|
int PAUSE;
|
|
int IndexNumber;
|
|
|
|
ILibWebClient_TimeoutHandler timeoutHandler;
|
|
void *timeoutUser;
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
ILibWebClient_RequestToken_HTTPS requestMode;
|
|
char *sniHost;
|
|
#endif
|
|
|
|
char* CertificateHashPtr; // Points to the certificate hash (next field) if set
|
|
char CertificateHash[32]; // Used by the Mesh to store NodeID of this session
|
|
}ILibWebClientDataObject;
|
|
|
|
struct ILibWebRequest;
|
|
typedef struct ILibWebClient_PipelineRequestToken
|
|
{
|
|
struct ILibWebClientDataObject *wcdo;
|
|
void* timer;
|
|
char* WebSocketKey;
|
|
int WebSocketMaxBuffer;
|
|
ILibWebClient_OnSendOK WebSocketSendOK;
|
|
struct ILibWebRequest *parent;
|
|
char host[255];
|
|
char reserved[29];
|
|
}ILibWebClient_PipelineRequestToken;
|
|
|
|
const int ILibMemory_WebClient_RequestToken_CONTAINERSIZE = sizeof(ILibWebClient_PipelineRequestToken);
|
|
typedef struct ILibWebRequest_buffer
|
|
{
|
|
struct ILibWebRequest_buffer* next;
|
|
int bufferLength;
|
|
char buffer[];
|
|
}ILibWebRequest_buffer;
|
|
typedef struct ILibWebRequest
|
|
{
|
|
char **Buffer;
|
|
int *BufferLength;
|
|
int *UserFree;
|
|
int NumberOfBuffers;
|
|
|
|
char *WebSocketKey;
|
|
ILibWebRequest_buffer *buffered;
|
|
struct sockaddr_in6 remote;
|
|
void *user1,*user2;
|
|
ILibWebClient_OnConnectHandler ConnectSink, DisconnectSink;
|
|
int connectionCloseWasSpecified;
|
|
|
|
struct ILibWebClient_PipelineRequestToken *requestToken;
|
|
struct ILibWebClient_StreamedRequestState *streamedState;
|
|
|
|
int IsHeadRequest;
|
|
|
|
ILibWebClient_OnResponse OnResponse;
|
|
}ILibWebRequest;
|
|
|
|
typedef struct ILibWebClient_WebSocketState
|
|
{
|
|
char reserved; // Intentionaly left blank for detection purposes
|
|
int WebSocketFragmentFlag; // WebSocketFragmentFlag
|
|
int WebSocketDataFrameType; // WebSocketDataFrameType
|
|
char* WebSocketFragmentBuffer; // WebSocketFragmentBuffer
|
|
int WebSocketFragmentIndex; // WebSocketFragmentIndex;
|
|
int WebSocketFragmentBufferSize; // WebSocketFragmentBufferSize;
|
|
int WebSocketFragmentMaxBufferSize; // WebSocketFragmentMaxBufferSize;
|
|
char WebSocketCouldNotAutoReassemble; // WebSocketCouldNotAutoReassemble
|
|
char WebSocketCloseFrameSent; // WebSocketCloseFrameSent
|
|
ILibWebClient_OnSendOK OnSendOK; // WebSocket OnSendOK
|
|
|
|
ILibWebClient_WebSocket_PingHandler pingHandler;
|
|
ILibWebClient_WebSocket_PongHandler pongHandler;
|
|
void* pingPongUser;
|
|
}ILibWebClient_WebSocketState;
|
|
|
|
int *ILibWebClient_WCDO_ServerFlag(ILibWebClient_StateObject j)
|
|
{
|
|
return(&(((ILibWebClientDataObject*)j)->Server));
|
|
}
|
|
|
|
void ILibWebClient_Timeout_Sink(ILibAsyncSocket_SocketModule module, void *user)
|
|
{
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)user;
|
|
if (wcdo->timeoutHandler != NULL)
|
|
{
|
|
wcdo->timeoutHandler(wcdo, wcdo->timeoutUser);
|
|
}
|
|
}
|
|
void ILibWebClient_SetTimeout(ILibWebClient_StateObject state, int timeoutSeconds, ILibWebClient_TimeoutHandler handler, void *user)
|
|
{
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)state;
|
|
wcdo->timeoutHandler = handler;
|
|
wcdo->timeoutUser = user;
|
|
|
|
ILibAsyncSocket_SetTimeout(wcdo->SOCK, timeoutSeconds, ILibWebClient_Timeout_Sink);
|
|
}
|
|
|
|
//
|
|
// Internal method used to free resources associated with a WebRequest
|
|
//
|
|
// <param name="wr">The WebRequest to free</param>
|
|
void ILibWebClient_DestroyWebRequest(struct ILibWebRequest *wr)
|
|
{
|
|
int i;
|
|
struct ILibWebClient_StreamedRequestBuffer *b;
|
|
|
|
if (wr == NULL) return;
|
|
if (wr != NULL && wr->connectionCloseWasSpecified != 0 && wr->DisconnectSink != NULL) { wr->DisconnectSink(wr->requestToken); }
|
|
if (wr->buffered != NULL)
|
|
{
|
|
while (wr->buffered != NULL)
|
|
{
|
|
ILibWebRequest_buffer * rb = wr->buffered->next;
|
|
free(wr->buffered);
|
|
wr->buffered = rb;
|
|
}
|
|
}
|
|
if (wr->streamedState != NULL)
|
|
{
|
|
while (ILibQueue_IsEmpty(wr->streamedState->BufferQueue) == 0)
|
|
{
|
|
b = (struct ILibWebClient_StreamedRequestBuffer*)ILibQueue_DeQueue(wr->streamedState->BufferQueue);
|
|
if (b == NULL) break;
|
|
if (b->MemoryOwnership == ILibAsyncSocket_MemoryOwnership_CHAIN) free(b->buffer);
|
|
free(b);
|
|
}
|
|
ILibQueue_Destroy(wr->streamedState->BufferQueue);
|
|
free(wr->streamedState);
|
|
wr->streamedState = NULL;
|
|
}
|
|
|
|
for(i=0; i < wr->NumberOfBuffers; ++i)
|
|
{
|
|
// If we own any of the buffers, we need to free them
|
|
if (wr->UserFree[i] == ILibAsyncSocket_MemoryOwnership_CHAIN) { free(wr->Buffer[i]); }
|
|
}
|
|
|
|
//
|
|
// Free the other resources
|
|
//
|
|
free(wr->Buffer);
|
|
free(wr->BufferLength);
|
|
free(wr->UserFree);
|
|
if (wr->requestToken != NULL)
|
|
{
|
|
ILibLifeTime_Remove(wr->requestToken->timer, wr->requestToken->wcdo);
|
|
free(wr->requestToken);
|
|
wr->requestToken = NULL;
|
|
}
|
|
free(wr);
|
|
wr = NULL;
|
|
}
|
|
|
|
|
|
// Creates a unique token from (IP address + Port + Index)
|
|
// This is an optimized version using a memcpy and the family portion to store the index.
|
|
int ILibCreateTokenStr(struct sockaddr* addr, int i, char* token)
|
|
{
|
|
int len = (addr->sa_family == AF_INET)?8:24;
|
|
memcpy_s(token, len, addr, len);
|
|
((struct sockaddr*)token)->sa_family = (unsigned short)i;
|
|
if (addr->sa_family == AF_INET6) ((struct sockaddr_in6*)token)->sin6_flowinfo = 0; // Just to make sure, the sin6_flowinfo of all IPv6 tokens must be empty
|
|
return len;
|
|
}
|
|
|
|
|
|
//
|
|
// Internal method used to free resources associated with a WebClientDataObject
|
|
//
|
|
// <param name="token">The WebClientDataObject to free</param>
|
|
void ILibWebClient_DestroyWebClientDataObject(ILibWebClient_StateObject token)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)token;
|
|
struct ILibWebRequest *wr;
|
|
int zero = 0;
|
|
|
|
if (wcdo == NULL) return;
|
|
|
|
if (wcdo->Closing < 0)
|
|
{
|
|
// This connection is already in the process of closing somewhere, so we can just exit
|
|
return;
|
|
}
|
|
|
|
if (wcdo->SOCK != NULL && ILibAsyncSocket_IsFree(wcdo->SOCK) == 0)
|
|
{
|
|
//
|
|
// This connection needs to be disconnected first
|
|
//
|
|
wcdo->Closing = -1;
|
|
ILibAsyncSocket_Disconnect(wcdo->SOCK);
|
|
}
|
|
|
|
if (wcdo->header != NULL)
|
|
{
|
|
//
|
|
// The header needs to be freed
|
|
//
|
|
ILibDestructPacket(wcdo->header); // TODO: *********** CRASH ON EXIT SOMETIMES OCCURS HERE
|
|
wcdo->header = NULL;
|
|
}
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
if (wcdo->chunk != NULL)
|
|
{
|
|
//
|
|
// The resources associated with the Chunk Processing needs to be freed
|
|
//
|
|
if (wcdo->chunk->buffer != NULL) { free(wcdo->chunk->buffer); }
|
|
free(wcdo->chunk);
|
|
wcdo->chunk = NULL;
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
//
|
|
// Iterate through all the pending requests
|
|
//
|
|
while ((wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue))!=NULL)
|
|
{
|
|
if (wcdo->Server == 0 && wr->OnResponse != NULL)
|
|
{
|
|
//
|
|
// If this is a client request, then we need to signal
|
|
// that this request is being aborted
|
|
//
|
|
wr->OnResponse(
|
|
wcdo,
|
|
WEBCLIENT_DESTROYED,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&zero);
|
|
}
|
|
if (wcdo->IsWebSocket != 0)
|
|
{
|
|
free(((ILibWebClient_WebSocketState*)wr->Buffer[0])->WebSocketFragmentBuffer);
|
|
}
|
|
wr->connectionCloseWasSpecified = 2;
|
|
ILibWebClient_DestroyWebRequest(wr);
|
|
ILibQueue_DeQueue(wcdo->RequestQueue);
|
|
}
|
|
|
|
ILibQueue_Destroy(wcdo->RequestQueue);
|
|
if (wcdo->DigestData != NULL) { free(ILibMemory_AllocateA_Raw(wcdo->DigestData)); }
|
|
#ifndef MICROSTACK_NOTLS
|
|
if (wcdo->sniHost != NULL) { free(wcdo->sniHost); }
|
|
#endif
|
|
free(wcdo);
|
|
}
|
|
|
|
//
|
|
// Internal method to free resources associated with an ILibWebClient object
|
|
//
|
|
// <param name="object">The ILibWebClient to free</param>
|
|
void ILibDestroyWebClient(void *object)
|
|
{
|
|
struct ILibWebClientManager *manager = (struct ILibWebClientManager*)object;
|
|
void *en;
|
|
void *wcdo;
|
|
char *key;
|
|
int keyLength;
|
|
|
|
//
|
|
// Iterate through all the WebClientDataObjects
|
|
//
|
|
en = ILibHashTree_GetEnumerator(manager->DataTable);
|
|
while (ILibHashTree_MoveNext(en)==0)
|
|
{
|
|
//
|
|
// Free the WebClientDataObject
|
|
//
|
|
ILibHashTree_GetValue(en, &key, &keyLength, &wcdo);
|
|
ILibWebClient_DestroyWebClientDataObject(wcdo); // TODO: *********** BAD CRASH ON EXIT SOMETIMES OCCURS HERE
|
|
}
|
|
ILibHashTree_DestroyEnumerator(en);
|
|
|
|
//
|
|
// Free all the other associated resources
|
|
//
|
|
ILibQueue_Destroy(manager->backlogQueue);
|
|
ILibDestroyHashTree(manager->idleTable);
|
|
ILibDestroyHashTree(manager->DataTable);
|
|
sem_destroy(&(manager->QLock));
|
|
free(manager->socks);
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
if (manager->EnableHTTPS_Called != 0 && manager->ssl_ctx != NULL)
|
|
{
|
|
SSL_TRACE1("ILibDestroyWebClient()");
|
|
SSL_CTX_free(manager->ssl_ctx);
|
|
SSL_TRACE2("ILibDestroyWebClient()");
|
|
}
|
|
manager->ssl_ctx = NULL;
|
|
manager->OnSslConnection = NULL;
|
|
#endif
|
|
}
|
|
|
|
void ILibWebClient_TimerInterruptSink(void *object)
|
|
{
|
|
UNREFERENCED_PARAMETER( object );
|
|
}
|
|
int ILibWebClient_IsFinHeader(ILibWebClient_StateObject wcdo)
|
|
{
|
|
return(((ILibWebClientDataObject*)wcdo)->FinHeader);
|
|
}
|
|
void ILibWebClient_ResetWCDO(struct ILibWebClientDataObject *wcdo)
|
|
{
|
|
ILibWebClient_RequestToken rt = NULL;
|
|
if (wcdo == NULL) return;
|
|
rt = ILibWebClient_GetRequestToken_FromStateObject(wcdo);
|
|
if (rt != NULL)
|
|
{
|
|
struct ILibWebClient_PipelineRequestToken * plrt = ( struct ILibWebClient_PipelineRequestToken * ) rt;
|
|
|
|
// Check the cancel request in the timer list
|
|
if ( plrt->timer != NULL ) ILibLifeTime_Remove(plrt->timer, plrt);
|
|
}
|
|
wcdo->webSocketMaskOverride = 0;
|
|
wcdo->PAUSE = 0;
|
|
wcdo->CancelRequest = 0;
|
|
wcdo->Chunked = 0;
|
|
wcdo->FinHeader = 0;
|
|
wcdo->WaitForClose = 0;
|
|
wcdo->InitialRequestAnswered = 1;
|
|
wcdo->DisconnectSent=0;
|
|
wcdo->PendingConnectionIndex=-1;
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
wcdo->ConnectionCloseSpecified=0;
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
|
|
if (wcdo->chunk != NULL)
|
|
{
|
|
if (wcdo->chunk->buffer != NULL) { free(wcdo->chunk->buffer); }
|
|
free(wcdo->chunk);
|
|
wcdo->chunk = NULL;
|
|
}
|
|
if (wcdo->header != NULL)
|
|
{
|
|
ILibDestructPacket(wcdo->header);
|
|
wcdo->header = NULL;
|
|
}
|
|
}
|
|
//
|
|
// Internal method dispatched by the LifeTimeMonitor
|
|
//
|
|
//
|
|
// This timed callback is used to close idle sockets. A socket is considered idle
|
|
// if after a request is answered, another request isn't received
|
|
// within the time specified by HTTP_SESSION_IDLE_TIMEOUT
|
|
//
|
|
// <param name="object">The WebClientDataObject</param>
|
|
void ILibWebClient_TimerSink(void *object)
|
|
{
|
|
void *enumerator;
|
|
char IPAddress[24]; // Unoptimized version uses 300
|
|
int IPAddressLength;
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)object;
|
|
struct ILibWebClientDataObject *wcdo2;
|
|
|
|
char *key;
|
|
int keyLength;
|
|
void *data;
|
|
|
|
void *DisconnectSocket = NULL;
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_TimerSink", 1, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
if (ILibQueue_IsEmpty(wcdo->RequestQueue)!=0)
|
|
{
|
|
//
|
|
// This connection is idle, because there are no pending requests
|
|
//
|
|
if (wcdo->SOCK != NULL && ILibAsyncSocket_IsFree(wcdo->SOCK) == 0)
|
|
{
|
|
//
|
|
// We need to close this socket
|
|
//
|
|
wcdo->Closing = 1;
|
|
DisconnectSocket = wcdo->SOCK;
|
|
}
|
|
if (wcdo->Parent->idleCount > MAX_IDLE_SESSIONS)
|
|
{
|
|
//
|
|
// We need to remove an entry from the idleTable, if there are too
|
|
// many entries in it
|
|
//
|
|
--wcdo->Parent->idleCount;
|
|
enumerator = ILibHashTree_GetEnumerator(wcdo->Parent->idleTable);
|
|
ILibHashTree_MoveNext(enumerator);
|
|
ILibHashTree_GetValue(enumerator, &key, &keyLength, &data);
|
|
ILibHashTree_DestroyEnumerator(enumerator);
|
|
wcdo2 = (struct ILibWebClientDataObject*)ILibGetEntry(wcdo->Parent->DataTable, key, keyLength);
|
|
ILibDeleteEntry(wcdo->Parent->DataTable, key, keyLength);
|
|
ILibDeleteEntry(wcdo->Parent->idleTable, key, keyLength);
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_TimerSink",2,wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
ILibWebClient_DestroyWebClientDataObject(wcdo2);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Add this DataObject into the idleTable for use later
|
|
//
|
|
IPAddressLength = ILibCreateTokenStr((struct sockaddr*)&(wcdo->remote), wcdo->IndexNumber, IPAddress);
|
|
|
|
ILibAddEntry(wcdo->Parent->idleTable, IPAddress, IPAddressLength, wcdo);
|
|
++wcdo->Parent->idleCount;
|
|
wcdo->SOCK = NULL;
|
|
wcdo->DisconnectSent=0;
|
|
}
|
|
}
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_TimerSink", 3, wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
//
|
|
// Let the user know, the socket has been disconnected
|
|
//
|
|
if (DisconnectSocket != NULL) ILibAsyncSocket_Disconnect(DisconnectSocket);
|
|
}
|
|
//
|
|
// Internal method called by ILibWebServer, when a response was completed
|
|
//
|
|
// <param name="_wcdo">The associated WebClientDataObject</param>
|
|
void ILibWebClient_FinishedResponse_Server(ILibWebClient_StateObject _wcdo)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)_wcdo;
|
|
|
|
if (wcdo == NULL) return;
|
|
if (wcdo->Chunked != 0)
|
|
{
|
|
if (wcdo->chunk == NULL || wcdo->chunk->complete == 0)
|
|
{
|
|
wcdo->NeedFlush = 1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
if (wcdo->chunk != NULL)
|
|
{
|
|
//
|
|
// Free any resources associated with the Chunk Processor
|
|
//
|
|
free(wcdo->chunk->buffer);
|
|
free(wcdo->chunk);
|
|
wcdo->chunk = NULL;
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
if (wcdo->header!=NULL)
|
|
{
|
|
//
|
|
// Free the resources associated with the header
|
|
//
|
|
ILibDestructPacket(wcdo->header);
|
|
wcdo->header = NULL;
|
|
}
|
|
//
|
|
// Reset all the flags
|
|
//
|
|
ILibWebClient_ResetWCDO(wcdo);
|
|
}
|
|
//
|
|
// Internal method called when a WebClient has finished processing a Request/Response
|
|
//
|
|
// <param name="socketModule">The WebClient</param>
|
|
// <param name="wcdo">The associated WebClientDataObject</param>
|
|
void ILibWebClient_FinishedResponse(ILibAsyncSocket_SocketModule socketModule, struct ILibWebClientDataObject *wcdo)
|
|
{
|
|
int i;
|
|
struct ILibWebRequest *wr;
|
|
int closeSpecified = 0;
|
|
UNREFERENCED_PARAMETER( socketModule );
|
|
|
|
if (wcdo == NULL) return;
|
|
|
|
// Only continue if this is a client calling this
|
|
if (wcdo->Server != 0) return;
|
|
if (wcdo->IsOrphan != 0)
|
|
{
|
|
ILibWebClient_DestroyWebClientDataObject(wcdo);
|
|
return;
|
|
}
|
|
|
|
|
|
// The current request was cancelled, so it can't really be finished, so we
|
|
// need to skip this, because otherwise we will end up duplicating some calls.
|
|
if (wcdo->CancelRequest != 0) return;
|
|
|
|
if (wcdo->header != NULL && wcdo->header->StatusCode >= 100 && wcdo->header->StatusCode < 200)
|
|
{
|
|
// This was an informational packet, and so it did not conclude this session
|
|
ILibWebClient_ResetWCDO(wcdo);
|
|
wcdo->InitialRequestAnswered = 0;
|
|
return;
|
|
}
|
|
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
if (wcdo->chunk!=NULL)
|
|
{
|
|
// Free any resources associated with the Chunk Processor
|
|
free(wcdo->chunk->buffer);
|
|
free(wcdo->chunk);
|
|
wcdo->chunk = NULL;
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
|
|
if (wcdo->header != NULL)
|
|
{
|
|
// Free any resources associated with the header
|
|
ILibDestructPacket(wcdo->header);
|
|
wcdo->header = NULL;
|
|
}
|
|
|
|
// Reset the flags
|
|
closeSpecified = wcdo->ConnectionCloseSpecified;
|
|
ILibWebClient_ResetWCDO(wcdo);
|
|
|
|
// If this socket isn't connected, it's because it was previously closed,
|
|
// so this finished response isn't valid anymore
|
|
if (wcdo->SOCK == NULL || ILibAsyncSocket_IsFree(wcdo->SOCK))
|
|
{
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_FinishedResponse", 1, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
wr = (struct ILibWebRequest*)ILibQueue_DeQueue(wcdo->RequestQueue);
|
|
if (wr != NULL)
|
|
{
|
|
wr->connectionCloseWasSpecified = 2;
|
|
ILibWebClient_DestroyWebRequest(wr);
|
|
}
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_FinishedResponse", 2, wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
return;
|
|
}
|
|
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_FinishedResponse", 1, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
wr = (struct ILibWebRequest*)ILibQueue_DeQueue(wcdo->RequestQueue);
|
|
if (wr != NULL)
|
|
{
|
|
//
|
|
// Only execute this logic, if there was a pending request. If there wasn't one, that means
|
|
// that this session was closed the last time the app as called with data, making this next step unnecessary.
|
|
//
|
|
wr->connectionCloseWasSpecified = closeSpecified;
|
|
ILibWebClient_DestroyWebRequest(wr);
|
|
wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
if (wr == NULL)
|
|
{
|
|
//
|
|
// Since the request queue is empty, that means this connection is now idle.
|
|
// Set a timed callback, so we can free this resource if neccessary
|
|
//
|
|
if (ILibIsChainBeingDestroyed(wcdo->Parent->ChainLink.ParentChain) == 0)
|
|
{
|
|
ILibLifeTime_Add(wcdo->Parent->timer, wcdo, HTTP_SESSION_IDLE_TIMEOUT, &ILibWebClient_TimerSink, &ILibWebClient_TimerInterruptSink);
|
|
}
|
|
}
|
|
}
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_FinishedResponse", 2 ,wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
if (wr != NULL)
|
|
{
|
|
//
|
|
// There are still pending requests in the queue, so try to send them
|
|
//
|
|
if (wcdo->PipelineFlag != PIPELINE_NO && closeSpecified == 0)
|
|
{
|
|
ILibWebRequest_buffer *b;
|
|
//
|
|
// If the connection is still open, and we didn't flag this as not supporting
|
|
// persistent connections, than obviously it is supported
|
|
//
|
|
wcdo->PipelineFlag = PIPELINE_YES;
|
|
|
|
for(i = 0; i < wr->NumberOfBuffers; ++i)
|
|
{
|
|
//
|
|
// Try to send the request
|
|
//
|
|
ILibAsyncSocket_Send(wcdo->SOCK, wr->Buffer[i], wr->BufferLength[i], ILibAsyncSocket_MemoryOwnership_STATIC);
|
|
}
|
|
|
|
b = wr->buffered;
|
|
while (b != NULL)
|
|
{
|
|
ILibAsyncSocket_Send(wcdo->SOCK, b->buffer, b->bufferLength, ILibAsyncSocket_MemoryOwnership_USER);
|
|
b = b->next;
|
|
}
|
|
}
|
|
}
|
|
if (wcdo->PipelineFlag == PIPELINE_NO || closeSpecified != 0)
|
|
{
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
/* Pipelining is not supported, so we should just close the socket, instead
|
|
of waiting for the other guy to close it, because if they forget to, it will
|
|
screw us over if there are pending requests */
|
|
|
|
//
|
|
// It should also be noted, that when this closes, the module will realize there are
|
|
// pending requests, in which case it will open a new connection for the requests.
|
|
//
|
|
if (ILibIsChainBeingDestroyed(wcdo->Parent->ChainLink.ParentChain) == 0)
|
|
{
|
|
//
|
|
// Only do this if the chain is still alive, otherwise things will get screwed
|
|
// up, because modules may not be ready.
|
|
//
|
|
ILibAsyncSocket_Disconnect(wcdo->SOCK);
|
|
}
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
}
|
|
|
|
void ILibWebClient_ResetUserObjects(ILibWebClient_StateObject webstate, void *user1, void *user2)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)webstate;
|
|
struct ILibWebRequest *wr = NULL;
|
|
|
|
if (wcdo!=NULL)
|
|
{
|
|
wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
if (wr!=NULL)
|
|
{
|
|
wr->user1 = user1;
|
|
wr->user2 = user2;
|
|
}
|
|
}
|
|
}
|
|
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
//
|
|
// Internal method called to decode chunked transfers
|
|
//
|
|
// <param name="socketModule">The ILibWebClient token</param>
|
|
// <param name="wcdo">The associated WebClientDataObject</param>
|
|
// <param name="buffer">The receive buffer</param>
|
|
// <param name="p_beginPointer">The buffer start pointer</param>
|
|
// <param name="endPointer">The length of the buffer</param>
|
|
void ILibWebClient_ProcessChunk(ILibAsyncSocket_SocketModule socketModule, struct ILibWebClientDataObject *wcdo, char *buffer, int *p_beginPointer, int endPointer)
|
|
{
|
|
char *hex, *tmp;
|
|
int i;
|
|
struct parser_result *pr;
|
|
struct ILibWebRequest *wr;
|
|
int bp;
|
|
int resize;
|
|
int passthru=0;
|
|
|
|
if (wcdo==NULL) {return;}
|
|
|
|
//
|
|
// ToDo: Document what a Parent is
|
|
//
|
|
if (wcdo->Parent!=NULL)
|
|
{
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_ProcessChunk",1,wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
}
|
|
wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
if (wcdo->Parent!=NULL)
|
|
{
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_ProcessChunk",2,wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
}
|
|
|
|
if (wcdo->chunk==NULL)
|
|
{
|
|
//
|
|
// Create a state object for the Chunk Processor
|
|
//
|
|
if ((wcdo->chunk = (struct ILibWebClient_ChunkData*)malloc(sizeof(struct ILibWebClient_ChunkData))) == NULL) ILIBCRITICALEXIT(254);
|
|
memset(wcdo->chunk,0,sizeof(struct ILibWebClient_ChunkData));
|
|
|
|
if ((wcdo->chunk->buffer = (char*)malloc(INITIAL_BUFFER_SIZE)) == NULL) ILIBCRITICALEXIT(254);
|
|
wcdo->chunk->mallocSize = INITIAL_BUFFER_SIZE;
|
|
}
|
|
|
|
switch(wcdo->chunk->Flag)
|
|
{
|
|
//
|
|
// Based on the Chunk Flag, we can figure out how to parse this thing
|
|
//
|
|
case STARTCHUNK:
|
|
// Reading Chunk Header
|
|
if (endPointer < 3) {return;}
|
|
for(i=2;i<endPointer;++i)
|
|
{
|
|
if (buffer[i - 2]=='\r' && buffer[i - 1]=='\n')
|
|
{
|
|
//
|
|
// The chunk header is terminated with a CRLF. The part before the ';'
|
|
// is the hex number representing the length of the chunk
|
|
//
|
|
pr = ILibParseString(buffer,0,i-2,";",1);
|
|
pr->FirstResult->data[pr->FirstResult->datalength] = '\0';
|
|
wcdo->chunk->bytesLeft = (int)strtol(pr->FirstResult->data,&hex,16);
|
|
*p_beginPointer = i;
|
|
wcdo->chunk->Flag=wcdo->chunk->bytesLeft==0?FOOTERCHUNK:DATACHUNK;
|
|
ILibDestructParserResults(pr);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case ENDCHUNK:
|
|
if (endPointer>=2)
|
|
{
|
|
//
|
|
// There is more chunks to come
|
|
//
|
|
*p_beginPointer = 2;
|
|
wcdo->chunk->Flag = STARTCHUNK;
|
|
}
|
|
break;
|
|
case DATACHUNK:
|
|
if (endPointer >= wcdo->chunk->bytesLeft)
|
|
{
|
|
//
|
|
// Only consume what we need
|
|
//
|
|
i = wcdo->chunk->bytesLeft;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Consume all of the data
|
|
//
|
|
i=endPointer;
|
|
}
|
|
|
|
if (wcdo->chunk->read_BeginPointer != wcdo->chunk->read_EndPointer)
|
|
{
|
|
//
|
|
// The user didn't consume all of the data, so we need to copy the data
|
|
// into this buffer, before passing it to the user
|
|
//
|
|
// But before we do that, we need to check to see if our buffer is big enough
|
|
//
|
|
if (wcdo->chunk->offset+endPointer > wcdo->chunk->mallocSize)
|
|
{
|
|
//
|
|
// The buffer is too small, we need to make it bigger
|
|
// ToDo: Add code to enforce a max buffer size if specified
|
|
// ToDo: Does the above layer need to know when buffers were realloced?
|
|
//
|
|
resize = wcdo->chunk->offset+endPointer-wcdo->chunk->mallocSize>INITIAL_BUFFER_SIZE?wcdo->chunk->offset+endPointer-wcdo->chunk->mallocSize:INITIAL_BUFFER_SIZE;
|
|
tmp = (char*)realloc(wcdo->chunk->buffer,wcdo->chunk->mallocSize+resize);
|
|
if (tmp == NULL) ILIBCRITICALEXIT(254);
|
|
wcdo->chunk->buffer = tmp;
|
|
wcdo->chunk->mallocSize += resize;
|
|
}
|
|
//
|
|
// Write the decoded chunk blob into the buffer
|
|
//
|
|
memcpy_s(wcdo->chunk->buffer + wcdo->chunk->offset, wcdo->chunk->mallocSize - wcdo->chunk->offset, buffer, i);
|
|
MEMCHECK(assert(wcdo->chunk->offset+i<=wcdo->chunk->mallocSize);)
|
|
//
|
|
// Adjust our counters
|
|
//
|
|
wcdo->chunk->offset+=i;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We aren't growing this buffer for the user, so we can start out
|
|
// by trying to just pass the underlying buffer, instead of copying it
|
|
//
|
|
passthru=1;
|
|
}
|
|
|
|
bp=0;
|
|
if (wr != NULL && wr->OnResponse != NULL && wcdo->NeedFlush == 0)
|
|
{
|
|
//
|
|
// Let the user know we got some data
|
|
//
|
|
|
|
if (passthru)
|
|
{
|
|
bp = 0;
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
buffer,
|
|
&bp,
|
|
i,
|
|
ILibWebClient_ReceiveStatus_MoreDataToBeReceived,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
|
|
if (bp == 0)
|
|
{
|
|
//
|
|
// User didn't consume anything, so probably
|
|
// wants to grow the buffer
|
|
//
|
|
wcdo->chunk->read_BeginPointer = 0;
|
|
wcdo->chunk->read_EndPointer = i;
|
|
|
|
//
|
|
// We need to copy this data, so the stack can continue processing
|
|
//
|
|
if (wcdo->chunk->offset+endPointer > wcdo->chunk->mallocSize)
|
|
{
|
|
//
|
|
// The buffer is too small, we need to make it bigger
|
|
// ToDo: Add code to enforce a max buffer size if specified
|
|
// ToDo: Does the above layer need to know when buffers were realloced?
|
|
//
|
|
resize = wcdo->chunk->offset+endPointer-wcdo->chunk->mallocSize>INITIAL_BUFFER_SIZE?wcdo->chunk->offset+endPointer-wcdo->chunk->mallocSize:INITIAL_BUFFER_SIZE;
|
|
tmp = (char*)realloc(wcdo->chunk->buffer,wcdo->chunk->mallocSize+resize);
|
|
if (tmp == NULL) ILIBCRITICALEXIT(254);
|
|
wcdo->chunk->buffer = tmp;
|
|
wcdo->chunk->mallocSize += resize;
|
|
}
|
|
memcpy_s(wcdo->chunk->buffer + wcdo->chunk->offset, wcdo->chunk->mallocSize - wcdo->chunk->offset, buffer, i);
|
|
wcdo->chunk->offset+=i;
|
|
bp = i;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// User consumed something at least. We don't need to
|
|
// copy anything just yet, because maybe they'll consume
|
|
// everything on the next pass
|
|
//
|
|
if (bp != i)
|
|
{
|
|
wcdo->chunk->read_BeginPointer = bp;
|
|
wcdo->chunk->read_EndPointer = i; // ***** TODO: THIS CASE IS NOT TESTED AND DOES NOT WORK!! Buffer needs to be copied to the front, etc.
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Everything was consumed, so we can
|
|
// set things up, so on the next pass we just
|
|
// allow the buffer to passthru
|
|
//
|
|
wcdo->chunk->read_BeginPointer = 0;
|
|
wcdo->chunk->read_EndPointer = 0;
|
|
wcdo->chunk->offset = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bp = 0;
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
wcdo->chunk->buffer + wcdo->chunk->read_BeginPointer,
|
|
&bp,
|
|
wcdo->chunk->offset - wcdo->chunk->read_BeginPointer,
|
|
ILibWebClient_ReceiveStatus_MoreDataToBeReceived,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
|
|
if (bp==wcdo->chunk->offset - wcdo->chunk->read_BeginPointer)
|
|
{
|
|
//
|
|
// Everything was consumed, so we can
|
|
// set things up, so on the next pass we just
|
|
// allow the buffer to passthru
|
|
//
|
|
wcdo->chunk->read_BeginPointer = 0;
|
|
wcdo->chunk->read_EndPointer = 0;
|
|
wcdo->chunk->offset = 0;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Not everything was consumed
|
|
//
|
|
wcdo->chunk->read_BeginPointer += bp; // ***** TODO: THIS CASE IS NOT TESTED AND DOES NOT WORK!! Buffer needs to be copied to the front, etc.
|
|
wcdo->chunk->read_EndPointer = wcdo->chunk->offset;
|
|
}
|
|
bp=i;
|
|
}
|
|
}
|
|
|
|
|
|
wcdo->chunk->bytesLeft-=bp;
|
|
*p_beginPointer = bp;
|
|
if (wcdo->chunk->bytesLeft == 0)
|
|
{
|
|
wcdo->chunk->Flag = ENDCHUNK;
|
|
}
|
|
break;
|
|
case FOOTERCHUNK:
|
|
if (endPointer>=2)
|
|
{
|
|
for (i=2;i<=endPointer;++i)
|
|
{
|
|
if (buffer[i-2]=='\r' && buffer[i-1]=='\n')
|
|
{
|
|
//
|
|
// An empty line means the chunk is finished
|
|
//
|
|
if (i==2)
|
|
{
|
|
// FINISHED
|
|
wcdo->chunk->complete = 1;
|
|
if (wr != NULL && wr->OnResponse!=NULL && wcdo->NeedFlush == 0)
|
|
{
|
|
bp = wcdo->chunk->read_BeginPointer;
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
wcdo->chunk->buffer,
|
|
&bp,
|
|
wcdo->chunk->read_EndPointer,
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
}
|
|
if (wcdo->NeedFlush != 0)
|
|
{
|
|
wcdo->NeedFlush = 0;
|
|
ILibWebClient_FinishedResponse_Server(wcdo);
|
|
}
|
|
if (socketModule==NULL || ILibAsyncSocket_IsFree(socketModule)==0)
|
|
{
|
|
//
|
|
// Free the resources associated with this chunk
|
|
//
|
|
if (wcdo->chunk!=NULL)
|
|
{
|
|
if (wcdo->chunk->buffer!=NULL) {free(wcdo->chunk->buffer);}
|
|
free(wcdo->chunk);
|
|
wcdo->chunk = NULL;
|
|
}
|
|
ILibWebClient_FinishedResponse(wcdo->SOCK,wcdo);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// ToDo: This is where to add code to add support trailers
|
|
//
|
|
}
|
|
*p_beginPointer = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
extern int ILibWebServer_WebSocket_CreateHeader(char* header, unsigned short FLAGS, unsigned short OPCODE, int payloadLength);
|
|
|
|
ILibWebClient_WebSocketState* ILibWebClient_WebSocket_GetState(ILibWebRequest *wr)
|
|
{
|
|
if ((wr->Buffer[0])[0] != 0)
|
|
{
|
|
// Let's repurpose a buffer, and initialize WebSocket State Variables
|
|
if (wr->BufferLength[0] < sizeof(ILibWebClient_WebSocketState))
|
|
{
|
|
free(wr->Buffer[0]); wr->Buffer[0] = ILibMemory_Allocate(sizeof(ILibWebClient_WebSocketState), 0, NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
memset(wr->Buffer[0], 0, sizeof(ILibWebClient_WebSocketState));
|
|
}
|
|
|
|
((ILibWebClient_WebSocketState*)wr->Buffer[0])->WebSocketFragmentMaxBufferSize = wr->requestToken->WebSocketMaxBuffer;
|
|
((ILibWebClient_WebSocketState*)wr->Buffer[0])->WebSocketFragmentBufferSize = 4096;
|
|
((ILibWebClient_WebSocketState*)wr->Buffer[0])->WebSocketFragmentBuffer = ILibMemory_Allocate(4096, 0, NULL, NULL);
|
|
((ILibWebClient_WebSocketState*)wr->Buffer[0])->OnSendOK = wr->requestToken->WebSocketSendOK;
|
|
}
|
|
return((ILibWebClient_WebSocketState*)wr->Buffer[0]);
|
|
}
|
|
|
|
|
|
ILibAsyncSocket_SendStatus ILibWebClient_WebSocket_Send(ILibWebClient_StateObject obj, ILibWebClient_WebSocket_DataTypes bufferType, char* _buffer, int _bufferLen, ILibAsyncSocket_MemoryOwnership _userFree, ILibWebClient_WebSocket_FragmentFlags _bufferFragment)
|
|
{
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)obj;
|
|
char dataFrame[WEBSOCKET_MAX_OUTPUT_FRAMESIZE];
|
|
char header[10];
|
|
char maskKey[4];
|
|
int maskKeyInt;
|
|
int headerLen;
|
|
unsigned short flags = WEBSOCKET_MASK;
|
|
ILibAsyncSocket_SendStatus RetVal = ILibAsyncSocket_SEND_ON_CLOSED_SOCKET_ERROR;
|
|
ILibWebRequest *wr = (ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
ILibWebClient_WebSocketState *state;
|
|
char *buffer;
|
|
int bufferLen;
|
|
ILibWebClient_WebSocket_FragmentFlags bufferFragment;
|
|
int i = 0;
|
|
|
|
if (_bufferLen == 0 && ((bufferType == (ILibWebClient_WebSocket_DataTypes)WEBSOCKET_OPCODE_PING || bufferType == (ILibWebClient_WebSocket_DataTypes)WEBSOCKET_OPCODE_PONG))) { i = -1; }
|
|
if (wcdo->SOCK == NULL) { return(ILibAsyncSocket_SEND_ON_CLOSED_SOCKET_ERROR); }
|
|
if (wr == NULL) { return RetVal; }
|
|
state = ILibWebClient_WebSocket_GetState(wr);
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
#ifdef MICROSTACK_TLS_DETECT
|
|
if (wcdo->webSocketMaskOverride == 0 && ILibAsyncSocket_IsUsingTls(wcdo->SOCK) == 1) flags = 0; // If we are using TLS, disable websocket masking
|
|
#endif
|
|
#endif
|
|
|
|
sem_wait(ILibAsyncSocket_GetSendLock(wcdo->SOCK));
|
|
while (i < _bufferLen)
|
|
{
|
|
if (i < 0) { i = 0; }
|
|
buffer = _buffer + i;
|
|
bufferLen = (_bufferLen - i) > WEBSOCKET_MAX_OUTPUT_FRAMESIZE ? WEBSOCKET_MAX_OUTPUT_FRAMESIZE : (_bufferLen - i);
|
|
i += (_bufferLen - i > WEBSOCKET_MAX_OUTPUT_FRAMESIZE ? WEBSOCKET_MAX_OUTPUT_FRAMESIZE : _bufferLen - i);
|
|
bufferFragment = i < _bufferLen ? ILibWebClient_WebSocket_FragmentFlag_Incomplete : _bufferFragment;
|
|
|
|
if (bufferFragment == ILibWebClient_WebSocket_FragmentFlag_Complete)
|
|
{
|
|
if (state->WebSocketFragmentFlag == 0)
|
|
{
|
|
// This is a self contained fragment
|
|
headerLen = ILibWebServer_WebSocket_CreateHeader(header, flags | WEBSOCKET_FIN, (unsigned short)bufferType, bufferLen);
|
|
}
|
|
else
|
|
{
|
|
// Termination of an ongoing Fragment
|
|
state->WebSocketFragmentFlag = 0;
|
|
headerLen = ILibWebServer_WebSocket_CreateHeader(header, flags | WEBSOCKET_FIN, WEBSOCKET_OPCODE_FRAMECONT, bufferLen);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (state->WebSocketFragmentFlag == 0)
|
|
{
|
|
// Start a new fragment
|
|
state->WebSocketFragmentFlag = 1;
|
|
headerLen = ILibWebServer_WebSocket_CreateHeader(header, flags, (unsigned short)bufferType, bufferLen);
|
|
}
|
|
else
|
|
{
|
|
// Continuation of an ongoing fragment
|
|
headerLen = ILibWebServer_WebSocket_CreateHeader(header, flags, WEBSOCKET_OPCODE_FRAMECONT, bufferLen);
|
|
}
|
|
}
|
|
|
|
if (flags & WEBSOCKET_MASK) {
|
|
// Mask the payload
|
|
util_random(4, maskKey);
|
|
maskKeyInt = ((int*)maskKey)[0];
|
|
if (bufferLen > 0) {
|
|
int x;
|
|
for (x = 0; x < (bufferLen >> 2); ++x) { ((int*)dataFrame)[x] = ((int*)buffer)[x] ^ (int)maskKeyInt; } // Mask 4 bytes at a time
|
|
for (x = (x << 2); x < bufferLen; ++x) { dataFrame[x] = buffer[x] ^ maskKey[x % 4]; } // Mask the reminder
|
|
//for (x = 0; x < bufferLen; ++x) { dataFrame[x] = buffer[x] ^ maskKey[x % 4]; } // This is the slower version
|
|
}
|
|
RetVal = ILibAsyncSocket_SendTo_MultiWrite(wcdo->SOCK, NULL, 3 | ILibAsyncSocket_LOCK_OVERRIDE, header, headerLen, ILibAsyncSocket_MemoryOwnership_USER, maskKey, 4, ILibAsyncSocket_MemoryOwnership_USER, dataFrame, bufferLen, ILibAsyncSocket_MemoryOwnership_USER);
|
|
} else {
|
|
// Send payload without masking
|
|
RetVal = ILibAsyncSocket_SendTo_MultiWrite(wcdo->SOCK, NULL, 2 | ILibAsyncSocket_LOCK_OVERRIDE, header, headerLen, ILibAsyncSocket_MemoryOwnership_USER, buffer, bufferLen, ILibAsyncSocket_MemoryOwnership_USER);
|
|
}
|
|
}
|
|
sem_post(ILibAsyncSocket_GetSendLock(wcdo->SOCK));
|
|
return RetVal;
|
|
}
|
|
void ILibWebClient_WebSocket_SetPingPongHandler(ILibWebClient_StateObject obj, ILibWebClient_WebSocket_PingHandler pingHandler, ILibWebClient_WebSocket_PongHandler pongHandler, void *user)
|
|
{
|
|
ILibWebRequest *wr = (ILibWebRequest*)ILibQueue_PeekQueue(((ILibWebClientDataObject*)obj)->RequestQueue);
|
|
ILibWebClient_WebSocketState *state = wr != NULL ? ILibWebClient_WebSocket_GetState(wr) : NULL;
|
|
|
|
if (state != NULL)
|
|
{
|
|
state->pingHandler = pingHandler;
|
|
state->pongHandler = pongHandler;
|
|
state->pingPongUser = user;
|
|
}
|
|
}
|
|
int ILibWebClient_ProcessWebSocketData(char* buffer, int offset, int length, ILibWebClientDataObject *wcdo, int *PAUSE)
|
|
{
|
|
int x;
|
|
int i = offset + 2;
|
|
int plen;
|
|
unsigned short hdr;
|
|
char* maskingKey = NULL;
|
|
int FIN;
|
|
unsigned char OPCODE;
|
|
int tempBegin = 0;
|
|
ILibWebRequest *wr = (ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
ILibWebClient_WebSocketState *state;
|
|
|
|
if (wr == NULL || wr->OnResponse == NULL)
|
|
{
|
|
// No point in processing any data, since nobody will care
|
|
ILibWebClient_Disconnect(wcdo);
|
|
return(length);
|
|
}
|
|
if (length < 2) { return(offset); } // We need at least 2 bytes to read enough of the headers to know how long the frame is
|
|
state = (ILibWebClient_WebSocketState*)wr->Buffer[0];
|
|
|
|
hdr = ntohs(((unsigned short*)(buffer + offset))[0]);
|
|
FIN = (hdr & WEBSOCKET_FIN) != 0;
|
|
OPCODE = (hdr & WEBSOCKET_OPCODE) >> 8;
|
|
|
|
plen = (unsigned char)(hdr & WEBSOCKET_PLEN);
|
|
if (plen == 126)
|
|
{
|
|
if (length < 4) { return(offset); } // We need at least 4 bytes to read enough of the headers
|
|
plen = (unsigned short)ntohs(((unsigned short*)(buffer + offset))[1]);
|
|
i += 2;
|
|
}
|
|
else if (plen == 127)
|
|
{
|
|
if (length < 10)
|
|
{
|
|
return(offset); // We need at least 10 bytes to read enough of the headers
|
|
}
|
|
else
|
|
{
|
|
unsigned long long v = ILibNTOHLL(((unsigned long long*)(buffer + offset + 2))[0]);
|
|
if(v > 0x7FFFFFFFUL)
|
|
{
|
|
// this value is too big to store in a 32 bit signed variable, so disconnect the websocket.
|
|
ILibWebClient_Disconnect(wcdo);
|
|
return(length);
|
|
}
|
|
else
|
|
{
|
|
// this value can be represented with a signed 32 bit variable
|
|
plen = (int)v;
|
|
i += 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (length < (i + plen + ((unsigned char)(hdr & WEBSOCKET_MASK) != 0 ? 4 : 0)))
|
|
{
|
|
return(offset); // Don't have the entire packet
|
|
}
|
|
|
|
maskingKey = ((unsigned char)(hdr & WEBSOCKET_MASK) == 0) ? NULL : (buffer + i);
|
|
if (maskingKey != NULL)
|
|
{
|
|
// Unmask the data
|
|
i += 4; // Move ptr to start of data
|
|
|
|
for (x = 0; x < plen; ++x)
|
|
{
|
|
buffer[i + x] = buffer[i + x] ^ maskingKey[x % 4];
|
|
}
|
|
}
|
|
|
|
if (OPCODE < 0x8)
|
|
{
|
|
// NON-CONTROL OP-CODE
|
|
if (state->WebSocketFragmentMaxBufferSize == 0)
|
|
{
|
|
// We will just pass the data up, and let the app handle fragment re-assembly
|
|
state->WebSocketDataFrameType = (int)OPCODE;
|
|
tempBegin = 0;
|
|
wr->OnResponse(wcdo, 0, wcdo->header, buffer + i, &tempBegin, plen, FIN == 0 ? ILibWebClient_ReceiveStatus_Partial : ILibWebClient_ReceiveStatus_LastPartial, wr->user1, wr->user2, PAUSE);
|
|
}
|
|
else
|
|
{
|
|
// We will try to automatically re-assemble fragments, up to the max buffer size the user specified
|
|
if (OPCODE != 0) { state->WebSocketDataFrameType = (int)OPCODE; } // Set the DataFrame Type, so the user can query it
|
|
|
|
if (FIN != 0 && state->WebSocketFragmentIndex == 0 && state->WebSocketCouldNotAutoReassemble == 0)
|
|
{
|
|
// We have an entire fragment, and we didn't save any of it yet... We can just forward it up without copying the buffer
|
|
tempBegin = 0;
|
|
wr->OnResponse(wcdo, 0, wcdo->header, buffer + i, &tempBegin, plen, ILibWebClient_ReceiveStatus_MoreDataToBeReceived, wr->user1, wr->user2, PAUSE);
|
|
}
|
|
else
|
|
{
|
|
if (state->WebSocketFragmentIndex + plen >= state->WebSocketFragmentBufferSize)
|
|
{
|
|
// Need to grow the buffer
|
|
|
|
if (state->WebSocketFragmentBufferSize == state->WebSocketFragmentMaxBufferSize)
|
|
{
|
|
// We are already maxed out, so just send what we have as an unfinished fragment
|
|
state->WebSocketCouldNotAutoReassemble = 1; // Set this flag, becuase we can't reassemble, so our FIN flag will be different to reflect that
|
|
tempBegin = 0;
|
|
wr->OnResponse(wcdo, 0, wcdo->header, state->WebSocketFragmentBuffer, &tempBegin, state->WebSocketFragmentIndex, ILibWebClient_ReceiveStatus_Partial, wr->user1, wr->user2, PAUSE);
|
|
state->WebSocketFragmentIndex = 0; // Reset the index, becuase new data is going to go to the front
|
|
}
|
|
else
|
|
{
|
|
// We can grow the buffer
|
|
state->WebSocketFragmentBufferSize = state->WebSocketFragmentBufferSize * 2;
|
|
if (state->WebSocketFragmentBufferSize > state->WebSocketFragmentMaxBufferSize) { state->WebSocketFragmentBufferSize = state->WebSocketFragmentMaxBufferSize; }
|
|
if ((state->WebSocketFragmentBuffer = (char*)realloc(state->WebSocketFragmentBuffer, state->WebSocketFragmentBufferSize)) == NULL) { ILIBCRITICALEXIT(254); } // MS Static Analyser erroneously reports that this leaks the original memory block
|
|
}
|
|
}
|
|
|
|
memcpy_s(state->WebSocketFragmentBuffer + state->WebSocketFragmentIndex, state->WebSocketFragmentBufferSize - state->WebSocketFragmentIndex, buffer + i, plen);
|
|
state->WebSocketFragmentIndex += plen;
|
|
|
|
if (FIN != 0)
|
|
{
|
|
wr->OnResponse(wcdo, 0, wcdo->header, state->WebSocketFragmentBuffer, &tempBegin, state->WebSocketFragmentIndex, state->WebSocketCouldNotAutoReassemble == 0 ? ILibWebClient_ReceiveStatus_MoreDataToBeReceived : ILibWebClient_ReceiveStatus_LastPartial, wr->user1, wr->user2, PAUSE);
|
|
state->WebSocketCouldNotAutoReassemble = 0; // Reset (We can try to auto-assemble)
|
|
state->WebSocketFragmentIndex = 0; // Reset (We can write to the start of the buffer)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// CONTROL
|
|
switch (OPCODE)
|
|
{
|
|
case WEBSOCKET_OPCODE_CLOSE:
|
|
ILibWebClient_Disconnect(wcdo);
|
|
break;
|
|
case WEBSOCKET_OPCODE_PING:
|
|
if (state->pingHandler == NULL || state->pingHandler(wcdo, state->pingPongUser) == ILibWebClient_WebSocket_PingResponse_Respond)
|
|
{
|
|
ILibWebClient_WebSocket_Send(wcdo, (ILibWebClient_WebSocket_DataTypes)WEBSOCKET_OPCODE_PONG, NULL, 0, ILibAsyncSocket_MemoryOwnership_STATIC, ILibWebClient_WebSocket_FragmentFlag_Complete);
|
|
}
|
|
break;
|
|
case WEBSOCKET_OPCODE_PONG:
|
|
// PONG
|
|
if (state->pongHandler != NULL) { state->pongHandler(wcdo, state->pingPongUser); }
|
|
break;
|
|
}
|
|
}
|
|
|
|
return(i + plen);
|
|
}
|
|
|
|
void ILibWebClient_OnWebSocketData(ILibAsyncSocket_SocketModule socketModule, char* buffer, int *p_beginPointer, int endPointer, ILibAsyncSocket_OnInterrupt* OnInterrupt, void **user, int *PAUSE)
|
|
{
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)(*user);
|
|
*p_beginPointer = ILibWebClient_ProcessWebSocketData(buffer, *p_beginPointer, endPointer, wcdo, PAUSE);
|
|
UNREFERENCED_PARAMETER(OnInterrupt);
|
|
UNREFERENCED_PARAMETER(socketModule);
|
|
}
|
|
|
|
|
|
//
|
|
// Internal method dispatched by the OnData event of the underlying ILibAsyncSocket
|
|
//
|
|
// <param name="socketModule">The underlying ILibAsyncSocket</param>
|
|
// <param name="buffer">The receive buffer</param>
|
|
// <param name="p_beginPointer">start pointer in the buffer</param>
|
|
// <param name="endPointer">The length of the buffer</param>
|
|
// <param name="InterruptPtr">Function Pointer that triggers when a connection is interrupted</param>
|
|
// <param name="user">User data that can be set/received</param>
|
|
// <param name="PAUSE">Flag to tell the underlying socket to pause reading data</param>
|
|
ILibWebClient_DataResults ILibWebClient_OnData(ILibAsyncSocket_SocketModule socketModule, char* buffer, int *p_beginPointer, int endPointer, void (**InterruptPtr)(void *socketModule, void *user), void **user, int *PAUSE)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)(*user);
|
|
struct ILibWebRequest *wr;
|
|
struct packetheader *tph;
|
|
struct packetheader_field_node *phfn;
|
|
int zero = 0;
|
|
int i = 0;
|
|
ILibWebClient_ReceiveStatus Fini;
|
|
void* tmp;
|
|
|
|
UNREFERENCED_PARAMETER( InterruptPtr );
|
|
|
|
//char *tempIP;
|
|
//char *tempPath;
|
|
//unsigned short tempPort;
|
|
|
|
if (wcdo == NULL || wcdo->RequestQueue == NULL) return(ILibWebClient_DataResults_OK);
|
|
if (wcdo->IsWebSocket != 0 && wcdo->Server == 0) { ILibWebClient_OnWebSocketData(socketModule, buffer, p_beginPointer, endPointer, InterruptPtr, user, PAUSE); return(ILibWebClient_DataResults_OK); }
|
|
|
|
if (wcdo->Server == 0)
|
|
{
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_OnData", 1, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
}
|
|
wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
if (wcdo->Server == 0)
|
|
{
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_OnData", 2, wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
}
|
|
if (wr == NULL)
|
|
{
|
|
//
|
|
// There are no pending requests, so we have no idea what we are supposed to do with
|
|
// this data, other than just recycling the receive buffer, so we don't leak memory.
|
|
// If this code executes, this usually signifies a processing error of some sort. Most
|
|
// of the time, it means the remote endpoint is sending invalid packets.
|
|
//
|
|
*p_beginPointer = endPointer;
|
|
return(ILibWebClient_DataResults_OK);
|
|
}
|
|
if (wcdo->FinHeader == 0)
|
|
{
|
|
//Still Reading Headers
|
|
if (endPointer - (*p_beginPointer) >= 4)
|
|
{
|
|
while (i <= (endPointer - (*p_beginPointer)) - 4)
|
|
{
|
|
#if defined(MAX_HTTP_HEADER_SIZE)
|
|
if (i > MAX_HTTP_HEADER_SIZE)
|
|
{
|
|
//
|
|
// We've exceeded the maximum allowed header size
|
|
//
|
|
if (wcdo->header == NULL)
|
|
{
|
|
//
|
|
// Let's parse what we can first
|
|
//
|
|
tph = ILibParsePacketHeader(buffer,*p_beginPointer,endPointer-(*p_beginPointer));
|
|
if (tph == NULL) ILIBCRITICALEXIT(253); // TODO: Handle this better?
|
|
|
|
//
|
|
// We need to free this memory, so we need to copy this packet
|
|
//
|
|
wcdo->header = ILibClonePacket(tph);
|
|
ILibDestructPacket(tph);
|
|
}
|
|
|
|
//
|
|
// Toss the rest
|
|
//
|
|
*p_beginPointer = i;
|
|
break;
|
|
}
|
|
#endif
|
|
if (((wcdo->header!=NULL && buffer[*p_beginPointer+i]==0) ||
|
|
(wcdo->header==NULL && buffer[*p_beginPointer+i]=='\r')) &&
|
|
buffer[*p_beginPointer+i+1]=='\n' &&
|
|
buffer[*p_beginPointer+i+2]=='\r' &&
|
|
buffer[*p_beginPointer+i+3]=='\n')
|
|
{
|
|
//
|
|
// Headers are delineated with a CRLF, and terminated with an empty line
|
|
//
|
|
/*
|
|
memset(&(wcdo->source),0,sizeof(struct sockaddr_in));
|
|
wcdo->source.sin_family = AF_INET;
|
|
wcdo->source.sin_addr.s_addr = ILibAsyncSocket_GetRemoteInterface(socketModule);
|
|
wcdo->source.sin_port = htons(ILibAsyncSocket_GetRemotePort(socketModule));
|
|
*/
|
|
if (socketModule != NULL) { ILibAsyncSocket_GetRemoteInterface(socketModule, (struct sockaddr*)(&wcdo->source)); }
|
|
|
|
wcdo->HeaderLength = i + 4;
|
|
wcdo->WaitForClose = 1;
|
|
wcdo->BytesLeft = -1;
|
|
wcdo->FinHeader = 1;
|
|
if (wcdo->header == NULL)
|
|
{
|
|
wcdo->header = ILibParsePacketHeader(buffer, *p_beginPointer, endPointer - (*p_beginPointer));
|
|
}
|
|
if (wcdo->header != NULL)
|
|
{
|
|
if (wcdo->header->Directive == NULL && wcdo->header->StatusCode != -1 && wcdo->Server == 1)
|
|
{
|
|
return(ILibWebClient_DataResults_InvalidRequest); // We're a server, but we received a Response Packet...
|
|
}
|
|
|
|
//
|
|
// Check to see if this has an absolute path. Might be able to convert it
|
|
// for easier processing
|
|
//
|
|
/* TODO: Make an IPv6 version of the code
|
|
if (wcdo->Server!=0 && wcdo->header->DirectiveObjLength>7 && memcmp(wcdo->header->DirectiveObj,"http://",7)==0)
|
|
{
|
|
wcdo->header->DirectiveObj[wcdo->header->DirectiveObjLength]=0;
|
|
ILibParseUri(wcdo->header->DirectiveObj,&tempIP,&tempPort,&tempPath);
|
|
if (strcmp(tempIP,ILibIPAddress_ToDottedQuad(ILibWebServer_GetLocalInterface(wr->user2)))==0 && tempPort==ILibWebServer_GetPortNumber(wr->user1))
|
|
{
|
|
//
|
|
// We can simplify this, because it's an absolute path pointing to this server
|
|
//
|
|
memcpy(wcdo->header->DirectiveObj,tempPath,(int)strlen(tempPath));
|
|
wcdo->header->DirectiveObjLength = (int)strlen(tempPath);
|
|
wcdo->header->DirectiveObj[wcdo->header->DirectiveObjLength]=0;
|
|
}
|
|
free(tempIP);
|
|
free(tempPath);
|
|
}
|
|
*/
|
|
|
|
//wcdo->header->Source = &(wcdo->source);
|
|
//wcdo->header->ReceivingAddress = wcdo->LocalIP;
|
|
if (wcdo->source.sin6_family != 0) memcpy_s(&(wcdo->header->Source), sizeof(wcdo->header->Source), &(wcdo->source), INET_SOCKADDR_LENGTH(wcdo->source.sin6_family));
|
|
if (wcdo->LocalIP.sin6_family != 0) memcpy_s(&(wcdo->header->ReceivingAddress), sizeof(wcdo->header->ReceivingAddress), &(wcdo->LocalIP), INET_SOCKADDR_LENGTH(wcdo->LocalIP.sin6_family));
|
|
|
|
//
|
|
// Introspect Request, to see what to do next
|
|
//
|
|
phfn = wcdo->header->FirstField;
|
|
while (phfn!=NULL)
|
|
{
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
if (phfn->FieldLength == 17 && strncasecmp(phfn->Field, "transfer-encoding", 17)==0)
|
|
{
|
|
if (phfn->FieldDataLength == 7 && strncasecmp(phfn->FieldData, "chunked", 7)==0)
|
|
{
|
|
//
|
|
// This packet was chunk encoded
|
|
//
|
|
wcdo->WaitForClose = 0;
|
|
wcdo->Chunked = 1;
|
|
}
|
|
}
|
|
if (phfn->FieldLength==10 && strncasecmp(phfn->Field, "connection", 10)==0)
|
|
{
|
|
if (phfn->FieldDataLength==5 && strncasecmp(phfn->FieldData, "close", 5)==0)
|
|
{
|
|
//
|
|
// This packet specified connection: close token
|
|
//
|
|
wcdo->ConnectionCloseSpecified = 1;
|
|
}
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
if (phfn->FieldLength==14 && strncasecmp(phfn->Field, "content-length", 14)==0)
|
|
{
|
|
//
|
|
// This packet has a Content-Length
|
|
//
|
|
wcdo->WaitForClose = 0;
|
|
phfn->FieldData[phfn->FieldDataLength] = '\0';
|
|
wcdo->BytesLeft = atoi(phfn->FieldData);
|
|
if (wcdo->BytesLeft < 0)
|
|
{
|
|
wcdo->BytesLeft = 0;
|
|
return(ILibWebClient_DataResults_InvalidContentLength);
|
|
}
|
|
}
|
|
phfn = phfn->NextField;
|
|
}
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
if (atof(wcdo->header->Version) > 1.0)
|
|
{
|
|
//
|
|
// This is HTTP/1.1 or better, so we need to check to see
|
|
// 1. Transfer-Encoding is not chunked
|
|
// 2. Connection: close is NOT specified
|
|
// 3. Content-Length is not specified
|
|
// because than the packet MUST NOT have a body.
|
|
//
|
|
if (wcdo->Chunked == 0 && wcdo->ConnectionCloseSpecified == 0 && wcdo->WaitForClose != 0)
|
|
{
|
|
wcdo->WaitForClose = 0;
|
|
wcdo->BytesLeft = 0;
|
|
}
|
|
}
|
|
if (wcdo->header->StatusCode >= 100 && wcdo->header->StatusCode <= 199)
|
|
{
|
|
if (wr->requestToken == NULL)
|
|
{
|
|
int zro = 0;
|
|
if (wr->OnResponse != NULL) { wr->OnResponse(wcdo, 0, wcdo->header, NULL, &zro, 0, ILibWebClient_ReceiveStatus_Connection_Established, wr->user1, wr->user2, &(wcdo->PAUSE)); }
|
|
*p_beginPointer += wcdo->HeaderLength;
|
|
return(ILibWebClient_DataResults_OK);
|
|
}
|
|
else if (wcdo->header->StatusCode == 101 && wr->requestToken->WebSocketKey != NULL)
|
|
{
|
|
// WebSocket
|
|
char* skey = ILibGetHeaderLine(wcdo->header, "Sec-WebSocket-Accept", 20);
|
|
if (skey != NULL && strcmp(skey, wr->requestToken->WebSocketKey) == 0)
|
|
{
|
|
int zro = 0;
|
|
char requestToken[24];
|
|
int hdrLen = wcdo->HeaderLength;
|
|
|
|
ILibCreateTokenStr((struct sockaddr*)&(wcdo->remote), 0, requestToken);
|
|
|
|
// WebSocket Handshake Success!
|
|
wcdo->IsWebSocket = 1;
|
|
ILibWebClient_WebSocket_GetState(wr); // If not done already, repurpose the buffer for WebSocket state
|
|
|
|
if (wr->OnResponse != NULL) { wr->OnResponse(wcdo, 0, wcdo->header, NULL, &zro, 0, ILibWebClient_ReceiveStatus_Connection_Established, wr->user1, wr->user2, &(wcdo->PAUSE)); }
|
|
*p_beginPointer += hdrLen;
|
|
return(ILibWebClient_DataResults_OK);
|
|
}
|
|
else
|
|
{
|
|
// WebSocket Handshake Error... Unrecoverable, Abort connection
|
|
ILibWebClient_Disconnect(wcdo);
|
|
ILibWebClient_DestroyWebClientDataObject(wcdo); // We are destroying here, because WebSockets have independent WCDO objects
|
|
return(ILibWebClient_DataResults_OK);
|
|
}
|
|
}
|
|
if (wr->streamedState == NULL)
|
|
{
|
|
//
|
|
// HTTP Informational, ignore packet
|
|
// because this request was not a streaming request
|
|
// so we know there is no reason to bother the upper layers
|
|
// with this piece of information
|
|
//
|
|
wcdo->FinHeader = 0;
|
|
*p_beginPointer += wcdo->HeaderLength;
|
|
ILibDestructPacket(wcdo->header);
|
|
wcdo->header = NULL;
|
|
break;
|
|
}
|
|
}
|
|
else if (wcdo->header->StatusCode == 204 || wcdo->header->StatusCode == 304)
|
|
{
|
|
// No Body
|
|
wcdo->WaitForClose = 0;
|
|
wcdo->BytesLeft = 0;
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
if (wcdo->Server != 0 && wcdo->BytesLeft == -1 && wcdo->Chunked == 0)
|
|
{
|
|
//
|
|
// This request has no body
|
|
//
|
|
wcdo->BytesLeft = 0;
|
|
}
|
|
tmp = ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
if (wcdo->RequestQueue != NULL && tmp != NULL)
|
|
{
|
|
if (((struct ILibWebRequest*)tmp)->IsHeadRequest != 0)
|
|
{
|
|
wcdo->BytesLeft = 0;
|
|
wcdo->WaitForClose = 0;
|
|
wcdo->Chunked = 0;
|
|
}
|
|
}
|
|
if (wcdo->BytesLeft == 0)
|
|
{
|
|
//
|
|
// We already have the complete Response Packet
|
|
//
|
|
|
|
//
|
|
// We need to set this, because we want to prevent the
|
|
// layer above us from calling disconnect, and throwing this
|
|
// object away prematurely
|
|
//
|
|
wcdo->DeferDestruction=1;
|
|
|
|
if (wr->OnResponse!=NULL)
|
|
{
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
NULL,
|
|
&zero,
|
|
0,
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
}
|
|
*p_beginPointer = *p_beginPointer + i + 4;
|
|
wcdo->DeferDestruction=0;
|
|
ILibWebClient_FinishedResponse(socketModule,wcdo);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// There is still data we need to read. Lets see if any of the
|
|
// body arrived yet
|
|
//
|
|
if (wcdo->Chunked==0)
|
|
{
|
|
//
|
|
// This isn't chunked, so we can process normally
|
|
//
|
|
if (wcdo->BytesLeft!=-1 && (endPointer-(*p_beginPointer)) - (i+4) >= wcdo->BytesLeft)
|
|
{
|
|
//
|
|
// We have the entire body now, so we have the entire packet
|
|
//
|
|
|
|
//
|
|
// We need to set this, because we want to prevent the
|
|
// layer above us from calling disconnect, and throwing this
|
|
// object away prematurely
|
|
//
|
|
wcdo->DeferDestruction=1;
|
|
if (wr->OnResponse!=NULL)
|
|
{
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
buffer+i+4,
|
|
&zero,
|
|
wcdo->BytesLeft,
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
}
|
|
*p_beginPointer = *p_beginPointer + i + 4 + (zero==0?wcdo->BytesLeft:zero);
|
|
wcdo->DeferDestruction = 0;
|
|
ILibWebClient_FinishedResponse(socketModule,wcdo);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We read some of the body, but not all of it yet
|
|
//
|
|
if (wr->OnResponse!=NULL)
|
|
{
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
buffer+i+4,
|
|
&zero,
|
|
(endPointer - (*p_beginPointer) - (i+4)),
|
|
ILibWebClient_ReceiveStatus_MoreDataToBeReceived,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
}
|
|
if (socketModule==NULL || ILibAsyncSocket_IsFree(socketModule)==0)
|
|
{
|
|
wcdo->HeaderLength = 0;
|
|
*p_beginPointer = i+4+zero;
|
|
wcdo->BytesLeft -= zero;
|
|
//
|
|
// We are consuming the header portion of the buffer
|
|
// so we need to copy it out, so we can recycle the buffer
|
|
// for the body
|
|
//
|
|
tph = ILibClonePacket(wcdo->header);
|
|
ILibDestructPacket(wcdo->header);
|
|
wcdo->header = tph;
|
|
}
|
|
}
|
|
}
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
else
|
|
{
|
|
//
|
|
// Before we run this through our chunk processor, let's
|
|
// make the callback, if the Chunk state is not set.
|
|
// (If it's not set, it means we haven't looked at the body yet)
|
|
// We need to do this, because the header could contain information
|
|
// such as an Expect: 100-Continue, that the above layer will need
|
|
// to look at and send a 100 Continue, before the other end will
|
|
// send the rest of the request.
|
|
//
|
|
// There is nothing to lose anyways by calling this. Worse comes to
|
|
// worse, they'll just ignore it, and life will go on.
|
|
//
|
|
if (wcdo->chunk==NULL && wr->OnResponse!=NULL)
|
|
{
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
buffer+i+4,
|
|
&zero,
|
|
0,
|
|
ILibWebClient_ReceiveStatus_MoreDataToBeReceived,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
if (wcdo->FinHeader == 0)
|
|
{
|
|
//
|
|
// The user sent a response already, so advance the
|
|
// beginPointer and return.
|
|
//
|
|
// If the user sent a response, and it wasn't in relation to a 100 Continue, then the
|
|
// user is in ERROR, because the done flag is not set here, which means that
|
|
// the entire request did not get received yet. Simply answering the request
|
|
// without receiving the entire request, without closing the socket is in
|
|
// VIOLATION of the http specification.
|
|
//
|
|
*p_beginPointer = i + 4;
|
|
return(ILibWebClient_DataResults_OK);
|
|
}
|
|
if (socketModule != NULL && ILibAsyncSocket_IsFree(socketModule)!=0)
|
|
{
|
|
//
|
|
// The user closed the socket, so just return
|
|
//
|
|
return(ILibWebClient_DataResults_OK);
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// This packet is chunk encoded, so we need to run it
|
|
// through our Chunk Processor
|
|
//
|
|
ILibWebClient_ProcessChunk(socketModule,wcdo,buffer+i+4,&zero,(endPointer - (*p_beginPointer) - (i+4)));
|
|
*p_beginPointer = i+4+zero;
|
|
tph = ILibClonePacket(wcdo->header);
|
|
ILibDestructPacket(wcdo->header);
|
|
wcdo->header = tph;
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// ToDo: There was an error parsing the headers. What should we do about it?
|
|
// Right now, we don't care
|
|
//
|
|
}
|
|
break;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We already processed the headers, so we are only expecting the body now
|
|
//
|
|
if (wcdo->Chunked==0)
|
|
{
|
|
//
|
|
// This isn't chunk encoded
|
|
//
|
|
if (wcdo->WaitForClose==0)
|
|
{
|
|
//
|
|
// This is to determine if we have everything
|
|
//
|
|
Fini = ((endPointer - (*p_beginPointer))>=wcdo->BytesLeft)?ILibWebClient_ReceiveStatus_Complete:ILibWebClient_ReceiveStatus_MoreDataToBeReceived;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// We need to read until the socket closes
|
|
//
|
|
Fini = ILibWebClient_ReceiveStatus_MoreDataToBeReceived;
|
|
}
|
|
|
|
if (wr->OnResponse!=NULL)
|
|
{
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
wcdo->header,
|
|
buffer,
|
|
p_beginPointer,
|
|
wcdo->WaitForClose==0?(((endPointer - (*p_beginPointer))>=wcdo->BytesLeft)?wcdo->BytesLeft:(endPointer - (*p_beginPointer))):(endPointer-(*p_beginPointer)),
|
|
Fini,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
}
|
|
if (socketModule==NULL || ILibAsyncSocket_IsFree(socketModule)==0)
|
|
{
|
|
if (wcdo->WaitForClose==0)
|
|
{
|
|
//
|
|
// Decrement our counters
|
|
//
|
|
wcdo->BytesLeft -= *p_beginPointer;
|
|
if (Fini!=0)
|
|
{
|
|
//
|
|
// We finished processing this, so signal it
|
|
//
|
|
*p_beginPointer = *p_beginPointer + wcdo->BytesLeft;
|
|
ILibWebClient_FinishedResponse(socketModule,wcdo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
else
|
|
{
|
|
//
|
|
// This is chunk encoded, so run it through our Chunk Processor
|
|
//
|
|
ILibWebClient_ProcessChunk(socketModule,wcdo,buffer,p_beginPointer,endPointer);
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
}
|
|
if (socketModule==NULL || ILibAsyncSocket_IsFree(socketModule)==0)
|
|
{
|
|
//
|
|
// If the user said to pause this connection, do so
|
|
//
|
|
*PAUSE = wcdo->PAUSE;
|
|
}
|
|
return(ILibWebClient_DataResults_OK);
|
|
}
|
|
|
|
//
|
|
// Internal method dispatched by the OnSendOK event of the underlying ILibAsyncSocket
|
|
//
|
|
// <param name="socketModule">The underlying ILibAsyncSocket</param>
|
|
// <param name="user">The associated WebClientDataObject</param>
|
|
void ILibWebClient_OnSendOKSink(ILibAsyncSocket_SocketModule socketModule, void *user)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)user;
|
|
struct ILibWebRequest *wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
|
|
UNREFERENCED_PARAMETER( socketModule );
|
|
|
|
if (wr!=NULL && wr->streamedState!=NULL)
|
|
{
|
|
wr->streamedState->OnSendOK(wcdo,wr->user1,wr->user2);
|
|
}
|
|
else if (wcdo->IsWebSocket != 0 && wr != NULL && ((ILibWebClient_WebSocketState*)wr->Buffer[0])->OnSendOK != NULL)
|
|
{
|
|
((ILibWebClient_WebSocketState*)wr->Buffer[0])->OnSendOK(wcdo, wr->user1, wr->user2);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Internal method dispatched by the OnConnect event of the underlying ILibAsyncSocket
|
|
//
|
|
// <param name="socketModule">The underlying ILibAsyncSocket</param>
|
|
// <param name="Connected">Flag indicating connect status: Nonzero indicates success</param>
|
|
// <param name="user">Associated WebClientDataObject</param>
|
|
void ILibWebClient_OnConnect(ILibAsyncSocket_SocketModule socketModule, int Connected, void *user)
|
|
{
|
|
int i;
|
|
struct ILibWebRequest *r;
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)user;
|
|
|
|
int keyLength;
|
|
char key[24]; // Unoptimized version uses 300
|
|
|
|
//printf("ILibWebClient_OnConnect(). Connected=%d, DisconnectSent=%d\r\n", Connected, wcdo->DisconnectSent);
|
|
|
|
|
|
if (wcdo->Closing != 0) return; // Already closing, exit now
|
|
|
|
wcdo->SOCK = socketModule;
|
|
wcdo->InitialRequestAnswered = 0;
|
|
wcdo->DisconnectSent = 0;
|
|
wcdo->PendingConnectionIndex = 0;
|
|
|
|
if (Connected != 0 && wcdo->DisconnectSent == 0)
|
|
{
|
|
// Success: Send First Request
|
|
ILibAsyncSocket_GetLocalInterface(socketModule, (struct sockaddr*)&(wcdo->LocalIP));
|
|
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_OnConnect", 1, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
r = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_OnConnect", 2, wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
if (r != NULL)
|
|
{
|
|
if (r->ConnectSink != NULL) { r->ConnectSink(r->requestToken); }
|
|
ILibWebRequest_buffer *b;
|
|
for(i = 0; i < r->NumberOfBuffers; ++i)
|
|
{
|
|
ILibAsyncSocket_Send(socketModule, r->Buffer[i], r->BufferLength[i], (enum ILibAsyncSocket_MemoryOwnership)-1);
|
|
}
|
|
|
|
b = r->buffered;
|
|
while (b != NULL)
|
|
{
|
|
ILibAsyncSocket_Send(socketModule, b->buffer, b->bufferLength, ILibAsyncSocket_MemoryOwnership_USER);
|
|
b = b->next;
|
|
}
|
|
if (r->streamedState != NULL)
|
|
{
|
|
if (r->streamedState->idleTimeout > 0 && r->streamedState->idleTimeoutHandler != NULL) { ILibAsyncSocket_SetTimeoutEx(socketModule, r->streamedState->idleTimeout, r->streamedState->idleTimeoutHandler); }
|
|
ILibWebClient_OnSendOKSink(socketModule, wcdo);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// The connection failed, so lets set a timed callback, and try again
|
|
//
|
|
if (wcdo->DisconnectSent == 0)
|
|
{
|
|
wcdo->Closing = 2; // This is required, so we don't notify the user yet
|
|
ILibAsyncSocket_Disconnect(socketModule);
|
|
wcdo->Closing = 0;
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
wcdo->PipelineFlag = PIPELINE_UNKNOWN;
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
}
|
|
|
|
//
|
|
// Retried enough times, give up
|
|
//
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_OnConnect", 3, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
|
|
keyLength = ILibCreateTokenStr((struct sockaddr*)&(wcdo->remote), wcdo->IndexNumber, key);
|
|
|
|
ILibDeleteEntry(wcdo->Parent->DataTable, key, keyLength);
|
|
ILibDeleteEntry(wcdo->Parent->idleTable, key, keyLength);
|
|
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_OnConnect", 4, wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
|
|
ILibWebClient_DestroyWebClientDataObject(wcdo);
|
|
}
|
|
}
|
|
//
|
|
// Internal method dispatched by the OnDisconnect event of the underlying ILibAsyncSocket
|
|
//
|
|
// <param name="socketModule">The underlying ILibAsyncSocket</param>
|
|
// <param name="user">The associated WebClientDataObject</param>
|
|
void ILibWebClient_OnDisconnectSink(ILibAsyncSocket_SocketModule socketModule, void *user)
|
|
{
|
|
char *buffer;
|
|
int BeginPointer, EndPointer;
|
|
struct packetheader *h;
|
|
struct ILibWebRequest *wr;
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)user;
|
|
|
|
//printf("ILibWebClient_OnDisconnectSink()\r\n");
|
|
|
|
if (wcdo == NULL) { return; }
|
|
if (wcdo->DeferDestruction && wcdo->CancelRequest == 0) { return; }
|
|
|
|
if (wcdo->DisconnectSent != 0)
|
|
{
|
|
// We probably closed the socket on purpose, and don't want to tell
|
|
// anyone about it yet
|
|
return;
|
|
}
|
|
else if (ILibQueue_PeekQueue(wcdo->RequestQueue) != NULL && wcdo->WaitForClose != 0 && wcdo->CancelRequest == 0)
|
|
{
|
|
// There are still pending requests, so we probably already
|
|
// send the disconnect event up
|
|
wcdo->DisconnectSent = 1;
|
|
}
|
|
|
|
wcdo->SOCK = NULL;
|
|
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
if (wcdo->Closing == 0)
|
|
{
|
|
if (ILibQueue_PeekQueue(wcdo->RequestQueue) != NULL && wcdo->CancelRequest == 0)
|
|
{
|
|
//
|
|
// If there are still pending requests, than obviously this server
|
|
// doesn't do persistent connections
|
|
//
|
|
//wcdo->PipelineFlag = PIPELINE_NO;
|
|
}
|
|
}
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
|
|
if (wcdo->WaitForClose != 0 && wcdo->CancelRequest == 0 && wcdo->IsOrphan == 0)
|
|
{
|
|
//
|
|
// Since we had to read until the socket closes, we finally have
|
|
// all the data we need
|
|
//
|
|
ILibAsyncSocket_GetBuffer(socketModule,&buffer,&BeginPointer,&EndPointer);
|
|
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_OnDisconnect", 1, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
wr = (struct ILibWebRequest*)ILibQueue_DeQueue(wcdo->RequestQueue);
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_OnDisconnect", 2, wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
wcdo->InitialRequestAnswered = 1;
|
|
//{{{ REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT--> }}}
|
|
//wcdo->PipelineFlag = PIPELINE_NO;
|
|
//{{{ <--REMOVE_THIS_FOR_HTTP/1.0_ONLY_SUPPORT }}}
|
|
wcdo->FinHeader = 0;
|
|
h = wcdo->header;
|
|
wcdo->header = NULL;
|
|
if (wr != NULL && wr->OnResponse != NULL)
|
|
{
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
h,
|
|
buffer,
|
|
&BeginPointer,
|
|
EndPointer,
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
}
|
|
ILibWebClient_ResetWCDO(wcdo);
|
|
if (wcdo->DisconnectSent == 1) wcdo->DisconnectSent = 0;
|
|
if (wr != NULL && wr->DisconnectSink != NULL) { wr->DisconnectSink(wr->requestToken); }
|
|
if (wr != NULL) { wr->connectionCloseWasSpecified = 3; ILibWebClient_DestroyWebRequest(wr); }
|
|
if (h != NULL) ILibDestructPacket(h);
|
|
}
|
|
|
|
if (wcdo->Closing != 0) { return; }
|
|
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_OnDisconnect", 3, wcdo->Parent);)
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_OnDisconnect", 4, wcdo->Parent);)
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
|
|
if (wr != NULL)
|
|
{
|
|
// Still Requests to be made
|
|
if (wcdo->InitialRequestAnswered == 0 && wcdo->CancelRequest == 0)
|
|
{
|
|
if (wcdo->IsOrphan != 0 || wcdo->IsWebSocket != 0)
|
|
{
|
|
ILibWebClient_FinishedResponse(socketModule, wcdo);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Error
|
|
wr->OnResponse(
|
|
wcdo,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(wcdo->PAUSE));
|
|
ILibWebClient_FinishedResponse(socketModule, wcdo);
|
|
}
|
|
}
|
|
|
|
// Make Another Connection and Continue
|
|
wcdo->Closing = 0;
|
|
ILibQueue_EnQueue(wcdo->Parent->backlogQueue, wcdo);
|
|
}
|
|
wcdo->CancelRequest = 0;
|
|
}
|
|
|
|
void ILibWebClient_OnInterrupt(ILibAsyncSocket_SocketModule socketModule, void *user)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)user;
|
|
if (wcdo->IsWebSocket != 0 || wcdo->IsOrphan != 0)
|
|
{
|
|
ILibWebClient_DestroyWebClientDataObject(wcdo);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Chain PreSelect handler
|
|
//
|
|
// <param name="WebClientModule">The WebClient token</param>
|
|
// <param name="readset"></param>
|
|
// <param name="writeset"></param>
|
|
// <param name="errorset"></param>
|
|
// <param name="blocktime"></param>
|
|
void ILibWebClient_PreProcess(void* WebClientModule, fd_set *readset, fd_set *writeset, fd_set *errorset, int* blocktime)
|
|
{
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager*)WebClientModule;
|
|
struct ILibWebClientDataObject *wcdo;
|
|
int xOK = 0;
|
|
int i;
|
|
|
|
UNREFERENCED_PARAMETER( readset );
|
|
UNREFERENCED_PARAMETER( writeset );
|
|
UNREFERENCED_PARAMETER( errorset );
|
|
UNREFERENCED_PARAMETER( blocktime );
|
|
|
|
//
|
|
// Try and satisfy as many things as we can. If we have resources
|
|
// grab a socket and go
|
|
//
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_PreProcess",1,wcm);)
|
|
sem_wait(&(wcm->QLock));
|
|
while (xOK == 0 && ILibQueue_IsEmpty(wcm->backlogQueue) == 0)
|
|
{
|
|
xOK = 1;
|
|
for(i=0;i<wcm->socksLength;++i)
|
|
{
|
|
if (ILibAsyncSocket_IsFree(wcm->socks[i]) != 0)
|
|
{
|
|
xOK = 0;
|
|
wcdo = (struct ILibWebClientDataObject *)ILibQueue_DeQueue(wcm->backlogQueue);
|
|
if (wcdo != NULL)
|
|
{
|
|
wcdo->Closing = 0;
|
|
wcdo->PendingConnectionIndex = i;
|
|
|
|
#ifdef MICROSTACK_PROXY
|
|
if (wcdo->proxy.sin6_family != AF_UNSPEC)
|
|
{
|
|
// Use Proxy
|
|
ILibAsyncSocket_ConnectToProxy(
|
|
wcm->socks[i],
|
|
NULL,
|
|
(struct sockaddr*)&wcdo->remote,
|
|
(struct sockaddr*)&wcdo->proxy,
|
|
NULL,
|
|
NULL,
|
|
ILibWebClient_OnInterrupt,
|
|
wcdo);
|
|
}
|
|
else
|
|
{
|
|
// Don't use proxy
|
|
ILibAsyncSocket_ClearProxySettings(wcm->socks[i]);
|
|
ILibAsyncSocket_ConnectTo(
|
|
wcm->socks[i],
|
|
NULL,
|
|
(struct sockaddr*)&wcdo->remote,
|
|
ILibWebClient_OnInterrupt,
|
|
wcdo);
|
|
}
|
|
#else
|
|
// No Proxy support
|
|
ILibAsyncSocket_ConnectTo(
|
|
wcm->socks[i],
|
|
NULL,
|
|
(struct sockaddr*)&wcdo->remote,
|
|
ILibWebClient_OnInterrupt,
|
|
wcdo);
|
|
#endif
|
|
|
|
// We need SOCKET information to timeout connect for Network Discovery purpose.
|
|
wcdo->SOCK = wcm->socks[i];
|
|
|
|
// Addition for TLS purpose
|
|
#ifndef MICROSTACK_NOTLS
|
|
if (wcm->ssl_ctx != NULL && wcdo->requestMode == ILibWebClient_RequestToken_USE_HTTPS)
|
|
{
|
|
SSL* ssl = ILibAsyncSocket_SetSSLContextEx(wcdo->SOCK, wcm->ssl_ctx, 0, wcdo->sniHost);
|
|
if (ssl != NULL && ILibWebClientDataObjectIndex >= 0)
|
|
{
|
|
SSL_set_ex_data(ssl, ILibWebClientDataObjectIndex, wcdo);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
if (ILibQueue_IsEmpty(wcm->backlogQueue) != 0) break;
|
|
}
|
|
}
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_PreProcess", 2, wcm);)
|
|
sem_post(&(wcm->QLock));
|
|
}
|
|
|
|
//
|
|
// Internal method dispatched by either ILibWebServer or ILibWebClient, to recheck the headers
|
|
//
|
|
// In certain cases, when the underlying buffer has been reallocated, the pointers in the
|
|
// header structure will be invalid.
|
|
//
|
|
// <param name="token">The sender</param>
|
|
// <param name="user">The WCDO object</param>
|
|
// <param name="offSet">The offSet to the new buffer location</param>
|
|
void ILibWebClient_OnBufferReAllocate(ILibAsyncSocket_SocketModule token, void *user, ptrdiff_t offSet)
|
|
{
|
|
struct packetheader_field_node *n;
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)user;
|
|
|
|
UNREFERENCED_PARAMETER( token );
|
|
|
|
if (wcdo != NULL && wcdo->header != NULL)
|
|
{
|
|
//
|
|
// Sometimes, the header was copied, in which case this realloc doesn't affect us
|
|
//
|
|
if (wcdo->header->ClonedPacket == 0)
|
|
{
|
|
//
|
|
// Sometimes the user instantiated the string, so again
|
|
// this may not concern us
|
|
//
|
|
if (wcdo->header->UserAllocStrings == 0)
|
|
{
|
|
if (wcdo->header->StatusCode == -1)
|
|
{
|
|
// Request Packet
|
|
wcdo->header->Directive += offSet;
|
|
wcdo->header->DirectiveObj += offSet;
|
|
}
|
|
else
|
|
{
|
|
// Response Packet
|
|
wcdo->header->StatusData += offSet;
|
|
}
|
|
}
|
|
//
|
|
// Sometimes the user instantiated the string, so again
|
|
// this may not concern us
|
|
//
|
|
if (wcdo->header->UserAllocVersion == 0)
|
|
{
|
|
wcdo->header->Version += offSet;
|
|
}
|
|
n = wcdo->header->FirstField;
|
|
while (n != NULL)
|
|
{
|
|
//
|
|
// Sometimes the user instantiated the string, so again
|
|
// this may not concern us
|
|
//
|
|
if (n->UserAllocStrings == 0)
|
|
{
|
|
n->Field += offSet;
|
|
n->FieldData += offSet;
|
|
}
|
|
n = n->NextField;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Internal method called by the ILibWebServer module to create a WebClientDataObject
|
|
//
|
|
// <param name="OnResponse">Function pointer to handle data reception</param>
|
|
// <param name="socketModule">The underlying ILibAsyncSocket</param>
|
|
// <param name="user1"></param>
|
|
// <param name="user2"></param>
|
|
// <returns>The WebClientDataObject</returns>
|
|
ILibWebClient_StateObject ILibCreateWebClientEx(ILibWebClient_OnResponse OnResponse, ILibAsyncSocket_SocketModule socketModule, void *user1, void *user2)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo;
|
|
struct ILibWebRequest *wr;
|
|
|
|
if ((wcdo = (struct ILibWebClientDataObject*)malloc(sizeof(struct ILibWebClientDataObject))) == NULL) ILIBCRITICALEXIT(254);
|
|
memset(wcdo, 0 , sizeof(struct ILibWebClientDataObject));
|
|
wcdo->Parent = NULL;
|
|
wcdo->RequestQueue = ILibQueue_Create();
|
|
wcdo->Server = 1;
|
|
wcdo->SOCK = socketModule;
|
|
wcdo->PendingConnectionIndex = -1;
|
|
|
|
if ((wr = (struct ILibWebRequest*)malloc(sizeof(struct ILibWebRequest))) == NULL) ILIBCRITICALEXIT(254);
|
|
memset(wr, 0, sizeof(struct ILibWebRequest));
|
|
wr->OnResponse = OnResponse;
|
|
wr->user1 = user1;
|
|
wr->user2 = user2;
|
|
ILibQueue_EnQueue(wcdo->RequestQueue, wr);
|
|
return wcdo;
|
|
}
|
|
|
|
/*! \fn ILibCreateWebClient(int PoolSize,void *Chain)
|
|
\brief Constructor to create a new ILibWebClient
|
|
\param PoolSize The max number of ILibAsyncSockets to have in the pool
|
|
\param Chain The chain to add this module to. (Chain must <B>not</B> be running)
|
|
\returns An ILibWebClient
|
|
*/
|
|
ILibWebClient_RequestManager ILibCreateWebClient(int PoolSize, void *Chain)
|
|
{
|
|
int i;
|
|
struct ILibWebClientManager *RetVal;
|
|
|
|
if (Chain == NULL) return NULL;
|
|
|
|
if ((RetVal = (struct ILibWebClientManager*)malloc(sizeof(struct ILibWebClientManager))) == NULL) ILIBCRITICALEXIT(254);
|
|
memset(RetVal, 0, sizeof(struct ILibWebClientManager));
|
|
RetVal->MaxConnectionsToSameServer = 1;
|
|
RetVal->ChainLink.MetaData = "ILibWebClient";
|
|
RetVal->ChainLink.DestroyHandler = &ILibDestroyWebClient;
|
|
RetVal->ChainLink.PreSelectHandler = &ILibWebClient_PreProcess;
|
|
//RetVal->PostSelect = &ILibWebClient_PreProcess;
|
|
|
|
RetVal->socksLength = PoolSize;
|
|
RetVal->socks = (void**)malloc(PoolSize * sizeof(void*));
|
|
if (RetVal->socks == NULL) ILIBCRITICALEXIT(254);
|
|
sem_init(&(RetVal->QLock), 0, 1);
|
|
RetVal->ChainLink.ParentChain = Chain;
|
|
|
|
RetVal->backlogQueue = ILibQueue_Create();
|
|
RetVal->DataTable = ILibInitHashTree();
|
|
RetVal->idleTable = ILibInitHashTree();
|
|
//RetVal->WebSocketTable = ILibHashtable_Create();
|
|
|
|
ILibAddToChain(Chain,RetVal);
|
|
RetVal->timer = ILibGetBaseTimer(Chain); //ILibCreateLifeTime(Chain);
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
RetVal->ssl_ctx = NULL;
|
|
#endif
|
|
|
|
// Create our pool of sockets
|
|
for(i = 0; i < PoolSize; ++i)
|
|
{
|
|
RetVal->socks[i] = ILibCreateAsyncSocketModule(
|
|
Chain,
|
|
INITIAL_BUFFER_SIZE,
|
|
(ILibAsyncSocket_OnData)&ILibWebClient_OnData,
|
|
&ILibWebClient_OnConnect,
|
|
&ILibWebClient_OnDisconnectSink,
|
|
&ILibWebClient_OnSendOKSink);
|
|
//
|
|
// We want to know about any buffer reallocations, because we may need to fix some things
|
|
//
|
|
ILibAsyncSocket_SetReAllocateNotificationCallback(RetVal->socks[i], &ILibWebClient_OnBufferReAllocate);
|
|
}
|
|
return((void*)RetVal);
|
|
}
|
|
|
|
void ILibWebClient_SetMaxConcurrentSessionsToServer(ILibWebClient_RequestManager WebClient, int maxConnections)
|
|
{
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager*)WebClient;
|
|
wcm->MaxConnectionsToSameServer = maxConnections;
|
|
}
|
|
|
|
/*! \fn ILibWebClient_PipelineRequest(ILibWebClient_RequestManager WebClient, struct sockaddr_in *RemoteEndpoint, struct packetheader *packet, ILibWebClient_OnResponse OnResponse, void *user1, void *user2)
|
|
\brief Queues a new web request
|
|
\param WebClient The ILibWebClient to queue the requests to
|
|
\param RemoteEndpoint The destination
|
|
\param packet The packet to send
|
|
\param OnResponse Response Handler
|
|
\param user1 User object
|
|
\param user2 User object
|
|
\returns Request Token
|
|
*/
|
|
ILibWebClient_RequestToken ILibWebClient_PipelineRequest(
|
|
ILibWebClient_RequestManager WebClient,
|
|
struct sockaddr *RemoteEndpoint,
|
|
struct packetheader *packet,
|
|
ILibWebClient_OnResponse OnResponse,
|
|
void *user1,
|
|
void *user2)
|
|
{
|
|
int bufferLength;
|
|
char *buffer;
|
|
ILibWebClient_RequestToken retVal;
|
|
char *webSocketKey;
|
|
int webSocketKeyLen;
|
|
bufferLength = ILibGetRawPacket(packet,&buffer);
|
|
|
|
retVal = ILibWebClient_PipelineRequestEx(WebClient, RemoteEndpoint, buffer, bufferLength, ILibAsyncSocket_MemoryOwnership_CHAIN, NULL, 0, 0, OnResponse, user1, user2);
|
|
|
|
if ((webSocketKey = ILibGetHeaderLineEx(packet, "Host", 4, &webSocketKeyLen)) != NULL)
|
|
{
|
|
if (webSocketKeyLen < sizeof(((ILibWebClient_PipelineRequestToken*)retVal)->host))
|
|
{
|
|
strncpy_s(((ILibWebClient_PipelineRequestToken*)retVal)->host, sizeof(((ILibWebClient_PipelineRequestToken*)retVal)->host), webSocketKey, sizeof(((ILibWebClient_PipelineRequestToken*)retVal)->host));
|
|
}
|
|
}
|
|
|
|
|
|
if ((webSocketKey = ILibGetHeaderLineEx(packet, "Sec-WebSocket-Key", 17, &webSocketKeyLen)) != NULL)
|
|
{
|
|
// WebSocket Reqeust
|
|
char wsguid[] = WEBSOCKET_GUID;
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)ILibWebClient_GetStateObjectFromRequestToken(retVal);
|
|
int tokenLength;
|
|
char requestToken[24];
|
|
int i;
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager*)WebClient;
|
|
//char *tokenWebSocketKey = ILibMemory_GetExtraMemory(retVal, sizeof(ILibWebClient_PipelineRequestToken));
|
|
char *tokenWebSocketKey = ((ILibWebClient_PipelineRequestToken*)retVal)->reserved;
|
|
char *keyResult;
|
|
int keyResultLen;
|
|
char shvalue[21];
|
|
union { int i; void*p; }u;
|
|
|
|
keyResult = ILibString_Cat(webSocketKey, webSocketKeyLen, wsguid, sizeof(WEBSOCKET_GUID));
|
|
util_sha1(keyResult, (int)strnlen_s(keyResult, webSocketKeyLen + sizeof(WEBSOCKET_GUID)), shvalue);
|
|
free(keyResult);
|
|
|
|
keyResultLen = ILibBase64Encode((unsigned char*)shvalue, 20, (unsigned char**)&tokenWebSocketKey);
|
|
tokenWebSocketKey[keyResultLen] = 0;
|
|
|
|
u.p = ILibHTTPPacket_Stash_Get(packet, "_WebSocketBufferSize", 20);
|
|
((ILibWebClient_PipelineRequestToken*)retVal)->WebSocketKey = tokenWebSocketKey;
|
|
((ILibWebClient_PipelineRequestToken*)retVal)->WebSocketMaxBuffer = u.i;
|
|
((ILibWebClient_PipelineRequestToken*)retVal)->WebSocketSendOK = ILibHTTPPacket_Stash_Get(packet, "_WebSocketOnSendOK", 18);
|
|
if (ILibHTTPPacket_Stash_HasKey(packet, "_WebSocketMaskOverride", 22)) { wcdo->webSocketMaskOverride = 1; }
|
|
|
|
for (i = 0; i < wcm->MaxConnectionsToSameServer; ++i)
|
|
{
|
|
tokenLength = ILibCreateTokenStr(RemoteEndpoint, i, requestToken);
|
|
if (ILibGetEntry(wcm->DataTable, requestToken, tokenLength) == wcdo)
|
|
{
|
|
// We need to remove our WCDO from the DataTable, because this WCDO will no longer service HTTP requests
|
|
// after it is converted into a WebSocket. If we remove it from this table, then a future web request will create a new WCDO object
|
|
ILibDeleteEntry(wcm->DataTable, requestToken, tokenLength);
|
|
wcdo->IsOrphan = 1;
|
|
//ILibHashtable_Put(wcm->WebSocketTable, NULL, (char*)&wcdo, sizeof(void*), wcdo);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ILibDestructPacket(packet);
|
|
|
|
return retVal;
|
|
}
|
|
|
|
ILibWebClient_RequestToken ILibWebClient_PipelineRequestEx2(
|
|
ILibWebClient_RequestManager WebClient,
|
|
struct sockaddr *RemoteEndpoint,
|
|
char *headerBuffer,
|
|
int headerBufferLength,
|
|
int headerBuffer_FREE,
|
|
char *bodyBuffer,
|
|
int bodyBufferLength,
|
|
int bodyBuffer_FREE,
|
|
ILibWebClient_OnResponse OnResponse,
|
|
struct ILibWebClient_StreamedRequestState *state,
|
|
void *user1,
|
|
void *user2)
|
|
{
|
|
int ForceUnBlock = 0;
|
|
char RequestToken[24];
|
|
int RequestTokenLength = 0;
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager*)WebClient;
|
|
struct ILibWebClientDataObject *wcdo;
|
|
struct ILibWebRequest *request;
|
|
int i = 0;
|
|
|
|
int indexWithLeast;
|
|
int numberOfItems;
|
|
|
|
|
|
if ((request = (struct ILibWebRequest*)malloc(sizeof(struct ILibWebRequest))) == NULL) ILIBCRITICALEXIT(254);
|
|
memset(request, 0, sizeof(struct ILibWebRequest));
|
|
|
|
request->NumberOfBuffers = bodyBuffer != NULL?2:1;
|
|
if ((request->Buffer = (char**)malloc(request->NumberOfBuffers * sizeof(char*))) == NULL) ILIBCRITICALEXIT(254);
|
|
if ((request->BufferLength = (int*)malloc(request->NumberOfBuffers * sizeof(int))) == NULL) ILIBCRITICALEXIT(254);
|
|
if ((request->UserFree = (int*)malloc(request->NumberOfBuffers * sizeof(int))) == NULL) ILIBCRITICALEXIT(254);
|
|
|
|
request->Buffer[0] = headerBuffer;
|
|
request->BufferLength[0] = headerBufferLength;
|
|
request->UserFree[0] = headerBuffer_FREE;
|
|
|
|
ILibMemory_Allocate(sizeof(ILibWebClient_PipelineRequestToken), 32, (void**)&(request->requestToken), NULL);
|
|
request->requestToken->parent = request;
|
|
request->requestToken->timer = wcm->timer;
|
|
|
|
if (headerBufferLength > 5 && strncasecmp("HEAD ", headerBuffer, 5) == 0)
|
|
{
|
|
request->IsHeadRequest = 1;
|
|
}
|
|
|
|
if (bodyBuffer != NULL)
|
|
{
|
|
request->Buffer[1] = bodyBuffer;
|
|
request->BufferLength[1] = bodyBufferLength;
|
|
request->UserFree[1] = bodyBuffer_FREE;
|
|
}
|
|
|
|
if (state != NULL)
|
|
{
|
|
// We were called from ILibWebClient_PipelineStreamedRequest
|
|
request->streamedState = state;
|
|
}
|
|
|
|
request->OnResponse = OnResponse;
|
|
request->user1 = user1;
|
|
request->user2 = user2;
|
|
|
|
memcpy_s(&request->remote, sizeof(struct sockaddr_in6), RemoteEndpoint, INET_SOCKADDR_LENGTH(RemoteEndpoint->sa_family));
|
|
|
|
// If there is ILibAsyncSocket_MemoryOwnership_USER data in the buffers, change it to ILibAsyncSocket_MemoryOwnership_CHAIN
|
|
for(i = 0; i < request->NumberOfBuffers; ++i)
|
|
{
|
|
char* buf;
|
|
if (request->UserFree[i] == ILibAsyncSocket_MemoryOwnership_USER)
|
|
{
|
|
if ((buf = (char*)malloc(request->BufferLength[i])) == NULL) ILIBCRITICALEXIT(254);
|
|
memcpy_s(buf, request->BufferLength[i], request->Buffer[i], request->BufferLength[i]);
|
|
request->Buffer[i] = buf;
|
|
request->UserFree[i] = ILibAsyncSocket_MemoryOwnership_CHAIN;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Does the client already have a connection to the server?
|
|
//
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_PipelineRequestEx", 1, wcm);)
|
|
sem_wait(&(wcm->QLock));
|
|
|
|
if (wcm->MaxConnectionsToSameServer > 1)
|
|
{
|
|
for(i = 0; i < wcm->MaxConnectionsToSameServer; ++i)
|
|
{
|
|
RequestTokenLength = ILibCreateTokenStr(RemoteEndpoint, i, RequestToken);
|
|
if (ILibHasEntry(wcm->DataTable, RequestToken, RequestTokenLength) == 0)
|
|
{
|
|
// A free slot!
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i == wcm->MaxConnectionsToSameServer)
|
|
{
|
|
// There were not any free slots, so we need to find the one with the
|
|
// fewest number of requests.
|
|
indexWithLeast = -1;
|
|
numberOfItems = -1;
|
|
for(i = 0; i < wcm->MaxConnectionsToSameServer; ++i)
|
|
{
|
|
RequestTokenLength = ILibCreateTokenStr(RemoteEndpoint, i, RequestToken);
|
|
wcdo = (struct ILibWebClientDataObject*)ILibGetEntry(wcm->DataTable, RequestToken, RequestTokenLength);
|
|
if (wcdo == NULL) ILIBCRITICALEXIT(253); // TODO: Better handling
|
|
|
|
if (numberOfItems == -1)
|
|
{
|
|
numberOfItems = (int)ILibLinkedList_GetCount(wcdo->RequestQueue);
|
|
indexWithLeast = i;
|
|
}
|
|
else
|
|
{
|
|
if (ILibLinkedList_GetCount(wcdo->RequestQueue) < numberOfItems)
|
|
{
|
|
numberOfItems = (int)ILibLinkedList_GetCount(wcdo->RequestQueue);
|
|
indexWithLeast = i;
|
|
}
|
|
}
|
|
}
|
|
RequestTokenLength = ILibCreateTokenStr(RemoteEndpoint, indexWithLeast, RequestToken);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
i = 0;
|
|
RequestTokenLength = ILibCreateTokenStr(RemoteEndpoint, 0, RequestToken);
|
|
}
|
|
|
|
if ((wcdo = (struct ILibWebClientDataObject*)ILibGetEntry(wcm->DataTable, RequestToken, RequestTokenLength)) != NULL)
|
|
{
|
|
// Previous connection exists!
|
|
request->requestToken->wcdo = wcdo;
|
|
if (ILibQueue_IsEmpty(wcdo->RequestQueue) != 0)
|
|
{
|
|
// There are no pending requests however, so we can try to send this right away!
|
|
ILibQueue_EnQueue(wcdo->RequestQueue, request);
|
|
|
|
// Take out of Idle State
|
|
wcm->idleCount = wcm->idleCount == 0 ? 0 : wcm->idleCount - 1;
|
|
ILibDeleteEntry(wcm->idleTable, RequestToken, RequestTokenLength);
|
|
ILibLifeTime_Remove(wcm->timer, wcdo);
|
|
if (wcdo->DisconnectSent == 0 && (wcdo->SOCK == NULL || ILibAsyncSocket_IsFree(wcdo->SOCK)))
|
|
{
|
|
// If this was in our idleTable, then most likely the select doesn't know about
|
|
// it, so we need to force it to unblock
|
|
ILibQueue_EnQueue(wcm->backlogQueue, wcdo);
|
|
ForceUnBlock = 1;
|
|
}
|
|
else if (wcdo->SOCK != NULL)
|
|
{
|
|
// Socket is still there
|
|
if (wcdo->WaitForClose == 0)
|
|
{
|
|
for (i = 0; i < request->NumberOfBuffers; ++i)
|
|
{
|
|
// TODO: Sandeep: This function call can be locking!!
|
|
ILibAsyncSocket_Send(wcdo->SOCK, request->Buffer[i], request->BufferLength[i], ILibAsyncSocket_MemoryOwnership_STATIC);
|
|
}
|
|
if (request->streamedState != NULL)
|
|
{
|
|
if (request->streamedState->idleTimeout > 0 && request->streamedState->idleTimeoutHandler != NULL) { ILibAsyncSocket_SetTimeoutEx(wcdo->SOCK, request->streamedState->idleTimeout, request->streamedState->idleTimeoutHandler); }
|
|
ILibWebClient_OnSendOKSink(wcdo->SOCK, wcdo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// There are still pending requests, so lets just queue this up
|
|
ILibQueue_EnQueue(wcdo->RequestQueue, request);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// There is no previous connection, so we need to set it up
|
|
if ((wcdo = (struct ILibWebClientDataObject*)malloc(sizeof(struct ILibWebClientDataObject))) == NULL) ILIBCRITICALEXIT(254);
|
|
request->requestToken->wcdo = wcdo;
|
|
memset(wcdo, 0, sizeof(struct ILibWebClientDataObject));
|
|
wcdo->Parent = wcm;
|
|
wcdo->PendingConnectionIndex = -1;
|
|
wcdo->RequestQueue = ILibQueue_Create();
|
|
|
|
memcpy_s(&wcdo->remote, sizeof(struct sockaddr_in6), RemoteEndpoint, INET_SOCKADDR_LENGTH(RemoteEndpoint->sa_family));
|
|
wcdo->IndexNumber = i;
|
|
|
|
ILibQueue_EnQueue(wcdo->RequestQueue, request);
|
|
ILibAddEntry(wcm->DataTable, RequestToken, RequestTokenLength, wcdo);
|
|
if (wcdo->DisconnectSent == 0)
|
|
{
|
|
// Queue it up in our Backlog, because we don't want to burden ourselves, so we
|
|
// need to see if we have the resources for it. The Pool will grab one when it can.
|
|
// The chain doesn't know about us, so we need to force it to unblock, to process this.
|
|
ILibQueue_EnQueue(wcm->backlogQueue, wcdo);
|
|
ForceUnBlock = 1;
|
|
}
|
|
}
|
|
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_PipelineRequestEx", 2, wcm);)
|
|
sem_post(&(wcm->QLock));
|
|
if (ForceUnBlock != 0) ILibForceUnBlockChain(wcm->ChainLink.ParentChain);
|
|
SESSION_TRACK(request->requestToken, NULL, "PipelinedRequestEx");
|
|
return(request->requestToken);
|
|
}
|
|
|
|
/*! \fn ILibWebClient_PipelineRequestEx(
|
|
ILibWebClient_RequestManager WebClient,
|
|
struct sockaddr_in *RemoteEndpoint,
|
|
char *headerBuffer,
|
|
int headerBufferLength,
|
|
int headerBuffer_FREE,
|
|
char *bodyBuffer,
|
|
int bodyBufferLength,
|
|
int bodyBuffer_FREE,
|
|
ILibWebClient_OnResponse OnResponse,
|
|
void *user1,
|
|
void *user2)
|
|
\brief Queues a new web request
|
|
\par
|
|
This method differs from ILibWebClient_PiplineRequest, in that this method
|
|
allows you to directly specify the buffers, rather than a packet structure
|
|
\param WebClient The ILibWebClient to queue the requests to
|
|
\param RemoteEndpoint The destination
|
|
\param headerBuffer The buffer containing the headers
|
|
\param headerBufferLength The length of the headers
|
|
\param headerBuffer_FREE Flag indicating memory ownership of buffer
|
|
\param bodyBuffer The buffer containing the HTTP body
|
|
\param bodyBufferLength The length of the buffer
|
|
\param bodyBuffer_FREE Flag indicating memory ownership of buffer
|
|
\param OnResponse Data reception handler
|
|
\param user1 User object
|
|
\param user2 User object
|
|
\returns Request Token
|
|
*/
|
|
ILibWebClient_RequestToken ILibWebClient_PipelineRequestEx(
|
|
ILibWebClient_RequestManager WebClient,
|
|
struct sockaddr *RemoteEndpoint,
|
|
char *headerBuffer,
|
|
int headerBufferLength,
|
|
int headerBuffer_FREE,
|
|
char *bodyBuffer,
|
|
int bodyBufferLength,
|
|
int bodyBuffer_FREE,
|
|
ILibWebClient_OnResponse OnResponse,
|
|
void *user1,
|
|
void *user2)
|
|
{
|
|
return(ILibWebClient_PipelineRequestEx2(WebClient, RemoteEndpoint, headerBuffer, headerBufferLength, headerBuffer_FREE, bodyBuffer, bodyBufferLength, bodyBuffer_FREE, OnResponse, NULL, user1, user2));
|
|
}
|
|
ILibWebClient_RequestToken ILibWebClient_PipelineRequest2(
|
|
ILibWebClient_RequestManager WebClient,
|
|
struct sockaddr *RemoteEndpoint,
|
|
struct packetheader *packet,
|
|
ILibWebClient_OnResponse OnResponse,
|
|
struct ILibWebClient_StreamedRequestState *state,
|
|
void *user1,
|
|
void *user2)
|
|
{
|
|
int bufferLength;
|
|
char *buffer;
|
|
|
|
bufferLength = ILibGetRawPacket(packet, &buffer);
|
|
ILibDestructPacket(packet);
|
|
return(ILibWebClient_PipelineRequestEx2(WebClient, RemoteEndpoint, buffer, bufferLength, ILibAsyncSocket_MemoryOwnership_CHAIN, NULL, 0, 0, OnResponse, state, user1, user2));
|
|
}
|
|
|
|
//
|
|
// Returns the headers from a given WebClientDataObject
|
|
//
|
|
// <param name="token">The WebClientDataObject to query</param>
|
|
// <returns>The headers</returns>
|
|
struct packetheader *ILibWebClient_GetHeaderFromDataObject(ILibWebClient_StateObject token)
|
|
{
|
|
return(token == NULL?NULL:((struct ILibWebClientDataObject*)token)->header);
|
|
}
|
|
|
|
/*! \fn ILibWebClient_DeleteRequests(ILibWebClient_RequestManager WebClientToken,char *IP,int Port)
|
|
\brief Deletes all pending requests to a specific IP/Port combination
|
|
\param WebClientToken The ILibWebClient to purge
|
|
\param IP The IP address of the destination
|
|
\param Port The destination port
|
|
*/
|
|
void ILibWebClient_DeleteRequests(ILibWebClient_RequestManager WebClientToken, struct sockaddr* addr)
|
|
{
|
|
int i;
|
|
int zero = 0;
|
|
void *RemoveQ;
|
|
char RequestToken[24]; // Unoptimized version uses 300
|
|
int RequestTokenLength;
|
|
struct ILibWebRequest *wr;
|
|
struct ILibWebClientDataObject *wcdo = NULL;
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager*)WebClientToken;
|
|
|
|
if (wcm == NULL) {return;}
|
|
RemoveQ = ILibQueue_Create();
|
|
|
|
for(i=0;i<wcm->MaxConnectionsToSameServer;++i)
|
|
{
|
|
RequestTokenLength = ILibCreateTokenStr(addr, i, RequestToken);
|
|
|
|
//
|
|
// Are there any pending requests to this IP/Port combo?
|
|
//
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_DeleteRequests", 1, wcm);)
|
|
sem_wait(&(wcm->QLock));
|
|
if (ILibHasEntry(wcm->DataTable, RequestToken, RequestTokenLength) != 0)
|
|
{
|
|
//
|
|
// Yes there is!. Lets iterate through them
|
|
//
|
|
wcdo = (struct ILibWebClientDataObject*)ILibGetEntry(wcm->DataTable, RequestToken, RequestTokenLength);
|
|
if (wcdo!=NULL)
|
|
{
|
|
//
|
|
// Remove ourselves from the backlog queue, because a previous connection attempt may be in progress.
|
|
//
|
|
ILibLinkedList_Remove_ByData(wcm->backlogQueue, wcdo);
|
|
while (ILibQueue_IsEmpty(wcdo->RequestQueue) == 0)
|
|
{
|
|
//
|
|
// Put all the pending requests into this queue, so we can trigger them outside of this lock
|
|
//
|
|
wr = (struct ILibWebRequest*)ILibQueue_DeQueue(wcdo->RequestQueue);
|
|
ILibQueue_EnQueue(RemoveQ, wr);
|
|
}
|
|
|
|
ILibLifeTime_Remove(wcdo->Parent->timer, wcdo);
|
|
}
|
|
}
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_DeleteRequests",2,wcm);)
|
|
sem_post(&(wcm->QLock));
|
|
}
|
|
|
|
|
|
//
|
|
// Lets iterate through all the requests that we need to get rid of
|
|
//
|
|
while (ILibQueue_IsEmpty(RemoveQ) == 0)
|
|
{
|
|
//
|
|
// Tell the user, we are aborting these requests
|
|
//
|
|
wr = (struct ILibWebRequest*)ILibQueue_DeQueue(RemoveQ);
|
|
if (wr != NULL && wr->OnResponse != NULL)
|
|
{
|
|
wr->OnResponse(
|
|
NULL,
|
|
WEBCLIENT_DELETED,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
0,
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&zero);
|
|
}
|
|
|
|
ILibWebClient_DestroyWebRequest(wr);
|
|
}
|
|
if (wcdo != NULL && wcdo->Parent != NULL)
|
|
{
|
|
if (wcdo->PendingConnectionIndex != -1)
|
|
{
|
|
ILibAsyncSocket_Disconnect(wcm->socks[wcdo->PendingConnectionIndex]);
|
|
wcdo->PendingConnectionIndex = -1;
|
|
}
|
|
}
|
|
if (wcdo != NULL && wcdo->SOCK != NULL)
|
|
{
|
|
ILibAsyncSocket_Disconnect(wcdo->SOCK);
|
|
}
|
|
ILibQueue_Destroy(RemoveQ);
|
|
}
|
|
|
|
/*! \fn ILibWebClient_Resume(ILibWebClient_StateObject wcdo)
|
|
\brief Resumes a paused connection
|
|
\par
|
|
If the client has set the PAUSED flag, the underlying socket will no longer
|
|
read data from the NIC. This method, resumes the socket.
|
|
\param wcdo The associated WebClientDataObject
|
|
*/
|
|
void ILibWebClient_Resume(ILibWebClient_StateObject wcdo)
|
|
{
|
|
struct ILibWebClientDataObject *d = (struct ILibWebClientDataObject*)wcdo;
|
|
|
|
if (d!=NULL)
|
|
{
|
|
d->PAUSE = 0;
|
|
ILibAsyncSocket_Resume(d->SOCK);
|
|
}
|
|
}
|
|
void ILibWebClient_Pause(ILibWebClient_StateObject wcdo)
|
|
{
|
|
struct ILibWebClientDataObject *d = (struct ILibWebClientDataObject*)wcdo;
|
|
if (d!=NULL)
|
|
{
|
|
d->PAUSE = 1;
|
|
}
|
|
}
|
|
|
|
/*! \fn ILibWebClient_Disconnect(ILibWebClient_StateObject wcdo)
|
|
\brief Disconnects the underlying socket, of a client object.
|
|
\par
|
|
<b>Note</b>: This is <b>not</b> to be used to close an HTTP Session, as ILibWebClient does not
|
|
keep separate states for client sessions. The HTTP behavior is abstracted, such that the user <b>must</b> not
|
|
make any assumptions about the connection, because multiple requests could be multiplexed into a single connection.
|
|
<br><br>
|
|
If the user desires to cancel their client session, they need to cancel the requests that they had made. That can
|
|
be accomplished by calling \a ILibWebClient_CancelRequest, with the RequestToken obtained from \a ILibWebClient_PipelineRequest.
|
|
\param wcdo WebClient State Object, obtained from \a ILibCreateWebClientEx.
|
|
*/
|
|
void ILibWebClient_Disconnect(ILibWebClient_StateObject wcdo)
|
|
{
|
|
struct ILibWebClientDataObject *d = (struct ILibWebClientDataObject*)wcdo;
|
|
if (d!=NULL && d->SOCK != NULL)
|
|
{
|
|
ILibAsyncSocket_Disconnect(d->SOCK);
|
|
}
|
|
}
|
|
|
|
void ILibWebClient_CancelRequestEx2(ILibWebClient_StateObject wcdo, void *userRequest)
|
|
{
|
|
void *node,*nextnode;
|
|
struct ILibWebRequest *wr;
|
|
struct ILibWebClientDataObject *_wcdo = (struct ILibWebClientDataObject*)wcdo;
|
|
int HeadDeleted = 0;
|
|
void *head;
|
|
int BeginPointer=0;
|
|
int EndPointer=0;
|
|
void *PendingRequestQ = NULL;
|
|
|
|
if (wcdo != NULL)
|
|
{
|
|
PendingRequestQ = ILibQueue_Create();
|
|
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_CancelRequest",1,_wcdo->Parent);)
|
|
sem_wait(&(_wcdo->Parent->QLock));
|
|
|
|
head = node = ILibLinkedList_GetNode_Head(_wcdo->RequestQueue);
|
|
while (node != NULL)
|
|
{
|
|
nextnode = ILibLinkedList_GetNextNode(node);
|
|
|
|
wr = (struct ILibWebRequest*)ILibLinkedList_GetDataFromNode(node);
|
|
if (wr->requestToken == userRequest)
|
|
{
|
|
if (node == head)
|
|
{
|
|
SESSION_TRACK(wr->requestToken, NULL, "Cancelling Current Request");
|
|
HeadDeleted = 1;
|
|
}
|
|
else
|
|
{
|
|
SESSION_TRACK(wr->requestToken, NULL, "Cancelling Request");
|
|
}
|
|
|
|
ILibQueue_EnQueue(PendingRequestQ, wr);
|
|
ILibLinkedList_Remove(node);
|
|
}
|
|
node = nextnode;
|
|
}
|
|
|
|
// If this connection was unsuccessful, remove it from the retry
|
|
// logic, because we will have to manually re-connect anyways, in order
|
|
// for the rest of the pending requests to go through, because we have to manually
|
|
// disconnect the socket, in order to interrupt this session.
|
|
ILibLifeTime_Remove(_wcdo->Parent->timer, _wcdo);
|
|
if (HeadDeleted != 0 && _wcdo->SOCK != NULL)
|
|
{
|
|
//
|
|
// Since the request we removed was the currently active request, we
|
|
// must close the socket, because that is the only way to interrupt the server
|
|
//
|
|
ILibWebClient_ResetWCDO((struct ILibWebClientDataObject*)wcdo);
|
|
_wcdo->Closing = 2;
|
|
_wcdo->CancelRequest = 1;
|
|
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_CancelRequest", 2, _wcdo->Parent);)
|
|
sem_post(&(_wcdo->Parent->QLock));
|
|
|
|
ILibWebClient_Disconnect(_wcdo);
|
|
|
|
SEM_TRACK(WebClient_TrackLock("ILibWebClient_CancelRequest", 3, _wcdo->Parent);)
|
|
sem_wait(&(_wcdo->Parent->QLock));
|
|
|
|
_wcdo->Closing = 0;
|
|
_wcdo->CancelRequest = 0;
|
|
|
|
//
|
|
// Now that we disconnected the session, re-connect it, if there are more requests
|
|
// to be handled
|
|
//
|
|
if (ILibQueue_PeekQueue(_wcdo->RequestQueue) != NULL)
|
|
{
|
|
//
|
|
// Queue the session to be reconnected
|
|
//
|
|
ILibQueue_EnQueue(_wcdo->Parent->backlogQueue, _wcdo);
|
|
}
|
|
}
|
|
|
|
SEM_TRACK(WebClient_TrackUnLock("ILibWebClient_CancelRequest", 2, _wcdo->Parent);)
|
|
sem_post(&(_wcdo->Parent->QLock));
|
|
|
|
wr = (struct ILibWebRequest*)ILibQueue_DeQueue(PendingRequestQ);
|
|
while (wr != NULL)
|
|
{
|
|
if (wr->OnResponse != NULL)
|
|
{
|
|
wr->OnResponse(
|
|
_wcdo,
|
|
0,
|
|
NULL,
|
|
NULL, // No Buffer to pass in
|
|
&BeginPointer, // This is zero
|
|
EndPointer, // This is zero
|
|
ILibWebClient_ReceiveStatus_Complete,
|
|
wr->user1,
|
|
wr->user2,
|
|
&(_wcdo->PAUSE)); // Pause is also zero
|
|
}
|
|
|
|
ILibWebClient_DestroyWebRequest(wr);
|
|
wr = (struct ILibWebRequest*)ILibQueue_DeQueue(PendingRequestQ);
|
|
}
|
|
ILibQueue_Destroy(PendingRequestQ);
|
|
}
|
|
}
|
|
|
|
void ILibWebClient_CancelRequestEx(void *chain, void *RequestToken)
|
|
{
|
|
ILibWebClient_CancelRequestEx2(((struct ILibWebClient_PipelineRequestToken*)RequestToken)->wcdo,RequestToken);
|
|
}
|
|
|
|
/*! \fn ILibWebClient_CancelRequest(ILibWebClient_RequestToken RequestToken)
|
|
\brief Cancels a pending request via the RequestToken received when making the request.
|
|
\param RequestToken The identifier obtained from calls to \a ILibWebClient_PipelineRequest.
|
|
*/
|
|
void ILibWebClient_CancelRequest(ILibWebClient_RequestToken RequestToken)
|
|
{
|
|
if (RequestToken != NULL)
|
|
{
|
|
ILibChain_RunOnMicrostackThread(((struct ILibWebClient_PipelineRequestToken*)RequestToken)->wcdo->Parent->ChainLink.ParentChain, ILibWebClient_CancelRequestEx, RequestToken);
|
|
}
|
|
}
|
|
/*! \fn ILibWebClient_GetRequestToken_FromStateObject(ILibWebClient_StateObject WebStateObject)
|
|
\brief Obtains the Request Token associated with the specified WebReader (response) token.
|
|
\param WebStateObject The response token obtained from the response handler passed into \a ILibWebClient_PipelineRequest.
|
|
\returns The request identifier of the request this response was for
|
|
*/
|
|
ILibWebClient_RequestToken ILibWebClient_GetRequestToken_FromStateObject(ILibWebClient_StateObject WebStateObject)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)WebStateObject;
|
|
struct ILibWebRequest *wr;
|
|
|
|
if (wcdo == NULL) return(NULL);
|
|
wr = (struct ILibWebRequest*)ILibQueue_PeekQueue(wcdo->RequestQueue);
|
|
if (wr != NULL) { return(wr->requestToken); } else { return(NULL); }
|
|
}
|
|
|
|
void **ILibWebClient_RequestToken_GetUserObjects(ILibWebClient_RequestToken tok)
|
|
{
|
|
ILibWebClient_PipelineRequestToken *prt = (ILibWebClient_PipelineRequestToken*)tok;
|
|
return(prt->parent != NULL ? &(prt->parent->user1) : NULL);
|
|
}
|
|
|
|
/*! \fn ILibWebClient_StateObject ILibWebClient_GetStateObjectFromRequestToken(ILibWebClient_RequestToken token)
|
|
\brief Obtain the user object that was associated with a request, from the request token
|
|
\param token The token that was obtained from a call to \a ILibWebClient_PipelineRequest or \a ILibWebClient_PipelineRequestEx
|
|
*/
|
|
ILibWebClient_StateObject ILibWebClient_GetStateObjectFromRequestToken(ILibWebClient_RequestToken token)
|
|
{
|
|
return(((struct ILibWebClient_PipelineRequestToken*)token)->wcdo);
|
|
}
|
|
|
|
|
|
/*! \fn ILibWebClient_RequestToken ILibWebClient_PipelineStreamedRequest(ILibWebClient_RequestManager WebClient,struct sockaddr_in *RemoteEndpoint,struct packetheader *packet,ILibWebClient_OnResponse OnResponse,ILibWebClient_OnSendOK OnSendOK,void *user1,void *user2)
|
|
\brief Queues a web request, but allows for streaming of the request body
|
|
\param WebClient The client to queue to request onto
|
|
\param RemoteEndpoint The server to make the request to
|
|
\param packet The HTTP headers to send to the server
|
|
\param OnResponse Function pointer that will get triggered apon receipt of a response
|
|
\param OnSendOK Function pointer that will trigger when a connection has been established and again when all calls to ILibWebClient_StreamRequestBody() have completed
|
|
\param user1 User state object
|
|
\param user2 User state object
|
|
*/
|
|
ILibWebClient_RequestToken ILibWebClient_PipelineStreamedRequest(ILibWebClient_RequestManager WebClient,struct sockaddr *RemoteEndpoint, struct packetheader *packet,ILibWebClient_OnResponse OnResponse,ILibWebClient_OnSendOK OnSendOK,void *user1,void *user2)
|
|
{
|
|
struct ILibWebClient_StreamedRequestState *state;
|
|
|
|
if ((state = (struct ILibWebClient_StreamedRequestState*)malloc(sizeof(struct ILibWebClient_StreamedRequestState))) == NULL) ILIBCRITICALEXIT(254);
|
|
memset(state,0,sizeof(struct ILibWebClient_StreamedRequestState));
|
|
|
|
state->BufferQueue = ILibQueue_Create();
|
|
state->OnSendOK = OnSendOK;
|
|
state->doNotSendRightAway = 1;
|
|
if (ILibHTTPPacket_Stash_HasKey(packet, "_idleTimeout", 12) != 0)
|
|
{
|
|
union { int i; void*p; }u;
|
|
u.p = ILibHTTPPacket_Stash_Get(packet, "_idleTimeout", 12);
|
|
state->idleTimeout = u.i;
|
|
state->idleTimeoutHandler = (ILibAsyncSocket_TimeoutHandler)ILibHTTPPacket_Stash_Get(packet, "_idleTimeoutHandler", 19);
|
|
}
|
|
|
|
ILibAddHeaderLine(packet,"Transfer-Encoding",17,"chunked",7);
|
|
|
|
return(ILibWebClient_PipelineRequest2(WebClient,RemoteEndpoint,packet,OnResponse,state,user1,user2));
|
|
}
|
|
|
|
/*! \fn void ILibWebClient_StreamRequestBody(ILibWebClient_RequestToken token, char *body,int bodyLength, enum ILibAsyncSocket_MemoryOwnership MemoryOwnership,int done)
|
|
\brief Streams part of the request body
|
|
\param token The RequestToken received from a call to ILibWebClient_PipelineStreamedRequest()
|
|
\param body The buffer to send
|
|
\param bodyLength Size of the buffer to send
|
|
\param MemoryOwnership Memory ownership flag for the buffer
|
|
\param done Non-zero if all of the body has been submitted
|
|
*/
|
|
ILibTransport_DoneState ILibWebClient_StreamRequestBody(
|
|
ILibWebClient_RequestToken token,
|
|
char *body,
|
|
int bodyLength,
|
|
enum ILibAsyncSocket_MemoryOwnership MemoryOwnership,
|
|
ILibTransport_DoneState done
|
|
)
|
|
{
|
|
struct ILibWebClient_PipelineRequestToken *t = (struct ILibWebClient_PipelineRequestToken*)token;
|
|
struct ILibWebRequest *wr;
|
|
|
|
char hex[16];
|
|
int hexLen;
|
|
ILibTransport_DoneState result = ILibTransport_DoneState_INCOMPLETE;
|
|
|
|
if (t != NULL && t->wcdo != NULL)
|
|
{
|
|
wr = t->parent;
|
|
if (t->wcdo->SOCK == NULL || ILibQueue_GetCount(t->wcdo->RequestQueue)>1)
|
|
{
|
|
// Connection not established yet, so buffer the data
|
|
ILibWebRequest_buffer *b = (ILibWebRequest_buffer*)ILibMemory_Allocate(sizeof(ILibWebRequest_buffer) + 25 + bodyLength, 0, NULL, NULL);
|
|
b->bufferLength = 0;
|
|
|
|
if (body != NULL && bodyLength > 0)
|
|
{
|
|
b->bufferLength = sprintf_s(b->buffer, 16, "%X\r\n", bodyLength);
|
|
memcpy_s(b->buffer + b->bufferLength, 25 + bodyLength - b->bufferLength, body, bodyLength); b->bufferLength += bodyLength;
|
|
memcpy_s(b->buffer + b->bufferLength, 25 + bodyLength - b->bufferLength, "\r\n", 2); b->bufferLength += 2;
|
|
}
|
|
if (done != 0)
|
|
{
|
|
memcpy_s(b->buffer + b->bufferLength, 25 + bodyLength - b->bufferLength, "0\r\n\r\n", 5); b->bufferLength += 5;
|
|
}
|
|
|
|
if (body != NULL && MemoryOwnership == ILibAsyncSocket_MemoryOwnership_CHAIN) { free(body); }
|
|
if (b->bufferLength > 0)
|
|
{
|
|
if (wr->buffered == NULL)
|
|
{
|
|
wr->buffered = b;
|
|
}
|
|
else
|
|
{
|
|
ILibWebRequest_buffer *c = wr->buffered;
|
|
while (c->next != NULL)
|
|
{
|
|
c = c->next;
|
|
}
|
|
c->next = b;
|
|
}
|
|
return(ILibTransport_DoneState_INCOMPLETE);
|
|
}
|
|
else
|
|
{
|
|
free(b);
|
|
return(ILibTransport_DoneState_ERROR);
|
|
}
|
|
}
|
|
if (body != NULL && bodyLength > 0)
|
|
{
|
|
hexLen = sprintf_s(hex, 16, "%X\r\n", bodyLength);
|
|
result = (ILibTransport_DoneState)ILibAsyncSocket_Send(t->wcdo->SOCK, hex, hexLen, ILibAsyncSocket_MemoryOwnership_USER);
|
|
if (result != ILibTransport_DoneState_ERROR)
|
|
{
|
|
result = (ILibTransport_DoneState)ILibAsyncSocket_Send(t->wcdo->SOCK, body ,bodyLength, MemoryOwnership);
|
|
if (result != ILibTransport_DoneState_ERROR)
|
|
{
|
|
result = (ILibTransport_DoneState)ILibAsyncSocket_Send(t->wcdo->SOCK, "\r\n", 2, ILibAsyncSocket_MemoryOwnership_STATIC);
|
|
}
|
|
}
|
|
else if (MemoryOwnership == ILibAsyncSocket_MemoryOwnership_CHAIN)
|
|
{
|
|
free(body);
|
|
}
|
|
}
|
|
if (result != ILibTransport_DoneState_ERROR && done != 0)
|
|
{
|
|
result = (ILibTransport_DoneState)ILibAsyncSocket_Send(t->wcdo->SOCK, "0\r\n\r\n", 5, ILibAsyncSocket_MemoryOwnership_STATIC);
|
|
}
|
|
if (result == ILibTransport_DoneState_COMPLETE && wr != NULL && wr->streamedState != NULL && wr->streamedState->OnSendOK != NULL && done == 0)
|
|
{
|
|
// All the data was sent, so call OnSendOK
|
|
wr->streamedState->OnSendOK(t->wcdo, wr->user1, wr->user2);
|
|
}
|
|
}
|
|
return(result);
|
|
}
|
|
/*! \fn void ILibWebClient_Parse_ContentRange(char *contentRange, int *Start, int *End, int *TotalLength)
|
|
\brief Parses an HTTP/206 Partial Content, Content-Range header, to obtain the range that was returned.
|
|
\param contentRange The Content-Range header to parse. This can be obtained with a call to \a ILibGetHeaderLine
|
|
\param[out] Start Pointer to the int value where the Start byte position will be stored
|
|
\param[out] End Pointer to the int value where the End byte position will be stored
|
|
\param[out] TotalLength Pointer to the int value where the total available length will be stored
|
|
*/
|
|
void ILibWebClient_Parse_ContentRange(char *contentRange, int *Start, int *End, int *TotalLength)
|
|
{
|
|
struct parser_result *pr,*pr2,*pr3;
|
|
int hasErrors = 0;
|
|
|
|
*Start = 0;
|
|
*End = 0;
|
|
*TotalLength = 0;
|
|
|
|
|
|
pr = ILibParseString(contentRange,0,(int)strnlen_s(contentRange, sizeof(ILibScratchPad) - sizeof(void*))," ",1);
|
|
//
|
|
// bytes x-y/z
|
|
//
|
|
if (pr->NumResults == 2)
|
|
{
|
|
//
|
|
// Verify that the first token is: bytes
|
|
//
|
|
if (pr->FirstResult->datalength == 5 && memcmp(pr->FirstResult->data,"bytes",5)==0)
|
|
{
|
|
pr2 = ILibParseString(pr->LastResult->data,0,pr->LastResult->datalength,"/",1);
|
|
if (pr2->NumResults == 2)
|
|
{
|
|
if (memcmp(pr2->LastResult->data, "*", 1)==0)
|
|
{
|
|
hasErrors = 1;
|
|
}
|
|
*TotalLength = atoi(pr2->LastResult->data);
|
|
pr3 = ILibParseString(pr2->FirstResult->data, 0, pr2->FirstResult->datalength, "-", 1);
|
|
if (pr3->NumResults==2)
|
|
{
|
|
pr3->FirstResult->data[pr3->FirstResult->datalength] = 0;
|
|
pr3->LastResult->data[pr3->LastResult->datalength] = 0;
|
|
*Start = atoi(pr3->FirstResult->data);
|
|
*End = atoi(pr3->LastResult->data);
|
|
if (pr3->FirstResult->datalength == 0 || pr3->LastResult->datalength == 0)
|
|
{
|
|
hasErrors = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// not: bytes x-y/z
|
|
//
|
|
hasErrors = 1;
|
|
}
|
|
ILibDestructParserResults(pr3);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// not: bytes x-y/z
|
|
//
|
|
hasErrors = 1;
|
|
}
|
|
ILibDestructParserResults(pr2);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// was not: bytes x-y/z
|
|
//
|
|
hasErrors = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Was not in the form: bytes x-y/z
|
|
//
|
|
hasErrors = 1;
|
|
}
|
|
ILibDestructParserResults(pr);
|
|
if (hasErrors != 0)
|
|
{
|
|
*Start = -1;
|
|
*End = -1;
|
|
*TotalLength = -1;
|
|
}
|
|
}
|
|
/*! \fn int ILibWebClient_Parse_Range(char *Range, long *Start, long *Length, long TotalLength)
|
|
\brief Parses the Range request header, to obtain the requested range
|
|
\param Range The Range header to parse. This can be obtained with a call to \a ILibGetHeaderLine
|
|
\param[out] Start Pointer to the long value where the Start byte position will be stored
|
|
\param[out] Length Pointer to the long value where the desired length will be stored.
|
|
\param TotalLength The total length of available content.
|
|
\returns 0 = Success, 1 = Failure
|
|
*/
|
|
enum ILibWebClient_Range_Result ILibWebClient_Parse_Range(char *Range, long *Start, long *Length, long TotalLength)
|
|
{
|
|
struct parser_result *pr,*pr2;
|
|
long x=-1;
|
|
long y=-1;
|
|
enum ILibWebClient_Range_Result RetVal = ILibWebClient_Range_Result_OK;
|
|
|
|
*Start = 0;
|
|
*Length = 0;
|
|
|
|
pr = ILibParseString(Range, 0, (int)strnlen_s(Range, sizeof(ILibScratchPad) - sizeof(void*)), "=", 1);
|
|
if (pr->NumResults == 2 && pr->FirstResult->datalength==5 && memcmp(pr->FirstResult->data, "bytes",5)==0)
|
|
{
|
|
pr2 = ILibParseString(pr->LastResult->data, 0, pr->LastResult->datalength, "-", 1);
|
|
if (pr2->NumResults == 2)
|
|
{
|
|
if (pr2->FirstResult->datalength == 0)
|
|
{
|
|
x = -1;
|
|
}
|
|
else
|
|
{
|
|
pr2->FirstResult->data[pr2->FirstResult->datalength]=0;
|
|
x = atol(pr2->FirstResult->data);
|
|
}
|
|
if (pr2->LastResult->datalength == 0)
|
|
{
|
|
if (x!=-1)
|
|
{
|
|
y = TotalLength - x;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pr2->LastResult->data[pr2->LastResult->datalength] = 0;
|
|
if (x != -1)
|
|
{
|
|
y = 1 + atol(pr2->LastResult->data) - x;
|
|
}
|
|
else
|
|
{
|
|
x = TotalLength - atol(pr2->LastResult->data);
|
|
y = TotalLength - x;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RetVal = ILibWebClient_Range_Result_BAD_REQUEST;
|
|
}
|
|
ILibDestructParserResults(pr2);
|
|
}
|
|
else
|
|
{
|
|
RetVal = ILibWebClient_Range_Result_BAD_REQUEST;
|
|
}
|
|
ILibDestructParserResults(pr);
|
|
|
|
if (x >= 0 && y >= 0 && x <= TotalLength)
|
|
{
|
|
*Start = x;
|
|
*Length = y;
|
|
RetVal = ILibWebClient_Range_Result_OK;
|
|
}
|
|
else if (RetVal == ILibWebClient_Range_Result_OK)
|
|
{
|
|
RetVal = ILibWebClient_Range_Result_INVALID_RANGE;
|
|
}
|
|
return RetVal;
|
|
}
|
|
void ILibWebClient_SetUser(ILibWebClient_RequestManager manager, void *user)
|
|
{
|
|
((struct ILibWebClientManager*)manager)->user = user;
|
|
}
|
|
void* ILibWebClient_GetUser(ILibWebClient_RequestManager manager)
|
|
{
|
|
return(((struct ILibWebClientManager*)manager)->user);
|
|
}
|
|
void* ILibWebClient_GetChain(ILibWebClient_RequestManager manager)
|
|
{
|
|
return(((struct ILibWebClientManager*)manager)->ChainLink.ParentChain);
|
|
}
|
|
void* ILibWebClient_GetChainFromWebStateObject(ILibWebClient_StateObject wcdo)
|
|
{
|
|
if (((ILibWebClientDataObject*)wcdo)->Closing == 0)
|
|
{
|
|
return ((ILibWebClientDataObject*)wcdo)->Parent->ChainLink.ParentChain;
|
|
}
|
|
else
|
|
{
|
|
return NULL;
|
|
}
|
|
}
|
|
#ifndef MICROSTACK_NOTLS
|
|
static int ILibWebClient_Https_AuthenticateServer(int preverify_ok, X509_STORE_CTX *ctx)
|
|
{
|
|
int retVal = 0;
|
|
SSL_TRACE1("ILibWebClient_Https_AuthenticateServer()");
|
|
|
|
SSL *ssl = (SSL*)X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx());
|
|
ILibWebClientDataObject *wcdo = SSL_get_ex_data(ssl, ILibWebClientDataObjectIndex);
|
|
ILibWebClient_RequestToken token = ILibWebClient_GetRequestToken_FromStateObject(wcdo);
|
|
|
|
if (wcdo->Parent->OnHttpsConnection != NULL)
|
|
{
|
|
STACK_OF(X509) *certChain = X509_STORE_CTX_get_chain(ctx);
|
|
retVal = wcdo->Parent->OnHttpsConnection(token, preverify_ok, certChain, &(wcdo->remote));
|
|
//sk_X509_free(certChain);
|
|
}
|
|
SSL_TRACE2("ILibWebClient_Https_AuthenticateServer()");
|
|
|
|
return retVal;
|
|
}
|
|
void ILibWebClient_SetTLS(ILibWebClient_RequestManager manager, void *ssl_ctx, ILibWebClient_OnSslConnection OnSslConnection)
|
|
{
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager *)manager;
|
|
wcm->ssl_ctx = (SSL_CTX*)ssl_ctx;
|
|
wcm->OnSslConnection = OnSslConnection;
|
|
}
|
|
int ILibWebClient_EnableHTTPS(ILibWebClient_RequestManager manager, struct util_cert* leafCert, X509* nonLeafCert, ILibWebClient_OnHttpsConnection OnHttpsConnection)
|
|
{
|
|
SSL_CTX* ctx;
|
|
if (((struct ILibWebClientManager *)manager)->ssl_ctx != NULL) // SSL Context was already previously set
|
|
{
|
|
ILibRemoteLogging_printf(ILibChainGetLogger(((struct ILibWebClientManager*)manager)->ChainLink.ParentChain), ILibRemoteLogging_Modules_Microstack_Web, ILibRemoteLogging_Flags_VerbosityLevel_1, "ILibWebClient_EnableHTTPS(): Error, SSL_CTX was already set on RequestManager: %p", (void*)manager);
|
|
return 1;
|
|
}
|
|
|
|
SSL_TRACE1("ILibWebClient_EnableHTTPS()");
|
|
|
|
ctx = SSL_CTX_new(SSLv23_client_method()); // HTTPS client
|
|
if (ctx == NULL)
|
|
{
|
|
ILibRemoteLogging_printf(ILibChainGetLogger(((struct ILibWebClientManager*)manager)->ChainLink.ParentChain), ILibRemoteLogging_Modules_Microstack_Web, ILibRemoteLogging_Flags_VerbosityLevel_1, "ILibWebClient_EnableHTTPS(): Error initializing OpenSSL on RequestManager: %p", (void*)manager);
|
|
return 1;
|
|
}
|
|
|
|
SSL_CTX_set_options(ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
|
|
if (leafCert != NULL)
|
|
{
|
|
SSL_CTX_use_certificate(ctx, leafCert->x509);
|
|
SSL_CTX_use_PrivateKey(ctx, leafCert->pkey);
|
|
|
|
if (nonLeafCert != NULL)
|
|
{
|
|
SSL_CTX_add_extra_chain_cert(ctx, X509_dup(nonLeafCert));
|
|
}
|
|
}
|
|
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, ILibWebClient_Https_AuthenticateServer); // Ask for server authentication
|
|
|
|
if (ILibWebClientDataObjectIndex < 0)
|
|
{
|
|
ILibWebClientDataObjectIndex = SSL_get_ex_new_index(0, "ILibWebClient_Module index", NULL, NULL, NULL);
|
|
}
|
|
|
|
((struct ILibWebClientManager *)manager)->ssl_ctx = ctx;
|
|
((struct ILibWebClientManager *)manager)->OnHttpsConnection = OnHttpsConnection;
|
|
((struct ILibWebClientManager *)manager)->EnableHTTPS_Called = 1;
|
|
SSL_TRACE2("ILibWebClient_EnableHTTPS()");
|
|
return 0;
|
|
}
|
|
void ILibWebClient_Request_SetHTTPS(ILibWebClient_RequestToken reqToken, ILibWebClient_RequestToken_HTTPS requestMode)
|
|
{
|
|
((struct ILibWebClientDataObject*)ILibWebClient_GetStateObjectFromRequestToken(reqToken))->requestMode = requestMode;
|
|
}
|
|
void ILibWebClient_Request_SetSNI(ILibWebClient_RequestToken reqToken, char *host, int hostLen)
|
|
{
|
|
((struct ILibWebClientDataObject*)ILibWebClient_GetStateObjectFromRequestToken(reqToken))->sniHost = ILibMemory_Allocate(hostLen + 1, 0, NULL, NULL);
|
|
memcpy_s(((struct ILibWebClientDataObject*)ILibWebClient_GetStateObjectFromRequestToken(reqToken))->sniHost, hostLen, host, hostLen);
|
|
((struct ILibWebClientDataObject*)ILibWebClient_GetStateObjectFromRequestToken(reqToken))->sniHost[hostLen] = 0;
|
|
}
|
|
#endif
|
|
|
|
int ILibWebClient_GetLocalInterface(void* socketModule, struct sockaddr *localAddress)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)socketModule;
|
|
return ILibAsyncSocket_GetLocalInterface(wcdo->SOCK, localAddress);
|
|
}
|
|
|
|
int ILibWebClient_GetRemoteInterface(void* socketModule, struct sockaddr *remoteAddress)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)socketModule;
|
|
memcpy_s(remoteAddress, INET_SOCKADDR_LENGTH(wcdo->remote.sin6_family), &(wcdo->remote), INET_SOCKADDR_LENGTH(wcdo->remote.sin6_family));
|
|
return INET_SOCKADDR_LENGTH(wcdo->remote.sin6_family);
|
|
}
|
|
|
|
#ifndef MICROSTACK_NOTLS
|
|
X509* ILibWebClient_SslGetCert(void* socketModule)
|
|
{
|
|
return ILibAsyncSocket_SslGetCert(((struct ILibWebClientDataObject*)socketModule)->SOCK);
|
|
}
|
|
|
|
STACK_OF(X509)* ILibWebClient_SslGetCerts(void* socketModule)
|
|
{
|
|
return ILibAsyncSocket_SslGetCerts(((struct ILibWebClientDataObject*)socketModule)->SOCK);
|
|
}
|
|
|
|
char* ILibWebClient_GetCertificateHash(void* socketModule)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)socketModule;
|
|
return wcdo->CertificateHashPtr;
|
|
}
|
|
|
|
char* ILibWebClient_SetCertificateHash(void* socketModule, char* ptr)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)socketModule;
|
|
return wcdo->CertificateHashPtr = ptr;
|
|
}
|
|
|
|
char* ILibWebClient_GetCertificateHashEx(void* socketModule)
|
|
{
|
|
struct ILibWebClientDataObject *wcdo = (struct ILibWebClientDataObject*)socketModule;
|
|
return wcdo->CertificateHash;
|
|
}
|
|
#endif
|
|
|
|
// Returns 1 if we already have an HTTP client allocated towards this IP address, regardless of port
|
|
int ILibWebClient_HasAllocatedClient(ILibWebClient_RequestManager WebClient, struct sockaddr *remoteAddress)
|
|
{
|
|
int i;
|
|
struct sockaddr_in6 addr;
|
|
char* addr1;
|
|
int addr1len;
|
|
char* addr2;
|
|
int addr2len;
|
|
int sock;
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager*)WebClient;
|
|
|
|
// Get the actual address 1
|
|
addr1len = ILibGetAddrBlob(remoteAddress, &addr1);
|
|
|
|
// Get address 2 & check it.
|
|
for(i = 0; i < wcm->socksLength; ++i)
|
|
{
|
|
sock = *((int*)ILibAsyncSocket_GetSocket((ILibAsyncSocket_SocketModule)wcm->socks[i]));
|
|
if (sock != ~0)
|
|
{
|
|
ILibAsyncSocket_GetRemoteInterface((ILibAsyncSocket_SocketModule)wcm->socks[i], (struct sockaddr*)&addr);
|
|
addr2len = ILibGetAddrBlob((struct sockaddr*)&addr, &addr2);
|
|
if (addr1len == addr2len && memcmp(addr1, addr2, addr1len) == 0) return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Returns 1 if we already have an HTTP client allocated towards this IP address, regardless of port
|
|
int ILibWebClient_GetActiveClientCount(ILibWebClient_RequestManager WebClient)
|
|
{
|
|
int i, count = 0;
|
|
struct ILibWebClientManager *wcm = (struct ILibWebClientManager*)WebClient;
|
|
for(i = 0; i < wcm->socksLength; ++i)
|
|
{
|
|
if ((*((int*)ILibAsyncSocket_GetSocket((ILibAsyncSocket_SocketModule)wcm->socks[i]))) != ~0) count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
|
|
extern void ILibWebServer_Digest_ParseAuthenticationHeader(void* table, char* value, int valueLen);
|
|
int ILibWebClient_Digest_NeedAuthenticate(ILibWebClient_StateObject state)
|
|
{
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)state;
|
|
char* authenticate = ILibGetHeaderLine(wcdo->header, "WWW-Authenticate", 16);
|
|
return(wcdo->header->StatusCode == 401 && authenticate != NULL);
|
|
}
|
|
void* ILibWebClient_Digest_GenerateTableEx(ILibWebClient_StateObject state, void *ReservedMemory)
|
|
{
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)state;
|
|
char* authenticate = ILibGetHeaderLineSP(((ILibWebClientDataObject*)state)->header, "WWW-Authenticate", 16);
|
|
|
|
if (wcdo->DigestData == NULL) { wcdo->DigestData = ILibMemory_SmartAllocate(1024); }
|
|
if (authenticate != NULL)
|
|
{
|
|
ILibMemory_Init(ILibMemory_RawPtr(wcdo->DigestData), ILibMemory_RawSize(wcdo->DigestData), 0, ILibMemory_Types_HEAP);
|
|
strncpy_s(wcdo->DigestData, ILibMemory_Size(wcdo->DigestData), authenticate, strnlen_s(authenticate, sizeof(ILibScratchPad)));
|
|
}
|
|
else
|
|
{
|
|
authenticate = wcdo->DigestData;
|
|
}
|
|
|
|
void* table = ILibInitHashTree_CaseInSensitiveEx(ReservedMemory);
|
|
ILibWebServer_Digest_ParseAuthenticationHeader(table, authenticate, (int)strnlen_s(authenticate, sizeof(ILibScratchPad) - sizeof(void*)));
|
|
return(table);
|
|
}
|
|
#define ILibWebClient_Digest_GenerateTable(state) ILibWebClient_Digest_GenerateTableEx(state, NULL)
|
|
char* ILibWebClient_Digest_GetRealm(ILibWebClient_StateObject state)
|
|
{
|
|
void* table = ILibWebClient_Digest_GenerateTable(state);
|
|
char* retVal = (char*)ILibGetEntry(table, "realm", 5);
|
|
ILibDestroyHashTree(table);
|
|
|
|
return retVal;
|
|
}
|
|
|
|
void ILibWebClient_GenerateAuthenticationHeader(ILibWebClient_StateObject state, ILibHTTPPacket *packet, char* username, char* password)
|
|
{
|
|
ILibWebClientDataObject *wcdo = (ILibWebClientDataObject*)state;
|
|
int tmpLen;
|
|
char result1[33];
|
|
char result2[33];
|
|
char result3[33];
|
|
|
|
void *ReservedMemory = packet->ReservedMemory == NULL ? ILibMemory_AllocateA(8000) : packet->ReservedMemory;
|
|
void* table = ILibWebClient_Digest_GenerateTableEx(state, ReservedMemory);
|
|
char* realm = (char*)ILibGetEntry(table, "realm", 5);
|
|
char* nonce = (char*)ILibGetEntry(table, "nonce", 5);
|
|
char* opaque = (char*)ILibGetEntry(table, "opaque", 6);
|
|
char *qop = (char*)ILibGetEntry(table, "qop", 3);
|
|
|
|
tmpLen = sprintf_s(ILibScratchPad2, sizeof(ILibScratchPad2), "%s:%s:%s", username, realm, password);
|
|
util_md5hex(ILibScratchPad2, tmpLen, result1);
|
|
|
|
packet->Directive[packet->DirectiveLength] = 0;
|
|
packet->DirectiveObj[packet->DirectiveObjLength] = 0;
|
|
tmpLen = sprintf_s(ILibScratchPad2, sizeof(ILibScratchPad2), "%s:%s", packet->Directive, packet->DirectiveObj);
|
|
util_md5hex(ILibScratchPad2, tmpLen, result2);
|
|
|
|
if (qop == NULL)
|
|
{
|
|
tmpLen = sprintf_s(ILibScratchPad2, sizeof(ILibScratchPad2), "%s:%s:%s", result1, nonce, result2);
|
|
}
|
|
else
|
|
{
|
|
if (wcdo->NC == 0)
|
|
{
|
|
util_randomtext(sizeof(wcdo->CNONCE)-1, wcdo->CNONCE);
|
|
wcdo->CNONCE[sizeof(wcdo->CNONCE) - 1] = 0;
|
|
}
|
|
wcdo->NC++;
|
|
tmpLen = sprintf_s(ILibScratchPad2, sizeof(ILibScratchPad2), "%s:%s:%08x:%s:%s:%s", result1, nonce, wcdo->NC, wcdo->CNONCE, qop, result2);
|
|
}
|
|
util_md5hex(ILibScratchPad2, tmpLen, result3);
|
|
|
|
tmpLen = sprintf_s(ILibScratchPad2, sizeof(ILibScratchPad2), "Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\"", username, realm, nonce, packet->DirectiveObj);
|
|
if (opaque != NULL) { tmpLen += sprintf_s(ILibScratchPad2 + tmpLen, sizeof(ILibScratchPad2) - tmpLen, ", opaque=\"%s\"", opaque); }
|
|
if (qop != NULL)
|
|
{
|
|
tmpLen += sprintf_s(ILibScratchPad2 + tmpLen, sizeof(ILibScratchPad2) - tmpLen, ", qop=\"%s\", nc=%08x, cnonce=\"%s\"", qop, wcdo->NC, wcdo->CNONCE);
|
|
}
|
|
|
|
tmpLen += sprintf_s(ILibScratchPad2 + tmpLen, sizeof(ILibScratchPad2) - tmpLen, ", response=\"%s\"", result3);
|
|
ILibAddHeaderLine(packet, "Authorization", 13, ILibScratchPad2, tmpLen);
|
|
}
|
|
void ILibWebClient_AddWebSocketRequestHeaders(ILibHTTPPacket *packet, int FragmentReassemblyMaxBufferSize, ILibWebClient_OnSendOK OnSendOK)
|
|
{
|
|
char nonce[16];
|
|
char value[26];
|
|
int len;
|
|
union { int i; void* p; }u;
|
|
char *enc = value;
|
|
|
|
util_random(16, nonce);
|
|
len = ILibBase64Encode((unsigned char*)nonce, 16, (unsigned char**)&enc);
|
|
|
|
ILibAddHeaderLine(packet, "Upgrade", 7, "websocket", 9);
|
|
ILibAddHeaderLine(packet, "Connection", 10, "Upgrade", 7);
|
|
ILibAddHeaderLine(packet, "Sec-WebSocket-Key", 17, value, len);
|
|
ILibAddHeaderLine(packet, "Sec-WebSocket-Version", 21, "13", 2);
|
|
|
|
u.i = FragmentReassemblyMaxBufferSize;
|
|
ILibHTTPPacket_Stash_Put(packet, "_WebSocketBufferSize", 20, u.p);
|
|
ILibHTTPPacket_Stash_Put(packet, "_WebSocketOnSendOK", 18, OnSendOK);
|
|
}
|
|
void ILibWebClient_RequestToken_ConnectionHandler_Set(ILibWebClient_RequestToken token, ILibWebClient_OnConnectHandler OnConnect, ILibWebClient_OnConnectHandler OnDisconnect)
|
|
{
|
|
ILibWebClientDataObject *wcdo = ILibWebClient_GetStateObjectFromRequestToken(token);
|
|
struct ILibWebRequest *wr;
|
|
|
|
if (wcdo != NULL)
|
|
{
|
|
sem_wait(&(wcdo->Parent->QLock));
|
|
wr = (struct ILibWebRequest*)ILibQueue_PeekTail(wcdo->RequestQueue);
|
|
if (wr != NULL)
|
|
{
|
|
wr->ConnectSink = OnConnect;
|
|
wr->DisconnectSink = OnDisconnect;
|
|
}
|
|
sem_post(&(wcdo->Parent->QLock));
|
|
}
|
|
}
|
|
#ifdef MICROSTACK_PROXY
|
|
struct sockaddr_in6* ILibWebClient_SetProxy(ILibWebClient_RequestToken token, char *proxyHost, unsigned short proxyPort, char *username, char *password)
|
|
{
|
|
ILibWebClientDataObject *wcdo = ILibWebClient_GetStateObjectFromRequestToken(token);
|
|
if (wcdo == NULL) { return(NULL); }
|
|
if (proxyHost == NULL)
|
|
{
|
|
memset(&(wcdo->proxy), 0, sizeof(struct sockaddr_in6));
|
|
return(NULL);
|
|
}
|
|
|
|
if (ILibResolveEx(proxyHost, proxyPort, &(wcdo->proxy)) != 0 || wcdo->proxy.sin6_family == AF_UNSPEC)
|
|
{
|
|
memset(&(wcdo->proxy), 0, sizeof(struct sockaddr_in6));
|
|
ILibRemoteLogging_printf(ILibChainGetLogger(wcdo->Parent->ChainLink.ParentChain), ILibRemoteLogging_Modules_Microstack_Web, ILibRemoteLogging_Flags_VerbosityLevel_1, "ILibWebClient_SetProxy(): Unable to resolve proxy %s", proxyHost);
|
|
return(NULL);
|
|
}
|
|
else
|
|
{
|
|
return(&(wcdo->proxy));
|
|
}
|
|
}
|
|
void ILibWebClient_SetProxyEx(ILibWebClient_RequestToken token, struct sockaddr_in6* proxyServer, char *username, char *password)
|
|
{
|
|
ILibWebClientDataObject *wcdo = ILibWebClient_GetStateObjectFromRequestToken(token);
|
|
if (wcdo == NULL) { return; }
|
|
|
|
memcpy_s(&(wcdo->proxy), sizeof(struct sockaddr_in6), proxyServer, sizeof(struct sockaddr_in6));
|
|
}
|
|
#endif
|