Add fastcgi parser

This commit is contained in:
Stefan Bühler 2008-09-20 23:29:37 +02:00
parent 0d936dff7f
commit 01d3a3b5df
9 changed files with 513 additions and 5 deletions

42
cmake/FindRagel.cmake Normal file
View File

@ -0,0 +1,42 @@
IF(NOT RAGEL_EXECUTABLE)
MESSAGE(STATUS "Looking for ragel")
FIND_PROGRAM(RAGEL_EXECUTABLE ragel)
IF(RAGEL_EXECUTABLE)
EXECUTE_PROCESS(COMMAND "${RAGEL_EXECUTABLE}" -v OUTPUT_VARIABLE _version)
STRING(REGEX MATCH "[0-9.]+" RAGEL_VERSION ${_version})
SET(RAGEL_FOUND TRUE)
ENDIF(RAGEL_EXECUTABLE)
ELSE(NOT RAGEL_EXECUTABLE)
EXECUTE_PROCESS(COMMAND "${RAGEL_EXECUTABLE}" -v OUTPUT_VARIABLE _version)
STRING(REGEX MATCH "[0-9.]+" RAGEL_VERSION ${_version})
SET(RAGEL_FOUND TRUE)
ENDIF(NOT RAGEL_EXECUTABLE)
IF(RAGEL_FOUND)
IF (NOT Ragel_FIND_QUIETLY)
MESSAGE(STATUS "Found ragel: ${RAGEL_EXECUTABLE} (${RAGEL_VERSION})")
ENDIF (NOT Ragel_FIND_QUIETLY)
IF(NOT RAGEL_FLAGS)
SET(RAGEL_FLAGS "-T1")
ENDIF(NOT RAGEL_FLAGS)
MACRO(RAGEL_PARSER SRCFILE)
GET_FILENAME_COMPONENT(SRCBASE "${SRCFILE}" NAME_WE)
SET(OUTFILE "${CMAKE_CURRENT_BINARY_DIR}/${SRCBASE}.c")
SET(INFILE "${CMAKE_CURRENT_SOURCE_DIR}/${SRCFILE}")
ADD_CUSTOM_COMMAND(OUTPUT ${OUTFILE}
COMMAND "${RAGEL_EXECUTABLE}"
ARGS -C ${RAGEL_FLAGS} -o "${OUTFILE}" "${INFILE}"
DEPENDS "${INFILE}"
COMMENT "Generating ${SRCBASE}.c from ${SRCFILE}"
)
ENDMACRO(RAGEL_PARSER)
ELSE(RAGEL_FOUND)
IF(Ragel_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find ragel")
ENDIF(Ragel_FIND_REQUIRED)
ENDIF(RAGEL_FOUND)

View File

@ -9,6 +9,7 @@ INCLUDE(CPack)
INCLUDE(FindPkgConfig)
INCLUDE(LighttpdMacros)
FIND_PACKAGE(Ragel REQUIRED)
cmake_policy(SET CMP0005 OLD)
ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGE_FILES)
@ -42,12 +43,17 @@ ADD_DEFINITIONS(-DHAVE_CONFIG_H)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
RAGEL_PARSER(parse-fastcgi.rl)
ADD_EXECUTABLE(fcgi-debug
fcgi-debug.c
connection.c
tools.c
stream.c
log.c
debug-fastcgi.c
parse-fastcgi.c
)
ADD_TARGET_PROPERTIES(fcgi-debug LINK_FLAGS ${EV_LDFLAGS})

View File

@ -2,6 +2,14 @@
static void connection_close(connection *con) {
stream_close(con->srv, &con->s_server, &con->s_client);
if (con->df_server) {
con->df_server(con->ctx_server);
con->df_server = NULL;
}
if (con->df_client) {
con->df_client(con->ctx_client);
con->df_client = NULL;
}
g_slice_free(connection, con);
}
@ -48,8 +56,11 @@ static void fd_server_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
connection_close(con);
return;
}
log_raw("raw from server", con->con_id, g_string_set_const(&s, readbuf, len));
// log_raw("raw", TRUE, con->con_id, g_string_set_const(&s, readbuf, len));
stream_append(srv, &con->s_client, readbuf, len);
if (con->da_server) {
con->da_server(con->ctx_server, readbuf, len);
}
}
if (revents & EV_WRITE) {
if (-1 == stream_write(srv, &con->s_server)) {
@ -74,8 +85,11 @@ static void fd_client_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
connection_close(con);
return;
}
log_raw("raw from client", con->con_id, g_string_set_const(&s, readbuf, len));
// log_raw("raw", FALSE, con->con_id, g_string_set_const(&s, readbuf, len));
stream_append(srv, &con->s_server, readbuf, len);
if (con->da_client) {
con->da_client(con->ctx_client, readbuf, len);
}
}
if (revents & EV_WRITE) {
if (-1 == stream_write(srv, &con->s_client)) {
@ -104,5 +118,6 @@ void connection_new(server *srv, int fd_server) {
con->client_connected = FALSE;
g_print("new connection (%u)\n", con->con_id);
stream_init(srv, &con->s_server, &con->s_client, fd_server, fd_client, fd_server_cb, fd_client_cb, con);
setup_debug_fastcgi(con);
connection_connect(con);
}

55
src/debug-fastcgi.c Normal file
View File

@ -0,0 +1,55 @@
#include "debug-fastcgi.h"
#define SWITCH_HANDLE(x) case x: return #x
const char* fcgi_type2string(enum FCGI_Type val) {
switch (val) {
SWITCH_HANDLE(FCGI_BEGIN_REQUEST);
SWITCH_HANDLE(FCGI_ABORT_REQUEST);
SWITCH_HANDLE(FCGI_END_REQUEST);
SWITCH_HANDLE(FCGI_PARAMS);
SWITCH_HANDLE(FCGI_STDIN);
SWITCH_HANDLE(FCGI_STDOUT);
SWITCH_HANDLE(FCGI_STDERR);
SWITCH_HANDLE(FCGI_DATA);
SWITCH_HANDLE(FCGI_GET_VALUES);
SWITCH_HANDLE(FCGI_GET_VALUES_RESULT);
SWITCH_HANDLE(FCGI_UNKNOWN_TYPE);
default: return "<unknown>";
}
}
const char* fcgi_flags2string(guint8 flags) {
switch (flags) {
case 0: return "none";
case 1: return "FCGI_KEEP_CONN";
default: return "<unknown>";
}
}
const char* fcgi_role2string(enum FCGI_Role role) {
switch (role) {
SWITCH_HANDLE(FCGI_RESPONDER);
SWITCH_HANDLE(FCGI_AUTHORIZER);
SWITCH_HANDLE(FCGI_FILTER);
default: return "<unknown>";
}
}
const char* fcgi_protocol_status2string(enum FCGI_ProtocolStatus val) {
switch (val) {
SWITCH_HANDLE(FCGI_REQUEST_COMPLETE);
SWITCH_HANDLE(FCGI_CANT_MPX_CONN);
SWITCH_HANDLE(FCGI_OVERLOADED);
SWITCH_HANDLE(FCGI_UNKNOWN_ROLE);
default: return "<unknown>";
}
}
void setup_debug_fastcgi(connection *con) {
con->ctx_server = fcgi_context_new(TRUE, con->con_id);
con->da_server = fcgi_context_append;
con->df_server = fcgi_context_free;
con->ctx_client = fcgi_context_new(FALSE, con->con_id);
con->da_client = fcgi_context_append;
con->df_client = fcgi_context_free;
}

69
src/debug-fastcgi.h Normal file
View File

@ -0,0 +1,69 @@
#ifndef FCGI_DEBUG_DEBUG_FASTCGI_H
#define FCGI_DEBUG_DEBUG_FASTCGI_H
struct fcgi_context;
typedef struct fcgi_context fcgi_context;
#include "fcgi-debug.h"
struct fcgi_context {
GString *buffer;
GString *s_params;
gboolean error;
struct {
guint8 version;
guint8 type;
guint16 requestID;
guint16 contentLength;
guint8 paddingLength;
} FCGI_Record;
gboolean from_server;
guint con_id;
};
#define FCGI_HEADER_LEN 8
enum FCGI_Type {
FCGI_BEGIN_REQUEST = 1,
FCGI_ABORT_REQUEST = 2,
FCGI_END_REQUEST = 3,
FCGI_PARAMS = 4,
FCGI_STDIN = 5,
FCGI_STDOUT = 6,
FCGI_STDERR = 7,
FCGI_DATA = 8,
FCGI_GET_VALUES = 9,
FCGI_GET_VALUES_RESULT = 10,
FCGI_UNKNOWN_TYPE = 11
};
#define FCGI_MAXTYPE (FCGI_UNKNOWN_TYPE)
enum FCGI_Flags {
FCGI_KEEP_CONN = 1
};
enum FCGI_Role {
FCGI_RESPONDER = 1,
FCGI_AUTHORIZER = 2,
FCGI_FILTER = 3
};
enum FCGI_ProtocolStatus {
FCGI_REQUEST_COMPLETE = 0,
FCGI_CANT_MPX_CONN = 1,
FCGI_OVERLOADED = 2,
FCGI_UNKNOWN_ROLE = 3
};
fcgi_context* fcgi_context_new(gboolean from_server, guint con_id);
void fcgi_context_free(gpointer _ctx);
void fcgi_context_append(gpointer _ctx, const gchar* buf, gssize buflen);
const char* fcgi_type2string(enum FCGI_Type val);
const char* fcgi_flags2string(guint8 flags);
const char* fcgi_role2string(enum FCGI_Role role);
const char* fcgi_protocol_status2string(enum FCGI_ProtocolStatus val);
#endif

View File

@ -1,3 +1,5 @@
#ifndef FCGI_DEBUG_H
#define FCGI_DEBUG_H
#include <ev.h>
#include <glib.h>
@ -61,10 +63,17 @@ struct stream {
GString *buffer;
};
typedef void (*DebugAppend)(gpointer ctx, const gchar *buf, gssize buflen);
typedef void (*DebugFree)(gpointer ctx);
struct connection {
server *srv;
guint con_id;
stream s_server, s_client;
gpointer ctx_server, ctx_client;
DebugAppend da_server, da_client;
DebugFree df_server, df_client;
gboolean client_connected;
};
@ -79,6 +88,7 @@ void ev_io_rem_events(struct ev_loop *loop, ev_io *watcher, int events);
void ev_io_set_events(struct ev_loop *loop, ev_io *watcher, int events);
GString* g_string_set_const(GString* s, const gchar *data, gsize len);
GString* g_string_escape(GString *data);
/* connection.c */
void connection_new(server *srv, int fd_server);
@ -96,4 +106,10 @@ void stream_append(server *srv, stream *s, char *buf, gssize bufsize);
gssize stream_write(server *srv, stream *s);
/* log.c */
void log_raw(const gchar *head, guint con_id, GString *data);
void log_raw(const gchar *head, gboolean from_server, guint con_id, GString *data);
void log_raw_split(const gchar *head, gboolean from_server, guint con_id, GString *data);
/* debug-fastcgi.c */
void setup_debug_fastcgi(connection *con);
#endif

View File

@ -1,6 +1,6 @@
#include "fcgi-debug.h"
void log_raw(const gchar *head, guint con_id, GString *data) {
void log_raw_split(const gchar *head, gboolean from_server, guint con_id, GString *data) {
const gchar *start = data->str, *end = start+data->len, *i;
GString *line = g_string_sized_new(0);
for ( ; start < end; ) {
@ -30,8 +30,14 @@ void log_raw(const gchar *head, guint con_id, GString *data) {
g_string_append_c(line, c);
}
}
g_print("%s (%u): %s\n", head, con_id, line->str);
g_print("%s from %s (%u): %s\n", head, from_server ? "server" : "client", con_id, line->str);
start = i;
}
g_string_free(line, TRUE);
}
void log_raw(const gchar *head, gboolean from_server, guint con_id, GString *data) {
GString *line = g_string_escape(data);
g_print("%s from %s (%u): %s\n", head, from_server ? "server" : "client", con_id, line->str);
g_string_free(line, TRUE);
}

277
src/parse-fastcgi.rl Normal file
View File

@ -0,0 +1,277 @@
#include "debug-fastcgi.h"
fcgi_context* fcgi_context_new(gboolean from_server, guint con_id) {
fcgi_context *ctx = g_slice_new0(fcgi_context);
ctx->buffer = g_string_sized_new(0);
ctx->from_server = from_server;
ctx->con_id = con_id;
ctx->s_params = g_string_sized_new(0);
return ctx;
}
void fcgi_context_free(gpointer _ctx) {
fcgi_context* ctx = (fcgi_context*) _ctx;
g_string_free(ctx->buffer, TRUE);
g_string_free(ctx->s_params, TRUE);
g_slice_free(fcgi_context, ctx);
}
#define USE_STREAM(s, p, pe) do {\
g_string_append_len(ctx->s, (gchar*) p, pe - p); \
p = (guint8*) ctx->s->str; \
pe = p + ctx->s->len; \
} while(0)
#define EAT_STREAM(s, p) do {\
g_string_erase(ctx->s, 0, p - (guint8*) ctx->s->str); \
p = (guint8*) ctx->s->str; \
pe = p + ctx->s->len; \
} while(0)
static gboolean get_key_value_pair_len(guint8 **_p, guint8 *pe, guint *_len1, guint *_len2) {
guint8 *p = *_p;
guint len1, len2;
/* need at least 2 bytes */
if (p + 2 >= pe) return FALSE;
len1 = *p++;
if (len1 & 0x80) {
/* need at least 3+1 bytes */
if (p + 4 >= pe) return FALSE;
len1 = ((len1 & 0x7F) << 24) | (p[0] << 16) | (p[1] << 8) | p[2];
p += 3;
}
len2 = *p++;
if (len2 & 0x80) {
/* need at least 3 bytes */
if (p + 3 >= pe) return FALSE;
len2 = ((len2 & 0x7F) << 24) | (p[0] << 16) | (p[1] << 8) | p[2];
p += 3;
}
*_len1 = len1;
*_len2 = len2;
*_p = p;
return TRUE;
}
void fcgi_packet_parse(fcgi_context *ctx, guint8 *p, guint8 *pe) {
guint8 *eof = pe;
GString tmp1, tmp2;
UNUSED(eof);
switch (ctx->FCGI_Record.type) {
case FCGI_BEGIN_REQUEST: {
guint role;
guint8 flags;
if (ctx->FCGI_Record.contentLength != 8) {
g_print("wrong FCGI_BEGIN_REQUEST size from %s (%u): %u\n",
ctx->from_server ? "server" : "client", ctx->con_id, (guint) ctx->FCGI_Record.contentLength);
return;
}
role = (p[0] << 8) | p[1];
flags = p[2];
g_print("begin request from %s (%u): role: %s, flags: %s\n",
ctx->from_server ? "server" : "client", ctx->con_id,
fcgi_role2string(role),
fcgi_flags2string(flags)
);
break;
}
case FCGI_ABORT_REQUEST: {
if (ctx->FCGI_Record.contentLength) {
g_print("wrong FCGI_ABORT_REQUEST size from %s (%u): %u\n",
ctx->from_server ? "server" : "client", ctx->con_id, (guint) ctx->FCGI_Record.contentLength);
return;
}
g_print("abort request from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
break;
}
case FCGI_END_REQUEST: {
guint appStatus;
guint8 protocolStatus;
if (ctx->FCGI_Record.contentLength != 8) {
g_print("wrong FCGI_END_REQUEST size from %s (%u): %u\n",
ctx->from_server ? "server" : "client", ctx->con_id, (guint) ctx->FCGI_Record.contentLength);
return;
}
appStatus = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
protocolStatus = p[4];
g_print("end request from %s (%u): applicationStatus: %u, protocolStatus: %s\n",
ctx->from_server ? "server" : "client", ctx->con_id,
appStatus,
fcgi_protocol_status2string(protocolStatus)
);
break;
}
case FCGI_PARAMS: {
guint len1, len2;
GString *s1, *s2;
if (!ctx->FCGI_Record.contentLength) {
g_print("params end from %s (%u)%s\n",
ctx->from_server ? "server" : "client", ctx->con_id, ctx->s_params->len ? " (unexpected)" : "");
return;
}
USE_STREAM(s_params, p, pe);
while (get_key_value_pair_len(&p, pe, &len1, &len2)) {
if (p + len1 + len2 > pe) {
return;
}
s1 = g_string_escape(g_string_set_const(&tmp1, (gchar*) p, len1));
s2 = g_string_escape(g_string_set_const(&tmp2, (gchar*) p+len1, len2));
g_print("param from %s (%u): '%s' = '%s'\n",
ctx->from_server ? "server" : "client", ctx->con_id,
s1->str, s2->str);
g_string_free(s1, TRUE);
g_string_free(s2, TRUE);
p += len1 + len2;
EAT_STREAM(s_params, p);
}
break;
}
case FCGI_STDIN:
if (!ctx->FCGI_Record.contentLength) {
g_print("stdin closed from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
return;
}
log_raw_split("stdin", ctx->from_server, ctx->con_id, g_string_set_const(&tmp1, (gchar*) p, pe - p));
break;
case FCGI_STDOUT:
if (!ctx->FCGI_Record.contentLength) {
g_print("stdout closed from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
return;
}
log_raw_split("stdout", ctx->from_server, ctx->con_id, g_string_set_const(&tmp1, (gchar*) p, pe - p));
break;
case FCGI_STDERR:
if (!ctx->FCGI_Record.contentLength) {
g_print("stderr closed from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
return;
}
log_raw_split("stderr", ctx->from_server, ctx->con_id, g_string_set_const(&tmp1, (gchar*) p, pe - p));
break;
case FCGI_DATA:
if (!ctx->FCGI_Record.contentLength) {
g_print("data closed from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
return;
}
log_raw_split("data", ctx->from_server, ctx->con_id, g_string_set_const(&tmp1, (gchar*) p, pe - p));
break;
case FCGI_GET_VALUES: {
guint len1, len2;
GString *s1, *s2;
if (!ctx->FCGI_Record.contentLength) {
g_print("empty get values from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
return;
}
while (get_key_value_pair_len(&p, pe, &len1, &len2)) {
if (p + len1 + len2 > pe) {
return;
}
s1 = g_string_escape(g_string_set_const(&tmp1, (gchar*) p, len1));
s2 = g_string_escape(g_string_set_const(&tmp2, (gchar*) p+len1, len2));
if (len2) {
g_print("get values from %s (%u): '%s' = '%s'?\n",
ctx->from_server ? "server" : "client", ctx->con_id,
s1->str, s2->str);
} else {
g_print("get values from %s (%u): '%s'\n",
ctx->from_server ? "server" : "client", ctx->con_id,
s1->str);
}
g_string_free(s1, TRUE);
g_string_free(s2, TRUE);
p += len1 + len2;
}
if (p != pe) {
g_print("unexpected end of get values from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
}
break;
}
case FCGI_GET_VALUES_RESULT: {
guint len1, len2;
GString *s1, *s2;
if (!ctx->FCGI_Record.contentLength) {
g_print("empty get values result from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
return;
}
while (get_key_value_pair_len(&p, pe, &len1, &len2)) {
if (p + len1 + len2 > pe) {
return;
}
s1 = g_string_escape(g_string_set_const(&tmp1, (gchar*) p, len1));
s2 = g_string_escape(g_string_set_const(&tmp2, (gchar*) p+len1, len2));
g_print("get values result from %s (%u): '%s' = '%s'\n",
ctx->from_server ? "server" : "client", ctx->con_id,
s1->str, s2->str);
g_string_free(s1, TRUE);
g_string_free(s2, TRUE);
p += len1 + len2;
}
if (p != pe) {
g_print("unexpected end of get values result from %s (%u)\n",
ctx->from_server ? "server" : "client", ctx->con_id);
}
break;
}
case FCGI_UNKNOWN_TYPE:
if (ctx->FCGI_Record.contentLength != 8) {
g_print("wrong FCGI_UNKNOWN_TYPE size from %s (%u): %u\n",
ctx->from_server ? "server" : "client", ctx->con_id, (guint) ctx->FCGI_Record.contentLength);
return;
}
g_print("unknown type %u from %s (%u)\n", (guint) p[0],
ctx->from_server ? "server" : "client", ctx->con_id);
break;
default:
g_print("packet from %s (%u): type: %s, id: 0x%x, contentLength: 0x%x\n",
ctx->from_server ? "server" : "client", ctx->con_id,
fcgi_type2string(ctx->FCGI_Record.type),
(guint) ctx->FCGI_Record.requestID,
(guint) ctx->FCGI_Record.contentLength
);
log_raw("packet data", ctx->from_server, ctx->con_id, g_string_set_const(&tmp1, (gchar*) p, pe - p));
break;
}
}
void fcgi_context_append(gpointer _ctx, const gchar* buf, gssize buflen) {
fcgi_context *ctx = (fcgi_context*) _ctx;
guint8* data;
guint total_len;
if (ctx->error) return;
g_string_append_len(ctx->buffer, buf, buflen);
for (;;) {
data = (guint8*) ctx->buffer->str;
if (ctx->buffer->len < FCGI_HEADER_LEN) return;
ctx->FCGI_Record.version = data[0];
ctx->FCGI_Record.type = data[1];
ctx->FCGI_Record.requestID = (data[2] << 8) | (data[3]);
ctx->FCGI_Record.contentLength = (data[4] << 8) | (data[5]);
ctx->FCGI_Record.paddingLength = data[6];
/* ignore data[7] */
total_len = FCGI_HEADER_LEN + ctx->FCGI_Record.contentLength + ctx->FCGI_Record.paddingLength;
if (ctx->buffer->len < total_len) return;
fcgi_packet_parse(ctx, (guint8*) (FCGI_HEADER_LEN + ctx->buffer->str), (guint8*) (ctx->FCGI_Record.contentLength + FCGI_HEADER_LEN + ctx->buffer->str));
g_string_erase(ctx->buffer, 0, total_len);
}
}

View File

@ -55,3 +55,25 @@ GString* g_string_set_const(GString* s, const gchar *data, gsize len) {
s->len = len;
return s;
}
GString* g_string_escape(GString *data) {
GString *s = g_string_sized_new(0);
const gchar *start = data->str, *end = start+data->len, *i;
for (i = start; i < end; i++) {
guchar c = *i;
if (c == '\n') {
g_string_append_len(s, "\\n", 2);
} else if (c == '\r') {
g_string_append_len(s, "\\r", 2);
} else if (c < 0x20 || c >= 0x80) {
static char hex[5] = "\\x00";
hex[3] = ((c & 0xF) < 10) ? '0' + (c & 0xF) : 'A' + (c & 0xF) - 10;
c /= 16;
hex[2] = ((c & 0xF) < 10) ? '0' + (c & 0xF) : 'A' + (c & 0xF) - 10;
g_string_append_len(s, hex, 4);
} else {
g_string_append_c(s, c);
}
}
return s;
}