Remove deprecated files

This commit is contained in:
NADAL Jean-Baptiste
2020-02-20 12:08:21 +01:00
parent 17687d6836
commit 9d58995550
12 changed files with 0 additions and 3266 deletions

View File

@@ -1,35 +0,0 @@
/*!
* restd.h
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 23/12/2019
*
*/
#ifndef _RESTD_H
#define _RESTD_H
/*------------------------------- INCLUDES ----------------------------------*/
#include "restd_server.h"
#include "restd_http_handler.h"
#include "restd_rest_handler.h"
#endif /*_RESTD_H */

View File

@@ -1,162 +0,0 @@
/*!
* restd_http_handler.h
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 23/12/2019
*
*/
#ifndef _RESTD_HTTP_HANDLER_H
#define _RESTD_HTTP_HANDLER_H
/*------------------------------- INCLUDES ----------------------------------*/
/*----------------------------------------------------------------------------*\
| HTTP PROTOCOL SPECIFICS |
\*----------------------------------------------------------------------------*/
/* HTTP PROTOCOL CODES */
#define HTTP_PROTOCOL_09 "HTTP/0.9"
#define HTTP_PROTOCOL_10 "HTTP/1.0"
#define HTTP_PROTOCOL_11 "HTTP/1.1"
/* HTTP RESPONSE CODES */
#define HTTP_NO_RESPONSE (0)
#define HTTP_CODE_CONTINUE (100)
#define HTTP_CODE_OK (200)
#define HTTP_CODE_CREATED (201)
#define HTTP_CODE_NO_CONTENT (204)
#define HTTP_CODE_PARTIAL_CONTENT (206)
#define HTTP_CODE_MULTI_STATUS (207)
#define HTTP_CODE_MOVED_TEMPORARILY (302)
#define HTTP_CODE_NOT_MODIFIED (304)
#define HTTP_CODE_Brestd_REQUEST (400)
#define HTTP_CODE_UNAUTHORIZED (401)
#define HTTP_CODE_FORBIDDEN (403)
#define HTTP_CODE_NOT_FOUND (404)
#define HTTP_CODE_METHOD_NOT_ALLOWED (405)
#define HTTP_CODE_REQUEST_TIME_OUT (408)
#define HTTP_CODE_GONE (410)
#define HTTP_CODE_REQUEST_URI_TOO_LONG (414)
#define HTTP_CODE_LOCKED (423)
#define HTTP_CODE_INTERNAL_SERVER_ERROR (500)
#define HTTP_CODE_NOT_IMPLEMENTED (501)
#define HTTP_CODE_SERVICE_UNAVAILABLE (503)
/* DEFAULT BEHAVIORS */
#define HTTP_CRLF "\r\n"
#define HTTP_DEF_CONTENTTYPE "application/octet-stream"
/*----------------------------------------------------------------------------*\
| TYPEDEFS |
\*----------------------------------------------------------------------------*/
typedef struct restd_http_s restd_http_t;
/*!< Hook type */
#define RESTD_HOOK_ALL (0) /*!< call on each and every phases */
#define RESTD_HOOK_ON_CONNECT (1) /*!< call right after the establishment of connection */
#define RESTD_HOOK_AFTER_REQUESTLINE (1 << 2) /*!< call after parsing request line */
#define RESTD_HOOK_AFTER_HEADER (1 << 3) /*!< call after parsing all headers */
#define RESTD_HOOK_ON_BODY (1 << 4) /*!< call on every time body data received */
#define RESTD_HOOK_ON_REQUEST (1 << 5) /*!< call with complete request */
#define RESTD_HOOK_ON_CLOSE (1 << 6) /*!< call right before closing or next request */
#if 0
enum restd_http_request_status_e
{
RESTD_HTTP_REQ_INIT = 0, /*!< initial state */
RESTD_HTTP_REQ_REQUESTLINE_DONE, /*!< received 1st line */
RESTD_HTTP_REQ_HEADER_DONE, /*!< received headers completely */
RESTD_HTTP_REQ_DONE, /*!< received body completely. no more data expected */
RESTD_HTTP_ERROR, /*!< unrecoverable error found. */
};
#endif
/*----------------------------------------------------------------------------*\
| PUBLIC FUNCTIONS |
\*----------------------------------------------------------------------------*/
extern restd_http_t *http_new(struct evbuffer *out);
extern void http_free_cb(restd_conn_t *conn, void *userdata);
extern int restd_http_handler(short event, restd_conn_t *conn, void *userdata);
extern enum restd_http_request_status_e restd_http_get_status(restd_conn_t *conn);
extern struct evbuffer *restd_http_get_inbuf(restd_conn_t *conn);
extern struct evbuffer *restd_http_get_outbuf(restd_conn_t *conn);
extern const char *restd_http_get_request_header(restd_conn_t *conn, const char *name);
extern off_t restd_http_get_content_length(restd_conn_t *conn);
extern size_t restd_http_get_content_length_stored(restd_conn_t *conn);
extern void *restd_http_get_content(restd_conn_t *conn, size_t maxsize, size_t *storedsize);
extern int restd_http_is_keepalive_request(restd_conn_t *conn);
extern int http_parser(restd_http_t *http, struct evbuffer *in);
extern int restd_http_set_response_header(restd_conn_t *conn, const char *name, const char *value);
extern const char *restd_http_get_response_header(restd_conn_t *conn, const char *name);
extern int restd_http_set_response_code(restd_conn_t *conn, int code, const char *reason);
extern int restd_http_set_response_content(restd_conn_t *conn, const char *contenttype, off_t size);
extern size_t restd_http_response(restd_conn_t *conn, int code, const char *contenttype, const void *data, off_t size);
extern size_t restd_http_send_header(restd_conn_t *conn);
extern size_t restd_http_send_data(restd_conn_t *conn, const void *data, size_t size);
extern size_t restd_http_send_chunk(restd_conn_t *conn, const void *data, size_t size);
extern const char *restd_http_get_reason(int code);
/*---------------------------------------------------------------------------*\
| DATA STRUCTURES |
\*---------------------------------------------------------------------------*/
struct restd_http_s
{
// HTTP Request
struct
{
enum restd_http_request_status_e status; /*!< request status. */
struct evbuffer *inbuf; /*!< input data buffer. */
// request line - available on REQ_REQUESTLINE_DONE.
char *method; /*!< request method ex) GET */
char *uri; /*!< url+query ex) /data%20path?query=the%20value */
char *httpver; /*!< version ex) HTTP/1.1 */
char *path; /*!< decoded path ex) /data path */
char *query; /*!< query string ex) query=the%20value */
// request header - available on REQ_HEADER_DONE.
qlisttbl_t *headers; /*!< parsed request header entries */
char *host; /*!< host ex) www.domain.com or www.domain.com:8080 */
char *domain; /*!< domain name ex) www.domain.com (no port number) */
off_t contentlength; /*!< value of Content-Length header.*/
size_t bodyin; /*!< bytes moved to in-buff */
} request;
// HTTP Response
struct
{
struct evbuffer *outbuf; /*!< output data buffer. */
bool frozen_header; /*!< indicator whether we sent header out or not */
// response headers
int code; /*!< response status-code */
char *reason; /*!< reason-phrase */
qlisttbl_t *headers; /*!< response header entries */
off_t contentlength; /*!< content length in response */
size_t bodyout; /*!< bytes added to out-buffer */
} response;
};
#endif /* _RESTD_HTTP_HANDLER_H */

View File

@@ -1,33 +0,0 @@
/*!
* restd_rest_handler.h
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 23/12/2019
*
*/
#ifndef _RESTD_REST_HANDLER_H
#define _RESTD_REST_HANDLER_H
/*------------------------------- INCLUDES ----------------------------------*/
extern int restd_rest_handler(short event, restd_conn_t *conn);
#endif /* _RESTD_REST_HANDLER_H */

View File

@@ -1,176 +0,0 @@
/*!
* restd_server.h
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 23/12/2019
*
*/
#ifndef _RESTD_SERVER_H
#define _RESTD_SERVER_H
/*------------------------------- INCLUDES ----------------------------------*/
#include <pthread.h>
#include <qlibc/qlibc.h>
/*------------------------------- TYPEDEFS ----------------------------------*/
typedef struct restd_server_s restd_server_t;
typedef struct restd_conn_s restd_conn_t;
/*------------------------------- DEFINE ----------------------------------*/
/*
* Return values of user callback.
*/
#define RESTD_OK (0) /*!< I'm done with this request. Escalate to other hooks. */
#define RESTD_TAKEOVER (1) /*!< I'll handle the buffer directly this time, skip next hook */
#define RESTD_DONE (2) /*!< We're done with this request but keep the connection open. */
#define RESTD_CLOSE (3) /*!< We're done with this request. Close as soon as we sent all data out. */
#define RESTD_ERROR_METHOD_NOT_ALLOWED (1)
#define RESTD_ERROR_PATH_NOT_FOUND (2)
/*
* These flags are used for ad_log_level();
*/
enum restd_log_e
{
RESTD_LOG_DISABLE = 0,
RESTD_LOG_ERROR,
RESTD_LOG_WARN,
RESTD_LOG_INFO,
RESTD_LOG_DEBUG,
RESTD_LOG_DEBUG2,
};
/*---------------------------------------------------------------------------*\
| USER-CALLBACK |
\*---------------------------------------------------------------------------*/
/**
* User callback(hook) prototype.
*/
typedef int (*restd_call_hook_cb)(short event, restd_conn_t *conn);
typedef int (*restd_callback)(short event, restd_conn_t *conn, void *userdata);
typedef void (*restd_userdata_free_cb)(restd_conn_t *conn, void *userdata);
/**
* Event types
*/
#define RESTD_EVENT_INIT (1) /*!< Call once upon new connection. */
#define RESTD_EVENT_READ (1 << 1) /*!< Call on read */
#define RESTD_EVENT_WRITE (1 << 2) /*!< Call on write. */
#define RESTD_EVENT_CLOSE (1 << 3) /*!< Call before closing. */
#define RESTD_EVENT_TIMEOUT (1 << 4) /*!< Timeout indicator, this flag will be set with AD_EVENT_CLOSE. */
#define RESTD_EVENT_SHUTDOWN (1 << 5) /*!< Shutdown indicator, this flag will be set with AD_EVENT_CLOSE. */
/**
* Defaults
*/
#define RESTD_NUM_USERDATA (2) /*!< Number of userdata. Currently 0 is for userdata, 1 is for extra. */
/*---------------------------------------------------------------------------*\
| DATA STRUCTURES |
\*---------------------------------------------------------------------------*/
/**
* Server info container.
*/
struct restd_server_s
{
int errcode; /*!< exit status. 0 for normal exit, non zero for error. */
pthread_t *thread; /*!< thread object. not null if server runs as a thread */
qhashtbl_t *options; /*!< server options */
qhashtbl_t *stats; /*!< internal statistics */
qlist_t *hooks; /*!< list of registered hooks */
struct evconnlistener *listener; /*!< listener */
struct event_base *evbase; /*!< event base */
struct bufferevent *notify_buffer; /*!< internal notification channel */
restd_call_hook_cb call_hooks;
restd_callback request_handler;
restd_callback error_handler;
};
/**
* Connection structure.
*/
struct restd_conn_s
{
restd_server_t *server; /*!< reference pointer to server */
struct bufferevent *buffer; /*!< reference pointer to buffer */
struct evbuffer *in; /*!< in buffer */
struct evbuffer *out; /*!< out buffer */
int status; /*!< hook status such as AD_OK */
void *userdata[2]; /*!< userdata[0] for end user, userdata[1] for extra */
restd_userdata_free_cb userdata_free_cb[2]; /*!< callback to release user data */
char *method; /*!< request method. set by protocol handler */
int id;
};
/*
* User callback hook container.
*/
typedef struct restd_hook_s restd_hook_t;
struct restd_hook_s
{
char *method;
char *path;
restd_callback cb;
void *userdata;
};
/*----------------------------------------------------------------------------*\
| PUBLIC FUNCTIONS |
\*----------------------------------------------------------------------------*/
enum restd_log_e restd_log_level(enum restd_log_e log_level);
extern restd_server_t *restd_server_new(void);
extern void restd_server_free(restd_server_t *server);
extern int restd_server_start(restd_server_t *server);
extern int restd_server_attach_event_loop(restd_server_t *server, struct event_base *ev_base);
extern void restd_server_set_option(restd_server_t *server, const char *key, const char *value);
extern char *restd_server_get_option(restd_server_t *server, const char *key);
extern int restd_server_get_option_int(restd_server_t *server, const char *key);
extern void restd_server_register_request_handler(restd_server_t *server, restd_callback cb);
extern void restd_server_register_error_handler(restd_server_t *server, restd_callback cb);
extern void restd_server_register_call_hooks_handler(restd_server_t *server, restd_call_hook_cb cb);
extern void restd_server_register_hook(restd_server_t *server, restd_callback cb, void *userdata);
extern void restd_server_register_hook_on_method(restd_server_t *server, const char *method,
restd_callback cb, void *userdata);
extern void restd_server_register_hook_on_path(restd_server_t *server, const char *method, const char *path,
restd_callback cb, void *userdata);
extern void *restd_conn_set_extra(restd_conn_t *conn, const void *extra, restd_userdata_free_cb free_cb);
extern void *restd_conn_get_extra(restd_conn_t *conn);
extern void restd_conn_set_method(restd_conn_t *conn, char *method);
#endif /* _RESTD_SERVER_H */

View File

@@ -1,997 +0,0 @@
/*!
* restd_http_handler.c
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 23/12/2019
*
*/
// This is an independent project of an individual developer. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com
/*------------------------------- INCLUDES ----------------------------------*/
#include <stdbool.h>
#include <string.h>
#include <limits.h>
#include <assert.h>
#include <event2/buffer.h>
#include "restd_server.h"
#include "restd_http_handler.h"
#include "macro.h"
#ifndef _DOXYGEN_SKIP
static void http_free(restd_http_t *http);
static size_t http_add_inbuf(struct evbuffer *buffer, restd_http_t *http,
size_t maxsize);
static int parse_requestline(restd_http_t *http, char *line);
static int parse_headers(restd_http_t *http, struct evbuffer *in);
static int parse_body(restd_http_t *http, struct evbuffer *in);
static ssize_t parse_chunked_body(restd_http_t *http, struct evbuffer *in);
static bool isValidPathname(const char *path);
static void correctPathname(char *path);
static char *evbuffer_peekln(struct evbuffer *buffer, size_t *n_rerestd_out,
enum evbuffer_eol_style eol_style);
static ssize_t evbuffer_drainln(struct evbuffer *buffer, size_t *n_rerestd_out,
enum evbuffer_eol_style eol_style);
#endif
/**
* HTTP protocol handler hook.
*
* This hook provides an easy way to handle HTTP request/response.
*
* @note
* This hook must be registered at the top of hook chain.
*
* @code
* restd_server_t *server = restd_server_new();
* restd_server_register_hook(server, restd_http_handler, NULL);
* @endcode
*/
int restd_http_handler(short event, restd_conn_t *conn, void *userdata)
{
if (event & RESTD_EVENT_INIT)
{
DEBUG("==> HTTP INIT");
restd_http_t *http = http_new(conn->out);
if (http == NULL)
return RESTD_CLOSE;
restd_conn_set_extra(conn, http, http_free_cb);
return RESTD_OK;
}
else if (event & RESTD_EVENT_READ)
{
DEBUG("==> HTTP READ");
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
int status = http_parser(http, conn->in);
if (conn->method == NULL && http->request.method != NULL)
{
restd_conn_set_method(conn, http->request.method);
}
DEBUG("==> HTTP PATH: %s METHOD: %s", http->request.path, http->request.method);
if (conn->server->request_handler != NULL)
{
status = conn->server->request_handler(event, conn, NULL);
}
return status;
}
else if (event & RESTD_EVENT_WRITE)
{
DEBUG("==> HTTP WRITE");
return RESTD_OK;
}
else if (event & RESTD_EVENT_CLOSE)
{
DEBUG("==> HTTP CLOSE=%x (TIMEOUT=%d, SHUTDOWN=%d)",
event, event & RESTD_EVENT_TIMEOUT, event & RESTD_EVENT_SHUTDOWN);
return RESTD_OK;
}
BUG_EXIT();
return RESTD_CLOSE;
}
/**
* Return the request status.
*/
enum restd_http_request_status_e restd_http_get_status(restd_conn_t *conn)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http == NULL)
return RESTD_HTTP_ERROR;
return http->request.status;
}
/*--------------------------------------------------------------------------*/
struct evbuffer *restd_http_get_inbuf(restd_conn_t *conn)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
return http->request.inbuf;
}
/*--------------------------------------------------------------------------*/
struct evbuffer *restd_http_get_outbuf(restd_conn_t *conn)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
return http->response.outbuf;
}
/**
* Get request header.
*
* @param name name of header.
*
* @return value of string if found, otherwise NULL.
*/
const char *restd_http_get_request_header(restd_conn_t *conn, const char *name)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
return http->request.headers->getstr(http->request.headers, name, false);
}
/**
* Return the size of content from the request.
*/
off_t restd_http_get_content_length(restd_conn_t *conn)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
return http->request.contentlength;
}
/**
* Return the actual size of data stored in in-buffer
*/
size_t restd_http_get_content_length_stored(restd_conn_t *conn)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
return evbuffer_get_length(http->request.inbuf);
}
/**
* Remove content from the in-buffer.
*
* The return data gets null terminated for convenience. For an example,
* if it reads 3 bytes, it will allocate 4 bytes and the 4th byte will
* be set to null terminator. `storedsized` will still return 3.
*
* @param maxsize maximum length of data to read. 0 to read everything.
* @param storedsize the size of data read and stored in the return.
*/
void *restd_http_get_content(restd_conn_t *conn, size_t maxsize, size_t *storedsize)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
size_t inbuflen = evbuffer_get_length(http->request.inbuf);
size_t readlen =
(maxsize == 0) ? inbuflen : ((inbuflen < maxsize) ? inbuflen : maxsize);
if (readlen == 0)
return NULL;
void *data = malloc(readlen + 1);
if (data == NULL)
return NULL;
size_t removedlen = evbuffer_remove(http->request.inbuf, data, readlen);
((char *)data)[removedlen] = '\0';
if (storedsize)
*storedsize = removedlen;
return data;
}
/**
* Return whether the request is keep-alive request or not.
*
* @return 1 if keep-alive request, otherwise 0.
*/
int restd_http_is_keepalive_request(restd_conn_t *conn)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http->request.httpver == NULL)
{
return 0;
}
const char *connection = restd_http_get_request_header(conn, "Connection");
if (!strcmp(http->request.httpver, HTTP_PROTOCOL_11))
{
// In HTTP/1.1, Keep-Alive is on by default unless explicitly specified.
if (connection != NULL && !strcmp(connection, "close"))
{
return 0;
}
return 1;
}
else
{
// In older version, Keep-Alive is off by default unless requested.
if (connection != NULL && (!strcmp(connection, "Keep-Alive") || !strcmp(connection, "TE")))
{
return 1;
}
return 0;
}
}
/**
* Set response header.
*
* @param name name of header.
* @param value value string to set. NULL to remove the header.
*
* @return 0 on success, -1 if we already sent it out.
*/
int restd_http_set_response_header(restd_conn_t *conn, const char *name,
const char *value)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http->response.frozen_header)
{
return -1;
}
if (value != NULL)
{
http->response.headers->putstr(http->response.headers, name, value);
}
else
{
http->response.headers->remove(http->response.headers, name);
}
return 0;
}
/**
* Get response header.
*
* @param name name of header.
*
* @return value of string if found, otherwise NULL.
*/
const char *restd_http_get_response_header(restd_conn_t *conn, const char *name)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
return http->response.headers->getstr(http->response.headers, name, false);
}
/**
*
* @return 0 on success, -1 if we already sent it out.
*/
int restd_http_set_response_code(restd_conn_t *conn, int code, const char *reason)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http->response.frozen_header)
{
return -1;
}
http->response.code = code;
if (reason)
http->response.reason = strdup(reason);
return 0;
}
/**
*
* @param size content size. -1 for chunked transfer encoding.
* @return 0 on success, -1 if we already sent it out.
*/
int restd_http_set_response_content(restd_conn_t *conn, const char *contenttype,
off_t size)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http->response.frozen_header)
{
return -1;
}
// Set Content-Type header.
restd_http_set_response_header(
conn, "Content-Type",
(contenttype) ? contenttype : HTTP_DEF_CONTENTTYPE);
if (size >= 0)
{
char clenval[20 + 1];
sprintf(clenval, "%jd", size);
restd_http_set_response_header(conn, "Content-Length", clenval);
http->response.contentlength = size;
}
else
{
restd_http_set_response_header(conn, "Transfer-Encoding", "chunked");
http->response.contentlength = -1;
}
return 0;
}
/**
* @return total bytes sent, 0 on error.
*/
size_t restd_http_response(restd_conn_t *conn, int code, const char *contenttype,
const void *data, off_t size)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
// Sanity Check
if (http == NULL)
return -1;
if (http->response.frozen_header)
{
return 0;
}
// Set response headers.
if (restd_http_get_response_header(conn, "Connection") == NULL)
{
restd_http_set_response_header(
conn, "Connection",
(restd_http_is_keepalive_request(conn)) ? "Keep-Alive" : "close");
}
restd_http_set_response_code(conn, code, restd_http_get_reason(code));
restd_http_set_response_content(conn, contenttype, size);
return restd_http_send_data(conn, data, size);
}
/**
*
* @return 0 total bytes put in out buffer, -1 if we already sent it out.
*/
size_t restd_http_send_header(restd_conn_t *conn)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http->response.frozen_header)
{
return 0;
}
http->response.frozen_header = true;
// Send status line.
const char *reason =
(http->response.reason) ? http->response.reason : restd_http_get_reason(http->response.code);
evbuffer_add_printf(http->response.outbuf, "%s %d %s" HTTP_CRLF,
http->request.httpver, http->response.code, reason);
// Send headers.
qlisttbl_obj_t obj;
bzero((void *)&obj, sizeof(obj));
qlisttbl_t *tbl = http->response.headers;
tbl->lock(tbl);
while (tbl->getnext(tbl, &obj, NULL, false))
{
evbuffer_add_printf(http->response.outbuf, "%s: %s" HTTP_CRLF,
(char *)obj.name, (char *)obj.data);
}
tbl->unlock(tbl);
// Send empty line, indicator of end of header.
evbuffer_add(http->response.outbuf, HTTP_CRLF, CONST_STRLEN(HTTP_CRLF));
return evbuffer_get_length(http->response.outbuf);
}
/**
*
* @return 0 on success, -1 if we already sent it out.
*/
size_t restd_http_send_data(restd_conn_t *conn, const void *data, size_t size)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http->response.contentlength < 0)
{
WARN("Content-Length is not set. Invalid usage.");
return 0;
}
if ((http->response.bodyout + size) > http->response.contentlength)
{
WARN("Trying to send more data than supposed to");
return 0;
}
size_t beforesize = evbuffer_get_length(http->response.outbuf);
if (!http->response.frozen_header)
{
restd_http_send_header(conn);
}
if (data != NULL && size > 0)
{
if (evbuffer_add(http->response.outbuf, data, size))
return 0;
}
http->response.bodyout += size;
return (evbuffer_get_length(http->response.outbuf) - beforesize);
}
/*--------------------------------------------------------------------------*/
size_t restd_http_send_chunk(restd_conn_t *conn, const void *data, size_t size)
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
if (http->response.contentlength >= 0)
{
WARN("Content-Length is set. Invalid usage.");
return 0;
}
if (!http->response.frozen_header)
{
restd_http_send_header(conn);
}
size_t beforesize = evbuffer_get_length(http->response.outbuf);
int status = 0;
if (size > 0)
{
status += evbuffer_add_printf(http->response.outbuf, "%zu" HTTP_CRLF,
size);
status += evbuffer_add(http->response.outbuf, data, size);
status += evbuffer_add(http->response.outbuf, HTTP_CRLF,
CONST_STRLEN(HTTP_CRLF));
}
else
{
status += evbuffer_add_printf(http->response.outbuf,
"0" HTTP_CRLF HTTP_CRLF);
}
if (status != 0)
{
WARN("Failed to add data to out-buffer. (size:%jd)", size);
return 0;
}
size_t bytesout = evbuffer_get_length(http->response.outbuf) - beforesize;
http->response.bodyout += bytesout;
return bytesout;
}
/*--------------------------------------------------------------------------*/
const char *restd_http_get_reason(int code)
{
switch (code)
{
case HTTP_CODE_CONTINUE:
return "Continue";
case HTTP_CODE_OK:
return "OK";
case HTTP_CODE_CREATED:
return "Created";
case HTTP_CODE_NO_CONTENT:
return "No content";
case HTTP_CODE_PARTIAL_CONTENT:
return "Partial Content";
case HTTP_CODE_MULTI_STATUS:
return "Multi Status";
case HTTP_CODE_MOVED_TEMPORARILY:
return "Moved Temporarily";
case HTTP_CODE_NOT_MODIFIED:
return "Not Modified";
case HTTP_CODE_Brestd_REQUEST:
return "Bad Request";
case HTTP_CODE_UNAUTHORIZED:
return "Authorization Required";
case HTTP_CODE_FORBIDDEN:
return "Forbidden";
case HTTP_CODE_NOT_FOUND:
return "Not Found";
case HTTP_CODE_METHOD_NOT_ALLOWED:
return "Method Not Allowed";
case HTTP_CODE_REQUEST_TIME_OUT:
return "Request Time Out";
case HTTP_CODE_GONE:
return "Gone";
case HTTP_CODE_REQUEST_URI_TOO_LONG:
return "Request URI Too Long";
case HTTP_CODE_LOCKED:
return "Locked";
case HTTP_CODE_INTERNAL_SERVER_ERROR:
return "Internal Server Error";
case HTTP_CODE_NOT_IMPLEMENTED:
return "Not Implemented";
case HTTP_CODE_SERVICE_UNAVAILABLE:
return "Service Unavailable";
}
WARN("Undefined code found. %d", code);
return "-";
}
/******************************************************************************
* Private internal functions.
*****************************************************************************/
#ifndef _DOXYGEN_SKIP
restd_http_t *http_new(struct evbuffer *out)
{
// Create a new connection container.
restd_http_t *http = NEW_OBJECT(restd_http_t);
if (http == NULL)
return NULL;
// Allocate additional resources.
http->request.inbuf = evbuffer_new();
http->request.headers = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
http->response.headers = qlisttbl(QLISTTBL_UNIQUE | QLISTTBL_CASEINSENSITIVE);
if (http->request.inbuf == NULL || http->request.headers == NULL || http->response.headers == NULL)
{
http_free(http);
return NULL;
}
// Initialize structure.
http->request.status = RESTD_HTTP_REQ_INIT;
http->request.contentlength = -1;
http->response.contentlength = -1;
http->response.outbuf = out;
return http;
}
/*--------------------------------------------------------------------------*/
static void http_free(restd_http_t *http)
{
if (http)
{
if (http->request.inbuf)
evbuffer_free(http->request.inbuf);
if (http->request.method)
free(http->request.method);
if (http->request.uri)
free(http->request.uri);
if (http->request.httpver)
free(http->request.httpver);
if (http->request.path)
free(http->request.path);
if (http->request.query)
free(http->request.query);
if (http->request.headers)
http->request.headers->free(http->request.headers);
if (http->request.host)
free(http->request.host);
if (http->request.domain)
free(http->request.domain);
if (http->response.headers)
http->response.headers->free(http->response.headers);
if (http->response.reason)
free(http->response.reason);
free(http);
}
}
/*--------------------------------------------------------------------------*/
void http_free_cb(restd_conn_t *conn, void *userdata)
{
http_free((restd_http_t *)userdata);
}
/*--------------------------------------------------------------------------*/
static size_t http_add_inbuf(struct evbuffer *buffer, restd_http_t *http,
size_t maxsize)
{
if (maxsize == 0 || evbuffer_get_length(buffer) == 0)
{
return 0;
}
return evbuffer_remove_buffer(buffer, http->request.inbuf, maxsize);
}
/*--------------------------------------------------------------------------*/
int http_parser(restd_http_t *http, struct evbuffer *in)
{
ASSERT(http != NULL && in != NULL);
if (http->request.status == RESTD_HTTP_REQ_INIT)
{
char *line = evbuffer_readln(in, NULL, EVBUFFER_EOL_CRLF);
if (line == NULL)
return http->request.status;
http->request.status = parse_requestline(http, line);
free(line);
// Do not call user callbacks until I reach the next state.
if (http->request.status == RESTD_HTTP_REQ_INIT)
{
return RESTD_TAKEOVER;
}
}
if (http->request.status == RESTD_HTTP_REQ_REQUESTLINE_DONE)
{
http->request.status = parse_headers(http, in);
// Do not call user callbacks until I reach the next state.
if (http->request.status == RESTD_HTTP_REQ_REQUESTLINE_DONE)
{
return RESTD_TAKEOVER;
}
}
if (http->request.status == RESTD_HTTP_REQ_HEADER_DONE)
{
http->request.status = parse_body(http, in);
// Do not call user callbacks until I reach the next state.
if (http->request.status == RESTD_HTTP_REQ_HEADER_DONE)
{
return RESTD_TAKEOVER;
}
}
if (http->request.status == RESTD_HTTP_REQ_DONE)
{
return RESTD_OK;
}
if (http->request.status == RESTD_HTTP_ERROR)
{
return RESTD_CLOSE;
}
BUG_EXIT();
return RESTD_CLOSE;
}
/*--------------------------------------------------------------------------*/
static int parse_requestline(restd_http_t *http, char *line)
{
// Parse request line.
char *saveptr;
char *method = strtok_r(line, " ", &saveptr);
char *uri = strtok_r(NULL, " ", &saveptr);
char *httpver = strtok_r(NULL, " ", &saveptr);
char *tmp = strtok_r(NULL, " ", &saveptr);
if (method == NULL || uri == NULL || httpver == NULL || tmp != NULL)
{
DEBUG("Invalid request line. %s", line);
return RESTD_HTTP_ERROR;
}
// Set request method
http->request.method = qstrupper(strdup(method));
// Set HTTP version
http->request.httpver = qstrupper(strdup(httpver));
if (strcmp(http->request.httpver, HTTP_PROTOCOL_09) && strcmp(http->request.httpver, HTTP_PROTOCOL_10) && strcmp(http->request.httpver, HTTP_PROTOCOL_11))
{
DEBUG("Unknown protocol: %s", http->request.httpver);
return RESTD_HTTP_ERROR;
}
// Set URI
if (uri[0] == '/')
{
http->request.uri = strdup(uri);
}
else if ((tmp = strstr(uri, "://")))
{
// divide URI into host and path
char *path = strstr(tmp + CONST_STRLEN("://"), "/");
if (path == NULL)
{ // URI has no path ex) http://domain.com:80
http->request.headers->putstr(http->request.headers, "Host",
tmp + CONST_STRLEN("://"));
http->request.uri = strdup("/");
}
else
{ // URI has path, ex) http://domain.com:80/path
*path = '\0';
http->request.headers->putstr(http->request.headers, "Host",
tmp + CONST_STRLEN("://"));
*path = '/';
http->request.uri = strdup(path);
}
}
else
{
DEBUG("Invalid URI format. %s", uri);
return RESTD_HTTP_ERROR;
}
// Set request path. Only path part from URI.
http->request.path = strdup(http->request.uri);
tmp = strstr(http->request.path, "?");
if (tmp)
{
*tmp = '\0';
http->request.query = strdup(tmp + 1);
}
else
{
http->request.query = strdup("");
}
qurl_decode(http->request.path);
// check path
if (isValidPathname(http->request.path) == false)
{
DEBUG("Invalid URI format : %s", http->request.uri);
return RESTD_HTTP_ERROR;
}
correctPathname(http->request.path);
DEBUG("Method=%s, URI=%s, VER=%s", http->request.method, http->request.uri, http->request.httpver);
return RESTD_HTTP_REQ_REQUESTLINE_DONE;
}
/*--------------------------------------------------------------------------*/
static int parse_headers(restd_http_t *http, struct evbuffer *in)
{
char *line;
while ((line = evbuffer_readln(in, NULL, EVBUFFER_EOL_CRLF)))
{
if (IS_EMPTY_STR(line))
{
const char *clen = http->request.headers->getstr(
http->request.headers, "Content-Length", false);
http->request.contentlength = (clen) ? atol(clen) : -1;
free(line);
return RESTD_HTTP_REQ_HEADER_DONE;
}
// Parse
char *name, *value;
char *tmp = strstr(line, ":");
if (tmp)
{
*tmp = '\0';
name = qstrtrim(line);
value = qstrtrim(tmp + 1);
}
else
{
name = qstrtrim(line);
value = "";
}
// Add
http->request.headers->putstr(http->request.headers, name, value);
free(line);
}
return http->request.status;
}
/*--------------------------------------------------------------------------*/
static int parse_body(restd_http_t *http, struct evbuffer *in)
{
// Handle static data case.
if (http->request.contentlength == 0)
{
return RESTD_HTTP_REQ_DONE;
}
else if (http->request.contentlength > 0)
{
if (http->request.contentlength > http->request.bodyin)
{
size_t maxread = http->request.contentlength - http->request.bodyin;
if (maxread > 0 && evbuffer_get_length(in) > 0)
{
http->request.bodyin += http_add_inbuf(in, http, maxread);
}
}
if (http->request.contentlength == http->request.bodyin)
{
return RESTD_HTTP_REQ_DONE;
}
}
else
{
// Check if Transfer-Encoding is chunked.
const char *tranenc = http->request.headers->getstr(
http->request.headers, "Transfer-Encoding", false);
if (tranenc != NULL && !strcmp(tranenc, "chunked"))
{
// TODO: handle chunked encoding
for (;;)
{
ssize_t chunksize = parse_chunked_body(http, in);
if (chunksize > 0)
{
continue;
}
else if (chunksize == 0)
{
return RESTD_HTTP_REQ_DONE;
}
else if (chunksize == -1)
{
return http->request.status;
}
else
{
return RESTD_HTTP_ERROR;
}
}
}
else
{
return RESTD_HTTP_REQ_DONE;
}
}
return http->request.status;
}
/**
* Parse chunked body and append it to inbuf.
*
* @return number of bytes in a chunk. so 0 for the ending chunk. -1 for not enough data, -2 format error.
*/
static ssize_t parse_chunked_body(restd_http_t *http, struct evbuffer *in)
{
// Peek chunk size.
size_t crlf_len = 0;
char *line = evbuffer_peekln(in, &crlf_len, EVBUFFER_EOL_CRLF);
if (line == NULL)
return -1; // not enough data.
size_t linelen = strlen(line);
// Parse chunk size
int chunksize = -1;
sscanf(line, "%x", &chunksize);
free(line);
if (chunksize < 0)
return -2; // format error
// Check if we've received whole data of this chunk.
size_t datalen = linelen + crlf_len + chunksize + crlf_len;
size_t inbuflen = evbuffer_get_length(in);
if (inbuflen < datalen)
{
return -1; // not enough data.
}
// Copy chunk body
evbuffer_drainln(in, NULL, EVBUFFER_EOL_CRLF);
http_add_inbuf(in, http, chunksize);
evbuffer_drainln(in, NULL, EVBUFFER_EOL_CRLF);
return chunksize;
}
/**
* validate file path
*/
static bool isValidPathname(const char *path)
{
if (path == NULL)
return false;
int len = strlen(path);
if (len == 0 || len >= PATH_MAX)
return false;
else if (path[0] != '/')
return false;
else if (strpbrk(path, "\\:*?\"<>|") != NULL)
return false;
// check folder name length
int n;
char *t;
for (n = 0, t = (char *)path; *t != '\0'; t++)
{
if (*t == '/')
{
n = 0;
continue;
}
if (n >= FILENAME_MAX)
{
DEBUG("Filename too long.");
return false;
}
n++;
}
return true;
}
/**
* Correct pathname.
*
* @note
* remove : heading & tailing white spaces, double slashes, tailing slash
*/
static void correctPathname(char *path)
{
// Take care of head & tail white spaces.
qstrtrim(path);
// Take care of double slashes.
while (strstr(path, "//") != NULL)
qstrreplace("sr", path, "//", "/");
// Take care of tailing slash.
int len = strlen(path);
if (len <= 1)
return;
if (path[len - 1] == '/')
path[len - 1] = '\0';
}
/*--------------------------------------------------------------------------*/
static char *evbuffer_peekln(struct evbuffer *buffer, size_t *n_rerestd_out,
enum evbuffer_eol_style eol_style)
{
// Check if first line has arrived.
struct evbuffer_ptr ptr = evbuffer_search_eol(buffer, NULL, n_rerestd_out,
eol_style);
if (ptr.pos == -1)
return NULL;
char *line = (char *)malloc(ptr.pos + 1);
if (line == NULL)
return NULL;
// Linearizes buffer
if (ptr.pos > 0)
{
char *bufferptr = (char *)evbuffer_pullup(buffer, ptr.pos);
ASSERT(bufferptr != NULL);
strncpy(line, bufferptr, ptr.pos);
}
line[ptr.pos] = '\0';
return line;
}
/*--------------------------------------------------------------------------*/
static ssize_t evbuffer_drainln(struct evbuffer *buffer, size_t *n_rerestd_out,
enum evbuffer_eol_style eol_style)
{
char *line = evbuffer_readln(buffer, n_rerestd_out, eol_style);
if (line == NULL)
return -1;
size_t linelen = strlen(line);
free(line);
return linelen;
}
#endif // _DOXYGEN_SKIP

View File

@@ -1,284 +0,0 @@
/*!
* restd_rest_handler.c
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 23/12/2019
*
*/
// This is an independent project of an individual developer. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com
/*------------------------------- INCLUDES ----------------------------------*/
#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <event2/buffer.h>
#include "restd_server.h"
#include "restd_http_handler.h"
#include "restd_rest_handler.h"
#include "macro.h"
/*--------------------------------------------------------------------------*/
struct mimetype_s
{
const char *extn;
const char *mime;
};
/*--------------------------------------------------------------------------*/
static const struct mimetype_s g_mime_types[] = {
{"txt", "text/plain"},
{"log", "text/plain"},
{"js", "text/javascript"},
{"css", "text/css"},
{"htm", "text/html"},
{"html", "text/html"},
{"diff", "text/x-patch"},
{"patch", "text/x-patch"},
{"c", "text/x-csrc"},
{"h", "text/x-chdr"},
{"o", "text/x-object"},
{"ko", "text/x-object"},
{"bmp", "image/bmp"},
{"gif", "image/gif"},
{"png", "image/png"},
{"jpg", "image/jpeg"},
{"jpeg", "image/jpeg"},
{"svg", "image/svg+xml"},
{"json", "application/json"},
{"jsonp", "application/javascript"},
{"zip", "application/zip"},
{"pdf", "application/pdf"},
{"xml", "application/xml"},
{"xsl", "application/xml"},
{"doc", "application/msword"},
{"ppt", "application/vnd.ms-powerpoint"},
{"xls", "application/vnd.ms-excel"},
{"odt", "application/vnd.oasis.opendocument.text"},
{"odp", "application/vnd.oasis.opendocument.presentation"},
{"pl", "application/x-perl"},
{"sh", "application/x-shellscript"},
{"php", "application/x-php"},
{"deb", "application/x-deb"},
{"iso", "application/x-cd-image"},
{"tar.gz", "application/x-compressed-tar"},
{"tgz", "application/x-compressed-tar"},
{"gz", "application/x-gzip"},
{"tar.bz2", "application/x-bzip-compressed-tar"},
{"tbz", "application/x-bzip-compressed-tar"},
{"bz2", "application/x-bzip"},
{"tar", "application/x-tar"},
{"rar", "application/x-rar-compressed"},
{"mp3", "audio/mpeg"},
{"ogg", "audio/x-vorbis+ogg"},
{"wav", "audio/x-wav"},
{"mpg", "video/mpeg"},
{"mpeg", "video/mpeg"},
{"avi", "video/x-msvideo"},
{"README", "text/plain"},
{"log", "text/plain"},
{"cfg", "text/plain"},
{"conf", "text/plain"},
{"pac", "application/x-ns-proxy-autoconfig"},
{"wpad.dat", "application/x-ns-proxy-autoconfig"},
{"appcache", "text/cache-manifest"},
{"manifest", "text/cache-manifest"},
{NULL, NULL}};
/*--------------------------------------------------------------------------*/
static const char *file_mime_lookup(const char *path)
{
const struct mimetype_s *m = &g_mime_types[0];
const char *e;
while (m->extn)
{
e = &path[strlen(path) - 1];
while (e >= path)
{
if ((*e == '.' || *e == '/') && !strcasecmp(&e[1], m->extn))
return m->mime;
e--;
}
m++;
}
return "application/octet-stream";
}
/*--------------------------------------------------------------------------*/
static bool contain(const char *src, const char *dest, int len)
{
int len_src, len_dest;
int i = 0;
len_src = strlen(src);
if (len_src < len)
return false;
len_dest = strlen(dest);
if (len_dest < len)
return false;
while (i < len)
{
if (src[i] != dest[i])
return false;
i++;
}
return true;
}
/*! ----------------------------------------------------------------------------
* @fn restd_rest_handler
*
* @brief Main function to manage rest request.
*/
int restd_rest_handler(short event, restd_conn_t *conn)
{
if (event & RESTD_EVENT_INIT)
{
DEBUG("==> HTTP INIT");
restd_http_t *http = http_new(conn->out);
if (http == NULL)
return RESTD_CLOSE;
restd_conn_set_extra(conn, http, http_free_cb);
return RESTD_OK;
}
else if (event & RESTD_EVENT_CLOSE)
{
DEBUG("==> HTTP CLOSE=%x (TIMEOUT=%d, SHUTDOWN=%d)",
event, event & RESTD_EVENT_TIMEOUT, event & RESTD_EVENT_SHUTDOWN);
return RESTD_OK;
}
else if ((event & RESTD_EVENT_READ) || (event & RESTD_EVENT_WRITE))
{
restd_http_t *http = (restd_http_t *)restd_conn_get_extra(conn);
int status = http_parser(http, conn->in);
if (conn->method == NULL && http->request.method != NULL)
{
restd_conn_set_method(conn, http->request.method);
}
DEBUG("==> HTTP READ || HTTP WRITE");
int reason = RESTD_ERROR_PATH_NOT_FOUND;
DEBUG("********restd_rest_handler: event 0x%x", event);
char *root_path;
qlist_t *hooks = conn->server->hooks;
qlist_obj_t obj;
bzero((void *)&obj, sizeof(qlist_obj_t));
while (hooks->getnext(hooks, &obj, false) == true)
{
restd_hook_t *hook = (restd_hook_t *)obj.data;
if (hook->cb)
{
printf("==== call_hooks: method: %s - %s \n", hook->method, conn->method);
printf("==== call_hooks: path: %s - %s\n", hook->path, http->request.path);
printf("==== HOOK FOUND !!!!\n");
if ((hook->method == NULL) || (conn->method == NULL) || (strcmp(hook->method, conn->method) != 0))
{
printf("==== Hook found but method failed -> next.\n");
reason = RESTD_ERROR_METHOD_NOT_ALLOWED;
continue;
}
if ((hook->path != NULL) && (http->request.path != NULL))
{
int i = 0;
int pos = -1;
while (hook->path[i])
{
if (hook->path[i] == ':')
pos = i;
i++;
}
if (pos != -1 && contain(hook->path, http->request.path, pos))
{
const char *buffer = &http->request.path[pos];
// printf("buffer: <%s>\n", buffer);
conn->id = atoi(buffer);
return hook->cb(event, conn, hook->userdata);
}
else
{
int rett = strcmp(hook->path, http->request.path);
if (rett == 0)
return hook->cb(event, conn, hook->userdata);
}
}
}
}
// No Hook Found check if it's a real file into document root.
root_path = restd_server_get_option(conn->server, "server.root_path");
if ((root_path != NULL) && (strlen(root_path) != 0))
{
FILE *file;
char buf[1024] = "";
qstrcatf(buf, "%s%s", root_path, http->request.path);
if ((file = fopen(buf, "r")))
{
long fsize;
char *file_buf;
fseek(file, 0, SEEK_END);
fsize = ftell(file);
fseek(file, 0, SEEK_SET);
file_buf = malloc(fsize + 1);
fread(file_buf, 1, fsize, file);
fclose(file);
file_buf[fsize] = 0;
printf("mime type:%s\n", file_mime_lookup(buf));
restd_http_response(conn, 200, file_mime_lookup(buf), file_buf, fsize);
return RESTD_CLOSE;
}
}
if (conn->server->error_handler != NULL)
return conn->server->error_handler(reason, conn, NULL);
return RESTD_CLOSE;
}
BUG_EXIT();
return RESTD_CLOSE;
}

View File

@@ -1,837 +0,0 @@
/*!
* restd_server.c
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 23/12/2019
*
*/
// This is an independent project of an individual developer. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com
/*------------------------------- INCLUDES ----------------------------------*/
#include <stdbool.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/listener.h>
#ifdef __linux__
#include <sys/eventfd.h>
#else
#include <sys/event.h>
#endif
#include "macro.h"
#include "restd_server.h"
/*
* Local variables.
*/
static bool initialized = false;
/*
* Global variables.
*/
int _restd_log_level = RESTD_LOG_WARN;
/*
* Local functions.
*/
static int notify_loopexit(restd_server_t *server);
static void notify_cb(struct bufferevent *buffer, void *userdata);
static void *server_loop(void *instance);
static void close_server(restd_server_t *server);
static void libevent_log_cb(int severity, const char *msg);
static int set_undefined_options(restd_server_t *server);
static void listener_cb(struct evconnlistener *listener,
evutil_socket_t evsocket, struct sockaddr *sockaddr,
int socklen, void *userdata);
static restd_conn_t *conn_new(restd_server_t *server, struct bufferevent *buffer);
static void conn_reset(restd_conn_t *conn);
static void conn_free(restd_conn_t *conn);
static void conn_read_cb(struct bufferevent *buffer, void *userdata);
static void conn_write_cb(struct bufferevent *buffer, void *userdata);
static void conn_event_cb(struct bufferevent *buffer, short what, void *userdata);
static void conn_cb(restd_conn_t *conn, int event);
static int call_hooks(short event, restd_conn_t *conn);
static void *set_userdata(restd_conn_t *conn, int index, const void *userdata, restd_userdata_free_cb free_cb);
static void *get_userdata(restd_conn_t *conn, int index);
/**
* Server option names and default values.
*/
#define RESTD_SERVER_OPTIONS { \
{"server.port", "8888"}, \
\
{"server.root_path", ""}, \
\
/* Addr format IPv4="1.2.3.4", IPv6="1:2:3:4:5:6", Unix="/path" */ \
{"server.addr", "0.0.0.0"}, \
\
{"server.backlog", "128"}, \
\
/* Set read timeout seconds. 0 means no timeout. */ \
{"server.timeout", "0"}, \
\
/* Enable or disable request pipelining, this change AD_DONE's behavior */ \
{"server.request_pipelining", "1"}, \
\
/* Run server in a separate thread */ \
{"server.thread", "0"}, \
\
/* Collect resources after stop */ \
{"server.free_on_stop", "1"}, \
\
/* End of array marker. Do not remove */ \
{"", "_END_"}};
/*------------------------------- FUNCTIONS ----------------------------------*/
/**
* Set debug output level.
*
* @param debug_level debug output level. 0 to disable.
*
* @return previous debug level.
*
* @note
* debug_level:
* REST_LOG_DISABLE
* REST_LOG_ERROR
* REST_LOG_WARN (default)
* REST_LOG_INFO
* REST_LOG_DEBUG
* REST_LOG_DEBUG2
*/
enum restd_log_e restd_log_level(enum restd_log_e log_level)
{
int prev = _restd_log_level;
_restd_log_level = log_level;
return prev;
}
/**
* Create a server object.
*/
restd_server_t *restd_server_new(void)
{
if (initialized)
{
initialized = true;
}
restd_server_t *server = NEW_OBJECT(restd_server_t);
if (server == NULL)
{
return NULL;
}
// Initialize instance.
server->options = qhashtbl(0, 0);
server->stats = qhashtbl(100, QHASHTBL_THREADSAFE);
server->hooks = qlist(0);
server->call_hooks = call_hooks;
if (server->options == NULL || server->stats == NULL || server->hooks == NULL)
{
restd_server_free(server);
return NULL;
}
DEBUG("Created a server object.");
return server;
}
/**
* Release server object and all the resources.
*/
void restd_server_free(restd_server_t *server)
{
if (server == NULL)
return;
int thread = restd_server_get_option_int(server, "server.thread");
if (thread && server->thread)
{
notify_loopexit(server);
sleep(1);
close_server(server);
}
if (server->evbase)
{
event_base_free(server->evbase);
}
if (server->options)
{
server->options->free(server->options);
}
if (server->stats)
{
server->stats->free(server->stats);
}
if (server->hooks)
{
qlist_t *tbl = server->hooks;
restd_hook_t *hook;
while ((hook = tbl->popfirst(tbl, NULL)))
{
if (hook->method)
free(hook->method);
if (hook->path)
free(hook->path);
free(hook);
}
server->hooks->free(server->hooks);
}
free(server);
DEBUG("Server terminated.");
}
/**
* Start server.
*
* @return 0 if successful, otherwise -1.
*/
int restd_server_start(restd_server_t *server)
{
DEBUG("Starting a server.");
// Set default options that were not set by user..
set_undefined_options(server);
// Hookup libevent's log message.
if (_restd_log_level >= RESTD_LOG_DEBUG)
{
event_set_log_callback(libevent_log_cb);
if (_restd_log_level >= RESTD_LOG_DEBUG2)
{
event_enable_debug_mode();
}
}
// Parse addr
int port = restd_server_get_option_int(server, "server.port");
char *addr = restd_server_get_option(server, "server.addr");
struct sockaddr *sockaddr = NULL;
size_t sockaddr_len = 0;
if (addr[0] == '/')
{ // Unix socket.
struct sockaddr_un unixaddr;
bzero((void *)&unixaddr, sizeof(struct sockaddr_un));
if (strlen(addr) >= sizeof(unixaddr.sun_path))
{
errno = EINVAL;
DEBUG("Too long unix socket name. '%s'", addr);
return -1;
}
unixaddr.sun_family = AF_UNIX;
strcpy(unixaddr.sun_path, addr); // no need of strncpy()
sockaddr = (struct sockaddr *)&unixaddr;
sockaddr_len = sizeof(unixaddr);
}
else if (strstr(addr, ":"))
{ // IPv6
struct sockaddr_in6 ipv6addr;
bzero((void *)&ipv6addr, sizeof(struct sockaddr_in6));
ipv6addr.sin6_family = AF_INET6;
ipv6addr.sin6_port = htons(port);
evutil_inet_pton(AF_INET6, addr, &ipv6addr.sin6_addr);
sockaddr = (struct sockaddr *)&ipv6addr;
sockaddr_len = sizeof(ipv6addr);
}
else
{ // IPv4
struct sockaddr_in ipv4addr;
bzero((void *)&ipv4addr, sizeof(struct sockaddr_in));
ipv4addr.sin_family = AF_INET;
ipv4addr.sin_port = htons(port);
ipv4addr.sin_addr.s_addr =
(IS_EMPTY_STR(addr)) ? INADDR_ANY : inet_addr(addr);
sockaddr = (struct sockaddr *)&ipv4addr;
sockaddr_len = sizeof(ipv4addr);
}
// Bind
if (!server->evbase)
{
server->evbase = event_base_new();
if (!server->evbase)
{
ERROR("Failed to create a new event base.");
return -1;
}
}
// Create a eventfd for notification channel.
#ifdef __linux__
int notifyfd = eventfd(0, 0);
#else
int notifyfd = kqueue();
#endif
server->notify_buffer = bufferevent_socket_new(server->evbase, notifyfd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(server->notify_buffer, NULL, notify_cb, NULL, server);
if (!server->listener)
{
server->listener = evconnlistener_new_bind(
server->evbase, listener_cb, (void *)server,
LEV_OPT_THREADSAFE | LEV_OPT_REUSEABLE | LEV_OPT_CLOSE_ON_FREE,
restd_server_get_option_int(server, "server.backlog"),
sockaddr, sockaddr_len);
if (!server->listener)
{
ERROR("Failed to bind on %s:%d", addr, port);
return -1;
}
}
// Listen
INFO("Listening on %s:%d", addr, port);
int exitstatus = 0;
if (restd_server_get_option_int(server, "server.thread"))
{
DEBUG("Launching server as a thread.")
server->thread = NEW_OBJECT(pthread_t);
pthread_create(server->thread, NULL, &server_loop, (void *)server);
//pthread_detach(server->thread);
}
else
{
int *retval = server_loop(server);
exitstatus = *retval;
free(retval);
close_server(server);
if (restd_server_get_option_int(server, "server.free_on_stop"))
{
restd_server_free(server);
}
}
return exitstatus;
}
/*--------------------------------------------------------------------------*/
int restd_server_attach_event_loop(restd_server_t *server, struct event_base *ev_base)
{
if (server == NULL)
return -1;
server->evbase = ev_base;
return 0;
}
/**
* Set server option.
*
* @see AD_SERVER_OPTIONS
*/
void restd_server_set_option(restd_server_t *server, const char *key, const char *value)
{
server->options->putstr(server->options, key, value);
}
/**
* Retrieve server option.
*/
char *restd_server_get_option(restd_server_t *server, const char *key)
{
return server->options->getstr(server->options, key, false);
}
/**
* Retrieve server option in integer format.
*/
int restd_server_get_option_int(restd_server_t *server, const char *key)
{
char *value = restd_server_get_option(server, key);
return (value) ? atoi(value) : 0;
}
/**
* If there's no event, loopbreak or loopexit call won't work until one more
* event arrived. So we use eventfd as a internal notification channel to let
* server get out of the loop without waiting for an event.
*/
static int notify_loopexit(restd_server_t *server)
{
uint64_t x = 0;
return bufferevent_write(server->notify_buffer, &x, sizeof(uint64_t));
}
/*--------------------------------------------------------------------------*/
static void notify_cb(struct bufferevent *buffer, void *userdata)
{
restd_server_t *server = (restd_server_t *)userdata;
event_base_loopexit(server->evbase, NULL);
DEBUG("Existing loop.");
}
/*--------------------------------------------------------------------------*/
static void *server_loop(void *instance)
{
restd_server_t *server = (restd_server_t *)instance;
int *retval = NEW_OBJECT(int);
DEBUG("Loop start");
event_base_loop(server->evbase, 0);
DEBUG("Loop finished");
*retval = (event_base_got_break(server->evbase)) ? -1 : 0;
return retval;
}
/*--------------------------------------------------------------------------*/
static void close_server(restd_server_t *server)
{
DEBUG("Closing server.");
if (server->notify_buffer)
{
bufferevent_free(server->notify_buffer);
server->notify_buffer = NULL;
}
if (server->listener)
{
evconnlistener_free(server->listener);
server->listener = NULL;
}
if (server->thread)
{
void *retval = NULL;
DEBUG("Waiting server's last loop to finish.");
pthread_join(*(server->thread), &retval);
free(retval);
free(server->thread);
server->thread = NULL;
}
INFO("Server closed.");
}
/*--------------------------------------------------------------------------*/
static void libevent_log_cb(int severity, const char *msg)
{
switch (severity)
{
case _EVENT_LOG_MSG:
{
INFO("%s", msg);
break;
}
case _EVENT_LOG_WARN:
{
WARN("%s", msg);
break;
}
case _EVENT_LOG_ERR:
{
ERROR("%s", msg);
break;
}
default:
{
DEBUG("%s", msg);
break;
}
}
}
/*--------------------------------------------------------------------------*/
// Set default options that were not set by user..
static int set_undefined_options(restd_server_t *server)
{
int newentries = 0;
char *default_options[][2] = RESTD_SERVER_OPTIONS;
for (int i = 0; !IS_EMPTY_STR(default_options[i][0]); i++)
{
if (!restd_server_get_option(server, default_options[i][0]))
{
restd_server_set_option(server, default_options[i][0], default_options[i][1]);
newentries++;
}
DEBUG("%s=%s", default_options[i][0], restd_server_get_option(server, default_options[i][0]));
}
return newentries;
}
/*--------------------------------------------------------------------------*/
static void listener_cb(struct evconnlistener *listener, evutil_socket_t socket,
struct sockaddr *sockaddr, int socklen, void *userdata)
{
DEBUG("New connection.");
restd_server_t *server = (restd_server_t *)userdata;
// Create a new buffer.
struct bufferevent *buffer = NULL;
buffer = bufferevent_socket_new(server->evbase, socket, BEV_OPT_CLOSE_ON_FREE);
if (buffer == NULL)
goto error;
// Set read timeout.
int timeout = restd_server_get_option_int(server, "server.timeout");
if (timeout > 0)
{
struct timeval tm;
bzero((void *)&tm, sizeof(struct timeval));
tm.tv_sec = timeout;
bufferevent_set_timeouts(buffer, &tm, NULL);
}
// Create a connection.
void *conn = conn_new(server, buffer);
if (!conn)
goto error;
return;
error:
if (buffer)
bufferevent_free(buffer);
ERROR("Failed to create a connection handler.");
event_base_loopbreak(server->evbase);
server->errcode = ENOMEM;
}
/*--------------------------------------------------------------------------*/
static restd_conn_t *conn_new(restd_server_t *server, struct bufferevent *buffer)
{
if (server == NULL || buffer == NULL)
{
return NULL;
}
// Create a new connection container.
restd_conn_t *conn = NEW_OBJECT(restd_conn_t);
if (conn == NULL)
return NULL;
// Initialize with default values.
conn->server = server;
conn->buffer = buffer;
conn->in = bufferevent_get_input(buffer);
conn->out = bufferevent_get_output(buffer);
conn_reset(conn);
// Bind callback
bufferevent_setcb(buffer, conn_read_cb, conn_write_cb, conn_event_cb, (void *)conn);
bufferevent_setwatermark(buffer, EV_WRITE, 0, 0);
bufferevent_enable(buffer, EV_WRITE);
bufferevent_enable(buffer, EV_READ);
// Run callbacks with AD_EVENT_INIT event.
conn->status = conn->server->call_hooks(RESTD_EVENT_INIT | RESTD_EVENT_WRITE, conn);
//
conn->id = -1;
return conn;
}
/*--------------------------------------------------------------------------*/
static void conn_reset(restd_conn_t *conn)
{
conn->status = RESTD_OK;
for (int i = 0; i < RESTD_NUM_USERDATA; i++)
{
if (conn->userdata[i])
{
if (conn->userdata_free_cb[i] != NULL)
{
conn->userdata_free_cb[i](conn, conn->userdata[i]);
}
else
{
WARN("Found unreleased userdata.");
}
conn->userdata[i] = NULL;
}
}
if (conn->method)
{
free(conn->method);
conn->method = NULL;
}
}
/*--------------------------------------------------------------------------*/
static void conn_free(restd_conn_t *conn)
{
if (conn)
{
if (conn->status != RESTD_CLOSE)
{
conn->server->call_hooks(RESTD_EVENT_CLOSE | RESTD_EVENT_SHUTDOWN, conn);
}
conn_reset(conn);
if (conn->buffer)
{
bufferevent_free(conn->buffer);
}
free(conn);
}
}
/*--------------------------------------------------------------------------*/
#define DRAIN_EVBUFFER(b) evbuffer_drain(b, evbuffer_get_length(b))
static void conn_read_cb(struct bufferevent *buffer, void *userdata)
{
DEBUG("read_cb");
restd_conn_t *conn = userdata;
conn_cb(conn, RESTD_EVENT_READ);
}
/*--------------------------------------------------------------------------*/
static void conn_write_cb(struct bufferevent *buffer, void *userdata)
{
DEBUG("write_cb");
restd_conn_t *conn = userdata;
conn_cb(conn, RESTD_EVENT_WRITE);
}
/*--------------------------------------------------------------------------*/
static void conn_event_cb(struct bufferevent *buffer, short what, void *userdata)
{
DEBUG("event_cb 0x%x", what);
restd_conn_t *conn = userdata;
if (what & BEV_EVENT_EOF || what & BEV_EVENT_ERROR || what & BEV_EVENT_TIMEOUT)
{
conn->status = RESTD_CLOSE;
conn_cb(conn, RESTD_EVENT_CLOSE | ((what & BEV_EVENT_TIMEOUT) ? RESTD_EVENT_TIMEOUT : 0));
}
}
/*--------------------------------------------------------------------------*/
static void conn_cb(restd_conn_t *conn, int event)
{
DEBUG("conn_cb: status:0x%x, event:0x%x", conn->status, event)
if (conn->status == RESTD_OK || conn->status == RESTD_TAKEOVER)
{
int status = conn->server->call_hooks(event, conn);
// Update status only when it's higher then before.
if (!(conn->status == RESTD_CLOSE || (conn->status == RESTD_DONE && conn->status >= status)))
{
conn->status = status;
}
}
if (conn->status == RESTD_DONE)
{
if (restd_server_get_option_int(conn->server, "server.request_pipelining"))
{
conn->server->call_hooks(RESTD_EVENT_CLOSE, conn);
conn_reset(conn);
conn->server->call_hooks(RESTD_EVENT_INIT, conn);
}
else
{
// Do nothing but drain input buffer.
if (event == RESTD_EVENT_READ)
{
DEBUG("Draining in-buffer. %d", conn->status);
DRAIN_EVBUFFER(conn->in);
}
}
return;
}
else if (conn->status == RESTD_CLOSE)
{
if (evbuffer_get_length(conn->out) <= 0)
{
int newevent = (event & RESTD_EVENT_CLOSE) ? event : RESTD_EVENT_CLOSE;
//conn->server->call_hooks(newevent, conn);
conn_free(conn);
DEBUG("Connection closed.");
return;
}
}
}
/*--------------------------------------------------------------------------*/
void restd_server_register_request_handler(restd_server_t *server, restd_callback cb)
{
server->request_handler = cb;
}
/*--------------------------------------------------------------------------*/
void restd_server_register_error_handler(restd_server_t *server, restd_callback cb)
{
server->error_handler = cb;
}
/*--------------------------------------------------------------------------*/
void restd_server_register_call_hooks_handler(restd_server_t *server, restd_call_hook_cb cb)
{
server->call_hooks = cb;
}
/*--------------------------------------------------------------------------*/
/**
* Register user hook.
*/
void restd_server_register_hook(restd_server_t *server, restd_callback cb, void *userdata)
{
restd_server_register_hook_on_method(server, NULL, cb, userdata);
}
/*--------------------------------------------------------------------------*/
/**
* Register user hook on method name.
*/
void restd_server_register_hook_on_method(restd_server_t *server, const char *method, restd_callback cb, void *userdata)
{
restd_server_register_hook_on_path(server, method, NULL, cb, userdata);
}
/*--------------------------------------------------------------------------*/
void restd_server_register_hook_on_path(restd_server_t *server, const char *method, const char *path, restd_callback cb, void *userdata)
{
restd_hook_t hook;
bzero((void *)&hook, sizeof(restd_hook_t));
hook.method = (method) ? strdup(method) : NULL;
hook.path = (path) ? strdup(path) : NULL;
hook.cb = cb;
hook.userdata = userdata;
server->hooks->addlast(server->hooks, (void *)&hook, sizeof(restd_hook_t));
}
/*--------------------------------------------------------------------------*/
static int call_hooks(short event, restd_conn_t *conn)
{
DEBUG("call_hooks: event 0x%x", event);
qlist_t *hooks = conn->server->hooks;
qlist_obj_t obj;
bzero((void *)&obj, sizeof(qlist_obj_t));
while (hooks->getnext(hooks, &obj, false) == true)
{
restd_hook_t *hook = (restd_hook_t *)obj.data;
if (hook->cb)
{
if (hook->method && conn->method && strcmp(hook->method, conn->method))
{
continue;
}
int status = hook->cb(event, conn, hook->userdata);
if (status != RESTD_OK)
{
return status;
}
}
}
return RESTD_OK;
}
/*--------------------------------------------------------------------------*/
static void *set_userdata(restd_conn_t *conn, int index, const void *userdata, restd_userdata_free_cb free_cb)
{
void *prev = conn->userdata;
conn->userdata[index] = (void *)userdata;
conn->userdata_free_cb[index] = free_cb;
return prev;
}
/*--------------------------------------------------------------------------*/
static void *get_userdata(restd_conn_t *conn, int index)
{
return conn->userdata[index];
}
/*--------------------------------------------------------------------------*/
/**
* Set extra userdata into the connection.
*
* @return previous userdata;
*
* @note
* Extra userdata is for default protocol handler such as ad_http_handler to
* provide higher abstraction. End users should always use only ad_conn_set_userdata()
* to avoid any conflict with default handlers.
*/
void *restd_conn_set_extra(restd_conn_t *conn, const void *extra, restd_userdata_free_cb free_cb)
{
return set_userdata(conn, 1, extra, free_cb);
}
/**
* Get extra userdata attached in this connection.
*/
void *restd_conn_get_extra(restd_conn_t *conn)
{
return get_userdata(conn, 1);
}
/**
* Set method name on this connection.
*
* Once the method name is set, hooks registered by ad_server_register_hook_on_method()
* will be called if method name matches with the registered name.
*
* @see ad_server_register_hook_on_method()
*/
void restd_conn_set_method(restd_conn_t *conn, char *method)
{
char *prev = conn->method;
conn->method = (method != NULL) ? strdup(method) : NULL;
if (prev)
{
free(prev);
}
}

View File

@@ -1,580 +0,0 @@
/*
A trivial static http webserver using Libevent's evhttp.
This is not the best code in the world, and it does some fairly stupid stuff
that you would never want to do in a production webserver. Caveat hackor!
*/
/* Compatibility for possible missing IPv6 declarations */
#include "../util-internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include <getopt.h>
#include <io.h>
#include <fcntl.h>
#ifndef S_ISDIR
#define S_ISDIR(x) (((x) & S_IFMT) == S_IFDIR)
#endif
#else /* !_WIN32 */
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#endif /* _WIN32 */
#include <signal.h>
#ifdef EVENT__HAVE_SYS_UN_H
#include <sys/un.h>
#endif
#ifdef EVENT__HAVE_AFUNIX_H
#include <afunix.h>
#endif
#include <event2/event.h>
#include <event2/http.h>
#include <event2/listener.h>
#include <event2/buffer.h>
#include <event2/util.h>
#include <event2/keyvalq_struct.h>
#ifdef _WIN32
#include <event2/thread.h>
#endif /* _WIN32 */
#ifdef EVENT__HAVE_NETINET_IN_H
#include <netinet/in.h>
# ifdef _XOPEN_SOURCE_EXTENDED
# include <arpa/inet.h>
# endif
#endif
#ifdef _WIN32
#ifndef stat
#define stat _stat
#endif
#ifndef fstat
#define fstat _fstat
#endif
#ifndef open
#define open _open
#endif
#ifndef close
#define close _close
#endif
#ifndef O_RDONLY
#define O_RDONLY _O_RDONLY
#endif
#endif /* _WIN32 */
char uri_root[512];
static const struct table_entry {
const char *extension;
const char *content_type;
} content_type_table[] = {
{ "txt", "text/plain" },
{ "c", "text/plain" },
{ "h", "text/plain" },
{ "html", "text/html" },
{ "htm", "text/htm" },
{ "css", "text/css" },
{ "gif", "image/gif" },
{ "jpg", "image/jpeg" },
{ "jpeg", "image/jpeg" },
{ "png", "image/png" },
{ "pdf", "application/pdf" },
{ "ps", "application/postscript" },
{ NULL, NULL },
};
struct options {
int port;
int iocp;
int verbose;
int unlink;
const char *unixsock;
const char *docroot;
};
/* Try to guess a good content-type for 'path' */
static const char *
guess_content_type(const char *path)
{
const char *last_period, *extension;
const struct table_entry *ent;
last_period = strrchr(path, '.');
if (!last_period || strchr(last_period, '/'))
goto not_found; /* no exension */
extension = last_period + 1;
for (ent = &content_type_table[0]; ent->extension; ++ent) {
if (!evutil_ascii_strcasecmp(ent->extension, extension))
return ent->content_type;
}
not_found:
return "application/misc";
}
/* Callback used for the /dump URI, and for every non-GET request:
* dumps all information to stdout and gives back a trivial 200 ok */
static void
dump_request_cb(struct evhttp_request *req, void *arg)
{
const char *cmdtype;
struct evkeyvalq *headers;
struct evkeyval *header;
struct evbuffer *buf;
switch (evhttp_request_get_command(req)) {
case EVHTTP_REQ_GET: cmdtype = "GET"; break;
case EVHTTP_REQ_POST: cmdtype = "POST"; break;
case EVHTTP_REQ_HEAD: cmdtype = "HEAD"; break;
case EVHTTP_REQ_PUT: cmdtype = "PUT"; break;
case EVHTTP_REQ_DELETE: cmdtype = "DELETE"; break;
case EVHTTP_REQ_OPTIONS: cmdtype = "OPTIONS"; break;
case EVHTTP_REQ_TRACE: cmdtype = "TRACE"; break;
case EVHTTP_REQ_CONNECT: cmdtype = "CONNECT"; break;
case EVHTTP_REQ_PATCH: cmdtype = "PATCH"; break;
default: cmdtype = "unknown"; break;
}
printf("Received a %s request for %s\nHeaders:\n",
cmdtype, evhttp_request_get_uri(req));
headers = evhttp_request_get_input_headers(req);
for (header = headers->tqh_first; header;
header = header->next.tqe_next) {
printf(" %s: %s\n", header->key, header->value);
}
buf = evhttp_request_get_input_buffer(req);
puts("Input data: <<<");
while (evbuffer_get_length(buf)) {
int n;
char cbuf[128];
n = evbuffer_remove(buf, cbuf, sizeof(cbuf));
if (n > 0)
(void) fwrite(cbuf, 1, n, stdout);
}
puts(">>>");
evhttp_send_reply(req, 200, "OK", NULL);
}
/* This callback gets invoked when we get any http request that doesn't match
* any other callback. Like any evhttp server callback, it has a simple job:
* it must eventually call evhttp_send_error() or evhttp_send_reply().
*/
static void
send_document_cb(struct evhttp_request *req, void *arg)
{
struct evbuffer *evb = NULL;
struct options *o = arg;
const char *uri = evhttp_request_get_uri(req);
struct evhttp_uri *decoded = NULL;
const char *path;
char *decoded_path;
char *whole_path = NULL;
size_t len;
int fd = -1;
struct stat st;
if (evhttp_request_get_command(req) != EVHTTP_REQ_GET) {
dump_request_cb(req, arg);
return;
}
printf("Got a GET request for <%s>\n", uri);
/* Decode the URI */
decoded = evhttp_uri_parse(uri);
if (!decoded) {
printf("It's not a good URI. Sending BADREQUEST\n");
evhttp_send_error(req, HTTP_BADREQUEST, 0);
return;
}
/* Let's see what path the user asked for. */
path = evhttp_uri_get_path(decoded);
if (!path) path = "/";
/* We need to decode it, to see what path the user really wanted. */
decoded_path = evhttp_uridecode(path, 0, NULL);
if (decoded_path == NULL)
goto err;
/* Don't allow any ".."s in the path, to avoid exposing stuff outside
* of the docroot. This test is both overzealous and underzealous:
* it forbids aceptable paths like "/this/one..here", but it doesn't
* do anything to prevent symlink following." */
if (strstr(decoded_path, ".."))
goto err;
len = strlen(decoded_path)+strlen(o->docroot)+2;
if (!(whole_path = malloc(len))) {
perror("malloc");
goto err;
}
evutil_snprintf(whole_path, len, "%s/%s", o->docroot, decoded_path);
if (stat(whole_path, &st)<0) {
goto err;
}
/* This holds the content we're sending. */
evb = evbuffer_new();
if (S_ISDIR(st.st_mode)) {
/* If it's a directory, read the comments and make a little
* index page */
#ifdef _WIN32
HANDLE d;
WIN32_FIND_DATAA ent;
char *pattern;
size_t dirlen;
#else
DIR *d;
struct dirent *ent;
#endif
const char *trailing_slash = "";
if (!strlen(path) || path[strlen(path)-1] != '/')
trailing_slash = "/";
#ifdef _WIN32
dirlen = strlen(whole_path);
pattern = malloc(dirlen+3);
memcpy(pattern, whole_path, dirlen);
pattern[dirlen] = '\\';
pattern[dirlen+1] = '*';
pattern[dirlen+2] = '\0';
d = FindFirstFileA(pattern, &ent);
free(pattern);
if (d == INVALID_HANDLE_VALUE)
goto err;
#else
if (!(d = opendir(whole_path)))
goto err;
#endif
evbuffer_add_printf(evb,
"<!DOCTYPE html>\n"
"<html>\n <head>\n"
" <meta charset='utf-8'>\n"
" <title>%s</title>\n"
" <base href='%s%s'>\n"
" </head>\n"
" <body>\n"
" <h1>%s</h1>\n"
" <ul>\n",
decoded_path, /* XXX html-escape this. */
path, /* XXX html-escape this? */
trailing_slash,
decoded_path /* XXX html-escape this */);
#ifdef _WIN32
do {
const char *name = ent.cFileName;
#else
while ((ent = readdir(d))) {
const char *name = ent->d_name;
#endif
evbuffer_add_printf(evb,
" <li><a href=\"%s\">%s</a>\n",
name, name);/* XXX escape this */
#ifdef _WIN32
} while (FindNextFileA(d, &ent));
#else
}
#endif
evbuffer_add_printf(evb, "</ul></body></html>\n");
#ifdef _WIN32
FindClose(d);
#else
closedir(d);
#endif
evhttp_add_header(evhttp_request_get_output_headers(req),
"Content-Type", "text/html");
} else {
/* Otherwise it's a file; add it to the buffer to get
* sent via sendfile */
const char *type = guess_content_type(decoded_path);
if ((fd = open(whole_path, O_RDONLY)) < 0) {
perror("open");
goto err;
}
if (fstat(fd, &st)<0) {
/* Make sure the length still matches, now that we
* opened the file :/ */
perror("fstat");
goto err;
}
evhttp_add_header(evhttp_request_get_output_headers(req),
"Content-Type", type);
evbuffer_add_file(evb, fd, 0, st.st_size);
}
evhttp_send_reply(req, 200, "OK", evb);
goto done;
err:
evhttp_send_error(req, HTTP_NOTFOUND, NULL);
if (fd>=0)
close(fd);
done:
if (decoded)
evhttp_uri_free(decoded);
if (decoded_path)
free(decoded_path);
if (whole_path)
free(whole_path);
if (evb)
evbuffer_free(evb);
}
static void
print_usage(FILE *out, const char *prog, int exit_code)
{
fprintf(out,
"Syntax: %s [ OPTS ] <docroot>\n"
" -p - port\n"
" -U - bind to unix socket\n"
" -u - unlink unix socket before bind\n"
" -I - IOCP\n"
" -v - verbosity, enables libevent debug logging too\n", prog);
exit(exit_code);
}
static struct options
parse_opts(int argc, char **argv)
{
struct options o;
int opt;
memset(&o, 0, sizeof(o));
while ((opt = getopt(argc, argv, "hp:U:uIv")) != -1) {
switch (opt) {
case 'p': o.port = atoi(optarg); break;
case 'U': o.unixsock = optarg; break;
case 'u': o.unlink = 1; break;
case 'I': o.iocp = 1; break;
case 'v': ++o.verbose; break;
case 'h': print_usage(stdout, argv[0], 0); break;
default : fprintf(stderr, "Unknown option %c\n", opt); break;
}
}
if (optind >= argc || (argc - optind) > 1) {
print_usage(stdout, argv[0], 1);
}
o.docroot = argv[optind];
return o;
}
static void
do_term(int sig, short events, void *arg)
{
struct event_base *base = arg;
event_base_loopbreak(base);
fprintf(stderr, "Got %i, Terminating\n", sig);
}
static int
display_listen_sock(struct evhttp_bound_socket *handle)
{
struct sockaddr_storage ss;
evutil_socket_t fd;
ev_socklen_t socklen = sizeof(ss);
char addrbuf[128];
void *inaddr;
const char *addr;
int got_port = -1;
fd = evhttp_bound_socket_get_fd(handle);
memset(&ss, 0, sizeof(ss));
if (getsockname(fd, (struct sockaddr *)&ss, &socklen)) {
perror("getsockname() failed");
return 1;
}
if (ss.ss_family == AF_INET) {
got_port = ntohs(((struct sockaddr_in*)&ss)->sin_port);
inaddr = &((struct sockaddr_in*)&ss)->sin_addr;
} else if (ss.ss_family == AF_INET6) {
got_port = ntohs(((struct sockaddr_in6*)&ss)->sin6_port);
inaddr = &((struct sockaddr_in6*)&ss)->sin6_addr;
}
#ifdef EVENT__HAVE_STRUCT_SOCKADDR_UN
else if (ss.ss_family == AF_UNIX) {
printf("Listening on <%s>\n", ((struct sockaddr_un*)&ss)->sun_path);
return 0;
}
#endif
else {
fprintf(stderr, "Weird address family %d\n",
ss.ss_family);
return 1;
}
addr = evutil_inet_ntop(ss.ss_family, inaddr, addrbuf,
sizeof(addrbuf));
if (addr) {
printf("Listening on %s:%d\n", addr, got_port);
evutil_snprintf(uri_root, sizeof(uri_root),
"http://%s:%d",addr,got_port);
} else {
fprintf(stderr, "evutil_inet_ntop failed\n");
return 1;
}
return 0;
}
int
main(int argc, char **argv)
{
struct event_config *cfg = NULL;
struct event_base *base = NULL;
struct evhttp *http = NULL;
struct evhttp_bound_socket *handle = NULL;
struct evconnlistener *lev = NULL;
struct event *term = NULL;
struct options o = parse_opts(argc, argv);
int ret = 0;
#ifdef _WIN32
{
WORD wVersionRequested;
WSADATA wsaData;
wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
}
#else
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
ret = 1;
goto err;
}
#endif
setbuf(stdout, NULL);
setbuf(stderr, NULL);
/** Read env like in regress */
if (o.verbose || getenv("EVENT_DEBUG_LOGGING_ALL"))
event_enable_debug_logging(EVENT_DBG_ALL);
cfg = event_config_new();
#ifdef _WIN32
if (o.iocp) {
#ifdef EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED
evthread_use_windows_threads();
event_config_set_num_cpus_hint(cfg, 8);
#endif
event_config_set_flag(cfg, EVENT_BASE_FLAG_STARTUP_IOCP);
}
#endif
base = event_base_new_with_config(cfg);
if (!base) {
fprintf(stderr, "Couldn't create an event_base: exiting\n");
ret = 1;
}
event_config_free(cfg);
cfg = NULL;
/* Create a new evhttp object to handle requests. */
http = evhttp_new(base);
if (!http) {
fprintf(stderr, "couldn't create evhttp. Exiting.\n");
ret = 1;
}
/* The /dump URI will dump all requests to stdout and say 200 ok. */
evhttp_set_cb(http, "/dump", dump_request_cb, NULL);
/* We want to accept arbitrary requests, so we need to set a "generic"
* cb. We can also add callbacks for specific paths. */
evhttp_set_gencb(http, send_document_cb, &o);
if (o.unixsock) {
#ifdef EVENT__HAVE_STRUCT_SOCKADDR_UN
struct sockaddr_un addr;
if (o.unlink && (unlink(o.unixsock) && errno != ENOENT)) {
perror(o.unixsock);
ret = 1;
goto err;
}
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, o.unixsock);
lev = evconnlistener_new_bind(base, NULL, NULL,
LEV_OPT_CLOSE_ON_FREE, -1,
(struct sockaddr *)&addr, sizeof(addr));
if (!lev) {
perror("Cannot create listener");
ret = 1;
goto err;
}
handle = evhttp_bind_listener(http, lev);
if (!handle) {
fprintf(stderr, "couldn't bind to %s. Exiting.\n", o.unixsock);
ret = 1;
goto err;
}
#else /* !EVENT__HAVE_STRUCT_SOCKADDR_UN */
fprintf(stderr, "-U is not supported on this platform. Exiting.\n");
ret = 1;
goto err;
#endif /* EVENT__HAVE_STRUCT_SOCKADDR_UN */
}
else {
handle = evhttp_bind_socket_with_handle(http, "0.0.0.0", o.port);
if (!handle) {
fprintf(stderr, "couldn't bind to port %d. Exiting.\n", o.port);
ret = 1;
goto err;
}
}
if (display_listen_sock(handle)) {
ret = 1;
goto err;
}
term = evsignal_new(base, SIGINT, do_term, base);
if (!term)
goto err;
if (event_add(term, NULL))
goto err;
event_base_dispatch(base);
#ifdef _WIN32
WSACleanup();
#endif
err:
if (cfg)
event_config_free(cfg);
if (http)
evhttp_free(http);
if (term)
event_free(term);
if (base)
event_base_free(base);
return ret;
}

View File

@@ -1,37 +0,0 @@
#!/bin/sh
if [ $# = 0 ]; then
echo "This program is called by make. Please use \"make test\" command instead."
exit 1
fi
FAIL=0
FAILDESC=""
for EXECUTABLE in $*; do
./$EXECUTABLE
echo ""
if [ $? != 0 ]; then
FAIL=1
FAILDESC="$FAILDESC $EXECUTABLE"
fi
done
if [ $FAIL != 0 ]; then
echo "======================================================================"
echo "**** OOOOOPS!!! UNSUCESSFUL UNIT TEST FOUND. PLEASE FIX AND RERUN ****"
echo "======================================================================"
echo "Fails in =>$FAILDESC"
exit 1
fi
echo "======================================================================"
echo "**** Good job! All tests are successful ****"
echo "======================================================================"
echo "* ____ _ All tests have finished successfully. *"
echo "* / ___| ___ ___ __| | | | ___ | |__ | | *"
echo "* | | _ / _ \ / _ \ / _\` | _ | |/ _ \| '_ \ | | *"
echo "* | |_| | (_) | (_) | (_| | | |_| | (_) | |_) | |_| *"
echo "* \____|\___/ \___/ \__,_| \___/ \___/|_.__/ (_) *"
echo "======================================================================"
echo "Tested: $*"
exit 0

View File

@@ -1,102 +0,0 @@
/*!
* main.c
*
* Copyright (c) 2015-2019, NADAL Jean-Baptiste. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*
* @Author: NADAL Jean-Baptiste
* @Date: 08/11/2019
*
*/
// This is an independent project of an individual developer. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++, C#, and Java: http://www.viva64.com
/*-------------------------------- INCLUDES ---------------------------------*/
#include <getopt.h>
#include <restd.h>
/*--------------------------------------------------------------------------*/
int my_http_get_handler(short event, restd_conn_t *conn, void *userdata)
{
if (event & RESTD_EVENT_READ)
{
if (restd_http_get_status(conn) == RESTD_HTTP_REQ_DONE)
{
restd_http_response(conn, 200, "application/json", "{\"status\": false}", 17);
return restd_http_is_keepalive_request(conn) ? RESTD_DONE : RESTD_CLOSE;
}
}
return RESTD_OK;
}
/*--------------------------------------------------------------------------*/
int my_error_handler(short event, restd_conn_t *conn, void *userdata)
{
int http_code = 500;
if (event & RESTD_ERROR_METHOD_NOT_ALLOWED)
{
http_code = 405;
}
else if (event & RESTD_ERROR_PATH_NOT_FOUND)
{
http_code = 404;
}
restd_http_response(conn, http_code, "application/json", "{\"status\":\"error\"}", 18);
return RESTD_CLOSE; // Close connection.
}
/*--------------------------------------------------------------------------*/
void usage(void)
{
printf("Usage : domo-iot [web server path] \n");
printf("web server path: \t root path of the Web server.\n");
}
/*--------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
restd_server_t *server;
if (argc < 2)
{
usage();
return -1;
}
restd_log_level(RESTD_LOG_DEBUG);
server = restd_server_new();
restd_server_set_option(server, "server.port", "8888");
restd_server_set_option(server, "server.root_path", argv[1]);
restd_server_register_request_handler(server, restd_rest_handler);
restd_server_register_error_handler(server, my_error_handler);
restd_server_register_hook(server, restd_http_handler, NULL);
restd_server_register_hook_on_path(server, "GET", "/api/zob", my_http_get_handler, NULL);
restd_server_register_hook_on_path(server, "GET", "/api/zob2", my_http_get_handler, NULL);
restd_server_register_hook_on_path(server, "PUT", "/api/zob/25", my_http_get_handler, NULL);
return restd_server_start(server);
}

View File

@@ -1,15 +0,0 @@
#include "qunit.h"
#include "qlibc.h"
QUNIT_START("Test title");
TEST("Test name1") {
ASSERT_EQUAL_STR("abc", "abc");
ASSERT_EQUAL_INT(8, 8);
}
TEST("Test name2") {
ASSERT_EQUAL_PT(NULL == NULL);
}
QUNIT_END();