debug fastcgi streams while forwarding them https://redmine.lighttpd.net/projects/fcgi-debug
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

283 lines
9.4 KiB

#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;
}
static 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",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.contentLength);
return;
}
if (0 == ctx->FCGI_Record.requestID) {
g_print("invalid requestID from %s (%u): %u\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
}
role = (p[0] << 8) | p[1];
flags = p[2];
g_print("begin request from %s (%u, %u): role: %s, flags: %s\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID,
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): %u\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.contentLength,
(guint) ctx->FCGI_Record.requestID);
return;
}
g_print("abort request from %s (%u, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
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",
from_server_to_string(ctx->from_server), 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, %u): applicationStatus: %u, protocolStatus: %s\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID,
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, %u)%s\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID,
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, %u): '%s' = '%s'\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID,
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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
return;
}
log_raw_split("stdin", ctx->from_server, ctx->con_id, ctx->FCGI_Record.requestID, 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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
return;
}
log_raw_split("stdout", ctx->from_server, ctx->con_id, ctx->FCGI_Record.requestID, 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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
return;
}
log_raw_split("stderr", ctx->from_server, ctx->con_id, ctx->FCGI_Record.requestID, 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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
return;
}
log_raw_split("data", ctx->from_server, ctx->con_id, ctx->FCGI_Record.requestID, 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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
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, %u): '%s' = '%s'?\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID,
s1->str, s2->str);
} else {
g_print("get values from %s (%u, %u): '%s'\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID,
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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
}
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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
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, %u): '%s' = '%s'\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID,
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, %u)\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
}
break;
}
case FCGI_UNKNOWN_TYPE:
if (ctx->FCGI_Record.contentLength != 8) {
g_print("wrong FCGI_UNKNOWN_TYPE size from %s (%u, %u): %u\n",
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID, (guint) ctx->FCGI_Record.contentLength);
return;
}
g_print("unknown type %u from %s (%u, %u)\n", (guint) p[0],
from_server_to_string(ctx->from_server), ctx->con_id, (guint) ctx->FCGI_Record.requestID);
break;
default:
g_print("packet from %s (%u, %u): type: %s, contentLength: 0x%x\n",
from_server_to_string(ctx->from_server), ctx->con_id,
(guint) ctx->FCGI_Record.requestID,
fcgi_type2string(ctx->FCGI_Record.type),
(guint) ctx->FCGI_Record.contentLength
);
log_raw("packet data", ctx->from_server, ctx->con_id, ctx->FCGI_Record.requestID, 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);
}
}