mirror of
https://github.com/signalwire/freeswitch.git
synced 2025-02-10 01:40:26 +00:00
646 lines
19 KiB
C
646 lines
19 KiB
C
/* Copyright information is at the end of the file. */
|
|
|
|
#include <time.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <assert.h>
|
|
|
|
#include "bool.h"
|
|
#include "mallocvar.h"
|
|
#include "xmlrpc-c/util_int.h"
|
|
#include "xmlrpc-c/string_int.h"
|
|
#include "xmlrpc-c/sleep_int.h"
|
|
#include "xmlrpc-c/abyss.h"
|
|
#include "channel.h"
|
|
#include "server.h"
|
|
#include "thread.h"
|
|
#include "file.h"
|
|
|
|
#include "conn.h"
|
|
|
|
/*********************************************************************
|
|
** Conn
|
|
*********************************************************************/
|
|
|
|
static TThreadProc connJob;
|
|
|
|
static void
|
|
connJob(void * const userHandle) {
|
|
/*----------------------------------------------------------------------------
|
|
This is the root function for a thread that processes a connection
|
|
(performs HTTP transactions).
|
|
|
|
We never return. We ultimately exit the thread.
|
|
-----------------------------------------------------------------------------*/
|
|
TConn * const connectionP = userHandle;
|
|
|
|
(connectionP->job)(connectionP);
|
|
|
|
connectionP->finished = TRUE;
|
|
/* Note that if we are running in a forked process, setting
|
|
connectionP->finished has no effect, because it's just our own
|
|
copy of *connectionP. In this case, Parent must update his own
|
|
copy based on a SIGCHLD signal that the OS will generate right
|
|
after we exit.
|
|
*/
|
|
|
|
|
|
/* Note that ThreadExit() runs a cleanup function, which in our
|
|
case is connDone().
|
|
*/
|
|
ThreadExit(connectionP->threadP, 0);
|
|
}
|
|
|
|
|
|
|
|
/* This is the maximum amount of stack that 'connJob' itself uses --
|
|
does not count what user's connection job function uses.
|
|
*/
|
|
#define CONNJOB_STACK 1024
|
|
|
|
|
|
static void
|
|
connDone(TConn * const connectionP) {
|
|
|
|
/* In the forked case, this is designed to run in the parent
|
|
process after the child has terminated.
|
|
*/
|
|
connectionP->finished = TRUE;
|
|
|
|
if (connectionP->done)
|
|
connectionP->done(connectionP);
|
|
}
|
|
|
|
|
|
|
|
static TThreadDoneFn threadDone;
|
|
|
|
static void
|
|
threadDone(void * const userHandle) {
|
|
|
|
TConn * const connectionP = userHandle;
|
|
|
|
connDone(connectionP);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
makeThread(TConn * const connectionP,
|
|
enum abyss_foreback const foregroundBackground,
|
|
bool const useSigchld,
|
|
size_t const jobStackSize,
|
|
const char ** const errorP) {
|
|
|
|
switch (foregroundBackground) {
|
|
case ABYSS_FOREGROUND:
|
|
connectionP->hasOwnThread = FALSE;
|
|
*errorP = NULL;
|
|
break;
|
|
case ABYSS_BACKGROUND: {
|
|
const char * error;
|
|
connectionP->hasOwnThread = TRUE;
|
|
ThreadCreate(&connectionP->threadP, connectionP,
|
|
&connJob, &threadDone, useSigchld,
|
|
CONNJOB_STACK + jobStackSize,
|
|
&error);
|
|
if (error) {
|
|
xmlrpc_asprintf(errorP, "Unable to create thread to "
|
|
"process connection. %s", error);
|
|
xmlrpc_strfree(error);
|
|
} else
|
|
*errorP = NULL;
|
|
} break;
|
|
} /* switch */
|
|
}
|
|
|
|
|
|
|
|
void
|
|
ConnCreate(TConn ** const connectionPP,
|
|
TServer * const serverP,
|
|
TChannel * const channelP,
|
|
void * const channelInfoP,
|
|
TThreadProc * const job,
|
|
size_t const jobStackSize,
|
|
TThreadDoneFn * const done,
|
|
enum abyss_foreback const foregroundBackground,
|
|
bool const useSigchld,
|
|
const char ** const errorP) {
|
|
/*----------------------------------------------------------------------------
|
|
Create an HTTP connection.
|
|
|
|
A connection carries one or more HTTP transactions (request/response).
|
|
|
|
*channelP transports the requests and responses.
|
|
|
|
The connection handles those HTTP requests.
|
|
|
|
The connection handles the requests primarily by running the
|
|
function 'job' once. Some connections can do that autonomously, as
|
|
soon as the connection is created. Others don't until Caller
|
|
subsequently calls ConnProcess. Some connections complete the
|
|
processing before ConnProcess return, while others may run the
|
|
connection asynchronously to the creator, in the background, via a
|
|
TThread thread. 'foregroundBackground' determines which.
|
|
|
|
'job' calls methods of the connection to get requests and send
|
|
responses.
|
|
|
|
Some time after the HTTP transactions are all done, 'done' gets
|
|
called in some context.
|
|
|
|
'channelInfoP' == NULL means no channel info supplied.
|
|
-----------------------------------------------------------------------------*/
|
|
TConn * connectionP;
|
|
|
|
MALLOCVAR(connectionP);
|
|
|
|
if (connectionP == NULL)
|
|
xmlrpc_asprintf(errorP, "Unable to allocate memory for a connection "
|
|
"descriptor.");
|
|
else {
|
|
connectionP->server = serverP;
|
|
connectionP->channelP = channelP;
|
|
connectionP->channelInfoP = channelInfoP;
|
|
connectionP->buffer.b[0] = '\0';
|
|
connectionP->buffersize = 0;
|
|
connectionP->bufferpos = 0;
|
|
connectionP->finished = FALSE;
|
|
connectionP->job = job;
|
|
connectionP->done = done;
|
|
connectionP->inbytes = 0;
|
|
connectionP->outbytes = 0;
|
|
connectionP->trace = getenv("ABYSS_TRACE_CONN");
|
|
|
|
makeThread(connectionP, foregroundBackground, useSigchld,
|
|
jobStackSize, errorP);
|
|
}
|
|
*connectionPP = connectionP;
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
ConnProcess(TConn * const connectionP) {
|
|
/*----------------------------------------------------------------------------
|
|
Drive the main processing of a connection -- run the connection's
|
|
"job" function, which should read HTTP requests from the connection
|
|
and send HTTP responses.
|
|
|
|
If we succeed, we guarantee the connection's "done" function will get
|
|
called some time after all processing is complete. It might be before
|
|
we return or some time after. If we fail, we guarantee the "done"
|
|
function will not be called.
|
|
-----------------------------------------------------------------------------*/
|
|
bool retval;
|
|
|
|
if (connectionP->hasOwnThread) {
|
|
/* There's a background thread to handle this connection. Set
|
|
it running.
|
|
*/
|
|
assert(connectionP->threadP);
|
|
retval = ThreadRun(connectionP->threadP);
|
|
} else {
|
|
/* No background thread. We just handle it here while Caller waits. */
|
|
(connectionP->job)(connectionP);
|
|
connDone(connectionP);
|
|
retval = TRUE;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
ConnWaitAndRelease(TConn * const connectionP) {
|
|
|
|
if (connectionP->hasOwnThread) {
|
|
assert(connectionP->threadP);
|
|
ThreadWaitAndRelease(connectionP->threadP);
|
|
}
|
|
free(connectionP);
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
ConnKill(TConn * const connectionP) {
|
|
connectionP->finished = TRUE;
|
|
return ThreadKill(connectionP->threadP);
|
|
}
|
|
|
|
|
|
|
|
void
|
|
ConnReadInit(TConn * const connectionP) {
|
|
|
|
if (connectionP->buffersize > connectionP->bufferpos) {
|
|
connectionP->buffersize -= connectionP->bufferpos;
|
|
memmove(connectionP->buffer.b,
|
|
connectionP->buffer.b + connectionP->bufferpos,
|
|
connectionP->buffersize);
|
|
connectionP->bufferpos = 0;
|
|
} else
|
|
connectionP->buffersize = connectionP->bufferpos = 0;
|
|
|
|
connectionP->buffer.b[connectionP->buffersize] = '\0';
|
|
|
|
connectionP->inbytes = connectionP->outbytes = 0;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
traceReadTimeout(TConn * const connectionP,
|
|
uint32_t const timeout) {
|
|
|
|
if (connectionP->trace)
|
|
fprintf(stderr, "TIMED OUT waiting over %u seconds "
|
|
"for data from client.\n", timeout);
|
|
}
|
|
|
|
|
|
|
|
static size_t
|
|
nextLineSize(const char * const string,
|
|
size_t const startPos,
|
|
size_t const stringSize) {
|
|
/*----------------------------------------------------------------------------
|
|
Return the length of the line that starts at offset 'startPos' in the
|
|
string 'string', which is 'stringSize' characters long.
|
|
|
|
'string' in not NUL-terminated.
|
|
|
|
A line begins at beginning of string or after a newline character and
|
|
runs through the next newline character or end of string. The line
|
|
includes the newline character at the end, if any.
|
|
-----------------------------------------------------------------------------*/
|
|
size_t i;
|
|
|
|
for (i = startPos; i < stringSize && string[i] != '\n'; ++i);
|
|
|
|
if (i < stringSize)
|
|
++i; /* Include the newline */
|
|
|
|
return i - startPos;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
traceBuffer(const char * const label,
|
|
const unsigned char * const buffer,
|
|
unsigned int const size) {
|
|
|
|
const char * const buffer_t = (const char *)buffer;
|
|
|
|
size_t cursor; /* Index into buffer[] */
|
|
|
|
fprintf(stderr, "%s:\n\n", label);
|
|
|
|
for (cursor = 0; cursor < size; ) {
|
|
/* Print one line of buffer */
|
|
|
|
size_t const lineSize = nextLineSize(buffer_t, cursor, size);
|
|
const char * const printableLine =
|
|
xmlrpc_makePrintable_lp(&buffer_t[cursor], lineSize);
|
|
|
|
fprintf(stderr, "%s\n", printableLine);
|
|
|
|
cursor += lineSize;
|
|
|
|
xmlrpc_strfree(printableLine);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
traceBufferText(const char * const label,
|
|
const char * const buffer,
|
|
unsigned int const size) {
|
|
|
|
traceBuffer(label, (const unsigned char *)buffer, size);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
traceChannelRead(TConn * const connectionP,
|
|
unsigned int const size) {
|
|
|
|
if (connectionP->trace)
|
|
traceBuffer("READ FROM CHANNEL",
|
|
connectionP->buffer.b + connectionP->buffersize, size);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
traceChannelWrite(TConn * const connectionP,
|
|
const char * const buffer,
|
|
unsigned int const size,
|
|
bool const failed) {
|
|
|
|
if (connectionP->trace) {
|
|
const char * const label =
|
|
failed ? "FAILED TO WRITE TO CHANNEL" : "WROTE TO CHANNEL";
|
|
traceBufferText(label, buffer, size);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static uint32_t
|
|
bufferSpace(TConn * const connectionP) {
|
|
|
|
return BUFFER_SIZE - connectionP->buffersize;
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
readFromChannel(TConn * const connectionP,
|
|
bool * const eofP,
|
|
const char ** const errorP) {
|
|
/*----------------------------------------------------------------------------
|
|
Read some data from the channel of Connection *connectionP.
|
|
|
|
Iff there is none available to read, return *eofP == true.
|
|
-----------------------------------------------------------------------------*/
|
|
uint32_t bytesRead;
|
|
bool readError;
|
|
|
|
ChannelRead(connectionP->channelP,
|
|
connectionP->buffer.b + connectionP->buffersize,
|
|
bufferSpace(connectionP) - 1,
|
|
&bytesRead, &readError);
|
|
|
|
if (readError)
|
|
xmlrpc_asprintf(errorP, "Error reading from channel");
|
|
else {
|
|
*errorP = NULL;
|
|
if (bytesRead > 0) {
|
|
*eofP = FALSE;
|
|
traceChannelRead(connectionP, bytesRead);
|
|
connectionP->inbytes += bytesRead;
|
|
connectionP->buffersize += bytesRead;
|
|
connectionP->buffer.t[connectionP->buffersize] = '\0';
|
|
} else
|
|
*eofP = TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
dealWithReadTimeout(bool * const timedOutP,
|
|
bool const timedOut,
|
|
uint32_t const timeout,
|
|
const char ** const errorP) {
|
|
|
|
if (timedOutP)
|
|
*timedOutP = timedOut;
|
|
else {
|
|
if (timedOut)
|
|
xmlrpc_asprintf(errorP, "Read from Abyss client "
|
|
"connection timed out after %u seconds "
|
|
"or was interrupted",
|
|
timeout);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
dealWithReadEof(bool * const eofP,
|
|
bool const eof,
|
|
const char ** const errorP) {
|
|
|
|
if (eofP)
|
|
*eofP = eof;
|
|
else {
|
|
if (eof)
|
|
xmlrpc_asprintf(errorP, "Read from Abyss client "
|
|
"connection failed because client closed the "
|
|
"connection");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void
|
|
ConnRead(TConn * const connectionP,
|
|
uint32_t const timeout,
|
|
bool * const eofP,
|
|
bool * const timedOutP,
|
|
const char ** const errorP) {
|
|
/*----------------------------------------------------------------------------
|
|
Read some stuff on connection *connectionP from the channel. Read it into
|
|
the connection's buffer.
|
|
|
|
Don't wait more than 'timeout' seconds for data to arrive. If no data has
|
|
arrived by then and 'timedOutP' is null, fail. If 'timedOut' is non-null,
|
|
return as *timedOutP whether 'timeout' seconds passed without any data
|
|
arriving.
|
|
|
|
Also, stop waiting upon any interruption and treat it the same as a
|
|
timeout. An interruption is either a signal received (and caught) at
|
|
an appropriate time or a ChannelInterrupt() call before or during the
|
|
wait.
|
|
|
|
If 'eofP' is non-null, return *eofP == true, without reading anything, iff
|
|
there will no more data forthcoming on the connection because client has
|
|
closed the connection. If 'eofP' is null, fail in that case.
|
|
-----------------------------------------------------------------------------*/
|
|
uint32_t const timeoutMs = timeout * 1000;
|
|
|
|
if (timeoutMs < timeout)
|
|
/* Arithmetic overflow */
|
|
xmlrpc_asprintf(errorP, "Timeout value is too large");
|
|
else {
|
|
bool const waitForRead = TRUE;
|
|
bool const waitForWrite = FALSE;
|
|
|
|
bool readyForRead;
|
|
bool failed;
|
|
|
|
ChannelWait(connectionP->channelP, waitForRead, waitForWrite,
|
|
timeoutMs, &readyForRead, NULL, &failed);
|
|
|
|
if (failed)
|
|
xmlrpc_asprintf(errorP,
|
|
"Wait for stuff to arrive from client failed.");
|
|
else {
|
|
bool eof;
|
|
if (readyForRead) {
|
|
readFromChannel(connectionP, &eof, errorP);
|
|
} else {
|
|
/* Wait was interrupted, either by our requested timeout,
|
|
a (caught) signal, or a ChannelInterrupt().
|
|
*/
|
|
traceReadTimeout(connectionP, timeout);
|
|
*errorP = NULL;
|
|
eof = FALSE;
|
|
}
|
|
if (!*errorP)
|
|
dealWithReadTimeout(timedOutP, !readyForRead, timeout, errorP);
|
|
if (!*errorP)
|
|
dealWithReadEof(eofP, eof, errorP);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
ConnWrite(TConn * const connectionP,
|
|
const void * const buffer,
|
|
uint32_t const size) {
|
|
|
|
bool failed;
|
|
|
|
ChannelWrite(connectionP->channelP, buffer, size, &failed);
|
|
|
|
traceChannelWrite(connectionP, buffer, size, failed);
|
|
|
|
if (!failed)
|
|
connectionP->outbytes += size;
|
|
|
|
return !failed;
|
|
}
|
|
|
|
|
|
|
|
bool
|
|
ConnWriteFromFile(TConn * const connectionP,
|
|
const TFile * const fileP,
|
|
uint64_t const start,
|
|
uint64_t const last,
|
|
void * const buffer,
|
|
uint32_t const buffersize,
|
|
uint32_t const rate) {
|
|
/*----------------------------------------------------------------------------
|
|
Write the contents of the file stream *fileP, from offset 'start'
|
|
up through 'last', to the HTTP connection *connectionP.
|
|
|
|
Meter the reading so as not to read more than 'rate' bytes per second.
|
|
|
|
Use the 'bufferSize' bytes at 'buffer' as an internal buffer for this.
|
|
-----------------------------------------------------------------------------*/
|
|
bool retval;
|
|
uint32_t waittime;
|
|
bool success;
|
|
uint32_t readChunkSize;
|
|
uint32_t ChunkSize = 4096 * 2; /* read buffer size */
|
|
|
|
if (rate > 0) {
|
|
readChunkSize = MIN(buffersize, rate); /* One second's worth */
|
|
waittime = (1000 * buffersize) / rate;
|
|
} else {
|
|
readChunkSize = ChunkSize;
|
|
waittime = 0;
|
|
}
|
|
|
|
success = FileSeek(fileP, start, SEEK_SET);
|
|
if (!success)
|
|
retval = FALSE;
|
|
else {
|
|
uint64_t const totalBytesToRead = last - start + 1;
|
|
uint64_t bytesread = 0;
|
|
|
|
int32_t bytesReadThisTime = 0;
|
|
char * chunk = (char *) buffer; /* the beginning */
|
|
do {
|
|
|
|
if ((bytesReadThisTime = FileRead(fileP, chunk, readChunkSize)) <= 0 )
|
|
break;
|
|
|
|
bytesread += bytesReadThisTime;
|
|
chunk += bytesReadThisTime;
|
|
|
|
/* fix bug in ms ie as it doesn't render text/plain properly */
|
|
/* if CRLFs are split between reassembled tcp packets, */
|
|
/* ie "might" undeterministically render extra empty lines */
|
|
/* if it ends in CR or LF, read an extra chunk until the buffer is full */
|
|
/* or end of file is reached. You may still have bad luck, complaints go to MS) */
|
|
|
|
/* if (bytesReadThisTime == readChunkSize && chunk - (char *) buffer + readChunkSize < buffersize) {
|
|
* char * end = chunk - 1;
|
|
* if (*end == CR || *end == LF) {
|
|
* continue;
|
|
* }
|
|
* }
|
|
*/
|
|
if (!bytesReadThisTime || !ConnWrite(connectionP, buffer, chunk - (char *) buffer)) {
|
|
break;
|
|
}
|
|
|
|
chunk = (char *) buffer; /* a new beginning */
|
|
|
|
if (waittime > 0)
|
|
xmlrpc_millisecond_sleep(waittime);
|
|
|
|
} while (bytesReadThisTime == readChunkSize);
|
|
|
|
retval = (bytesread >= totalBytesToRead);
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
|
|
|
|
TServer *
|
|
ConnServer(TConn * const connectionP) {
|
|
return connectionP->server;
|
|
}
|
|
|
|
|
|
|
|
void
|
|
ConnFormatClientAddr(TConn * const connectionP,
|
|
const char ** const clientAddrPP) {
|
|
|
|
ChannelFormatPeerInfo(connectionP->channelP, clientAddrPP);
|
|
}
|
|
|
|
|
|
|
|
/*******************************************************************************
|
|
**
|
|
** conn.c
|
|
**
|
|
** This file is part of the ABYSS Web server project.
|
|
**
|
|
** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
** SUCH DAMAGE.
|
|
**
|
|
******************************************************************************/
|