summaryrefslogtreecommitdiffstats
path: root/extras
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:40:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 17:40:56 +0000
commitc248d29056abbc1fc4c5dc178bab48fb8d2c1fcb (patch)
tree4a13fc30604509224504e1911bc976e5df7bdf05 /extras
parentInitial commit. (diff)
downloadlibhtp-upstream/1%0.5.47.tar.xz
libhtp-upstream/1%0.5.47.zip
Adding upstream version 1:0.5.47.upstream/1%0.5.47
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extras')
-rw-r--r--extras/htptest.c569
-rw-r--r--extras/ruby/HTP.c1008
-rw-r--r--extras/ruby/README58
-rw-r--r--extras/ruby/example.rb116
-rw-r--r--extras/ruby/extconf.rb6
-rw-r--r--extras/ruby/htp.gemspec12
-rw-r--r--extras/ruby/htp_ruby.rb247
7 files changed, 2016 insertions, 0 deletions
diff --git a/extras/htptest.c b/extras/htptest.c
new file mode 100644
index 0000000..472db5e
--- /dev/null
+++ b/extras/htptest.c
@@ -0,0 +1,569 @@
+/***************************************************************************
+ * Copyright (c) 2009-2010 Open Information Security Foundation
+ * Copyright (c) 2010-2013 Qualys, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * - Neither the name of the Qualys, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ***************************************************************************/
+
+/**
+ * @file
+ * @author Ivan Ristic <ivanr@webkreator.com>
+ */
+
+/*
+ * This program is a simple example of how to use LibHTP to parse a HTTP
+ * connection stream. It uses libnids for TCP reassembly and LibHTP for
+ * HTTP parsing.
+ *
+ * This program is only meant as an demonstration; it is not suitable
+ * to be used in production. Furthermore, libnids itself was unreliable
+ * in my tests.
+ *
+ * Compile with:
+ *
+ * $ gcc htptest.c -lhtp -lz -lnids -o htptest
+ *
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdio.h>
+#include "nids.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <htp/htp.h>
+#include <htp/htp_list.h>
+#include <htp/htp_table.h>
+
+#define DIRECTION_CLIENT 1
+#define DIRECTION_SERVER 2
+
+typedef struct chunk_t chunk_t;
+typedef struct stream_data stream_data;
+
+/** Data chunk structure */
+struct chunk_t {
+ char *data;
+ size_t len;
+ int direction;
+ size_t consumed;
+};
+
+/** Per-stream data structure */
+struct stream_data {
+ int id;
+ htp_connp_t *connp;
+ int direction;
+ int fd;
+ int chunk_counter;
+ int log_level;
+ int req_count;
+ htp_list_t *chunks;
+ htp_list_t *inbound_chunks;
+ htp_list_t *outbound_chunks;
+};
+
+/** LibHTP parser configuration */
+htp_cfg_t *cfg;
+
+/** Connection counter */
+int counter = 1000;
+
+/**
+ * Free stream data.
+ *
+ * @param[in] sd
+ */
+void free_stream_data(stream_data *sd) {
+ if (sd == NULL) return;
+
+ // Free stream chunks, if any
+ if (sd->chunks != NULL) {
+ for (int i = 0, n = htp_list_size(sd->chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->chunks, i);
+ free(chunk->data);
+ free(chunk);
+ }
+
+ htp_list_destroy(sd->chunks);
+ sd->chunked = NULL;
+ }
+
+ // Free inbound chunks, if any
+ if (sd->inbound_chunks != NULL) {
+ for (int i = 0, n = htp_list_size(sd->inbound_chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->inbound_chunkds, i);
+ free(chunk->data);
+ free(chunk);
+ }
+
+ htp_list_destroy(sd->inbound_chunks);
+ sd->inbound_chunks = NULL;
+ }
+
+ // Free outbound chunks, if any
+ if (sd->outbound_chunks != NULL) {
+ for (int i = 0, n = htp_list_size(sd->outbound_chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->outbound_chunkds, i);
+ free(chunk->data);
+ free(chunk);
+ }
+
+ htp_list_destroy(sd->outbound_chunks);
+ sd->outbound_chunks = NULL;
+ }
+
+ // Close the stream file, if we have it open
+ if (sd->fd != -1) {
+ close(sd->fd);
+ }
+
+ free(sd);
+}
+
+/**
+ * Process as much buffered inbound and outbound data as possible
+ * (in that order)
+ *
+ * @param[in] sd
+ */
+void process_stored_stream_data(stream_data *sd) {
+ int loop = 0;
+
+ do {
+ // Reset loop
+ loop = 0;
+
+ // Send as much inbound data as possible
+ while((sd->connp->in_status == HTP_STREAM_DATA)&&(htp_list_size(sd->inbound_chunks) > 0)) {
+ chunk_t *chunk = (chunk_t *)htp_list_get(sd->inbound_chunks, 0);
+
+ int rc = htp_connp_req_data(sd->connp, 0, chunk->data + chunk->consumed, chunk->len - chunk->consumed);
+ if (rc == HTP_STREAM_DATA) {
+ // The entire chunk was consumed
+ htp_list_shift(sd->inbound_chunks);
+ free(chunk->data);
+ free(chunk);
+ } else {
+ // Partial consumption
+ chunk->consumed = htp_connp_req_data_consumed(sd->connp);
+ }
+ }
+
+ // Send as much outbound data as possible
+ while((sd->connp->out_status == HTP_STREAM_DATA)&&(htp_list_size(sd->outbound_chunks) > 0)) {
+ chunk_t *chunk = (chunk_t *)htp_list_get(sd->outbound_chunks, 0);
+
+ int rc = htp_connp_res_data(sd->connp, 0, chunk->data + chunk->consumed, chunk->len - chunk->consumed);
+ if (rc == HTP_STREAM_DATA) {
+ // The entire chunk was consumed
+ htp_list_shift(sd->outbound_chunks);
+ free(chunk->data);
+ free(chunk);
+ } else {
+ // Partial consumption
+ chunk->consumed = htp_connp_res_data_consumed(sd->connp);
+ }
+
+ // Whenever we send some outbound data to the parser,
+ // we need to go back and try to send inbound data
+ // if we have it.
+ loop = 1;
+ }
+ } while(loop);
+}
+
+/**
+ * Process a chunk of the connection stream.
+ *
+ * @param[in] sd
+ * @param[in] direction
+ * @param[in] hlf
+ */
+void process_stream_data(stream_data *sd, int direction, struct half_stream *hlf) {
+ chunk_t *chunk = NULL;
+ int rc;
+
+ //printf("#DATA direction %d bytes %d\n", sd->direction, hlf->count_new);
+
+ if (sd->direction == direction) {
+ // Inbound data
+
+ switch(sd->connp->in_status) {
+ case HTP_STREAM_NEW :
+ case HTP_STREAM_DATA :
+ // Send data to parser
+
+ rc = htp_connp_req_data(sd->connp, 0, hlf->data, hlf->count_new);
+ if (rc == HTP_STREAM_DATA_OTHER) {
+ // Encountered inbound parsing block
+
+ // Store partial chunk for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new - htp_connp_req_data_consumed(sd->connp);
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data + htp_connp_req_data_consumed(sd->connp), chunk->len);
+ htp_list_add(sd->inbound_chunks, chunk);
+ } else
+ if (rc != HTP_STREAM_DATA) {
+ // Inbound parsing error
+ sd->log_level = 0;
+ fprintf(stderr, "[#%d] Inbound parsing error: %d\n", sd->id, rc);
+ // TODO Write connection to disk
+ }
+ break;
+
+ case HTP_STREAM_ERROR :
+ // Do nothing
+ break;
+
+ case HTP_STREAM_DATA_OTHER :
+ // Store data for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new;
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data, chunk->len);
+ htp_list_add(sd->inbound_chunks, chunk);
+ break;
+ }
+ } else {
+ // Outbound data
+ switch(sd->connp->out_status) {
+ case HTP_STREAM_NEW :
+ case HTP_STREAM_DATA :
+ // Send data to parser
+
+ rc = htp_connp_res_data(sd->connp, 0, hlf->data, hlf->count_new);
+ if (rc == HTP_STREAM_DATA_OTHER) {
+ // Encountered outbound parsing block
+
+ // Store partial chunk for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new - htp_connp_res_data_consumed(sd->connp);
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data + htp_connp_res_data_consumed(sd->connp), chunk->len);
+ htp_list_add(sd->outbound_chunks, chunk);
+ } else
+ if (rc != HTP_STREAM_DATA) {
+ // Outbound parsing error
+ sd->log_level = 0;
+ fprintf(stderr, "[#%d] Outbound parsing error: %d\n", sd->id, rc);
+ }
+ break;
+
+ case HTP_STREAM_ERROR :
+ // Do nothing
+ break;
+
+ case HTP_STREAM_DATA_OTHER :
+ // Store data for later
+ chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->len = hlf->count_new;
+ chunk->data = malloc(chunk->len);
+ // TODO
+ memcpy(chunk->data, hlf->data, chunk->len);
+ htp_list_add(sd->outbound_chunks, chunk);
+ break;
+ }
+ }
+
+ // Process as much stored data as possible
+ process_stored_stream_data(sd);
+}
+
+/**
+ * Called by libnids whenever it has an event we have to handle.
+ *
+ * @param[in] tcp
+ * @param[in] user_data
+ */
+void tcp_callback (struct tcp_stream *tcp, void **user_data) {
+ stream_data *sd = *user_data;
+
+ // New connection
+ if (tcp->nids_state == NIDS_JUST_EST) {
+ tcp->client.collect++;
+ tcp->server.collect++;
+ tcp->server.collect_urg++;
+ tcp->client.collect_urg++;
+
+ // Allocate custom per-stream data
+ sd = calloc(1, sizeof(stream_data));
+ sd->id = counter++;
+ sd->direction = -1;
+ sd->fd = -1;
+ sd->log_level = -1;
+ sd->chunks = htp_list_array_create(16);
+ sd->inbound_chunks = htp_list_array_create(16);
+ sd->outbound_chunks = htp_list_array_create(16);
+ sd->req_count = 1;
+
+ // Init LibHTP parser
+ sd->connp = htp_connp_create(cfg);
+ if (sd->connp == NULL) {
+ fprintf(stderr, "Failed to create LibHTP parser instance.\n");
+ exit(1);
+ }
+
+ // Associate TCP stream information with the HTTP connection parser
+ htp_connp_set_user_data(sd->connp, sd);
+
+ // Associate TCP stream information with the libnids structures
+ *user_data = sd;
+
+ return;
+ }
+
+ // Connection close
+ if (tcp->nids_state == NIDS_CLOSE) {
+ if (sd == NULL) return;
+
+ // Destroy parser
+ htp_connp_destroy_all(sd->connp);
+
+ // Free custom per-stream data
+ free_stream_data(sd);
+
+ return;
+ }
+
+ // Connection close (RST)
+ if (tcp->nids_state == NIDS_RESET) {
+ if (sd == NULL) return;
+
+ // Destroy parser
+ htp_connp_destroy_all(sd->connp);
+
+ // Free custom per-stream data
+ free_stream_data(sd);
+
+ return;
+ }
+
+ if (tcp->nids_state == NIDS_DATA) {
+ struct half_stream *hlf;
+ int direction;
+
+ if (tcp->client.count_new) {
+ hlf = &tcp->client;
+ direction = DIRECTION_SERVER;
+ } else {
+ hlf = &tcp->server;
+ direction = DIRECTION_CLIENT;
+ }
+
+ if (sd == NULL) return;
+
+ if (sd->direction == -1) {
+ sd->direction = direction;
+ }
+
+ // Write data to disk or store for later
+ if (sd->fd == -1) {
+ // Store data, as we may need it later
+ chunk_t *chunk = calloc(1, sizeof(chunk_t));
+ // TODO
+ chunk->direction = direction;
+ chunk->data = malloc(hlf->count_new);
+ // TODO
+ chunk->len = hlf->count_new;
+ memcpy(chunk->data, hlf->data, chunk->len);
+
+ htp_list_add(sd->chunks, chunk);
+ } else {
+ // No need to store, write directly to file
+
+ if (sd->chunk_counter != 0) {
+ write(sd->fd, "\r\n", 2);
+ }
+
+ if (sd->direction == direction) {
+ write(sd->fd, ">>>\r\n", 5);
+ } else {
+ write(sd->fd, "<<<\r\n", 5);
+ }
+
+ write(sd->fd, hlf->data, hlf->count_new);
+
+ sd->chunk_counter++;
+ }
+
+ // Process data
+ process_stream_data(sd, direction, hlf);
+
+ return;
+ }
+}
+
+/**
+ * Invoked at the end of every transaction.
+ *
+ * @param[in] connp
+ */
+int callback_response(htp_connp_t *connp) {
+ stream_data *sd = (stream_data *)htp_connp_get_user_data(connp);
+
+ char *x = bstr_util_strdup_to_c(connp->out_tx->request_line);
+ fprintf(stdout, "[#%d/%d] %s\n", sd->id, sd->req_count, x);
+ free(x);
+
+ sd->req_count++;
+}
+
+/**
+ * Invoked every time LibHTP wants to log.
+ *
+ * @param[in] log
+ */
+int callback_log(htp_log_t *log) {
+ stream_data *sd = (stream_data *)htp_connp_get_user_data(log->connp);
+
+ if ((sd->log_level == -1)||(sd->log_level > log->level)) {
+ sd->log_level = log->level;
+ }
+
+ if (log->code != 0) {
+ fprintf(stderr, "[#%d/%d][%d][code %d][file %s][line %d] %s\n", sd->id, sd->req_count,
+ log->level, log->code, log->file, log->line, log->msg);
+ } else {
+ fprintf(stderr, "[#%d/%d][%d][file %s][line %d] %s\n", sd->id, sd->req_count,
+ log->level, log->file, log->line, log->msg);
+ }
+
+ // If this is the first time a log message was generated for this connection,
+ // start writing the entire thing to a file on disk.
+ if (sd->fd == -1) {
+ char filename[256];
+
+ // TODO Use IP addresses and ports in filename
+ snprintf(filename, 255, "conn-%d.t", sd->id);
+
+ sd->fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
+ if (sd->fd == -1) {
+ fprintf(stderr, "Failed to create file %s: %s\n", filename, strerror(errno));
+ exit(1);
+ }
+
+ // Write to disk the data we have in memory
+ for (int i = 0, n = htp_list_size(sd->chunks); i < n; i++) {
+ chunk_t *chunk = htp_list_get(sd->chunks, i);
+
+ if (sd->chunk_counter != 0) {
+ write(sd->fd, "\r\n", 2);
+ }
+
+ if (sd->direction == chunk->direction) {
+ write(sd->fd, ">>>\r\n", 5);
+ } else {
+ write(sd->fd, "<<<\r\n", 5);
+ }
+
+ write(sd->fd, chunk->data, chunk->len);
+
+ sd->chunk_counter++;
+ }
+ }
+}
+
+/**
+ * Prints usage.
+ */
+void print_usage() {
+ fprintf(stdout, "Usage: htpMon [-r file] [\"expression\"]\n");
+}
+
+/**
+ * Main entry point for this program.
+ *
+ * @param[in] argc
+ * @param[in] argv
+ */
+int main(int argc, char *argv[]) {
+ // Check parameters
+ if ((argc < 2)||(argc > 4)) {
+ print_usage();
+ return 1;
+ }
+
+ // Configure libnids
+ if (argc > 2) {
+ if (strcmp(argv[1], "-r") != 0) {
+ print_usage();
+ return 1;
+ }
+
+ nids_params.filename = argv[2];
+
+ if (argc == 4) {
+ nids_params.pcap_filter = argv[3];
+ }
+ } else {
+ nids_params.pcap_filter = argv[1];
+ }
+
+ // Initialize libnids
+ if (!nids_init()) {
+ fprintf(stderr, "libnids initialization failed: %s\n", nids_errbuf);
+ return 1;
+ }
+
+ // Create LibHTP configuration
+ cfg = htp_config_create();
+ htp_config_set_server_personality(cfg, HTP_SERVER_APACHE_2_2);
+
+ htp_config_register_response_complete(cfg, callback_response);
+ htp_config_register_log(cfg, callback_log);
+
+ // Run libnids
+ nids_register_tcp(tcp_callback);
+ nids_run();
+
+ // Destroy LibHTP configuration
+ htp_config_destroy(cfg);
+
+ return 0;
+}
+
diff --git a/extras/ruby/HTP.c b/extras/ruby/HTP.c
new file mode 100644
index 0000000..c83092d
--- /dev/null
+++ b/extras/ruby/HTP.c
@@ -0,0 +1,1008 @@
+/***************************************************************************
+ * Copyright (c) 2009-2010 Open Information Security Foundation
+ * Copyright (c) 2010-2013 Qualys, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+
+ * - Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+
+ * - Neither the name of the Qualys, Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ***************************************************************************/
+
+/**
+ * @author Christopher Alfeld <calfeld@qualys.com>
+ */
+
+#include <ruby.h>
+#include <htp/htp.h>
+
+/* Status
+ * Complete: Tx, Header, HeaderLine, URI, all numeric constants.
+ * Incomplete: Cfg, Connp
+ * Missing completely: file, file_data, log, tx_data (probably not needed)
+ */
+
+// Debug
+#ifdef RBHTP_DBEUG
+#include <stdio.h>
+#define P( value ) { VALUE inspect = rb_funcall( value, rb_intern( "inspect" ), 0 ); printf("%s\n",StringValueCStr(inspect)); }
+#else
+#define P( value )
+#endif
+
+static VALUE mHTP;
+static VALUE cCfg;
+static VALUE cConnp;
+static VALUE cTx;
+static VALUE cHeader;
+static VALUE cHeaderLine;
+static VALUE cURI;
+static VALUE cFile;
+static VALUE cConn;
+
+#define BSTR_TO_RSTR( B ) ( rb_str_new( bstr_ptr( B ), bstr_len( B ) ) )
+
+// Accessor Helpers
+#define RBHTP_R_INT( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ return INT2FIX( x->N ); \
+ }
+
+#define RBHTP_R_TV( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ return rb_time_new( x->N.tv_sec, x->N.tv_usec ); \
+ }
+
+#define RBHTP_R_CSTR( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ if ( x->N == NULL ) return Qnil; \
+ return rb_str_new2( x->N ); \
+ }
+
+#define RBHTP_W_INT( T, N ) \
+ VALUE rbhtp_## T ##_ ## N ## _set( VALUE self, VALUE v ) \
+ { \
+ Check_Type( v, T_FIXNUM ); \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ x->N = FIX2INT( v ); \
+ return Qnil; \
+ }
+
+#define RBHTP_RW_INT( T, N ) \
+ RBHTP_R_INT( T, N ) \
+ RBHTP_W_INT( T, N )
+
+#define RBHTP_R_BOOL( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ return x->N == 1 ? Qtrue : Qfalse; \
+ }
+
+#define RBHTP_R_STRING( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ if ( x->N == NULL ) \
+ return Qnil; \
+ return BSTR_TO_RSTR( x->N ); \
+ }
+
+#define RBHTP_R_HTP( T, N, H ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ if ( x->N == NULL ) \
+ return Qnil; \
+ return rb_funcall( H, rb_intern( "new" ), 1, \
+ Data_Wrap_Struct( rb_cObject, 0, 0, x->N ) ); \
+ }
+
+#define RBHTP_R_URI( T, N ) RBHTP_R_HTP( T, N, cURI )
+
+static VALUE rbhtp_r_string_table( htp_table_t* table )
+{
+ if ( table == NULL ) return Qnil;
+
+ bstr *k, *v;
+ VALUE r = rb_ary_new();
+ for (int i = 0, n = htp_table_size(table); i < n; i++) {
+ v = htp_table_get_index(table, i, &k);
+ rb_ary_push( r, rb_ary_new3( 2,
+ BSTR_TO_RSTR( *k ), BSTR_TO_RSTR( *v ) ) );
+ }
+ return r;
+}
+
+#define RBHTP_R_STRING_TABLE( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ return rbhtp_r_string_table( x->N ); \
+ }
+
+// We don't push the keys as they are duplicated in the header.
+static VALUE rbhtp_r_header_table( htp_table_t* table )
+{
+ if ( table == NULL ) return Qnil;
+ htp_header_t* v;
+ VALUE r = rb_ary_new();
+
+ for (int i = 0, n = htp_table_size(table); i < n; i++) {
+ v = htp_table_get_index(table, i, NULL);
+ rb_ary_push( r,
+ rb_funcall( cHeader, rb_intern( "new" ), 1,
+ Data_Wrap_Struct( rb_cObject, 0, 0, v ) ) );
+ }
+
+ return r;
+}
+
+#define RBHTP_R_HEADER_TABLE( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ return rbhtp_r_header_table( x->N ); \
+ }
+
+static VALUE rbhtp_r_header_line_list( htp_list_t* list )
+{
+ if ( list == NULL ) return Qnil;
+ VALUE r = rb_ary_new();
+ for (int i = 0, n = htp_list_size(list); i < n; i++) {
+ htp_header_line_t *v = htp_list_get(list, i);
+
+ rb_ary_push( r,
+ rb_funcall( cHeaderLine, rb_intern( "new" ), 1,
+ Data_Wrap_Struct( rb_cObject, 0, 0, v ) ) );
+ }
+ return r;
+}
+
+#define RBHTP_R_HEADER_LINE_LIST( T, N ) \
+ VALUE rbhtp_ ## T ## _ ## N( VALUE self ) \
+ { \
+ htp_ ## T ## _t* x = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@" #T ), htp_ ## T ## _t, x ); \
+ return rbhtp_r_header_line_list( x->N ); \
+ }
+
+// This function is only needed when we malloc the URI ourselves.
+void rbhtp_free_uri( void* p )
+{
+ htp_uri_t* uri = (htp_uri_t*)p;
+ free( uri );
+}
+
+//---- HTP ---
+VALUE rbhtp_get_version( VALUE self )
+{
+ return rb_str_new2( htp_get_version() );
+}
+
+// We return a HTP::URI and throw an exception on error.
+VALUE rbhtp_parse_uri( VALUE self, VALUE input )
+{
+ Check_Type( input, T_STRING );
+ bstr* input_b = bstr_dup_mem( RSTRING_PTR( input ), RSTRING_LEN( input ) );
+ htp_uri_t* uri = NULL; // htp_parse_uri will alloc.
+
+ int result = htp_parse_uri( input_b, &uri );
+ if ( result != HTP_OK ) {
+ bstr_free( input_b );
+ free( uri );
+ rb_raise( rb_eRuntimeError, "HTP error in htp_parse_uri: %d", result );
+ return Qnil; // Ignored?
+ }
+
+ bstr_free( input_b ); // Okay, as htp_parse_uri dups the data it needs.
+
+ return rb_funcall( cURI, rb_intern( "new" ), 1,
+ Data_Wrap_Struct( rb_cObject, 0, rbhtp_free_uri, uri )
+ );
+}
+
+//---- Cfg ----
+
+// Terminate list with "".
+static char* const rbhtp_config_pvars[] = {
+ "@request_proc",
+ "@request_proc",
+ "@transaction_start",
+ "@request_line",
+ "@request_headers",
+ "@request_trailer",
+ "@response_line",
+ "@response_headers",
+ "@response_trailers",
+ ""
+};
+
+void rbhtp_config_free( void* p )
+{
+ htp_cfg_t* cfg = (htp_cfg_t*)p;
+ htp_config_destroy( cfg );
+}
+
+VALUE rbhtp_config_initialize( VALUE self )
+{
+ char* const* v = &rbhtp_config_pvars[0];
+ while ( *v[0] != '\0' ) {
+ rb_iv_set( self, *v, Qnil );
+ ++v;
+ }
+
+ htp_cfg_t* cfg = htp_config_create();
+
+ rb_iv_set( self, "@cfg",
+ Data_Wrap_Struct( rb_cObject, 0, rbhtp_config_free, cfg )
+ );
+
+ return Qnil;
+}
+
+VALUE rbhtp_config_copy( VALUE self )
+{
+ // We create one too many copies here.
+ VALUE new_config = rb_funcall( cCfg, rb_intern( "new" ), 0 );
+ htp_cfg_t* cfg = NULL;
+ Data_Get_Struct( rb_iv_get( self, "@cfg" ), htp_cfg_t, cfg );
+
+ // Note that the existing new_config @cfg will be garbage collected as a
+ // result of this set.
+
+ rb_iv_set( new_config, "@cfg",
+ Data_Wrap_Struct( rb_cObject, 0, rbhtp_config_free,
+ htp_config_copy( cfg ) ) );
+
+ // Now copy over all our callbacks.
+ char* const* v = &rbhtp_config_pvars[0];
+ while ( *v[0] != '\0' ) {
+ rb_iv_set( new_config, *v, rb_iv_get( self, *v ) );
+ ++v;
+ }
+
+ return new_config;
+}
+
+VALUE rbhtp_config_set_server_personality( VALUE self, VALUE personality )
+{
+ Check_Type( personality, T_FIXNUM );
+
+ htp_cfg_t* cfg = NULL;
+ Data_Get_Struct( rb_iv_get( self, "@cfg" ), htp_cfg_t, cfg );
+
+ return INT2FIX(
+ htp_config_set_server_personality( cfg, FIX2INT( personality ) )
+ );
+}
+
+VALUE rbhtp_config_register_urlencoded_parser( VALUE self )
+{
+ htp_cfg_t* cfg = NULL;
+ Data_Get_Struct( rb_iv_get( self, "@cfg" ), htp_cfg_t, cfg );
+
+ htp_config_register_urlencoded_parser( cfg );
+
+ return Qnil;
+}
+
+#define RBHTP_CALLBACK_SUB( N ) \
+ VALUE rbhtp_config_register_ ## N( VALUE self ) \
+ { \
+ if ( ! rb_block_given_p() ) { \
+ rb_raise( rb_eArgError, "A block is required." ); \
+ return Qnil; \
+ } \
+ VALUE proc = rb_iv_get( self, "@" #N "_proc" ); \
+ if ( proc == Qnil ) { \
+ htp_cfg_t* cfg = NULL; \
+ Data_Get_Struct( rb_iv_get( self, "@cfg" ), htp_cfg_t, cfg ); \
+ htp_config_register_## N( cfg, rbhtp_config_callback_ ## N ); \
+ } \
+ rb_iv_set( self, "@" #N "_proc", rb_block_proc() ); \
+ return self; \
+ }
+
+#define RBHTP_CONNP_CALLBACK( N ) \
+ int rbhtp_config_callback_ ## N( htp_connp_t* connp ) \
+ { \
+ VALUE userdata = (VALUE)htp_connp_get_user_data( connp ); \
+ VALUE config = rb_iv_get( userdata, "@cfg" ); \
+ VALUE proc = rb_iv_get( config, "@" #N "_proc" ); \
+ if ( proc != Qnil ) { \
+ return INT2FIX( \
+ rb_funcall( proc, rb_intern( "call" ), 1, userdata ) \
+ ); \
+ } \
+ return 1; \
+ } \
+ RBHTP_CALLBACK_SUB( N )
+
+// Tx data is a tx and a data block. For *_body_data callbacks we pass
+// in the tx as first argument and the data as a string as the second argument.
+#define RBHTP_TXDATA_CALLBACK( N ) \
+ int rbhtp_config_callback_ ##N( htp_tx_data_t* txdata ) \
+ { \
+ htp_connp_t* connp = txdata->tx->connp; \
+ VALUE userdata = (VALUE)htp_connp_get_user_data( connp ); \
+ VALUE config = rb_iv_get( userdata, "@cfg" ); \
+ VALUE proc = rb_iv_get( config, "@" #N "_proc" ); \
+ if ( proc != Qnil ) { \
+ VALUE data = Qnil; \
+ if ( txdata->data ) \
+ data = rb_str_new( (char*)txdata->data, txdata->len ); \
+ return INT2FIX( \
+ rb_funcall( proc, rb_intern( "call" ), 2, \
+ rb_funcall( cTx, rb_intern( "new" ), 3, \
+ Data_Wrap_Struct( rb_cObject, 0, 0, txdata->tx ), \
+ config, \
+ userdata \
+ ), \
+ data \
+ ) \
+ ); \
+ } \
+ return 1; \
+ } \
+ RBHTP_CALLBACK_SUB( N )
+
+
+RBHTP_CONNP_CALLBACK( request )
+RBHTP_CONNP_CALLBACK( response )
+RBHTP_CONNP_CALLBACK( transaction_start )
+RBHTP_CONNP_CALLBACK( request_line )
+RBHTP_CONNP_CALLBACK( request_headers )
+RBHTP_CONNP_CALLBACK( request_trailer )
+RBHTP_CONNP_CALLBACK( response_line )
+RBHTP_CONNP_CALLBACK( response_headers )
+RBHTP_CONNP_CALLBACK( response_trailer )
+
+RBHTP_TXDATA_CALLBACK( request_body_data )
+RBHTP_TXDATA_CALLBACK( response_body_data )
+
+RBHTP_R_INT( cfg, spersonality )
+RBHTP_RW_INT( cfg, parse_request_cookies )
+
+// File data is a tx, file information, and file data. The callback thus
+// takes those three as arguments.
+int rbhtp_config_callback_request_file_data( htp_file_data_t* filedata )
+{
+ htp_connp_t* connp = filedata->tx->connp;
+ VALUE userdata = (VALUE)htp_connp_get_user_data( connp );
+ VALUE config = rb_iv_get( userdata, "@cfg" );
+ VALUE proc = rb_iv_get( config, "@request_file_data_proc" );
+ if ( proc != Qnil ) {
+ VALUE data = Qnil;
+ if ( filedata->data )
+ data = rb_str_new( (char*)filedata->data, filedata->len );
+ return INT2FIX(
+ rb_funcall( proc, rb_intern( "call" ), 2,
+ rb_funcall( cTx, rb_intern( "new" ), 1,
+ Data_Wrap_Struct( rb_cObject, 0, 0, filedata->tx )
+ ),
+ rb_funcall( cFile, rb_intern( "new" ), 1,
+ Data_Wrap_Struct( rb_cObject, 0, 0, filedata->file )
+ ),
+ data
+ )
+ );
+ }
+ return 1;
+}
+RBHTP_CALLBACK_SUB( request_file_data )
+
+//---- Connp ----
+
+void rbhtp_connp_free( void* p )
+{
+ htp_connp_t* connp = (htp_connp_t*)p;
+ if ( connp )
+ htp_connp_destroy_all( connp );
+}
+
+VALUE rbhtp_connp_initialize( VALUE self, VALUE config )
+{
+ rb_iv_set( self, "@cfg", config );
+
+ htp_cfg_t* cfg = NULL;
+ Data_Get_Struct( rb_iv_get( config, "@cfg" ), htp_cfg_t, cfg );
+
+ htp_connp_t* connp = htp_connp_create( cfg );
+ htp_connp_set_user_data( connp, (void*)self );
+ rb_iv_set( self, "@connp",
+ Data_Wrap_Struct( rb_cObject, 0, rbhtp_connp_free, connp )
+ );
+
+ return Qnil;
+}
+
+VALUE rbhtp_connp_req_data( VALUE self, VALUE timestamp, VALUE data )
+{
+ if ( strncmp( "Time", rb_class2name( CLASS_OF( timestamp ) ), 4 ) != 0 ) {
+ rb_raise( rb_eTypeError, "First argument must be a Time." );
+ return Qnil;
+ }
+
+ StringValue( data ); // try to make data a string.
+ Check_Type( data, T_STRING );
+
+ size_t len = RSTRING_LEN( data );
+ char* data_c = RSTRING_PTR( data );
+
+ htp_time_t timestamp_c;
+ timestamp_c.tv_sec =
+ FIX2INT( rb_funcall( timestamp, rb_intern( "tv_sec" ), 0 ) );
+ timestamp_c.tv_usec =
+ FIX2INT( rb_funcall( timestamp, rb_intern( "tv_usec" ), 0 ) );
+
+ VALUE connp_r = rb_iv_get( self, "@connp" );
+ htp_connp_t* connp = NULL;
+ Data_Get_Struct( connp_r, htp_connp_t, connp );
+
+ int result =
+ htp_connp_req_data( connp, &timestamp_c, (unsigned char*)data_c, len );
+
+ return INT2FIX( result );
+}
+
+VALUE rbhtp_connp_in_tx( VALUE self )
+{
+ VALUE connp_r = rb_iv_get( self, "@connp" );
+ VALUE config = rb_iv_get( self, "@cfg" );
+ htp_connp_t* connp = NULL;
+ Data_Get_Struct( connp_r, htp_connp_t, connp );
+
+ if ( connp->in_tx == NULL )
+ return Qnil;
+
+ return rb_funcall( cTx, rb_intern( "new" ), 3,
+ Data_Wrap_Struct( rb_cObject, 0, 0, connp->in_tx ),
+ config,
+ self
+ );
+}
+
+VALUE rbhtp_connp_conn( VALUE self )
+{
+ htp_connp_t* connp = NULL;
+ Data_Get_Struct( rb_iv_get( self, "@connp" ), htp_connp_t, connp );
+ if ( connp->conn == NULL )
+ return Qnil;
+ return rb_funcall( cConn, rb_intern( "new" ), 2,
+ Data_Wrap_Struct( rb_cObject, 0, 0, connp->conn ),
+ self
+ );
+}
+
+// Unlike Connp and Cfg, these are just wrapper. The lifetime of the
+// underlying objects are bound to the Connp.
+
+//---- Header ----
+VALUE rbhtp_header_initialize( VALUE self, VALUE raw_header )
+{
+ rb_iv_set( self, "@header", raw_header );
+ return Qnil;
+}
+
+RBHTP_R_STRING( header, name );
+RBHTP_R_STRING( header, value );
+RBHTP_R_INT( header, flags );
+
+// ---- Header Line ----
+VALUE rbhtp_header_line_initialize( VALUE self, VALUE raw_header_line )
+{
+ rb_iv_set( self, "@header_line", raw_header_line );
+ return Qnil;
+}
+
+VALUE rbhtp_header_line_header( VALUE self )
+{
+ htp_header_line_t* hline = NULL;
+ Data_Get_Struct( rb_iv_get( self, "@header_line" ), htp_header_line_t, hline );
+
+ if ( hline->header == NULL )
+ return Qnil;
+
+ return rb_funcall( cHeader, rb_intern( "new" ), 1,
+ Data_Wrap_Struct( rb_cObject, 0, 0, hline->header )
+ );
+}
+
+RBHTP_R_STRING( header_line, line );
+RBHTP_R_INT( header_line, name_offset );
+RBHTP_R_INT( header_line, name_len );
+RBHTP_R_INT( header_line, value_offset );
+RBHTP_R_INT( header_line, value_len );
+RBHTP_R_INT( header_line, has_nulls );
+RBHTP_R_INT( header_line, first_nul_offset );
+RBHTP_R_INT( header_line, flags );
+
+// ---- URI ----
+VALUE rbhtp_uri_initialize( VALUE self, VALUE raw_uri )
+{
+ rb_iv_set( self, "@uri", raw_uri );
+ return Qnil;
+}
+
+RBHTP_R_STRING( uri, scheme );
+RBHTP_R_STRING( uri, username );
+RBHTP_R_STRING( uri, password );
+RBHTP_R_STRING( uri, hostname );
+RBHTP_R_STRING( uri, port );
+RBHTP_R_INT( uri, port_number );
+RBHTP_R_STRING( uri, path );
+RBHTP_R_STRING( uri, query );
+RBHTP_R_STRING( uri, fragment );
+
+//---- Tx ----
+
+VALUE rbhtp_tx_initialize(
+ VALUE self,
+ VALUE raw_txn,
+ VALUE cfg,
+ VALUE connp )
+{
+ rb_iv_set( self, "@tx", raw_txn );
+ rb_iv_set( self, "@cfg", cfg );
+ rb_iv_set( self, "@connp", connp );
+
+ return Qnil;
+}
+
+RBHTP_R_INT( tx, request_ignored_lines )
+RBHTP_R_INT( tx, request_line_nul )
+RBHTP_R_INT( tx, request_line_nul_offset )
+RBHTP_R_INT( tx, request_method_number )
+RBHTP_R_INT( tx, request_protocol_number )
+RBHTP_R_INT( tx, protocol_is_simple )
+RBHTP_R_INT( tx, request_message_len )
+RBHTP_R_INT( tx, request_entity_len )
+RBHTP_R_INT( tx, request_nonfiledata_len )
+RBHTP_R_INT( tx, request_filedata_len )
+RBHTP_R_INT( tx, request_header_lines_no_trailers )
+RBHTP_R_INT( tx, request_headers_raw_lines )
+RBHTP_R_INT( tx, request_transfer_coding )
+RBHTP_R_INT( tx, request_content_encoding )
+RBHTP_R_INT( tx, request_params_query_reused )
+RBHTP_R_INT( tx, request_params_body_reused )
+RBHTP_R_INT( tx, request_auth_type )
+RBHTP_R_INT( tx, response_ignored_lines )
+RBHTP_R_INT( tx, response_protocol_number )
+RBHTP_R_INT( tx, response_status_number )
+RBHTP_R_INT( tx, response_status_expected_number )
+RBHTP_R_INT( tx, seen_100continue )
+RBHTP_R_INT( tx, response_message_len )
+RBHTP_R_INT( tx, response_entity_len )
+RBHTP_R_INT( tx, response_transfer_coding )
+RBHTP_R_INT( tx, response_content_encoding )
+RBHTP_R_INT( tx, flags )
+RBHTP_R_INT( tx, progress )
+
+RBHTP_R_STRING( tx, request_method )
+RBHTP_R_STRING( tx, request_line )
+RBHTP_R_STRING( tx, request_uri )
+RBHTP_R_STRING( tx, request_uri_normalized )
+RBHTP_R_STRING( tx, request_protocol )
+RBHTP_R_STRING( tx, request_headers_raw )
+RBHTP_R_STRING( tx, request_headers_sep )
+RBHTP_R_STRING( tx, request_content_type )
+RBHTP_R_STRING( tx, request_auth_username )
+RBHTP_R_STRING( tx, request_auth_password )
+RBHTP_R_STRING( tx, response_line )
+RBHTP_R_STRING( tx, response_protocol )
+RBHTP_R_STRING( tx, response_status )
+RBHTP_R_STRING( tx, response_message )
+RBHTP_R_STRING( tx, response_headers_sep )
+
+RBHTP_R_STRING_TABLE( tx, request_params_query )
+RBHTP_R_STRING_TABLE( tx, request_params_body )
+RBHTP_R_STRING_TABLE( tx, request_cookies )
+RBHTP_R_HEADER_TABLE( tx, request_headers )
+RBHTP_R_HEADER_TABLE( tx, response_headers )
+
+RBHTP_R_HEADER_LINE_LIST( tx, request_header_lines );
+RBHTP_R_HEADER_LINE_LIST( tx, response_header_lines );
+
+RBHTP_R_URI( tx, parsed_uri )
+RBHTP_R_URI( tx, parsed_uri_incomplete )
+
+VALUE rbhtp_tx_conn( VALUE self )
+{
+ htp_tx_t* tx = NULL;
+ Data_Get_Struct( rb_iv_get( self, "@tx" ), htp_tx_t, tx );
+ if ( tx->conn == NULL )
+ return Qnil;
+ return rb_funcall( cConn, rb_intern( "new" ), 2,
+ Data_Wrap_Struct( rb_cObject, 0, 0, tx->conn ),
+ rb_iv_get( self, "@connp" )
+ );
+}
+
+// ---- File ----
+VALUE rbhtp_file_initialize( VALUE self, VALUE raw_file )
+{
+ rb_iv_set( self, "@file", raw_file );
+ return Qnil;
+}
+
+RBHTP_R_INT( file, source )
+RBHTP_R_STRING( file, filename )
+RBHTP_R_INT( file, len )
+RBHTP_R_CSTR( file, tmpname )
+RBHTP_R_INT( file, fd )
+
+// ---- Conn ----
+VALUE rbhtp_conn_initialize( VALUE self, VALUE raw_conn, VALUE connp )
+{
+ rb_iv_set( self, "@conn", raw_conn );
+ rb_iv_set( self, "@connp", connp );
+ return Qnil;
+}
+
+RBHTP_R_CSTR( conn, remote_addr )
+RBHTP_R_INT( conn, remote_port )
+RBHTP_R_CSTR( conn, local_addr )
+RBHTP_R_INT( conn, local_port )
+RBHTP_R_INT( conn, flags )
+RBHTP_R_INT( conn, in_data_counter )
+RBHTP_R_INT( conn, out_data_counter )
+RBHTP_R_INT( conn, in_packet_counter )
+RBHTP_R_INT( conn, out_packet_counter )
+RBHTP_R_TV( conn, open_timestamp )
+RBHTP_R_TV( conn, close_timestamp )
+
+VALUE rbhtp_conn_transactions( VALUE self )
+{
+ htp_conn_t* conn = NULL;
+ Data_Get_Struct( rb_iv_get( self, "@conn" ), htp_conn_t, conn );
+
+ if ( conn->transactions == NULL ) return Qnil;
+
+ VALUE connp = rb_iv_get( self, "@connp" );
+ VALUE cfg = rb_iv_get( connp, "@cfg" );
+
+ VALUE r = rb_ary_new();
+
+ for (int i = 0, n = htp_list_size(conn->transactions); i < n; i++) {
+ htp_tx_t *v = htp_list_get(conn->transactions, i);
+
+ rb_ary_push( r,
+ rb_funcall( cTx, rb_intern( "new" ), 3,
+ Data_Wrap_Struct( rb_cObject, 0, 0, v ),
+ cfg,
+ connp
+ )
+ );
+ }
+ return r;
+}
+
+//---- Init ----
+void Init_htp( void )
+{
+ mHTP = rb_define_module( "HTP" );
+
+ rb_define_singleton_method( mHTP, "get_version", rbhtp_get_version, 0 );
+ rb_define_singleton_method( mHTP, "parse_uri", rbhtp_parse_uri, 1 );
+
+ // All numeric constants from htp.h.
+ rb_define_const( mHTP, "HTP_ERROR", INT2FIX( HTP_ERROR ) );
+ rb_define_const( mHTP, "HTP_OK", INT2FIX( HTP_OK ) );
+ rb_define_const( mHTP, "HTP_DATA", INT2FIX( HTP_DATA ) );
+ rb_define_const( mHTP, "HTP_DATA_OTHER", INT2FIX( HTP_DATA_OTHER ) );
+ rb_define_const( mHTP, "HTP_DECLINED", INT2FIX( HTP_DECLINED ) );
+ rb_define_const( mHTP, "PROTOCOL_UNKNOWN", INT2FIX( HTP_PROTOCOL_UNKNOWN ) );
+ rb_define_const( mHTP, "HTTP_0_9", INT2FIX( HTP_PROTOCOL_0_9 ) );
+ rb_define_const( mHTP, "HTTP_1_0", INT2FIX( HTP_PROTOCOL_1_0 ) );
+ rb_define_const( mHTP, "HTTP_1_1", INT2FIX( HTP_PROTOCOL_1_1 ) );
+ rb_define_const( mHTP, "HTP_LOG_ERROR", INT2FIX( HTP_LOG_ERROR ) );
+ rb_define_const( mHTP, "HTP_LOG_WARNING", INT2FIX( HTP_LOG_WARNING ) );
+ rb_define_const( mHTP, "HTP_LOG_NOTICE", INT2FIX( HTP_LOG_NOTICE ) );
+ rb_define_const( mHTP, "HTP_LOG_INFO", INT2FIX( HTP_LOG_INFO ) );
+ rb_define_const( mHTP, "HTP_LOG_DEBUG", INT2FIX( HTP_LOG_DEBUG ) );
+ rb_define_const( mHTP, "HTP_LOG_DEBUG2", INT2FIX( HTP_LOG_DEBUG2 ) );
+ rb_define_const( mHTP, "HTP_HEADER_MISSING_COLON", INT2FIX( HTP_HEADER_MISSING_COLON ) );
+ rb_define_const( mHTP, "HTP_HEADER_INVALID_NAME", INT2FIX( HTP_HEADER_INVALID_NAME ) );
+ rb_define_const( mHTP, "HTP_HEADER_LWS_AFTER_FIELD_NAME", INT2FIX( HTP_HEADER_LWS_AFTER_FIELD_NAME ) );
+ rb_define_const( mHTP, "HTP_LINE_TOO_LONG_HARD", INT2FIX( HTP_LINE_TOO_LONG_HARD ) );
+ rb_define_const( mHTP, "HTP_LINE_TOO_LONG_SOFT", INT2FIX( HTP_LINE_TOO_LONG_SOFT ) );
+ rb_define_const( mHTP, "HTP_HEADER_LIMIT_HARD", INT2FIX( HTP_HEADER_LIMIT_HARD ) );
+ rb_define_const( mHTP, "HTP_HEADER_LIMIT_SOFT", INT2FIX( HTP_HEADER_LIMIT_SOFT ) );
+ rb_define_const( mHTP, "HTP_VALID_STATUS_MIN", INT2FIX( HTP_VALID_STATUS_MIN ) );
+ rb_define_const( mHTP, "HTP_VALID_STATUS_MAX", INT2FIX( HTP_VALID_STATUS_MAX ) );
+ rb_define_const( mHTP, "M_UNKNOWN", INT2FIX( M_UNKNOWN ) );
+ rb_define_const( mHTP, "M_GET", INT2FIX( M_GET ) );
+ rb_define_const( mHTP, "M_PUT", INT2FIX( M_PUT ) );
+ rb_define_const( mHTP, "M_POST", INT2FIX( M_POST ) );
+ rb_define_const( mHTP, "M_DELETE", INT2FIX( M_DELETE ) );
+ rb_define_const( mHTP, "M_CONNECT", INT2FIX( M_CONNECT ) );
+ rb_define_const( mHTP, "M_OPTIONS", INT2FIX( M_OPTIONS ) );
+ rb_define_const( mHTP, "M_TRACE", INT2FIX( M_TRACE ) );
+ rb_define_const( mHTP, "M_PATCH", INT2FIX( M_PATCH ) );
+ rb_define_const( mHTP, "M_PROPFIND", INT2FIX( M_PROPFIND ) );
+ rb_define_const( mHTP, "M_PROPPATCH", INT2FIX( M_PROPPATCH ) );
+ rb_define_const( mHTP, "M_MKCOL", INT2FIX( M_MKCOL ) );
+ rb_define_const( mHTP, "M_COPY", INT2FIX( M_COPY ) );
+ rb_define_const( mHTP, "M_MOVE", INT2FIX( M_MOVE ) );
+ rb_define_const( mHTP, "M_LOCK", INT2FIX( M_LOCK ) );
+ rb_define_const( mHTP, "M_UNLOCK", INT2FIX( M_UNLOCK ) );
+ rb_define_const( mHTP, "M_VERSION_CONTROL", INT2FIX( M_VERSION_CONTROL ) );
+ rb_define_const( mHTP, "M_CHECKOUT", INT2FIX( M_CHECKOUT ) );
+ rb_define_const( mHTP, "M_UNCHECKOUT", INT2FIX( M_UNCHECKOUT ) );
+ rb_define_const( mHTP, "M_CHECKIN", INT2FIX( M_CHECKIN ) );
+ rb_define_const( mHTP, "M_UPDATE", INT2FIX( M_UPDATE ) );
+ rb_define_const( mHTP, "M_LABEL", INT2FIX( M_LABEL ) );
+ rb_define_const( mHTP, "M_REPORT", INT2FIX( M_REPORT ) );
+ rb_define_const( mHTP, "M_MKWORKSPACE", INT2FIX( M_MKWORKSPACE ) );
+ rb_define_const( mHTP, "M_MKACTIVITY", INT2FIX( M_MKACTIVITY ) );
+ rb_define_const( mHTP, "M_BASELINE_CONTROL", INT2FIX( M_BASELINE_CONTROL ) );
+ rb_define_const( mHTP, "M_MERGE", INT2FIX( M_MERGE ) );
+ rb_define_const( mHTP, "M_INVALID", INT2FIX( M_INVALID ) );
+ rb_define_const( mHTP, "M_HEAD", INT2FIX( HTP_M_HEAD ) );
+ rb_define_const( mHTP, "HTP_FIELD_UNPARSEABLE", INT2FIX( HTP_FIELD_UNPARSEABLE ) );
+ rb_define_const( mHTP, "HTP_FIELD_INVALID", INT2FIX( HTP_FIELD_INVALID ) );
+ rb_define_const( mHTP, "HTP_FIELD_FOLDED", INT2FIX( HTP_FIELD_FOLDED ) );
+ rb_define_const( mHTP, "HTP_FIELD_REPEATED", INT2FIX( HTP_FIELD_REPEATED ) );
+ rb_define_const( mHTP, "HTP_FIELD_LONG", INT2FIX( HTP_FIELD_LONG ) );
+ rb_define_const( mHTP, "HTP_FIELD_NUL_BYTE", INT2FIX( HTP_FIELD_RAW_NUL ) );
+ rb_define_const( mHTP, "HTP_REQUEST_SMUGGLING", INT2FIX( HTP_REQUEST_SMUGGLING ) );
+ rb_define_const( mHTP, "HTP_INVALID_FOLDING", INT2FIX( HTP_INVALID_FOLDING ) );
+ rb_define_const( mHTP, "HTP_INVALID_CHUNKING", INT2FIX( HTP_REQUEST_INVALID_T_E ) );
+ rb_define_const( mHTP, "HTP_MULTI_PACKET_HEAD", INT2FIX( HTP_MULTI_PACKET_HEAD ) );
+ rb_define_const( mHTP, "HTP_HOST_MISSING", INT2FIX( HTP_HOST_MISSING ) );
+ rb_define_const( mHTP, "HTP_AMBIGUOUS_HOST", INT2FIX( HTP_HOST_AMBIGUOUS ) );
+ rb_define_const( mHTP, "HTP_PATH_ENCODED_NUL", INT2FIX( HTP_PATH_ENCODED_NUL ) );
+ rb_define_const( mHTP, "HTP_PATH_INVALID_ENCODING", INT2FIX( HTP_PATH_INVALID_ENCODING ) );
+ rb_define_const( mHTP, "HTP_PATH_INVALID", INT2FIX( HTP_PATH_INVALID ) );
+ rb_define_const( mHTP, "HTP_PATH_OVERLONG_U", INT2FIX( HTP_PATH_OVERLONG_U ) );
+ rb_define_const( mHTP, "HTP_PATH_ENCODED_SEPARATOR", INT2FIX( HTP_PATH_ENCODED_SEPARATOR ) );
+ rb_define_const( mHTP, "HTP_PATH_UTF8_VALID", INT2FIX( HTP_PATH_UTF8_VALID ) );
+ rb_define_const( mHTP, "HTP_PATH_UTF8_INVALID", INT2FIX( HTP_PATH_UTF8_INVALID ) );
+ rb_define_const( mHTP, "HTP_PATH_UTF8_OVERLONG", INT2FIX( HTP_PATH_UTF8_OVERLONG ) );
+ rb_define_const( mHTP, "HTP_PATH_FULLWIDTH_EVASION", INT2FIX( HTP_PATH_HALF_FULL_RANGE ) );
+ rb_define_const( mHTP, "HTP_STATUS_LINE_INVALID", INT2FIX( HTP_STATUS_LINE_INVALID ) );
+ rb_define_const( mHTP, "PIPELINED_CONNECTION", INT2FIX( HTP_CONN_PIPELINED ) );
+ rb_define_const( mHTP, "HTP_SERVER_MINIMAL", INT2FIX( HTP_SERVER_MINIMAL ) );
+ rb_define_const( mHTP, "HTP_SERVER_GENERIC", INT2FIX( HTP_SERVER_GENERIC ) );
+ rb_define_const( mHTP, "HTP_SERVER_IDS", INT2FIX( HTP_SERVER_IDS ) );
+ rb_define_const( mHTP, "HTP_SERVER_IIS_4_0", INT2FIX( HTP_SERVER_IIS_4_0 ) );
+ rb_define_const( mHTP, "HTP_SERVER_IIS_5_0", INT2FIX( HTP_SERVER_IIS_5_0 ) );
+ rb_define_const( mHTP, "HTP_SERVER_IIS_5_1", INT2FIX( HTP_SERVER_IIS_5_1 ) );
+ rb_define_const( mHTP, "HTP_SERVER_IIS_6_0", INT2FIX( HTP_SERVER_IIS_6_0 ) );
+ rb_define_const( mHTP, "HTP_SERVER_IIS_7_0", INT2FIX( HTP_SERVER_IIS_7_0 ) );
+ rb_define_const( mHTP, "HTP_SERVER_IIS_7_5", INT2FIX( HTP_SERVER_IIS_7_5 ) );
+ rb_define_const( mHTP, "HTP_SERVER_TOMCAT_6_0", INT2FIX( HTP_SERVER_TOMCAT_6_0 ) );
+ rb_define_const( mHTP, "HTP_SERVER_APACHE", INT2FIX( HTP_SERVER_APACHE ) );
+ rb_define_const( mHTP, "HTP_SERVER_APACHE_2_2", INT2FIX( HTP_SERVER_APACHE_2_2 ) );
+ rb_define_const( mHTP, "NONE", INT2FIX( HTP_AUTH_NONE ) );
+ rb_define_const( mHTP, "IDENTITY", INT2FIX( HTP_CODING_IDENTITY ) );
+ rb_define_const( mHTP, "CHUNKED", INT2FIX( HTP_CODING_CHUNKED ) );
+ rb_define_const( mHTP, "TX_PROGRESS_NEW", INT2FIX( HTP_REQUEST_NOT_STARTED ) );
+ rb_define_const( mHTP, "TX_PROGRESS_REQ_LINE", INT2FIX( HTP_REQUEST_LINE ) );
+ rb_define_const( mHTP, "TX_PROGRESS_REQ_HEADERS", INT2FIX( HTP_REQUEST_HEADERS ) );
+ rb_define_const( mHTP, "TX_PROGRESS_REQ_BODY", INT2FIX( HTP_REQUEST_BODY ) );
+ rb_define_const( mHTP, "TX_PROGRESS_REQ_TRAILER", INT2FIX( HTP_REQUEST_TRAILER ) );
+ rb_define_const( mHTP, "RESPONSE_WAIT", INT2FIX( HTP_REQUEST_COMPLETE ) );
+ rb_define_const( mHTP, "TX_PROGRESS_RES_LINE", INT2FIX( HTP_RESPONSE_LINE ) );
+ rb_define_const( mHTP, "RESPONSE_HEADERS", INT2FIX( HTP_RESPONSE_HEADERS ) );
+ rb_define_const( mHTP, "RESPONSE_BODY", INT2FIX( HTP_RESPONSE_BODY ) );
+ rb_define_const( mHTP, "TX_PROGRESS_RES_TRAILER", INT2FIX( HTP_RESPONSE_TRAILER ) );
+ rb_define_const( mHTP, "TX_PROGRESS_COMPLETE", INT2FIX( HTP_RESPONSE_COMPLETE ) );
+ rb_define_const( mHTP, "HTP_STREAM_NEW", INT2FIX( HTP_STREAM_NEW ) );
+ rb_define_const( mHTP, "HTP_STREAM_OPEN", INT2FIX( HTP_STREAM_OPEN ) );
+ rb_define_const( mHTP, "HTP_STREAM_CLOSED", INT2FIX( HTP_STREAM_CLOSED ) );
+ rb_define_const( mHTP, "HTP_STREAM_ERROR", INT2FIX( HTP_STREAM_ERROR ) );
+ rb_define_const( mHTP, "HTP_STREAM_TUNNEL", INT2FIX( HTP_STREAM_TUNNEL ) );
+ rb_define_const( mHTP, "HTP_STREAM_DATA_OTHER", INT2FIX( HTP_STREAM_DATA_OTHER ) );
+ rb_define_const( mHTP, "HTP_STREAM_DATA", INT2FIX( HTP_STREAM_DATA ) );
+ rb_define_const( mHTP, "URL_DECODER_PRESERVE_PERCENT", INT2FIX( HTP_URL_DECODE_PRESERVE_PERCENT ) );
+ rb_define_const( mHTP, "URL_DECODER_REMOVE_PERCENT", INT2FIX( HTP_URL_DECODE_REMOVE_PERCENT ) );
+ rb_define_const( mHTP, "URL_DECODER_DECODE_INVALID", INT2FIX( HTP_URL_DECODE_PROCESS_INVALID ) );
+ rb_define_const( mHTP, "URL_DECODER_STATUS_400", INT2FIX( HTP_URL_DECODE_STATUS_400 ) );
+ rb_define_const( mHTP, "NO", INT2FIX( NO ) );
+ rb_define_const( mHTP, "BESTFIT", INT2FIX( BESTFIT ) );
+ rb_define_const( mHTP, "YES", INT2FIX( YES ) );
+ rb_define_const( mHTP, "TERMINATE", INT2FIX( TERMINATE ) );
+ rb_define_const( mHTP, "STATUS_400", INT2FIX( STATUS_400 ) );
+ rb_define_const( mHTP, "STATUS_404", INT2FIX( STATUS_404 ) );
+ rb_define_const( mHTP, "HTP_AUTH_NONE", INT2FIX( HTP_AUTH_NONE ) );
+ rb_define_const( mHTP, "HTP_AUTH_BASIC", INT2FIX( HTP_AUTH_BASIC ) );
+ rb_define_const( mHTP, "HTP_AUTH_DIGEST", INT2FIX( HTP_AUTH_DIGEST ) );
+ rb_define_const( mHTP, "HTP_AUTH_UNKNOWN", INT2FIX( HTP_AUTH_UNRECOGNIZED ) );
+ rb_define_const( mHTP, "HTP_FILE_MULTIPART", INT2FIX( HTP_FILE_MULTIPART ) );
+ rb_define_const( mHTP, "HTP_FILE_PUT", INT2FIX( HTP_FILE_PUT ) );
+ rb_define_const( mHTP, "CFG_NOT_SHARED", INT2FIX( CFG_NOT_SHARED ) );
+ rb_define_const( mHTP, "CFG_SHARED", INT2FIX( CFG_SHARED ) );
+
+ cCfg = rb_define_class_under( mHTP, "Cfg", rb_cObject );
+ rb_define_method( cCfg, "initialize", rbhtp_config_initialize, 0 );
+ rb_define_method( cCfg, "copy", rbhtp_config_copy, 0 );
+
+ rb_define_method( cCfg, "register_response", rbhtp_config_register_response, 0 );
+ rb_define_method( cCfg, "register_request", rbhtp_config_register_request, 0 );
+ rb_define_method( cCfg, "register_transaction_start", rbhtp_config_register_transaction_start, 0 );
+ rb_define_method( cCfg, "register_request_line", rbhtp_config_register_request_line, 0 );
+ rb_define_method( cCfg, "register_request_headers", rbhtp_config_register_request_headers, 0 );
+ rb_define_method( cCfg, "register_request_trailer", rbhtp_config_register_request_trailer, 0 );
+ rb_define_method( cCfg, "register_response_line", rbhtp_config_register_response_line, 0 );
+ rb_define_method( cCfg, "register_response_headers", rbhtp_config_register_response_headers, 0 );
+ rb_define_method( cCfg, "register_response_trailer", rbhtp_config_register_response_trailer, 0 );
+
+ rb_define_method( cCfg, "register_urlencoded_parser", rbhtp_config_register_urlencoded_parser, 0 );
+ rb_define_method( cCfg, "register_request_body_data", rbhtp_config_register_request_body_data, 0 );
+ rb_define_method( cCfg, "register_response_body_data", rbhtp_config_register_request_body_data, 0 );
+ rb_define_method( cCfg, "register_request_file_data", rbhtp_config_register_request_file_data, 0 );
+
+ // server_personality= and server_personality are defined in htp_ruby.rb
+ rb_define_method( cCfg, "set_server_personality", rbhtp_config_set_server_personality, 1 );
+ rb_define_method( cCfg, "spersonality", rbhtp_cfg_spersonality, 0 );
+
+ rb_define_method( cCfg, "parse_request_cookies", rbhtp_cfg_parse_request_cookies, 0 );
+ rb_define_method( cCfg, "parse_request_cookies=", rbhtp_cfg_parse_request_cookies_set, 1 );
+ // TODO: Much more to add.
+
+ cConnp = rb_define_class_under( mHTP, "Connp", rb_cObject );
+ rb_define_method( cConnp, "initialize", rbhtp_connp_initialize, 1 );
+ rb_define_method( cConnp, "req_data", rbhtp_connp_req_data, 2 );
+ rb_define_method( cConnp, "in_tx", rbhtp_connp_in_tx, 0 );
+ rb_define_method( cConnp, "conn", rbhtp_connp_conn, 0 );
+ // TODO: Much more to Add.
+
+ cHeader = rb_define_class_under( mHTP, "Header", rb_cObject );
+ rb_define_method( cHeader, "initialize", rbhtp_header_initialize, 1 );
+ rb_define_method( cHeader, "name", rbhtp_header_name, 0 );
+ rb_define_method( cHeader, "value", rbhtp_header_value, 0 );
+ rb_define_method( cHeader, "flags", rbhtp_header_flags, 0 );
+
+ cHeaderLine = rb_define_class_under( mHTP, "HeaderLine", rb_cObject );
+ rb_define_method( cHeaderLine, "initialize", rbhtp_header_line_initialize, 1 );
+ rb_define_method( cHeaderLine, "header", rbhtp_header_line_header, 0 );
+ rb_define_method( cHeaderLine, "line", rbhtp_header_line_line, 0 );
+ rb_define_method( cHeaderLine, "name_offset", rbhtp_header_line_name_offset, 0 );
+ rb_define_method( cHeaderLine, "name_len", rbhtp_header_line_name_len, 0 );
+ rb_define_method( cHeaderLine, "value_offset", rbhtp_header_line_value_offset, 0 );
+ rb_define_method( cHeaderLine, "value_len", rbhtp_header_line_value_len, 0 );
+ rb_define_method( cHeaderLine, "has_nulls", rbhtp_header_line_has_nulls, 0 );
+ rb_define_method( cHeaderLine, "first_nul_offset", rbhtp_header_line_first_nul_offset, 0 );
+ rb_define_method( cHeaderLine, "flags", rbhtp_header_line_flags, 0 );
+
+ cURI = rb_define_class_under( mHTP, "URI", rb_cObject );
+ rb_define_method( cURI, "initialize", rbhtp_uri_initialize, 1 );
+
+ rb_define_method( cURI, "scheme", rbhtp_uri_scheme, 0 );
+ rb_define_method( cURI, "username", rbhtp_uri_username, 0 );
+ rb_define_method( cURI, "password", rbhtp_uri_password, 0 );
+ rb_define_method( cURI, "hostname", rbhtp_uri_hostname, 0 );
+ rb_define_method( cURI, "port", rbhtp_uri_port, 0 );
+ rb_define_method( cURI, "port_number", rbhtp_uri_port_number, 0 );
+ rb_define_method( cURI, "path", rbhtp_uri_path, 0 );
+ rb_define_method( cURI, "query", rbhtp_uri_query, 0 );
+ rb_define_method( cURI, "fragment", rbhtp_uri_fragment, 0 );
+
+ cTx = rb_define_class_under( mHTP, "Tx", rb_cObject );
+ rb_define_method( cTx, "initialize", rbhtp_tx_initialize, 3 );
+
+ rb_define_method( cTx, "request_ignored_lines", rbhtp_tx_request_ignored_lines, 0 );
+ rb_define_method( cTx, "request_line_nul", rbhtp_tx_request_line_nul, 0 );
+ rb_define_method( cTx, "request_line_nul_offset", rbhtp_tx_request_line_nul_offset, 0 );
+ rb_define_method( cTx, "request_method_number", rbhtp_tx_request_method_number, 0 );
+ rb_define_method( cTx, "request_line", rbhtp_tx_request_line, 0 );
+ rb_define_method( cTx, "request_method", rbhtp_tx_request_method, 0 );
+ rb_define_method( cTx, "request_uri", rbhtp_tx_request_uri, 0 );
+ rb_define_method( cTx, "request_uri_normalized", rbhtp_tx_request_uri_normalized, 0 );
+ rb_define_method( cTx, "request_protocol", rbhtp_tx_request_protocol, 0 );
+ rb_define_method( cTx, "request_headers_raw", rbhtp_tx_request_headers_raw, 0 );
+ rb_define_method( cTx, "request_headers_sep", rbhtp_tx_request_headers_sep, 0 );
+ rb_define_method( cTx, "request_content_type", rbhtp_tx_request_content_type, 0 );
+ rb_define_method( cTx, "request_auth_username", rbhtp_tx_request_auth_username, 0 );
+ rb_define_method( cTx, "request_auth_password", rbhtp_tx_request_auth_password, 0 );
+ rb_define_method( cTx, "response_line", rbhtp_tx_response_line, 0 );
+ rb_define_method( cTx, "response_protocol", rbhtp_tx_response_protocol, 0 );
+ rb_define_method( cTx, "response_status", rbhtp_tx_response_status, 0 );
+ rb_define_method( cTx, "response_message", rbhtp_tx_response_message, 0 );
+ rb_define_method( cTx, "response_headers_sep", rbhtp_tx_response_headers_sep, 0 );
+ rb_define_method( cTx, "request_protocol_number", rbhtp_tx_request_protocol_number, 0 );
+ rb_define_method( cTx, "protocol_is_simple", rbhtp_tx_protocol_is_simple, 0 );
+ rb_define_method( cTx, "request_message_len", rbhtp_tx_request_message_len, 0 );
+ rb_define_method( cTx, "request_entity_len", rbhtp_tx_request_entity_len, 0 );
+ rb_define_method( cTx, "request_nonfiledata_len", rbhtp_tx_request_nonfiledata_len, 0 );
+ rb_define_method( cTx, "request_filedata_len", rbhtp_tx_request_filedata_len, 0 );
+ rb_define_method( cTx, "request_header_lines_no_trailers", rbhtp_tx_request_header_lines_no_trailers, 0 );
+ rb_define_method( cTx, "request_headers_raw_lines", rbhtp_tx_request_headers_raw_lines, 0 );
+ rb_define_method( cTx, "request_transfer_coding", rbhtp_tx_request_transfer_coding, 0 );
+ rb_define_method( cTx, "request_content_encoding", rbhtp_tx_request_content_encoding, 0 );
+ rb_define_method( cTx, "request_params_query_reused", rbhtp_tx_request_params_query_reused, 0 );
+ rb_define_method( cTx, "request_params_body_reused", rbhtp_tx_request_params_body_reused, 0 );
+ rb_define_method( cTx, "request_auth_type", rbhtp_tx_request_auth_type, 0 );
+ rb_define_method( cTx, "response_ignored_lines", rbhtp_tx_response_ignored_lines, 0 );
+ rb_define_method( cTx, "response_protocol_number", rbhtp_tx_response_protocol_number, 0 );
+ rb_define_method( cTx, "response_status_number", rbhtp_tx_response_status_number, 0 );
+ rb_define_method( cTx, "response_status_expected_number", rbhtp_tx_response_status_expected_number, 0 );
+ rb_define_method( cTx, "seen_100continue", rbhtp_tx_seen_100continue, 0 );
+ rb_define_method( cTx, "response_message_len", rbhtp_tx_response_message_len, 0 );
+ rb_define_method( cTx, "response_entity_len", rbhtp_tx_response_entity_len, 0 );
+ rb_define_method( cTx, "response_transfer_coding", rbhtp_tx_response_transfer_coding, 0 );
+ rb_define_method( cTx, "response_content_encoding", rbhtp_tx_response_content_encoding, 0 );
+ rb_define_method( cTx, "flags", rbhtp_tx_flags, 0 );
+ rb_define_method( cTx, "progress", rbhtp_tx_progress, 0 );
+
+ rb_define_method( cTx, "request_params_query", rbhtp_tx_request_params_query, 0 );
+ rb_define_method( cTx, "request_params_body", rbhtp_tx_request_params_body, 0 );
+ rb_define_method( cTx, "request_cookies", rbhtp_tx_request_cookies, 0 );
+ rb_define_method( cTx, "request_headers", rbhtp_tx_request_headers, 0 );
+ rb_define_method( cTx, "response_headers", rbhtp_tx_response_headers, 0 );
+
+ rb_define_method( cTx, "request_header_lines", rbhtp_tx_request_header_lines, 0 );
+ rb_define_method( cTx, "response_header_lines", rbhtp_tx_response_header_lines, 0 );
+
+ rb_define_method( cTx, "parsed_uri", rbhtp_tx_parsed_uri, 0 );
+ rb_define_method( cTx, "parsed_uri_incomplete", rbhtp_tx_parsed_uri_incomplete, 0 );
+
+ rb_define_method( cTx, "conn", rbhtp_tx_conn, 0 );
+
+ cFile = rb_define_class_under( mHTP, "File", rb_cObject );
+ rb_define_method( cFile, "initialize", rbhtp_file_initialize, 1 );
+
+ rb_define_method( cFile, "source", rbhtp_file_source, 0 );
+ rb_define_method( cFile, "filename", rbhtp_file_filename, 0 );
+ rb_define_method( cFile, "len", rbhtp_file_len, 0 );
+ rb_define_method( cFile, "tmpname", rbhtp_file_tmpname, 0 );
+ rb_define_method( cFile, "fd", rbhtp_file_fd, 0 );
+
+ cConn = rb_define_class_under( mHTP, "Conn", rb_cObject );
+ rb_define_method( cConn, "initialize", rbhtp_conn_initialize, 2 );
+
+ rb_define_method( cConn, "remote_addr", rbhtp_conn_remote_addr, 0 );
+ rb_define_method( cConn, "remote_port", rbhtp_conn_remote_port, 0 );
+ rb_define_method( cConn, "local_addr", rbhtp_conn_local_addr, 0 );
+ rb_define_method( cConn, "local_port", rbhtp_conn_local_port, 0 );
+ rb_define_method( cConn, "flags", rbhtp_conn_flags, 0 );
+ rb_define_method( cConn, "in_data_counter", rbhtp_conn_in_data_counter, 0 );
+ rb_define_method( cConn, "out_data_counter", rbhtp_conn_out_data_counter, 0 );
+ rb_define_method( cConn, "in_packet_counter", rbhtp_conn_in_packet_counter, 0 );
+ rb_define_method( cConn, "out_packet_counter", rbhtp_conn_out_packet_counter, 0 );
+ rb_define_method( cConn, "transactions", rbhtp_conn_transactions, 0 );
+ rb_define_method( cConn, "open_timestamp", rbhtp_conn_open_timestamp, 0 );
+ rb_define_method( cConn, "close_timestamp", rbhtp_conn_close_timestamp, 0 );
+
+ // Load ruby code.
+ rb_require( "htp_ruby" );
+}
diff --git a/extras/ruby/README b/extras/ruby/README
new file mode 100644
index 0000000..f946c41
--- /dev/null
+++ b/extras/ruby/README
@@ -0,0 +1,58 @@
+= Introduction =
+
+Here are ruby bindings for libHTP. This project was intended for rapid
+prototyping (and as an exercise for learning libHTP) and is not intended for
+production use.
+
+The library provides a partial interface to libHTP which, where it exists,
+closely matches the C interface. The main classes are HTP::Cfg and
+HTP::Connp which correspond to htp_config_t and htp_connp, respectively.
+Functions that begin htp_config_ and htp_connp_ are methods of HTP::Cfg and
+HTP::Connp respectively.
+
+All callbacks are taken as blocks. E.g.,
+
+ config.register_request do |connp|
+ ...
+ end
+
+See example.rb.
+
+libHTP constants (#defines) exist as constants in HTP.
+
+In addition, classes exist for the other HTP structures: HTP::Tx, HTP::URI,
+HTP::Header, HTP::HeaderLine, etc. These classes provide read accessors for
+the various fields. In addition, some additional methods are provided for more
+Rubyish style, e.g., Header#invalid?. See htp_ruby.rb for a complete list of
+API additions.
+
+HTP::Cfg and HTP::Connp lifetimes are managed by the usual Ruby cycle,
+i.e., garbage collected when no longer references. All other classes are bound
+to the lifetimes of either of those classes. So, make sure you keep your
+config and connection parser around as long as you need any of the data from
+them or copy your data out into non-HTP classes.
+
+If performance is a concern, you should not be using Ruby or these bindings.
+That being said, some small effort has been made to avoid binding performance
+penalties for data you don't use. For example, if you never look at
+Tx#request_headers, that data will not be converted into Rubyspace. A side
+effect of this behavior, is that return values should be cached. E.g.,
+Tx#request_cookies generates an array of cookies every time it is called, so
+consider caching the return value if you need to access it multiple times.
+
+
+= Missing =
+
+* Cfg and Connp are only minimally implemented.
+* Conn#messages is missing.
+* Logging is completely missing.
+* Connp, and Cfg lack meaningful to_s or inspect.
+* Bool support. As per C-interface, 0 and 1 are used instead of false and
+ true. Should add ...? accessors which latter values.
+* Automated unit tests.
+* API doc.
+* libHTP version detection.
+* Iterator parsing interface: Takes iterator of chunks and handles the various
+ parsing return codes. This allows the user to provide chunks as possible
+ via an iterator and have the parser just-work.
+
diff --git a/extras/ruby/example.rb b/extras/ruby/example.rb
new file mode 100644
index 0000000..e802003
--- /dev/null
+++ b/extras/ruby/example.rb
@@ -0,0 +1,116 @@
+#!/usr/bin/env ruby
+
+# Copyright (c) 2009-2010 Open Information Security Foundation
+# Copyright (c) 2010-2013 Qualys, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# - Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+
+# - Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+
+# - Neither the name of the Qualys, Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+$:.unshift( File.dirname( __FILE__ ) )
+
+require 'htp'
+
+# parse_uri example.
+uri = HTP::parse_uri( "http://host.com/hello/world" )
+puts uri
+
+puts "----"
+
+# Config and Connp example.
+cfg = HTP::Cfg.new
+
+cfg.server_personality = :apache
+cfg.register_urlencoded_parser
+# Comment out this line and notice that cookies vanish from output.
+cfg.parse_request_cookies = 1
+
+cfg.register_request do |connp|
+ tx = connp.in_tx
+
+ puts "Parsed URI: "
+ puts " " + tx.parsed_uri
+
+ if tx.request_headers
+ puts "Request Headers: "
+ tx.request_headers.each {|h| puts " " + h}
+ end
+
+ if tx.request_cookies
+ puts "Request Cookies: "
+ tx.request_cookies.each {|k,v| puts " #{k} = #{v}"}
+ end
+
+ if tx.request_params_query
+ puts "Request Params Query: "
+ tx.request_params_query.each {|k,v| puts " #{k} = #{v}"}
+ end
+
+ if tx.request_params_body
+ puts "Request Body Query: "
+ tx.request_params_body.each {|k,v| puts " #{k} = #{v}"}
+ end
+
+ 0
+end
+
+cfg.register_request_body_data do |tx,data|
+ puts "Body Data: #{data}"
+
+ 0
+end
+
+cfg.register_request_file_data do |tx,fileinfo,data|
+ puts "File Data for #{fileinfo}: #{data}"
+
+ 0
+end
+
+connp = HTP::Connp.new( cfg )
+input = DATA.read
+
+connp.req_data( Time.now, input )
+
+# Non-Callback Interface.
+puts "----"
+
+connp.conn.transactions.each do |tx|
+ # Might be an empty transaction.
+ next if ! tx.request_line
+ puts tx
+end
+
+__END__
+POST http://user@password:host/%61/b/c?foo=bar#hi HTTP/1.1
+User-Agent: Mozilla
+Cookie: foo=bar
+Content-Type: text/plain
+Content-Length: 9
+
+Body Text
+
+
diff --git a/extras/ruby/extconf.rb b/extras/ruby/extconf.rb
new file mode 100644
index 0000000..ad66ab0
--- /dev/null
+++ b/extras/ruby/extconf.rb
@@ -0,0 +1,6 @@
+require 'mkmf'
+
+dir_config( 'htp' )
+have_library( 'htp', 'htp_connp_create' ) || abort( "Can't find HTP library." )
+have_header( 'htp/htp.h' ) || abort( "Can't find htp.h" )
+create_makefile( 'htp' )
diff --git a/extras/ruby/htp.gemspec b/extras/ruby/htp.gemspec
new file mode 100644
index 0000000..37223de
--- /dev/null
+++ b/extras/ruby/htp.gemspec
@@ -0,0 +1,12 @@
+Gem::Specification.new do |s|
+ s.name = "htp"
+ s.version = "0.1"
+
+ s.authors = ["Chrustopher Alfeld"]
+ s.description = "Ruby Bindings for libHTP."
+ s.email = "calfeld@qualys.com"
+ s.files = ["htp_ruby.rb", "HTP.c", "extconf.rb", "example.rb"]
+ s.extensions = ["extconf.rb"]
+ s.summary = "libHTP Ruby bindings."
+ s.require_path = '.'
+end
diff --git a/extras/ruby/htp_ruby.rb b/extras/ruby/htp_ruby.rb
new file mode 100644
index 0000000..0eccaac
--- /dev/null
+++ b/extras/ruby/htp_ruby.rb
@@ -0,0 +1,247 @@
+# Copyright (c) 2009-2010 Open Information Security Foundation
+# Copyright (c) 2010-2013 Qualys, Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# - Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+
+# - Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+
+# - Neither the name of the Qualys, Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+# Author: Christopher Alfeld <calfeld@qualys.com>
+
+module HTP
+ # TODO: Lots to do. Good inspect for all classes would be a good start.
+ # As would an easier parsing interface that takes care of the return codes.
+
+ class Cfg
+ # Object.dup will just create a Config that points to the same underlying
+ # htp_cfg_t. By using #copy which maps to htp_config_copy, we can do
+ # the expected dup behavior.
+ alias :dup :copy
+
+ SERVER_PERSONALITY_ASSOC = [
+ [ :minimal, HTP_SERVER_MINIMAL ],
+ [ :generic, HTP_SERVER_GENERIC ],
+ [ :ids, HTP_SERVER_IDS ],
+ [ :iis_4_0, HTP_SERVER_IIS_4_0 ],
+ [ :iis_5_0, HTP_SERVER_IIS_5_0 ],
+ [ :iis_5_1, HTP_SERVER_IIS_5_1 ],
+ [ :iis_6_0, HTP_SERVER_IIS_6_0 ],
+ [ :iis_7_0, HTP_SERVER_IIS_7_0 ],
+ [ :iis_7_5, HTP_SERVER_IIS_7_5 ],
+ [ :tomcat_6_0, HTP_SERVER_TOMCAT_6_0 ],
+ [ :apache, HTP_SERVER_APACHE ],
+ [ :apache_2_2, HTP_SERVER_APACHE_2_2 ]
+ ].freeze
+
+ def server_personality
+ personality_id = spersonality
+ personality = SERVER_PERSONALITY_ASSOC.rassoc( personality_id )[0]
+ personality.nil? ? personality_id : personality
+ end
+ def server_personality=( personality )
+ if personality.is_a?( String )
+ personality = personality.to_sym
+ end
+ if personality.is_a?( Symbol )
+ personality_id = SERVER_PERSONALITY_ASSOC.assoc( personality )[1]
+ if personality_id.nil?
+ raise TypeError.new( "Unknown personality: #{personality}" )
+ end
+ personality = personality_id
+ end
+ if ! personality.is_a?( Fixnum )
+ raise TypeError.new( "Can't understand personality." )
+ end
+ set_server_personality( personality )
+ end
+ end
+
+ class Connp
+ attr_reader :cfg
+ end
+
+ class Header
+ def invalid?
+ flags & HTP_FIELD_INVALID != 0
+ end
+
+ def folded?
+ flags & HTP_FIELD_FOLDED != 0
+ end
+
+ def repeated?
+ flags & HTP_FIELD_REPEATED != 0
+ end
+
+ def to_s
+ r = "#{name}: #{value}"
+ r += " <INVALID>" if invalid?
+ r += " <FOLDER>" if folded?
+ r += " <REPEATED>" if repeated?
+ r
+ end
+
+ alias :inspect :to_s
+ alias :to_str :to_s
+ end
+
+ class HeaderLine
+ def invalid?
+ flags & HTP_FIELD_INVALID != 0
+ end
+
+ def long?
+ flags & HTP_FIELD_LONG != 0
+ end
+
+ def nul_byte?
+ flags & HTP_FIELD_NUL_BYTE != 0
+ end
+
+ def to_s
+ line
+ end
+
+ alias :inspect :to_s
+ alias :to_str :to_s
+ end
+
+ class URI
+ def to_s
+ if hostname
+ "http://" +
+ ( username ? username : '' ) +
+ ( password ? ":#{password}" : '' ) +
+ ( hostname && ( username || password ) ? '@' : '' ) +
+ ( hostname ? "#{hostname}:#{port}" : '' )
+ else
+ ''
+ end +
+ ( path ? path : '' ) +
+ ( query ? "?#{query}" : '' ) +
+ ( fragment ? "##{fragment}" : '' )
+ end
+
+ alias :inspect :to_s
+ alias :to_str :to_s
+ end
+
+ class Tx
+ attr_reader :connp
+ attr_reader :cfg
+
+ # Here we cache a variety of values that are built on demand.
+ [
+ :request_params_query,
+ :request_params_body,
+ :request_cookies,
+ :request_headers,
+ :response_headers,
+ :request_header_lines,
+ :response_header_lines
+ ].each do |name|
+ raw_name = ( "_" + name.to_s ).to_sym
+ alias_method( raw_name, name )
+ private( raw_name )
+ remove_method( name )
+ define_method name do
+ @cache ||= {}
+ @cache[name] ||= send( raw_name )
+ end
+ end
+
+ def invalid_chunking?
+ flags & HTP_INVALID_CHUNKING != 0
+ end
+
+ def invalid_folding?
+ flags & HTP_INVALID_FOLDING != 0
+ end
+
+ def request_smuggling?
+ flags & HTP_REQUEST_SMUGGLING != 0
+ end
+
+ def multi_packet_header?
+ flags & HTP_MULTI_PACKET_HEAD != 0
+ end
+
+ def field_unparseable?
+ flags & HTP_FIELD_UNPARSABLE != 0
+ end
+
+ def request_params_as_hash
+ if ! @request_params
+ @request_params = Hash.new {|h,k| h[k] = []}
+ [ request_params_query, request_params_body ].compact.each do |result|
+ result.each do |k,v|
+ @request_params[k] << v
+ end
+ end
+ end
+ @request_params
+ end
+
+ def request_cookies_as_hash
+ if ! @request_cookies
+ @request_cookies = Hash.new {|h,k| h[k] = []}
+ result = request_cookies
+ if result
+ result.each do |k,v|
+ @request_cookies[k] << v
+ end
+ end
+ end
+ @request_cookies
+ end
+
+ alias :to_s :request_line
+ alias :to_str :to_s
+ alias :inspect :to_s
+ end
+
+ class File
+ alias :to_s :filename
+ alias :inspect :to_s
+ alias :to_str :to_s
+ end
+
+ class Conn
+ attr_reader :connp
+
+ def pipelined_connection?
+ flags & PIPELINED_CONNECTION
+ end
+
+ def to_s
+ ( local_addr || "???" ) + ":#{local_port} -> " +
+ ( remote_addr || "???" ) + ":#{remote_port}"
+ end
+
+ alias :to_str :to_s
+ alias :inspect :to_s
+ end
+end \ No newline at end of file