mjpg-streamer/plugins/output_http/httpd.c

2077 lines
76 KiB
C

/*******************************************************************************
# #
# MJPG-streamer allows to stream JPG frames from an input-plugin #
# to several output plugins #
# #
# Copyright (C) 2007 busybox-project (base64 function) #
# Copyright (C) 2007 Tom Stöveken #
# #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; version 2 of the License. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program; if not, write to the Free Software #
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA #
# #
*******************************************************************************/
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netdb.h>
#include <errno.h>
#include <limits.h>
#include <linux/version.h>
#include <linux/types.h> /* for videodev2.h */
#include <linux/videodev2.h>
#include "../../mjpg_streamer.h"
#include "../../utils.h"
#include "httpd.h"
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32)
#define V4L2_CTRL_TYPE_STRING_SUPPORTED
#endif
#include "../output_file/output_file.h"
static globals *pglobal;
extern context servers[MAX_OUTPUT_PLUGINS];
int piggy_fine = 2; // FIXME make it command line parameter
/******************************************************************************
Description.: initializes the iobuffer structure properly
Input Value.: pointer to already allocated iobuffer
Return Value: iobuf
******************************************************************************/
void init_iobuffer(iobuffer *iobuf)
{
memset(iobuf->buffer, 0, sizeof(iobuf->buffer));
iobuf->level = 0;
}
/******************************************************************************
Description.: initializes the request structure properly
Input Value.: pointer to already allocated req
Return Value: req
******************************************************************************/
void init_request(request *req)
{
req->type = A_UNKNOWN;
req->parameter = NULL;
req->client = NULL;
req->credentials = NULL;
}
/******************************************************************************
Description.: If strings were assigned to the different members free them
This will fail if strings are static, so always use strdup().
Input Value.: req: pointer to request structure
Return Value: -
******************************************************************************/
void free_request(request *req)
{
if(req->parameter != NULL) free(req->parameter);
if(req->client != NULL) free(req->client);
if(req->credentials != NULL) free(req->credentials);
if(req->query_string != NULL) free(req->query_string);
}
/******************************************************************************
Description.: read with timeout, implemented without using signals
tries to read len bytes and returns if enough bytes were read
or the timeout was triggered. In case of timeout the return
value may differ from the requested bytes "len".
Input Value.: * fd.....: fildescriptor to read from
* iobuf..: iobuffer that allows to use this functions from multiple
threads because the complete context is the iobuffer.
* buffer.: The buffer to store values at, will be set to zero
before storing values.
* len....: the length of buffer
* timeout: seconds to wait for an answer
Return Value: * buffer.: will become filled with bytes read
* iobuf..: May get altered to save the context for future calls.
* func().: bytes copied to buffer or -1 in case of error
******************************************************************************/
int _read(int fd, iobuffer *iobuf, void *buffer, size_t len, int timeout)
{
int copied = 0, rc, i;
fd_set fds;
struct timeval tv;
memset(buffer, 0, len);
while((copied < len)) {
i = MIN(iobuf->level, len - copied);
memcpy(buffer + copied, iobuf->buffer + IO_BUFFER - iobuf->level, i);
iobuf->level -= i;
copied += i;
if(copied >= len)
return copied;
/* select will return in case of timeout or new data arrived */
tv.tv_sec = timeout;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(fd, &fds);
if((rc = select(fd + 1, &fds, NULL, NULL, &tv)) <= 0) {
if(rc < 0)
exit(EXIT_FAILURE);
/* this must be a timeout */
return copied;
}
init_iobuffer(iobuf);
/*
* there should be at least one byte, because select signalled it.
* But: It may happen (very seldomly), that the socket gets closed remotly between
* the select() and the following read. That is the reason for not relying
* on reading at least one byte.
*/
if((iobuf->level = read(fd, &iobuf->buffer, IO_BUFFER)) <= 0) {
/* an error occured */
return -1;
}
/* align data to the end of the buffer if less than IO_BUFFER bytes were read */
memmove(iobuf->buffer + (IO_BUFFER - iobuf->level), iobuf->buffer, iobuf->level);
}
return 0;
}
/******************************************************************************
Description.: Read a single line from the provided fildescriptor.
This funtion will return under two conditions:
* line end was reached
* timeout occured
Input Value.: * fd.....: fildescriptor to read from
* iobuf..: iobuffer that allows to use this functions from multiple
threads because the complete context is the iobuffer.
* buffer.: The buffer to store values at, will be set to zero
before storing values.
* len....: the length of buffer
* timeout: seconds to wait for an answer
Return Value: * buffer.: will become filled with bytes read
* iobuf..: May get altered to save the context for future calls.
* func().: bytes copied to buffer or -1 in case of error
******************************************************************************/
/* read just a single line or timeout */
int _readline(int fd, iobuffer *iobuf, void *buffer, size_t len, int timeout)
{
char c = '\0', *out = buffer;
int i;
memset(buffer, 0, len);
for(i = 0; i < len && c != '\n'; i++) {
if(_read(fd, iobuf, &c, 1, timeout) <= 0) {
/* timeout or error occured */
return -1;
}
*out++ = c;
}
return i;
}
/******************************************************************************
Description.: Decodes the data and stores the result to the same buffer.
The buffer will be large enough, because base64 requires more
space then plain text.
Hints.......: taken from busybox, but it is GPL code
Input Value.: base64 encoded data
Return Value: plain decoded data
******************************************************************************/
void decodeBase64(char *data)
{
const unsigned char *in = (const unsigned char *)data;
/* The decoded size will be at most 3/4 the size of the encoded */
unsigned ch = 0;
int i = 0;
while(*in) {
int t = *in++;
if(t >= '0' && t <= '9')
t = t - '0' + 52;
else if(t >= 'A' && t <= 'Z')
t = t - 'A';
else if(t >= 'a' && t <= 'z')
t = t - 'a' + 26;
else if(t == '+')
t = 62;
else if(t == '/')
t = 63;
else if(t == '=')
t = 0;
else
continue;
ch = (ch << 6) | t;
i++;
if(i == 4) {
*data++ = (char)(ch >> 16);
*data++ = (char)(ch >> 8);
*data++ = (char) ch;
i = 0;
}
}
*data = '\0';
}
/******************************************************************************
Description.: convert a hexadecimal ASCII character to integer
Input Value.: ASCII character
Return Value: corresponding value between 0 and 15, or -1 in case of error
******************************************************************************/
int hex_char_to_int(char in)
{
if(in >= '0' && in <= '9')
return in - '0';
if(in >= 'a' && in <= 'f')
return (in - 'a') + 10;
if(in >= 'A' && in <= 'F')
return (in - 'A') + 10;
return -1;
}
/******************************************************************************
Description.: replace %XX with the character code it represents, URI
Input Value.: string to unescape
Return Value: 0 if everything is ok, -1 in case of error
******************************************************************************/
int unescape(char *string)
{
char *source = string, *destination = string;
int src, dst, length = strlen(string), rc;
/* iterate over the string */
for(dst = 0, src = 0; src < length; src++) {
/* is it an escape character? */
if(source[src] != '%') {
/* no, so just go to the next character */
destination[dst] = source[src];
dst++;
continue;
}
/* yes, it is an escaped character */
/* check if there are enough characters */
if(src + 2 > length) {
return -1;
break;
}
/* perform replacement of %## with the corresponding character */
if((rc = hex_char_to_int(source[src+1])) == -1) return -1;
destination[dst] = rc * 16;
if((rc = hex_char_to_int(source[src+2])) == -1) return -1;
destination[dst] += rc;
/* advance pointers, here is the reason why the resulting string is shorter */
dst++; src += 2;
}
/* ensure the string is properly finished with a null-character */
destination[dst] = '\0';
return 0;
}
#ifdef MANAGMENT
/******************************************************************************
Description.: Adds a new client information struct to the ino list.
Input Value.: Client IP address as a string
Return Value: Returns with the newly added info or with a pointer to the existing item
******************************************************************************/
client_info *add_client(char *address)
{
unsigned int i = 0;
int name_length = strlen(address) + 1;
pthread_mutex_lock(&client_infos.mutex);
for (; i<client_infos.client_count; i++) {
if (strcmp(client_infos.infos[i]->address, address) == 0) {
pthread_mutex_unlock(&client_infos.mutex);
return client_infos.infos[i];
}
}
client_info *current_client_info = malloc(sizeof(client_info));
if (current_client_info == NULL) {
fprintf(stderr, "could not allocate memory\n");
pthread_mutex_unlock(&client_infos.mutex);
return NULL;
}
current_client_info->address = malloc(name_length * sizeof(char));
if (current_client_info->address == NULL) {
fprintf(stderr, "could not allocate memory\n");
pthread_mutex_unlock(&client_infos.mutex);
return NULL;
}
strcpy(current_client_info->address, address);
memset(&(current_client_info->last_take_time), 0, sizeof(struct timeval)); // set last time to zero
client_infos.infos = realloc(client_infos.infos, (client_infos.client_count + 1) * sizeof(client_info*));
client_infos.infos[client_infos.client_count] = current_client_info;
client_infos.client_count += 1;
pthread_mutex_unlock(&client_infos.mutex);
return current_client_info;
}
/******************************************************************************
Description.: Looks in the client_infos for the current ip address.
Input Value.: Client IP address as a string
Return Value: If a frame was served to it within the specified interval it returns 1
If not it returns with 0
******************************************************************************/
int check_client_status(client_info *client)
{
unsigned int i = 0;
pthread_mutex_lock(&client_infos.mutex);
for (; i<client_infos.client_count; i++) {
if (client_infos.infos[i] == client) {
long msec;
struct timeval tim;
gettimeofday(&tim, NULL);
msec =(tim.tv_sec - client_infos.infos[i]->last_take_time.tv_sec)*1000;
msec +=(tim.tv_usec - client_infos.infos[i]->last_take_time.tv_usec)/1000;
DBG("diff: %ld\n", msec);
if ((msec < 1000) && (msec > 0)) { // FIXME make it parameter
DBG("CHEATER\n");
pthread_mutex_unlock(&client_infos.mutex);
return 1;
} else {
pthread_mutex_unlock(&client_infos.mutex);
return 0;
}
}
}
DBG("Client not found in the client list! How did it happend?? This is a BUG\n");
pthread_mutex_unlock(&client_infos.mutex);
return 0;
}
void update_client_timestamp(client_info *client)
{
struct timeval tim;
pthread_mutex_lock(&client_infos.mutex);
gettimeofday(&tim, NULL);
memcpy(&client->last_take_time, &tim, sizeof(struct timeval));
pthread_mutex_unlock(&client_infos.mutex);
}
#endif
/******************************************************************************
Description.: Send a complete HTTP response and a single JPG-frame.
Input Value.: fildescriptor fd to send the answer to
Return Value: -
******************************************************************************/
void send_snapshot(cfd *context_fd, int input_number)
{
unsigned char *frame = NULL;
int frame_size = 0;
char buffer[BUFFER_SIZE] = {0};
struct timeval timestamp;
/* wait for a fresh frame */
pthread_mutex_lock(&pglobal->in[input_number].db);
pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);
/* read buffer */
frame_size = pglobal->in[input_number].size;
/* allocate a buffer for this single frame */
if((frame = malloc(frame_size + 1)) == NULL) {
free(frame);
pthread_mutex_unlock(&pglobal->in[input_number].db);
send_error(context_fd->fd, 500, "not enough memory");
return;
}
/* copy v4l2_buffer timeval to user space */
timestamp = pglobal->in[input_number].timestamp;
memcpy(frame, pglobal->in[input_number].buf, frame_size);
DBG("got frame (size: %d kB)\n", frame_size / 1024);
pthread_mutex_unlock(&pglobal->in[input_number].db);
#ifdef MANAGMENT
update_client_timestamp(context_fd->client);
#endif
/* write the response */
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Access-Control-Allow-Origin: *\r\n" \
STD_HEADER \
"Content-type: image/jpeg\r\n" \
"X-Timestamp: %d.%06d\r\n" \
"\r\n", (int) timestamp.tv_sec, (int) timestamp.tv_usec);
/* send header and image now */
if (write(context_fd->fd, buffer, strlen(buffer)) < 0 ||
write(context_fd->fd, frame, frame_size) < 0) {
free(frame);
return;
}
free(frame);
}
/******************************************************************************
Description.: Send a complete HTTP response and a stream of JPG-frames.
Input Value.: fildescriptor fd to send the answer to
Return Value: -
******************************************************************************/
void send_stream(cfd *context_fd, int input_number)
{
unsigned char *frame = NULL, *tmp = NULL;
int frame_size = 0, max_frame_size = 0;
char buffer[BUFFER_SIZE] = {0};
struct timeval timestamp;
DBG("preparing header\n");
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Access-Control-Allow-Origin: *\r\n" \
STD_HEADER \
"Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n" \
"\r\n" \
"--" BOUNDARY "\r\n");
if(write(context_fd->fd, buffer, strlen(buffer)) < 0) {
free(frame);
return;
}
DBG("Headers send, sending stream now\n");
while(!pglobal->stop) {
/* wait for fresh frames */
pthread_mutex_lock(&pglobal->in[input_number].db);
pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);
/* read buffer */
frame_size = pglobal->in[input_number].size;
/* check if framebuffer is large enough, increase it if necessary */
if(frame_size > max_frame_size) {
DBG("increasing buffer size to %d\n", frame_size);
max_frame_size = frame_size + TEN_K;
if((tmp = realloc(frame, max_frame_size)) == NULL) {
free(frame);
pthread_mutex_unlock(&pglobal->in[input_number].db);
send_error(context_fd->fd, 500, "not enough memory");
return;
}
frame = tmp;
}
/* copy v4l2_buffer timeval to user space */
timestamp = pglobal->in[input_number].timestamp;
memcpy(frame, pglobal->in[input_number].buf, frame_size);
DBG("got frame (size: %d kB)\n", frame_size / 1024);
pthread_mutex_unlock(&pglobal->in[input_number].db);
#ifdef MANAGMENT
update_client_timestamp(context_fd->client);
#endif
/*
* print the individual mimetype and the length
* sending the content-length fixes random stream disruption observed
* with firefox
*/
sprintf(buffer, "Content-Type: image/jpeg\r\n" \
"Content-Length: %d\r\n" \
"X-Timestamp: %d.%06d\r\n" \
"\r\n", frame_size, (int)timestamp.tv_sec, (int)timestamp.tv_usec);
DBG("sending intemdiate header\n");
if(write(context_fd->fd, buffer, strlen(buffer)) < 0) break;
DBG("sending frame\n");
if(write(context_fd->fd, frame, frame_size) < 0) break;
DBG("sending boundary\n");
sprintf(buffer, "\r\n--" BOUNDARY "\r\n");
if(write(context_fd->fd, buffer, strlen(buffer)) < 0) break;
}
free(frame);
}
#ifdef WXP_COMPAT
/******************************************************************************
Description.: Sends a mjpg stream in the same format as the WebcamXP does
Input Value.: fildescriptor fd to send the answer to
Return Value: -
******************************************************************************/
void send_stream_wxp(cfd *context_fd, int input_number)
{
unsigned char *frame = NULL, *tmp = NULL;
int frame_size = 0, max_frame_size = 0;
char buffer[BUFFER_SIZE] = {0};
struct timeval timestamp;
DBG("preparing header\n");
time_t curDate, expiresDate;
curDate = time(NULL);
expiresDate = curDate - 1380; // teh expires date is before the current date with 23 minute (1380) sec
char curDateBuffer[80];
char expDateBuffer[80];
strftime(curDateBuffer, 80, "%a, %d %b %Y %H:%M:%S %Z", localtime(&curDate));
strftime(expDateBuffer, 80, "%a, %d %b %Y %H:%M:%S %Z", localtime(&expiresDate));
sprintf(buffer, "HTTP/1.1 200 OK\r\n" \
"Connection: keep-alive\r\n" \
"Content-Type: multipart/x-mixed-replace; boundary=--myboundary\r\n" \
"Content-Length: 9999999\r\n" \
"Cache-control: no-cache, must revalidate\r\n" \
"Date: %s\r\n" \
"Expires: %s\r\n" \
"Pragma: no-cache\r\n" \
"Server: webcamXP\r\n"
"\r\n",
curDateBuffer,
expDateBuffer);
if(write(context_fd->fd, buffer, strlen(buffer)) < 0) {
free(frame);
return;
}
DBG("Headers send, sending stream now\n");
while(!pglobal->stop) {
/* wait for fresh frames */
pthread_mutex_lock(&pglobal->in[input_number].db);
pthread_cond_wait(&pglobal->in[input_number].db_update, &pglobal->in[input_number].db);
/* read buffer */
frame_size = pglobal->in[input_number].size;
/* check if framebuffer is large enough, increase it if necessary */
if(frame_size > max_frame_size) {
DBG("increasing buffer size to %d\n", frame_size);
max_frame_size = frame_size + TEN_K;
if((tmp = realloc(frame, max_frame_size)) == NULL) {
free(frame);
pthread_mutex_unlock(&pglobal->in[input_number].db);
send_error(context_fd->fd, 500, "not enough memory");
return;
}
frame = tmp;
}
/* copy v4l2_buffer timeval to user space */
timestamp = pglobal->in[input_number].timestamp;
#ifdef MANAGMENT
update_client_timestamp(context_fd->client);
#endif
memcpy(frame, pglobal->in[input_number].buf, frame_size);
DBG("got frame (size: %d kB)\n", frame_size / 1024);
pthread_mutex_unlock(&pglobal->in[input_number].db);
memset(buffer, 0, 50*sizeof(char));
sprintf(buffer, "mjpeg %07d12345", frame_size);
DBG("sending intemdiate header\n");
if(write(context_fd->fd, buffer, 50) < 0) break;
DBG("sending frame\n");
if(write(context_fd->fd, frame, frame_size) < 0) break;
}
free(frame);
}
#endif
/******************************************************************************
Description.: Send error messages and headers.
Input Value.: * fd.....: is the filedescriptor to send the message to
* which..: HTTP error code, most popular is 404
* message: append this string to the displayed response
Return Value: -
******************************************************************************/
void send_error(int fd, int which, char *message)
{
char buffer[BUFFER_SIZE] = {0};
if(which == 401) {
sprintf(buffer, "HTTP/1.0 401 Unauthorized\r\n" \
"Content-type: text/plain\r\n" \
STD_HEADER \
"WWW-Authenticate: Basic realm=\"MJPG-Streamer\"\r\n" \
"\r\n" \
"401: Not Authenticated!\r\n" \
"%s", message);
} else if(which == 404) {
sprintf(buffer, "HTTP/1.0 404 Not Found\r\n" \
"Content-type: text/plain\r\n" \
STD_HEADER \
"\r\n" \
"404: Not Found!\r\n" \
"%s", message);
} else if(which == 500) {
sprintf(buffer, "HTTP/1.0 500 Internal Server Error\r\n" \
"Content-type: text/plain\r\n" \
STD_HEADER \
"\r\n" \
"500: Internal Server Error!\r\n" \
"%s", message);
} else if(which == 400) {
sprintf(buffer, "HTTP/1.0 400 Bad Request\r\n" \
"Content-type: text/plain\r\n" \
STD_HEADER \
"\r\n" \
"400: Not Found!\r\n" \
"%s", message);
} else if (which == 403) {
sprintf(buffer, "HTTP/1.0 403 Forbidden\r\n" \
"Content-type: text/plain\r\n" \
STD_HEADER \
"\r\n" \
"403: Forbidden!\r\n" \
"%s", message);
} else {
sprintf(buffer, "HTTP/1.0 501 Not Implemented\r\n" \
"Content-type: text/plain\r\n" \
STD_HEADER \
"\r\n" \
"501: Not Implemented!\r\n" \
"%s", message);
}
if(write(fd, buffer, strlen(buffer)) < 0) {
DBG("write failed, done anyway\n");
}
}
/******************************************************************************
Description.: Send HTTP header and copy the content of a file. To keep things
simple, just a single folder gets searched for the file. Just
files with known extension and supported mimetype get served.
If no parameter was given, the file "index.html" will be copied.
Input Value.: * fd.......: filedescriptor to send data to
* id.......: specifies which server-context is the right one
* parameter: string that consists of the filename
Return Value: -
******************************************************************************/
void send_file(int id, int fd, char *parameter)
{
char buffer[BUFFER_SIZE] = {0};
char *extension, *mimetype = NULL;
int i, lfd;
config conf = servers[id].conf;
/* in case no parameter was given */
if(parameter == NULL || strlen(parameter) == 0)
parameter = "index.html";
/* find file-extension */
char * pch;
pch = strchr(parameter, '.');
int lastDot = 0;
while(pch != NULL) {
lastDot = pch - parameter;
pch = strchr(pch + 1, '.');
}
if(lastDot == 0) {
send_error(fd, 400, "No file extension found");
return;
} else {
extension = parameter + lastDot;
DBG("%s EXTENSION: %s\n", parameter, extension);
}
/* determine mime-type */
for(i = 0; i < LENGTH_OF(mimetypes); i++) {
if(strcmp(mimetypes[i].dot_extension, extension) == 0) {
mimetype = (char *)mimetypes[i].mimetype;
break;
}
}
/* in case of unknown mimetype or extension leave */
if(mimetype == NULL) {
send_error(fd, 404, "MIME-TYPE not known");
return;
}
/* now filename, mimetype and extension are known */
DBG("trying to serve file \"%s\", extension: \"%s\" mime: \"%s\"\n", parameter, extension, mimetype);
/* build the absolute path to the file */
strncat(buffer, conf.www_folder, sizeof(buffer) - 1);
strncat(buffer, parameter, sizeof(buffer) - strlen(buffer) - 1);
/* try to open that file */
if((lfd = open(buffer, O_RDONLY)) < 0) {
DBG("file %s not accessible\n", buffer);
send_error(fd, 404, "Could not open file");
return;
}
DBG("opened file: %s\n", buffer);
/* prepare HTTP header */
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Content-type: %s\r\n" \
STD_HEADER \
"\r\n", mimetype);
i = strlen(buffer);
/* first transmit HTTP-header, afterwards transmit content of file */
do {
if(write(fd, buffer, i) < 0) {
close(lfd);
return;
}
} while((i = read(lfd, buffer, sizeof(buffer))) > 0);
/* close file, job done */
close(lfd);
}
/******************************************************************************
Description.: Executes the specified CGI file if exists
Input Value.: * fd...........: filedescriptor to send data to
* id...........: specifies which server-context is the right one
* parameter....: the requested file name
* query_string.: query parameters
Return Value: -
******************************************************************************/
void execute_cgi(int id, int fd, char *parameter, char *query_string)
{
int lfd = 0, i;
int buffer_length = 0;
char *buffer = NULL;
char fn_buffer[BUFFER_SIZE] = {0};
FILE *f = NULL;
config conf = servers[id].conf;
/* build the absolute path to the file */
strncat(fn_buffer, conf.www_folder, sizeof(fn_buffer) - 1);
strncat(fn_buffer, parameter, sizeof(fn_buffer) - strlen(fn_buffer) - 1);
if((lfd = open(fn_buffer, O_RDONLY)) < 0) {
DBG("file %s not accessible\n", fn_buffer);
send_error(fd, 404, "Could not open file");
return;
}
char *enviroment =
"SERVER_SOFTWARE=\"mjpg-streamer\" "
//"SERVER_NAME=\"%s\" "
"SERVER_PROTOCOL=\"HTTP/1.1\" "
"SERVER_PORT=\"%d\" " // OK
"GATEWAY_INTERFACE=\"CGI/1.1\" "
"REQUEST_METHOD=\"GET\" "
"SCRIPT_NAME=\"%s\" " // OK
"QUERY_STRING=\"%s\" " //OK
//"REMOTE_ADDR=\"%s\" "
//"REMOTE_PORT=\"%d\" "
"%s"; // OK
buffer_length = 3;
buffer_length = strlen(fn_buffer) + strlen(enviroment) + strlen(parameter) + 256;
buffer = malloc(buffer_length);
if (buffer == NULL) {
exit(EXIT_FAILURE);
}
sprintf(buffer,
enviroment,
conf.port,
parameter,
query_string,
fn_buffer);
f = popen(buffer, "r");
if(f == NULL) {
DBG("Unable to execute the requested CGI script\n");
send_error(fd, 403, "CGI script cannot be executed");
free(buffer);
close(lfd);
return;
}
while((i = fread(buffer, 1, sizeof(buffer), f)) > 0) {
if (write(fd, buffer, i) < 0) {
fclose(f);
free(buffer);
close(lfd);
return;
}
}
fclose(f);
free(buffer);
close(lfd);
}
/******************************************************************************
Description.: Perform a command specified by parameter. Send response to fd.
Input Value.: * fd.......: filedescriptor to send HTTP response to.
* parameter: contains the command and value as string.
* id.......: specifies which server-context to choose.
Return Value: -
******************************************************************************/
void command(int id, int fd, char *parameter)
{
char buffer[BUFFER_SIZE] = {0};
char *command = NULL, *svalue = NULL, *value, *command_id_string;
int res = 0, ivalue = 0, command_id = -1, len = 0;
DBG("parameter is: %s\n", parameter);
/* sanity check of parameter-string */
if(parameter == NULL || strlen(parameter) >= 255 || strlen(parameter) == 0) {
DBG("parameter string looks bad\n");
send_error(fd, 400, "Parameter-string of command does not look valid.");
return;
}
/* command format:
?control&dest=0plugin=0&id=0&group=0&value=0
where:
dest: specifies the command destination (input, output, program itself) 0-1-2
plugin specifies the plugin id (not acceptable at the commands sent to the program itself)
id: the control id
group: the control's group eg. V4L2 control, jpg control, etc. This is optional
value: value the control
*/
/* search for required variable "command" */
if((command = strstr(parameter, "id=")) == NULL) {
DBG("no command id specified\n");
send_error(fd, 400, "no GET variable \"id=...\" found, it is required to specify which command id to execute");
return;
}
/* allocate and copy command string */
command += strlen("id=");
len = strspn(command, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_1234567890");
if((command = strndup(command, len)) == NULL) {
send_error(fd, 500, "could not allocate memory");
LOG("could not allocate memory\n");
return;
}
/* convert the command to id */
command_id_string = command;
len = strspn(command_id_string, "-1234567890");
if((svalue = strndup(command_id_string, len)) == NULL) {
if(command != NULL) free(command);
send_error(fd, 500, "could not allocate memory");
LOG("could not allocate memory\n");
return;
}
command_id = MAX(MIN(strtol(svalue, NULL, 10), INT_MAX), INT_MIN);
DBG("command id string: %s converted to int = %d\n", command, command_id);
/* find and convert optional parameter "value" */
if((value = strstr(parameter, "value=")) != NULL) {
value += strlen("value=");
len = strspn(value, "-1234567890");
if((svalue = strndup(value, len)) == NULL) {
if(command != NULL) free(command);
send_error(fd, 500, "could not allocate memory");
LOG("could not allocate memory\n");
return;
}
ivalue = MAX(MIN(strtol(svalue, NULL, 10), INT_MAX), INT_MIN);
DBG("The command value converted value form string %s to integer %d\n", svalue, ivalue);
}
int group = IN_CMD_GENERIC;
if((value = strstr(parameter, "group=")) != NULL) {
value += strlen("group=");
len = strspn(value, "-1234567890");
if((svalue = strndup(value, len)) == NULL) {
if(command != NULL) free(command);
send_error(fd, 500, "could not allocate memory");
LOG("could not allocate memory\n");
return;
}
group = MAX(MIN(strtol(svalue, NULL, 10), INT_MAX), INT_MIN);
DBG("The command type value converted value form string %s to integer %d\n", svalue, group);
}
int dest = Dest_Input;
if((value = strstr(parameter, "dest=")) != NULL) {
value += strlen("dest=");
len = strspn(value, "-1234567890");
if((svalue = strndup(value, len)) == NULL) {
if(command != NULL) free(command);
send_error(fd, 500, "could not allocate memory");
LOG("could not allocate memory\n");
return;
}
dest = MAX(MIN(strtol(svalue, NULL, 10), INT_MAX), INT_MIN);
#ifdef DEBUG
switch (dest) {
case Dest_Input:
DBG("The command destination value converted form the string \"%s\" to integer %d -> INPUT\n", svalue, dest );
break;
case Dest_Output:
DBG("The command destination value converted form the string \"%s\" to integer %d -> OUTPUT\n", svalue, dest );
break;
case Dest_Program:
DBG("The command destination value converted form the string \"%s\" to integer %d -> PROGRAM\n", svalue, dest );
break;
}
#endif
}
int plugin_no = 0; // default plugin no = 0 for compatibility reasons
if((value = strstr(parameter, "plugin=")) != NULL) {
value += strlen("plugin=");
len = strspn(value, "-1234567890");
if((svalue = strndup(value, len)) == NULL) {
if(command != NULL) free(command);
send_error(fd, 500, "could not allocate memory");
LOG("could not allocate memory\n");
return;
}
plugin_no = MAX(MIN(strtol(svalue, NULL, 10), INT_MAX), INT_MIN);
DBG("The plugin number value converted value form string %s to integer %d\n", svalue, plugin_no);
} else {
value = NULL;
}
switch(dest) {
case Dest_Input:
if(plugin_no < pglobal->incnt) {
res = pglobal->in[plugin_no].cmd(plugin_no, command_id, group, ivalue, value);
} else {
DBG("Invalid plugin number: %d because only %d input plugins loaded", plugin_no, pglobal->incnt-1);
}
break;
case Dest_Output:
if(plugin_no < pglobal->outcnt) {
res = pglobal->out[plugin_no].cmd(plugin_no, command_id, group, ivalue, value);
} else {
DBG("Invalid plugin number: %d because only %d output plugins loaded", plugin_no, pglobal->incnt-1);
}
break;
case Dest_Program:
break;
default:
fprintf(stderr, "Illegal command destination: %d\n", dest);
}
/* Send HTTP-response */
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Content-type: text/plain\r\n" \
STD_HEADER \
"\r\n" \
"%s: %d", command, res);
if(write(fd, buffer, strlen(buffer)) < 0) {
DBG("write failed, done anyway\n");
}
if(command != NULL) free(command);
if(svalue != NULL) free(svalue);
}
/******************************************************************************
Description.: Serve a connected TCP-client. This thread function is called
for each connect of a HTTP client like a webbrowser. It determines
if it is a valid HTTP request and dispatches between the different
response options.
Input Value.: arg is the filedescriptor and server-context of the connected TCP
socket. It must have been allocated so it is freeable by this
thread function.
Return Value: always NULL
******************************************************************************/
/* thread for clients that connected to this server */
void *client_thread(void *arg)
{
int cnt;
char query_suffixed = 0;
int input_number = 0;
char buffer[BUFFER_SIZE] = {0}, *pb = buffer;
iobuffer iobuf;
request req;
cfd lcfd; /* local-connected-file-descriptor */
/* we really need the fildescriptor and it must be freeable by us */
if(arg != NULL) {
memcpy(&lcfd, arg, sizeof(cfd));
free(arg);
} else
return NULL;
/* initializes the structures */
init_iobuffer(&iobuf);
init_request(&req);
/* What does the client want to receive? Read the request. */
memset(buffer, 0, sizeof(buffer));
if((cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer) - 1, 5)) == -1) {
close(lcfd.fd);
return NULL;
}
req.query_string = NULL;
/* determine what to deliver */
if(strstr(buffer, "GET /?action=snapshot") != NULL) {
req.type = A_SNAPSHOT;
query_suffixed = 255;
#ifdef MANAGMENT
if (check_client_status(lcfd.client)) {
req.type = A_UNKNOWN;
lcfd.client->last_take_time.tv_sec += piggy_fine;
send_error(lcfd.fd, 403, "frame already sent");
query_suffixed = 0;
}
#endif
#ifdef WXP_COMPAT
} else if((strstr(buffer, "GET /cam") != NULL) && (strstr(buffer, ".jpg") != NULL)) {
req.type = A_SNAPSHOT_WXP;
query_suffixed = 255;
#ifdef MANAGMENT
if (check_client_status(lcfd.client)) {
req.type = A_UNKNOWN;
lcfd.client->last_take_time.tv_sec += piggy_fine;
send_error(lcfd.fd, 403, "frame already sent");
query_suffixed = 0;
}
#endif
#endif
} else if(strstr(buffer, "POST /stream") != NULL) {
req.type = A_STREAM;
query_suffixed = 255;
#ifdef MANAGMENT
if (check_client_status(lcfd.client)) {
req.type = A_UNKNOWN;
lcfd.client->last_take_time.tv_sec += piggy_fine;
send_error(lcfd.fd, 403, "frame already sent");
query_suffixed = 0;
}
#endif
} else if(strstr(buffer, "GET /?action=stream") != NULL) {
req.type = A_STREAM;
query_suffixed = 255;
#ifdef MANAGMENT
if (check_client_status(lcfd.client)) {
req.type = A_UNKNOWN;
lcfd.client->last_take_time.tv_sec += piggy_fine;
send_error(lcfd.fd, 403, "frame already sent");
query_suffixed = 0;
}
#endif
#ifdef WXP_COMPAT
} else if((strstr(buffer, "GET /cam") != NULL) && (strstr(buffer, ".mjpg") != NULL)) {
req.type = A_STREAM_WXP;
query_suffixed = 255;
#ifdef MANAGMENT
if (check_client_status(lcfd.client)) {
req.type = A_UNKNOWN;
lcfd.client->last_take_time.tv_sec += piggy_fine;
send_error(lcfd.fd, 403, "frame already sent");
query_suffixed = 0;
}
#endif
#endif
} else if(strstr(buffer, "GET /?action=take") != NULL) {
int len;
req.type = A_TAKE;
query_suffixed = 255;
/* advance by the length of known string */
if((pb = strstr(buffer, "GET /?action=take")) == NULL) {
DBG("HTTP request seems to be malformed\n");
send_error(lcfd.fd, 400, "Malformed HTTP request");
close(lcfd.fd);
query_suffixed = 0;
return NULL;
}
pb += strlen("GET /?action=take"); // a pb points to thestring after the first & after command
/* only accept certain characters */
len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-=&1234567890%./"), 0), 100);
req.parameter = malloc(len + 1);
if(req.parameter == NULL) {
exit(EXIT_FAILURE);
}
memset(req.parameter, 0, len + 1);
strncpy(req.parameter, pb, len);
if(unescape(req.parameter) == -1) {
free(req.parameter);
send_error(lcfd.fd, 500, "could not properly unescape command parameter string");
LOG("could not properly unescape command parameter string\n");
close(lcfd.fd);
return NULL;
}
} else if((strstr(buffer, "GET /input") != NULL) && (strstr(buffer, ".json") != NULL)) {
req.type = A_INPUT_JSON;
query_suffixed = 255;
} else if((strstr(buffer, "GET /output") != NULL) && (strstr(buffer, ".json") != NULL)) {
req.type = A_OUTPUT_JSON;
query_suffixed = 255;
} else if(strstr(buffer, "GET /program.json") != NULL) {
req.type = A_PROGRAM_JSON;
#ifdef MANAGMENT
} else if(strstr(buffer, "GET /clients.json") != NULL) {
req.type = A_CLIENTS_JSON;
#endif
} else if(strstr(buffer, "GET /?action=command") != NULL) {
int len;
req.type = A_COMMAND;
/* advance by the length of known string */
if((pb = strstr(buffer, "GET /?action=command")) == NULL) {
DBG("HTTP request seems to be malformed\n");
send_error(lcfd.fd, 400, "Malformed HTTP request");
close(lcfd.fd);
return NULL;
}
pb += strlen("GET /?action=command"); // a pb points to thestring after the first & after command
/* only accept certain characters */
len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-=&1234567890%./"), 0), 100);
req.parameter = malloc(len + 1);
if(req.parameter == NULL) {
exit(EXIT_FAILURE);
}
memset(req.parameter, 0, len + 1);
strncpy(req.parameter, pb, len);
if(unescape(req.parameter) == -1) {
free(req.parameter);
send_error(lcfd.fd, 500, "could not properly unescape command parameter string");
LOG("could not properly unescape command parameter string\n");
close(lcfd.fd);
return NULL;
}
DBG("command parameter (len: %d): \"%s\"\n", len, req.parameter);
} else {
int len;
DBG("try to serve a file\n");
req.type = A_FILE;
if((pb = strstr(buffer, "GET /")) == NULL) {
DBG("HTTP request seems to be malformed\n");
send_error(lcfd.fd, 400, "Malformed HTTP request");
close(lcfd.fd);
return NULL;
}
pb += strlen("GET /");
len = MIN(MAX(strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-1234567890"), 0), 100);
req.parameter = malloc(len + 1);
if(req.parameter == NULL) {
exit(EXIT_FAILURE);
}
memset(req.parameter, 0, len + 1);
strncpy(req.parameter, pb, len);
if (strstr(pb, ".cgi") != NULL) {
req.type = A_CGI;
pb = strchr(pb, '?');
if (pb != NULL) {
pb++; // skip the ?
len = strspn(pb, "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-1234567890=&");
req.query_string = malloc(len + 1);
if (req.query_string == NULL)
exit(EXIT_FAILURE);
memset(req.query_string, 0, len + 1);
strncpy(req.query_string, pb, len);
} else {
req.query_string = malloc(2);
if (req.query_string == NULL)
exit(EXIT_FAILURE);
sprintf(req.query_string, " ");
}
}
DBG("parameter (len: %d): \"%s\"\n", len, req.parameter);
}
/*
* Since when we are working with multiple input plugins
* there are some url which could have a _[plugin number suffix]
* For compatibility reasons it could be left in that case the output will be
* generated from the 0. input plugin
*/
if(query_suffixed) {
char *sch = strchr(buffer, '_');
if(sch != NULL) { // there is an _ in the url so the input number should be present
DBG("Suffix character: %s\n", sch + 1); // FIXME if more than 10 input plugin is added
char numStr[3];
memset(numStr, 0, 3);
strncpy(numStr, sch + 1, 1);
input_number = atoi(numStr);
if ((req.type == A_SNAPSHOT_WXP) || (req.type == A_STREAM_WXP)) { // webcamxp adds offset to the camera number
input_number--;
}
}
DBG("plugin_no: %d\n", input_number);
}
/*
* parse the rest of the HTTP-request
* the end of the request-header is marked by a single, empty line with "\r\n"
*/
do {
memset(buffer, 0, sizeof(buffer));
if((cnt = _readline(lcfd.fd, &iobuf, buffer, sizeof(buffer) - 1, 5)) == -1) {
free_request(&req);
close(lcfd.fd);
return NULL;
}
if(strcasestr(buffer, "User-Agent: ") != NULL) {
req.client = strdup(buffer + strlen("User-Agent: "));
} else if(strcasestr(buffer, "Authorization: Basic ") != NULL) {
req.credentials = strdup(buffer + strlen("Authorization: Basic "));
decodeBase64(req.credentials);
DBG("username:password: %s\n", req.credentials);
}
} while(cnt > 2 && !(buffer[0] == '\r' && buffer[1] == '\n'));
/* check for username and password if parameter -c was given */
if(lcfd.pc->conf.credentials != NULL) {
if(req.credentials == NULL || strcmp(lcfd.pc->conf.credentials, req.credentials) != 0) {
DBG("access denied\n");
send_error(lcfd.fd, 401, "username and password do not match to configuration");
close(lcfd.fd);
free_request(&req);
return NULL;
}
DBG("access granted\n");
}
/* now it's time to answer */
if (query_suffixed) {
if (req.type == A_OUTPUT_JSON) {
if(!(input_number < pglobal->outcnt)) {
DBG("Output number: %d out of range (valid: 0..%d)\n", input_number, pglobal->outcnt-1);
send_error(lcfd.fd, 404, "Invalid output plugin number");
req.type = A_UNKNOWN;
}
} else {
if(!(input_number < pglobal->incnt)) {
DBG("Input number: %d out of range (valid: 0..%d)\n", input_number, pglobal->incnt-1);
send_error(lcfd.fd, 404, "Invalid input plugin number");
req.type = A_UNKNOWN;
}
}
}
switch(req.type) {
case A_SNAPSHOT_WXP:
case A_SNAPSHOT:
DBG("Request for snapshot from input: %d\n", input_number);
send_snapshot(&lcfd, input_number);
break;
case A_STREAM:
DBG("Request for stream from input: %d\n", input_number);
send_stream(&lcfd, input_number);
break;
#ifdef WXP_COMPAT
case A_STREAM_WXP:
DBG("Request for WXP compat stream from input: %d\n", input_number);
send_stream_wxp(&lcfd, input_number);
break;
#endif
case A_COMMAND:
if(lcfd.pc->conf.nocommands) {
send_error(lcfd.fd, 501, "this server is configured to not accept commands");
break;
}
command(lcfd.pc->id, lcfd.fd, req.parameter);
break;
case A_INPUT_JSON:
DBG("Request for the Input plugin descriptor JSON file\n");
send_input_JSON(lcfd.fd, input_number);
break;
case A_OUTPUT_JSON:
DBG("Request for the Output plugin descriptor JSON file\n");
send_output_JSON(lcfd.fd, input_number);
break;
case A_PROGRAM_JSON:
DBG("Request for the program descriptor JSON file\n");
send_program_JSON(lcfd.fd);
break;
#ifdef MANAGMENT
case A_CLIENTS_JSON:
DBG("Request for the clients JSON file\n");
send_clients_JSON(lcfd.fd);
break;
#endif
case A_FILE:
if(lcfd.pc->conf.www_folder == NULL)
send_error(lcfd.fd, 501, "no www-folder configured");
else
send_file(lcfd.pc->id, lcfd.fd, req.parameter);
break;
/*
With the take argument we try to save the current image to file before we transmit it to the user.
This is done trough the output_file plugin.
If it not loaded, or the file could not be saved then we won't transmit the frame.
*/
case A_TAKE: {
int i, ret = 0, found = 0;
for (i = 0; i<pglobal->outcnt; i++) {
if (pglobal->out[i].name != NULL) {
if (strstr(pglobal->out[i].name, "FILE output plugin")) {
found = 255;
DBG("output_file found id: %d\n", i);
char *filename = NULL;
char *filenamearg = NULL;
int len = 0;
DBG("Buffer: %s \n", req.parameter);
if((filename = strstr(req.parameter, "filename=")) != NULL) {
filename += strlen("filename=");
char *fn = strchr(filename, '&');
if (fn == NULL)
len = strlen(filename);
else
len = (int)(fn - filename);
filenamearg = (char*)calloc(len, sizeof(char));
memcpy(filenamearg, filename, len);
DBG("Filename = %s\n", filenamearg);
//int output_cmd(int plugin_id, unsigned int control_id, unsigned int group, int value, char *valueStr)
ret = pglobal->out[i].cmd(i, OUT_FILE_CMD_TAKE, IN_CMD_GENERIC, 0, filenamearg);
} else {
DBG("filename is not specified int the URL\n");
send_error(lcfd.fd, 404, "The &filename= must present for the take command in the URL");
}
break;
}
}
}
if (found == 0) {
LOG("FILE CHANGE TEST output plugin not loaded\n");
send_error(lcfd.fd, 404, "FILE output plugin not loaded, taking snapshot not possible");
} else {
if (ret == 0) {
send_snapshot(&lcfd, input_number);
} else {
send_error(lcfd.fd, 404, "Taking snapshot failed!");
}
}
} break;
case A_CGI:
DBG("cgi script: %s requested\n", req.parameter);
execute_cgi(lcfd.pc->id, lcfd.fd, req.parameter, req.query_string);
break;
default:
DBG("unknown request\n");
}
close(lcfd.fd);
free_request(&req);
DBG("leaving HTTP client thread\n");
return NULL;
}
/******************************************************************************
Description.: This function cleans up resources allocated by the server_thread
Input Value.: arg is not used
Return Value: -
******************************************************************************/
void server_cleanup(void *arg)
{
context *pcontext = arg;
int i;
OPRINT("cleaning up resources allocated by server thread #%02d\n", pcontext->id);
for(i = 0; i < MAX_SD_LEN; i++)
close(pcontext->sd[i]);
}
/******************************************************************************
Description.: Open a TCP socket and wait for clients to connect. If clients
connect, start a new thread for each accepted connection.
Input Value.: arg is a pointer to the globals struct
Return Value: always NULL, will only return on exit
******************************************************************************/
void *server_thread(void *arg)
{
int on;
pthread_t client;
struct addrinfo *aip, *aip2;
struct addrinfo hints;
struct sockaddr_storage client_addr;
socklen_t addr_len = sizeof(struct sockaddr_storage);
fd_set selectfds;
int max_fds = 0;
char name[NI_MAXHOST];
int err;
int i;
context *pcontext = arg;
pglobal = pcontext->pglobal;
/* set cleanup handler to cleanup resources */
pthread_cleanup_push(server_cleanup, pcontext);
bzero(&hints, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_flags = AI_PASSIVE;
hints.ai_socktype = SOCK_STREAM;
snprintf(name, sizeof(name), "%d", ntohs(pcontext->conf.port));
if((err = getaddrinfo(pcontext->conf.hostname, name, &hints, &aip)) != 0) {
perror(gai_strerror(err));
exit(EXIT_FAILURE);
}
for(i = 0; i < MAX_SD_LEN; i++)
pcontext->sd[i] = -1;
#ifdef MANAGMENT
if (pthread_mutex_init(&client_infos.mutex, NULL)) {
perror("Mutex initialization failed");
exit(EXIT_FAILURE);
}
client_infos.client_count = 0;
client_infos.infos = NULL;
#endif
/* open sockets for server (1 socket / address family) */
i = 0;
for(aip2 = aip; aip2 != NULL; aip2 = aip2->ai_next) {
if((pcontext->sd[i] = socket(aip2->ai_family, aip2->ai_socktype, 0)) < 0) {
continue;
}
/* ignore "socket already in use" errors */
on = 1;
if(setsockopt(pcontext->sd[i], SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
perror("setsockopt(SO_REUSEADDR) failed\n");
}
/* IPv6 socket should listen to IPv6 only, otherwise we will get "socket already in use" */
on = 1;
if(aip2->ai_family == AF_INET6 && setsockopt(pcontext->sd[i], IPPROTO_IPV6, IPV6_V6ONLY,
(const void *)&on , sizeof(on)) < 0) {
perror("setsockopt(IPV6_V6ONLY) failed\n");
}
/* perhaps we will use this keep-alive feature oneday */
/* setsockopt(sd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); */
if(bind(pcontext->sd[i], aip2->ai_addr, aip2->ai_addrlen) < 0) {
perror("bind");
pcontext->sd[i] = -1;
continue;
}
if(listen(pcontext->sd[i], 10) < 0) {
perror("listen");
pcontext->sd[i] = -1;
} else {
i++;
if(i >= MAX_SD_LEN) {
OPRINT("%s(): maximum number of server sockets exceeded", __FUNCTION__);
i--;
break;
}
}
}
pcontext->sd_len = i;
if(pcontext->sd_len < 1) {
OPRINT("%s(): bind(%d) failed\n", __FUNCTION__, htons(pcontext->conf.port));
closelog();
exit(EXIT_FAILURE);
}
/* create a child for every client that connects */
while(!pglobal->stop) {
//int *pfd = (int *)malloc(sizeof(int));
cfd *pcfd = malloc(sizeof(cfd));
if(pcfd == NULL) {
fprintf(stderr, "failed to allocate (a very small amount of) memory\n");
exit(EXIT_FAILURE);
}
DBG("waiting for clients to connect\n");
do {
FD_ZERO(&selectfds);
for(i = 0; i < MAX_SD_LEN; i++) {
if(pcontext->sd[i] != -1) {
FD_SET(pcontext->sd[i], &selectfds);
if(pcontext->sd[i] > max_fds)
max_fds = pcontext->sd[i];
}
}
err = select(max_fds + 1, &selectfds, NULL, NULL, NULL);
if(err < 0 && errno != EINTR) {
perror("select");
exit(EXIT_FAILURE);
}
} while(err <= 0);
for(i = 0; i < max_fds + 1; i++) {
if(pcontext->sd[i] != -1 && FD_ISSET(pcontext->sd[i], &selectfds)) {
pcfd->fd = accept(pcontext->sd[i], (struct sockaddr *)&client_addr, &addr_len);
pcfd->pc = pcontext;
/* start new thread that will handle this TCP connected client */
DBG("create thread to handle client that just established a connection\n");
if(getnameinfo((struct sockaddr *)&client_addr, addr_len, name, sizeof(name), NULL, 0, NI_NUMERICHOST) == 0) {
DBG("serving client: %s\n", name);
}
#if defined(MANAGMENT)
pcfd->client = add_client(name);
#endif
if(pthread_create(&client, NULL, &client_thread, pcfd) != 0) {
DBG("could not launch another client thread\n");
close(pcfd->fd);
free(pcfd);
continue;
}
pthread_detach(client);
}
}
}
DBG("leaving server thread, calling cleanup function now\n");
pthread_cleanup_pop(1);
return NULL;
}
/******************************************************************************
Description.: Send a JSON file which is contains information about the input plugin's
acceptable parameters
Input Value.: fildescriptor fd to send the answer to
Return Value: -
******************************************************************************/
void send_input_JSON(int fd, int input_number)
{
char buffer[BUFFER_SIZE*16] = {0}; // FIXME do reallocation if the buffer size is small
int i;
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Content-type: %s\r\n" \
STD_HEADER \
"\r\n", "application/x-javascript");
DBG("Serving the input plugin %d descriptor JSON file\n", input_number);
sprintf(buffer + strlen(buffer),
"{\n"
"\"controls\": [\n");
if(pglobal->in[input_number].in_parameters != NULL) {
for(i = 0; i < pglobal->in[input_number].parametercount; i++) {
char *menuString = NULL;
if(pglobal->in[input_number].in_parameters[i].ctrl.type == V4L2_CTRL_TYPE_MENU) {
if(pglobal->in[input_number].in_parameters[i].menuitems != NULL) {
int j, k = 1;
for(j = pglobal->in[input_number].in_parameters[i].ctrl.minimum; j <= pglobal->in[input_number].in_parameters[i].ctrl.maximum; j++) {
char *tempName = NULL; // temporary storage for name sanity checking
int prevSize = 0;
int itemLength = strlen((char*)&pglobal->in[input_number].in_parameters[i].menuitems[j].name);
tempName = (char*)calloc(itemLength + 1, sizeof(char)); // allocate space for the sanity checking
if (tempName == NULL) {
DBG("Realloc/calloc failed: %s\n", strerror(errno));
return;
}
check_JSON_string((char*)&pglobal->in[input_number].in_parameters[i].menuitems[j].name, tempName); // sanity check the string after non printable characters
itemLength += strlen("\"\": \"\"");
if (menuString == NULL) {
menuString = calloc(itemLength + 5, sizeof(char));
} else {
menuString = realloc(menuString, (strlen(menuString) + itemLength + 5) * (sizeof(char)));
}
if (menuString == NULL) {
DBG("Realloc/calloc failed: %s\n", strerror(errno));
return;
}
prevSize = strlen(menuString);
if(j != pglobal->in[input_number].in_parameters[i].ctrl.maximum) {
sprintf(menuString + prevSize, "\"%d\": \"%s\", ", j , tempName);
} else {
sprintf(menuString + prevSize, "\"%d\": \"%s\"", j , tempName);
}
k++;
free(tempName);
}
}
}
sprintf(buffer + strlen(buffer),
"{\n"
"\"name\": \"%s\",\n"
"\"id\": \"%d\",\n"
"\"type\": \"%d\",\n"
"\"min\": \"%d\",\n"
"\"max\": \"%d\",\n"
"\"step\": \"%d\",\n"
"\"default\": \"%d\",\n"
"\"value\": \"%d\",\n"
"\"dest\": \"0\",\n"
"\"flags\": \"%d\",\n"
"\"group\": \"%d\"",
pglobal->in[input_number].in_parameters[i].ctrl.name,
pglobal->in[input_number].in_parameters[i].ctrl.id,
pglobal->in[input_number].in_parameters[i].ctrl.type,
pglobal->in[input_number].in_parameters[i].ctrl.minimum,
pglobal->in[input_number].in_parameters[i].ctrl.maximum,
pglobal->in[input_number].in_parameters[i].ctrl.step,
pglobal->in[input_number].in_parameters[i].ctrl.default_value,
pglobal->in[input_number].in_parameters[i].value,
// 0 is the code of the input plugin
pglobal->in[input_number].in_parameters[i].ctrl.flags,
pglobal->in[input_number].in_parameters[i].group
);
// append the menu object to the menu typecontrols
if(pglobal->in[input_number].in_parameters[i].ctrl.type == V4L2_CTRL_TYPE_MENU) {
sprintf(buffer + strlen(buffer),
",\n"
"\"menu\": {%s}\n"
"}",
menuString);
} else {
sprintf(buffer + strlen(buffer),
"\n"
"}");
}
if(i != (pglobal->in[input_number].parametercount - 1)) {
sprintf(buffer + strlen(buffer), ",\n");
}
free(menuString);
}
} else {
DBG("The input plugin has no paramters\n");
}
sprintf(buffer + strlen(buffer),
"\n],\n"
/*"},\n"*/);
sprintf(buffer + strlen(buffer),
//"{\n"
"\"formats\": [\n");
if(pglobal->in[input_number].in_formats != NULL) {
for(i = 0; i < pglobal->in[input_number].formatCount; i++) {
char *resolutionsString = NULL;
int resolutionsStringLength = 0;
int j = 0;
for(j = 0; j < pglobal->in[input_number].in_formats[i].resolutionCount; j++) {
char buffer_num[6];
memset(buffer_num, '\0', 6);
// JSON format example:
// {"0": "320x240", "1": "640x480", "2": "960x720"}
sprintf(buffer_num, "%d", j);
resolutionsStringLength += strlen(buffer_num);
sprintf(buffer_num, "%d", pglobal->in[input_number].in_formats[i].supportedResolutions[j].width);
resolutionsStringLength += strlen(buffer_num);
sprintf(buffer_num, "%d", pglobal->in[input_number].in_formats[i].supportedResolutions[j].height);
resolutionsStringLength += strlen(buffer_num);
if(j != (pglobal->in[input_number].in_formats[i].resolutionCount - 1)) {
resolutionsStringLength += (strlen("\"\": \"x\", ") + 5);
if (resolutionsString == NULL)
resolutionsString = calloc(resolutionsStringLength, sizeof(char*));
else
resolutionsString = realloc(resolutionsString, resolutionsStringLength * sizeof(char*));
if (resolutionsString == NULL) {
DBG("Realloc/calloc failed\n");
return;
}
sprintf(resolutionsString + strlen(resolutionsString),
"\"%d\": \"%dx%d\", ",
j,
pglobal->in[input_number].in_formats[i].supportedResolutions[j].width,
pglobal->in[input_number].in_formats[i].supportedResolutions[j].height);
} else {
resolutionsStringLength += (strlen("\"\": \"x\"")+5);
if (resolutionsString == NULL)
resolutionsString = calloc(resolutionsStringLength, sizeof(char*));
else
resolutionsString = realloc(resolutionsString, resolutionsStringLength * sizeof(char*));
if (resolutionsString == NULL) {
DBG("Realloc/calloc failed\n");
return;
}
sprintf(resolutionsString + strlen(resolutionsString),
"\"%d\": \"%dx%d\"",
j,
pglobal->in[input_number].in_formats[i].supportedResolutions[j].width,
pglobal->in[input_number].in_formats[i].supportedResolutions[j].height);
}
}
sprintf(buffer + strlen(buffer),
"{\n"
"\"id\": \"%d\",\n"
"\"name\": \"%s\",\n"
#ifdef V4L2_FMT_FLAG_COMPRESSED
"\"compressed\": \"%s\",\n"
#endif
#ifdef V4L2_FMT_FLAG_EMULATED
"\"emulated\": \"%s\",\n"
#endif
"\"current\": \"%s\",\n"
"\"resolutions\": {%s}\n"
,
pglobal->in[input_number].in_formats[i].format.index,
pglobal->in[input_number].in_formats[i].format.description,
#ifdef V4L2_FMT_FLAG_COMPRESSED
pglobal->in[input_number].in_formats[i].format.flags & V4L2_FMT_FLAG_COMPRESSED ? "true" : "false",
#endif
#ifdef V4L2_FMT_FLAG_EMULATED
pglobal->in[input_number].in_formats[i].format.flags & V4L2_FMT_FLAG_EMULATED ? "true" : "false",
#endif
pglobal->in[input_number].in_formats[i].currentResolution != -1 ? "true" : "false",
resolutionsString
);
if(pglobal->in[input_number].in_formats[i].currentResolution != -1) {
sprintf(buffer + strlen(buffer),
",\n\"currentResolution\": \"%d\"\n",
pglobal->in[input_number].in_formats[i].currentResolution
);
}
if(i != (pglobal->in[input_number].formatCount - 1)) {
sprintf(buffer + strlen(buffer), "},\n");
} else {
sprintf(buffer + strlen(buffer), "}\n");
}
free(resolutionsString);
}
}
sprintf(buffer + strlen(buffer),
"\n]\n"
"}\n");
i = strlen(buffer);
/* first transmit HTTP-header, afterwards transmit content of file */
if(write(fd, buffer, i) < 0) {
DBG("unable to serve the control JSON file\n");
}
}
void send_program_JSON(int fd)
{
char buffer[BUFFER_SIZE*16] = {0}; // FIXME do reallocation if the buffer size is small
int i, k;
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Content-type: %s\r\n" \
STD_HEADER \
"\r\n", "application/x-javascript");
DBG("Serving the program descriptor JSON file\n");
sprintf(buffer + strlen(buffer),
"{\n"
/*"\"program\": [\n"
"{\n"*/
"\"inputs\":[\n");
for(k = 0; k < pglobal->incnt; k++) {
sprintf(buffer + strlen(buffer),
"{\n"
"\"id\": \"%d\",\n"
"\"name\": \"%s\",\n"
"\"plugin\": \"%s\",\n"
"\"args\": \"%s\"\n"
"}",
pglobal->in[k].param.id,
pglobal->in[k].name,
pglobal->in[k].plugin,
pglobal->in[k].param.parameters);
if(k != (pglobal->incnt - 1))
sprintf(buffer + strlen(buffer), ", \n");
else
sprintf(buffer + strlen(buffer), "\n");
}
sprintf(buffer + strlen(buffer),
/*"]\n"
"}\n"
"]\n"*/
"],\n");
sprintf(buffer + strlen(buffer),
"\"outputs\":[\n");
for(k = 0; k < pglobal->outcnt; k++) {
sprintf(buffer + strlen(buffer),
"{\n"
"\"id\": \"%d\",\n"
"\"name\": \"%s\",\n"
"\"plugin\": \"%s\",\n"
"\"args\": \"%s\"\n"
"}",
pglobal->out[k].param.id,
pglobal->out[k].name,
pglobal->out[k].plugin,
pglobal->out[k].param.parameters);
if(k != (pglobal->outcnt - 1))
sprintf(buffer + strlen(buffer), ", \n");
else
sprintf(buffer + strlen(buffer), "\n");
}
sprintf(buffer + strlen(buffer),
/*"]\n"
"}\n"
"]\n"*/
"]}\n");
i = strlen(buffer);
/* first transmit HTTP-header, afterwards transmit content of file */
if(write(fd, buffer, i) < 0) {
DBG("unable to serve the program JSON file\n");
}
}
/******************************************************************************
Description.: checks the source string for non printable characters and replaces them with space
the two arguments should be the same size allocated memory areas
Input Value.: source
Return Value: destination
******************************************************************************/
void check_JSON_string(char *source, char *destination)
{
int i = 0;
while (source[i] != '\0') {
if (isprint(source[i])) {
destination[i] = source [i];
} else {
destination[i] = ' ';
}
i++;
}
}
/******************************************************************************
Description.: Send a JSON file which is contains information about the output plugin's
acceptable parameters
Input Value.: fildescriptor fd to send the answer to
Return Value: -
******************************************************************************/
void send_output_JSON(int fd, int input_number)
{
char buffer[BUFFER_SIZE*16] = {0}; // FIXME do reallocation if the buffer size is small
int i;
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Content-type: %s\r\n" \
STD_HEADER \
"\r\n", "application/x-javascript");
DBG("Serving the output plugin %d descriptor JSON file\n", input_number);
sprintf(buffer + strlen(buffer),
"{\n"
"\"controls\": [\n");
if(pglobal->out[input_number].out_parameters != NULL) {
for(i = 0; i < pglobal->out[input_number].parametercount; i++) {
char *menuString = calloc(0, 0);
if(pglobal->out[input_number].out_parameters[i].ctrl.type == V4L2_CTRL_TYPE_MENU) {
if(pglobal->out[input_number].out_parameters[i].menuitems != NULL) {
int j, k = 1;
for(j = pglobal->out[input_number].out_parameters[i].ctrl.minimum; j <= pglobal->out[input_number].out_parameters[i].ctrl.maximum; j++) {
int prevSize = strlen(menuString);
int itemLength = strlen((char*)&pglobal->out[input_number].out_parameters[i].menuitems[j].name) + strlen("\"\": \"\"");
if (menuString == NULL) {
menuString = calloc(itemLength, sizeof(char));
} else {
menuString = realloc(menuString, (strlen(menuString) + itemLength) * (sizeof(char)));
}
if (menuString == NULL) {
DBG("Realloc/calloc failed: %s\n", strerror(errno));
return;
}
if(j != pglobal->out[input_number].out_parameters[i].ctrl.maximum) {
sprintf(menuString + prevSize, "\"%d\": \"%s\", ", j , (char*)&pglobal->out[input_number].out_parameters[i].menuitems[j].name);
} else {
sprintf(menuString + prevSize, "\"%d\": \"%s\"", j , (char*)&pglobal->out[input_number].out_parameters[i].menuitems[j].name);
}
k++;
}
}
}
sprintf(buffer + strlen(buffer),
"{\n"
"\"name\": \"%s\",\n"
"\"id\": \"%d\",\n"
"\"type\": \"%d\",\n"
"\"min\": \"%d\",\n"
"\"max\": \"%d\",\n"
"\"step\": \"%d\",\n"
"\"default\": \"%d\",\n"
"\"value\": \"%d\",\n"
"\"dest\": \"1\",\n"
"\"flags\": \"%d\",\n"
"\"group\": \"%d\"",
pglobal->out[input_number].out_parameters[i].ctrl.name,
pglobal->out[input_number].out_parameters[i].ctrl.id,
pglobal->out[input_number].out_parameters[i].ctrl.type,
pglobal->out[input_number].out_parameters[i].ctrl.minimum,
pglobal->out[input_number].out_parameters[i].ctrl.maximum,
pglobal->out[input_number].out_parameters[i].ctrl.step,
pglobal->out[input_number].out_parameters[i].ctrl.default_value,
pglobal->out[input_number].out_parameters[i].value,
// 1 is the code of the output plugin
pglobal->out[input_number].out_parameters[i].ctrl.flags,
pglobal->out[input_number].out_parameters[i].group
);
if(pglobal->out[input_number].out_parameters[i].ctrl.type == V4L2_CTRL_TYPE_MENU) {
sprintf(buffer + strlen(buffer),
",\n"
"\"menu\": {%s}\n"
"}",
menuString);
} else {
sprintf(buffer + strlen(buffer),
"\n"
"}");
}
if(i != (pglobal->out[input_number].parametercount - 1)) {
sprintf(buffer + strlen(buffer), ",\n");
}
free(menuString);
}
} else {
DBG("The output plugin %d has no paramters\n", input_number);
}
sprintf(buffer + strlen(buffer),
"\n]\n"
/*"},\n"*/);
sprintf(buffer + strlen(buffer),
"}\n");
i = strlen(buffer);
/* first transmit HTTP-header, afterwards transmit content of file */
if(write(fd, buffer, i) < 0) {
DBG("unable to serve the control JSON file\n");
}
}
#ifdef MANAGMENT
void send_clients_JSON(int fd)
{
char buffer[BUFFER_SIZE*16] = {0}; // FIXME do reallocation if the buffer size is small
unsigned long i = 0 ;
sprintf(buffer, "HTTP/1.0 200 OK\r\n" \
"Content-type: %s\r\n" \
STD_HEADER \
"\r\n", "application/x-javascript");
DBG("Serving the clients JSON file\n");
sprintf(buffer + strlen(buffer),
"{\n"
"\"clients\": [\n");
for (; i<client_infos.client_count; i++) {
sprintf(buffer + strlen(buffer),
"{\n"
"\"address\": \"%s\",\n"
"\"timestamp\": %ld\n"
"}\n",
client_infos.infos[i]->address,
(unsigned long)client_infos.infos[i]->last_take_time.tv_sec);
if(i != (client_infos.client_count - 1)) {
sprintf(buffer + strlen(buffer), ",\n");
}
}
sprintf(buffer + strlen(buffer),
"]");
sprintf(buffer + strlen(buffer),
"\n}\n");
i = strlen(buffer);
/* first transmit HTTP-header, afterwards transmit content of file */
if(write(fd, buffer, i) < 0) {
DBG("unable to serve the control JSON file\n");
}
}
#endif