diff options
Diffstat (limited to 'epan/dissectors/packet-http.c')
-rw-r--r-- | epan/dissectors/packet-http.c | 1759 |
1 files changed, 1759 insertions, 0 deletions
diff --git a/epan/dissectors/packet-http.c b/epan/dissectors/packet-http.c new file mode 100644 index 0000000000..76f70e289f --- /dev/null +++ b/epan/dissectors/packet-http.c @@ -0,0 +1,1759 @@ +/* packet-http.c + * Routines for HTTP packet disassembly + * RFC 1945 (HTTP/1.0) + * RFC 2616 (HTTP/1.1) + * + * Guy Harris <guy@alum.mit.edu> + * + * Copyright 2004, Jerry Talkington <jtalkington@users.sourceforge.net> + * Copyright 2002, Tim Potter <tpot@samba.org> + * Copyright 1999, Andrew Tridgell <tridge@samba.org> + * + * $Id$ + * + * Ethereal - Network traffic analyzer + * By Gerald Combs <gerald@ethereal.com> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string.h> +#include <ctype.h> + +#include <glib.h> +#include <epan/packet.h> +#include <epan/strutil.h> + +#include "util.h" +#include "req_resp_hdrs.h" +#include "packet-http.h" +#include "prefs.h" + +typedef enum _http_type { + HTTP_REQUEST, + HTTP_RESPONSE, + HTTP_NOTIFICATION, + HTTP_OTHERS +} http_type_t; + +#include "tap.h" + +static int http_tap = -1; + +static int proto_http = -1; +static int hf_http_notification = -1; +static int hf_http_response = -1; +static int hf_http_request = -1; +static int hf_http_basic = -1; +static int hf_http_request_method = -1; +static int hf_http_response_code = -1; +static int hf_http_authorization = -1; +static int hf_http_proxy_authenticate = -1; +static int hf_http_proxy_authorization = -1; +static int hf_http_www_authenticate = -1; +static int hf_http_content_type = -1; +static int hf_http_content_length = -1; +static int hf_http_content_encoding = -1; +static int hf_http_transfer_encoding = -1; + +static gint ett_http = -1; +static gint ett_http_ntlmssp = -1; +static gint ett_http_request = -1; +static gint ett_http_chunked_response = -1; +static gint ett_http_chunk_data = -1; +static gint ett_http_encoded_entity = -1; + +static dissector_handle_t data_handle; +static dissector_handle_t media_handle; +static dissector_handle_t http_handle; + +/* + * desegmentation of HTTP headers + * (when we are over TCP or another protocol providing the desegmentation API) + */ +static gboolean http_desegment_headers = FALSE; + +/* + * desegmentation of HTTP bodies + * (when we are over TCP or another protocol providing the desegmentation API) + * TODO let the user filter on content-type the bodies he wants desegmented + */ +static gboolean http_desegment_body = FALSE; + +/* + * De-chunking of content-encoding: chunk entity bodies. + */ +static gboolean http_dechunk_body = TRUE; + +/* + * Decompression of zlib encoded entities. + */ +#ifdef HAVE_LIBZ +static gboolean http_decompress_body = TRUE; +#else +static gboolean http_decompress_body = FALSE; +#endif + + +#define TCP_PORT_HTTP 80 +#define TCP_PORT_PROXY_HTTP 3128 +#define TCP_PORT_PROXY_ADMIN_HTTP 3132 +#define TCP_ALT_PORT_HTTP 8080 +#define TCP_PORT_HKP 11371 +/* + * SSDP is implemented atop HTTP (yes, it really *does* run over UDP). + */ +#define TCP_PORT_SSDP 1900 +#define UDP_PORT_SSDP 1900 + +/* + * Protocols implemented atop HTTP. + */ +typedef enum { + PROTO_HTTP, /* just HTTP */ + PROTO_SSDP /* Simple Service Discovery Protocol */ +} http_proto_t; + +typedef void (*RequestDissector)(tvbuff_t*, proto_tree*, int); + +/* + * Structure holding information from headers needed by main + * HTTP dissector code. + */ +typedef struct { + char *content_type; + char *content_type_parameters; + long content_length; /* XXX - make it 64-bit? */ + char *content_encoding; + char *transfer_encoding; +} headers_t; + +static int is_http_request_or_reply(const gchar *data, int linelen, http_type_t *type, + RequestDissector *req_dissector, int *req_strlen); +static int chunked_encoding_dissector(tvbuff_t **tvb_ptr, packet_info *pinfo, + proto_tree *tree, int offset); +static void process_header(tvbuff_t *tvb, int offset, int next_offset, + const guchar *line, int linelen, int colon_offset, packet_info *pinfo, + proto_tree *tree, headers_t *eh_ptr); +static gint find_header_hf_value(tvbuff_t *tvb, int offset, guint header_len); +static gboolean check_auth_ntlmssp(proto_item *hdr_item, tvbuff_t *tvb, + packet_info *pinfo, gchar *value); +static gboolean check_auth_basic(proto_item *hdr_item, tvbuff_t *tvb, + gchar *value); + +static dissector_table_t port_subdissector_table; +static dissector_table_t media_type_subdissector_table; +static heur_dissector_list_t heur_subdissector_list; + +static dissector_handle_t ntlmssp_handle=NULL; + + +/* Return a tvb that contains the binary representation of a base64 + string */ + +static tvbuff_t * +base64_to_tvb(const char *base64) +{ + tvbuff_t *tvb; + char *data = g_strdup(base64); + size_t len; + + len = epan_base64_decode(data); + tvb = tvb_new_real_data((const guint8 *)data, len, len); + + tvb_set_free_cb(tvb, g_free); + + return tvb; +} + +static void +dissect_http_ntlmssp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, + const char *line) +{ + tvbuff_t *ntlmssp_tvb; + + ntlmssp_tvb = base64_to_tvb(line); + tvb_set_child_real_data_tvbuff(tvb, ntlmssp_tvb); + add_new_data_source(pinfo, ntlmssp_tvb, "NTLMSSP Data"); + + call_dissector(ntlmssp_handle, ntlmssp_tvb, pinfo, tree); +} + +static void +cleanup_headers(void *arg) +{ + headers_t *headers = arg; + + if (headers->content_type != NULL) + g_free(headers->content_type); + /* + * The content_type_parameters field actually points into the + * content_type headers, so don't free it, as that'll double-free + * some memory. + */ + if (headers->content_encoding != NULL) + g_free(headers->content_encoding); + if (headers->transfer_encoding != NULL) + g_free(headers->transfer_encoding); +} + +/* + * TODO: remove this ugly global variable. + * + * XXX - we leak "http_info_value_t" structures. + * XXX - this gets overwritten if there's more than one HTTP request or + * reply in the tvbuff. + */ +static http_info_value_t *stat_info; + +static int +dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo, + proto_tree *tree) +{ + http_proto_t proto; + char *proto_tag; + proto_tree *http_tree = NULL; + proto_item *ti = NULL; + const guchar *line; + gint next_offset; + const guchar *linep, *lineend; + int orig_offset; + int first_linelen, linelen; + gboolean is_request_or_reply; + gboolean saw_req_resp_or_header; + guchar c; + http_type_t http_type; + proto_item *hdr_item; + RequestDissector req_dissector; + int req_strlen; + proto_tree *req_tree; + int colon_offset; + headers_t headers; + int datalen; + int reported_datalen; + dissector_handle_t handle; + gboolean dissected; + + /* + * Is this a request or response? + * + * Note that "tvb_find_line_end()" will return a value that + * is not longer than what's in the buffer, so the + * "tvb_get_ptr()" call won't throw an exception. + */ + first_linelen = tvb_find_line_end(tvb, offset, + tvb_ensure_length_remaining(tvb, offset), &next_offset, + FALSE); + /* + * Is the first line a request or response? + */ + line = tvb_get_ptr(tvb, offset, first_linelen); + http_type = HTTP_OTHERS; /* type not known yet */ + is_request_or_reply = is_http_request_or_reply((const gchar *)line, + first_linelen, &http_type, NULL, NULL); + if (is_request_or_reply) { + /* + * Yes, it's a request or response. + * Do header desegmentation if we've been told to, + * and do body desegmentation if we've been told to and + * we find a Content-Length header. + */ + if (!req_resp_hdrs_do_reassembly(tvb, pinfo, + http_desegment_headers, http_desegment_body)) { + /* + * More data needed for desegmentation. + */ + return -1; + } + } + + stat_info = g_malloc(sizeof(http_info_value_t)); + stat_info->response_code = 0; + stat_info->request_method = NULL; + + switch (pinfo->match_port) { + + case TCP_PORT_SSDP: /* TCP_PORT_SSDP = UDP_PORT_SSDP */ + proto = PROTO_SSDP; + proto_tag = "SSDP"; + break; + + default: + proto = PROTO_HTTP; + proto_tag = "HTTP"; + break; + } + + if (check_col(pinfo->cinfo, COL_PROTOCOL)) + col_set_str(pinfo->cinfo, COL_PROTOCOL, proto_tag); + if (check_col(pinfo->cinfo, COL_INFO)) { + /* + * Put the first line from the buffer into the summary + * if it's an HTTP request or reply (but leave out the + * line terminator). + * Otherwise, just call it a continuation. + * + * Note that "tvb_find_line_end()" will return a value that + * is not longer than what's in the buffer, so the + * "tvb_get_ptr()" call won't throw an exception. + */ + line = tvb_get_ptr(tvb, offset, first_linelen); + if (is_request_or_reply) + col_add_str(pinfo->cinfo, COL_INFO, + format_text(line, first_linelen)); + else + col_set_str(pinfo->cinfo, COL_INFO, "Continuation"); + } + + orig_offset = offset; + if (tree) { + ti = proto_tree_add_item(tree, proto_http, tvb, offset, -1, + FALSE); + http_tree = proto_item_add_subtree(ti, ett_http); + } + + /* + * Process the packet data, a line at a time. + */ + http_type = HTTP_OTHERS; /* type not known yet */ + headers.content_type = NULL; /* content type not known yet */ + headers.content_type_parameters = NULL; /* content type parameters too */ + headers.content_length = -1; /* content length not known yet */ + headers.content_encoding = NULL; /* content encoding not known yet */ + headers.transfer_encoding = NULL; /* transfer encoding not known yet */ + saw_req_resp_or_header = FALSE; /* haven't seen anything yet */ + CLEANUP_PUSH(cleanup_headers, &headers); + while (tvb_reported_length_remaining(tvb, offset) != 0) { + /* + * Find the end of the line. + */ + linelen = tvb_find_line_end(tvb, offset, + tvb_ensure_length_remaining(tvb, offset), &next_offset, + FALSE); + if (linelen < 0) + return -1; + + /* + * Get a buffer that refers to the line. + */ + line = tvb_get_ptr(tvb, offset, linelen); + lineend = line + linelen; + colon_offset = -1; + + /* + * OK, does it look like an HTTP request or response? + */ + req_dissector = NULL; + is_request_or_reply = is_http_request_or_reply((const gchar *)line, + linelen, &http_type, &req_dissector, &req_strlen); + if (is_request_or_reply) + goto is_http; + + /* + * No. Does it look like a blank line (as would appear + * at the end of an HTTP request)? + */ + if (linelen == 0) + goto is_http; /* Yes. */ + + /* + * No. Does it look like a header? + */ + linep = line; + colon_offset = offset; + while (linep < lineend) { + c = *linep++; + + /* + * This must be a CHAR to be part of a token; that + * means it must be ASCII. + */ + if (!isascii(c)) + break; /* not ASCII, thus not a CHAR */ + + /* + * This mustn't be a CTL to be part of a token. + * + * XXX - what about leading LWS on continuation + * lines of a header? + */ + if (iscntrl(c)) + break; /* CTL, not part of a header */ + + /* + * This mustn't be a SEP to be part of a token; + * a ':' ends the token, everything else is an + * indication that this isn't a header. + */ + switch (c) { + + case '(': + case ')': + case '<': + case '>': + case '@': + case ',': + case ';': + case '\\': + case '"': + case '/': + case '[': + case ']': + case '?': + case '=': + case '{': + case '}': + case ' ': + /* + * It's a separator, so it's not part of a + * token, so it's not a field name for the + * beginning of a header. + * + * (We don't have to check for HT; that's + * already been ruled out by "iscntrl()".) + */ + goto not_http; + + case ':': + /* + * This ends the token; we consider this + * to be a header. + */ + goto is_http; + + default: + colon_offset++; + break; + } + } + + /* + * We haven't seen the colon, but everything else looks + * OK for a header line. + * + * If we've already seen an HTTP request or response + * line, or a header line, and we're at the end of + * the tvbuff, we assume this is an incomplete header + * line. (We quit this loop after seeing a blank line, + * so if we've seen a request or response line, or a + * header line, this is probably more of the request + * or response we're presumably seeing. There is some + * risk of false positives, but the same applies for + * full request or response lines or header lines, + * although that's less likely.) + * + * We throw an exception in that case, by checking for + * the existence of the next byte after the last one + * in the line. If it exists, "tvb_ensure_bytes_exist()" + * throws no exception, and we fall through to the + * "not HTTP" case. If it doesn't exist, + * "tvb_ensure_bytes_exist()" will throw the appropriate + * exception. + */ + if (saw_req_resp_or_header) + tvb_ensure_bytes_exist(tvb, offset, linelen + 1); + + not_http: + /* + * We don't consider this part of an HTTP request or + * reply, so we don't display it. + * (Yeah, that means we don't display, say, a text/http + * page, but you can get that from the data pane.) + */ + break; + + is_http: + /* + * Process this line. + */ + if (linelen == 0) { + /* + * This is a blank line, which means that + * whatever follows it isn't part of this + * request or reply. + */ + proto_tree_add_text(http_tree, tvb, offset, + next_offset - offset, "%s", + tvb_format_text(tvb, offset, next_offset - offset)); + offset = next_offset; + break; + } + + /* + * Not a blank line - either a request, a reply, or a header + * line. + */ + saw_req_resp_or_header = TRUE; + if (is_request_or_reply) { + if (tree) { + hdr_item = proto_tree_add_text(http_tree, tvb, + offset, next_offset - offset, "%s", + tvb_format_text(tvb, offset, + next_offset - offset)); + if (req_dissector) { + req_tree = proto_item_add_subtree( + hdr_item, ett_http_request); + req_dissector(tvb, req_tree, + req_strlen); + } + } + } else { + /* + * Header. + */ + process_header(tvb, offset, next_offset, line, linelen, + colon_offset, pinfo, http_tree, &headers); + } + offset = next_offset; + } + + if (tree) { + switch (http_type) { + + case HTTP_NOTIFICATION: + proto_tree_add_boolean_hidden(http_tree, + hf_http_notification, tvb, 0, 0, 1); + break; + + case HTTP_RESPONSE: + proto_tree_add_boolean_hidden(http_tree, + hf_http_response, tvb, 0, 0, 1); + break; + + case HTTP_REQUEST: + proto_tree_add_boolean_hidden(http_tree, + hf_http_request, tvb, 0, 0, 1); + break; + + case HTTP_OTHERS: + default: + break; + } + } + + /* + * If a content length was supplied, the amount of data to be + * processed as HTTP payload is the minimum of the content + * length and the amount of data remaining in the frame. + * + * If no content length was supplied (or if a bad content length + * was supplied), the amount of data to be processed is the amount + * of data remaining in the frame. + * + * If there was no Content-Length entity header, we should + * accumulate all data until the end of the connection. + * That'd require that the TCP dissector call subdissectors + * for all frames with FIN, even if they contain no data, + * which would require subdissectors to deal intelligently + * with empty segments. + * + * Acccording to RFC 2616, however, 1xx responses, 204 responses, + * and 304 responses MUST NOT include a message body; if no + * content length is specified for them, we don't attempt to + * dissect the body. + * + * XXX - it says the same about responses to HEAD requests; + * unless there's a way to determine from the response + * whether it's a response to a HEAD request, we have to + * keep information about the request and associate that with + * the response in order to handle that. + */ + datalen = tvb_length_remaining(tvb, offset); + if (headers.content_length != -1) { + if (datalen > headers.content_length) + datalen = headers.content_length; + + /* + * XXX - limit the reported length in the tvbuff we'll + * hand to a subdissector to be no greater than the + * content length. + * + * We really need both unreassembled and "how long it'd + * be if it were reassembled" lengths for tvbuffs, so + * that we throw the appropriate exceptions for + * "not enough data captured" (running past the length), + * "packet needed reassembly" (within the length but + * running past the unreassembled length), and + * "packet is malformed" (running past the reassembled + * length). + */ + reported_datalen = tvb_reported_length_remaining(tvb, offset); + if (reported_datalen > headers.content_length) + reported_datalen = headers.content_length; + } else { + if ((stat_info->response_code/100) == 1 || + stat_info->response_code == 204 || + stat_info->response_code == 304) + datalen = 0; /* no content! */ + else + reported_datalen = -1; + } + + if (datalen > 0) { + /* + * There's stuff left over; process it. + */ + tvbuff_t *next_tvb; + void *save_private_data = NULL; + gint chunks_decoded = 0; + + /* + * Create a tvbuff for the payload. + * + * The amount of data to be processed that's + * available in the tvbuff is "datalen", which + * is the minimum of the amount of data left in + * the tvbuff and any specified content length. + * + * The amount of data to be processed that's in + * this frame, regardless of whether it was + * captured or not, is "reported_datalen", + * which, if no content length was specified, + * is -1, i.e. "to the end of the frame. + */ + next_tvb = tvb_new_subset(tvb, offset, datalen, + reported_datalen); + /* + * BEWARE - next_tvb is a subset of another tvb, + * so we MUST NOT attempt tvb_free(next_tvb); + */ + + /* + * Handle *transfer* encodings other than "identity". + */ + if (headers.transfer_encoding != NULL && + strcasecmp(headers.transfer_encoding, "identity") != 0) { + if (http_dechunk_body && + (strcasecmp(headers.transfer_encoding, "chunked") + == 0)) { + + chunks_decoded = chunked_encoding_dissector( + &next_tvb, pinfo, http_tree, 0); + + if (chunks_decoded <= 0) { + /* + * The chunks weren't reassembled, + * or there was a single zero + * length chunk. + */ + goto body_dissected; + } else { + /* + * Add a new data source for the + * de-chunked data. + */ + tvb_set_child_real_data_tvbuff(tvb, + next_tvb); + add_new_data_source(pinfo, next_tvb, + "De-chunked entity body"); + } + } else { + /* + * We currently can't handle, for example, + * "gzip", "compress", or "deflate" as + * *transfer* encodings; just handle them + * as data for now. + */ + call_dissector(data_handle, next_tvb, pinfo, + http_tree); + goto body_dissected; + } + } + /* + * At this point, any chunked *transfer* coding has been removed + * (the entity body has been dechunked) so it can be presented + * for the following operation (*content* encoding), or it has + * been been handed off to the data dissector. + * + * Handle *content* encodings other than "identity" (which + * shouldn't appear in a Content-Encoding header, but + * we handle it in any case). + */ + if (headers.content_encoding != NULL && + strcasecmp(headers.content_encoding, "identity") != 0) { + /* + * We currently can't handle, for example, "compress"; + * just handle them as data for now. + * + * After July 7, 2004 the LZW patent expires, so support + * might be added then. However, I don't think that + * anybody ever really implemented "compress", due to + * the aforementioned patent. + */ + tvbuff_t *uncomp_tvb = NULL; + proto_item *e_ti = NULL; + proto_tree *e_tree = NULL; + + if (http_decompress_body && + (strcasecmp(headers.content_encoding, "gzip") == 0 || + strcasecmp(headers.content_encoding, "deflate") + == 0)) { + + uncomp_tvb = tvb_uncompress(next_tvb, 0, + tvb_length(next_tvb)); + } + + /* + * Add the encoded entity to the protocol tree + */ + e_ti = proto_tree_add_text(http_tree, next_tvb, + 0, tvb_length(next_tvb), + "Content-encoded entity body (%s)", + headers.content_encoding); + e_tree = proto_item_add_subtree(e_ti, + ett_http_encoded_entity); + + if (uncomp_tvb != NULL) { + /* + * Decompression worked + */ + + /* XXX - Don't free this, since it's possible + * that the data was only partially + * decompressed, such as when desegmentation + * isn't enabled. + * + tvb_free(next_tvb); + */ + next_tvb = uncomp_tvb; + tvb_set_child_real_data_tvbuff(tvb, next_tvb); + add_new_data_source(pinfo, next_tvb, + "Uncompressed entity body"); + } else { + if (chunks_decoded > 1) { + tvb_set_child_real_data_tvbuff(tvb, + next_tvb); + add_new_data_source(pinfo, next_tvb, + "Compressed entity body"); + } + call_dissector(data_handle, next_tvb, pinfo, + e_tree); + + goto body_dissected; + } + } + /* + * Note that a new data source is added for the entity body + * only if it was content-encoded and/or transfer-encoded. + */ + + /* + * Do subdissector checks. + * + * First, check whether some subdissector asked that they + * be called if something was on some particular port. + */ + handle = dissector_get_port_handle(port_subdissector_table, + pinfo->match_port); + if (handle == NULL && headers.content_type != NULL) { + /* + * We didn't find any subdissector that + * registered for the port, and we have a + * Content-Type value. Is there any subdissector + * for that content type? + */ + save_private_data = pinfo->private_data; + /* + * XXX - this won't get freed if the subdissector + * throws an exception. Do we really need to + * strdup it? + */ + if (headers.content_type_parameters) + pinfo->private_data = g_strdup(headers.content_type_parameters); + else + pinfo->private_data = NULL; + /* + * Calling the string handle for the media type + * dissector table will set pinfo->match_string + * to headers.content_type for us. + */ + pinfo->match_string = headers.content_type; + handle = dissector_get_string_handle( + media_type_subdissector_table, + headers.content_type); + /* + * Calling the default media handle otherwise + */ + if (handle == NULL) { + handle = media_handle; + } + } + if (handle != NULL) { + /* + * We have a subdissector - call it. + */ + dissected = call_dissector(handle, next_tvb, pinfo, + tree); + } else { + /* + * We don't have a subdissector - try the heuristic + * subdissectors. + */ + dissected = dissector_try_heuristic( + heur_subdissector_list, next_tvb, pinfo, tree); + } + if (dissected) { + /* + * The subdissector dissected the body. + * Fix up the top-level item so that it doesn't + * include the stuff for that protocol. + */ + if (ti != NULL) + proto_item_set_len(ti, offset); + } else { + call_dissector(data_handle, next_tvb, pinfo, + http_tree); + } + + body_dissected: + /* + * Do *not* attempt at freeing the private data; + * it may be in use by subdissectors. + */ + if (save_private_data) + pinfo->private_data = save_private_data; + /* + * We've processed "datalen" bytes worth of data + * (which may be no data at all); advance the + * offset past whatever data we've processed. + */ + offset += datalen; + } + + /* + * Clean up any header stuff, by calling and popping the cleanup + * handler. + */ + CLEANUP_CALL_AND_POP; + + tap_queue_packet(http_tap, pinfo, stat_info); + + return offset - orig_offset; +} + +/* This can be used to dissect an HTTP request until such time + * that a more complete dissector is written for that HTTP request. + * This simple dissectory only puts http.request_method into a sub-tree. + */ +static void +basic_request_dissector(tvbuff_t *tvb, proto_tree *tree, int req_strlen) +{ + proto_tree_add_item(tree, hf_http_request_method, tvb, 0, req_strlen, FALSE); +} + +static void +basic_response_dissector(tvbuff_t *tvb, proto_tree *tree, int resp_strlen) +{ + gchar *data; + int minor, major, status_code; + + /* BEWARE - sscanf() only operates on C strings. + * The pointer returned by tvb_get_ptr points into the real data, + * which is not necessarily NULL terminated. For this reason, + * the sscanf() call is only applied to a buffer guaranteed to + * only contain a NULL terminated string. */ + data = g_strndup((const gchar *)tvb_get_ptr(tvb, 5, resp_strlen), resp_strlen); + if (sscanf((const gchar *)data, "%d.%d %d", &minor, &major, &status_code) == 3) { + proto_tree_add_uint(tree, hf_http_response_code, tvb, 9, 3, status_code); + stat_info->response_code = status_code; + } + g_free(data); +} + +/* + * Dissect the http data chunks and add them to the tree. + */ +static int +chunked_encoding_dissector(tvbuff_t **tvb_ptr, packet_info *pinfo, + proto_tree *tree, int offset) +{ + guint8 *chunk_string = NULL; + gint chunk_size = 0; + gint chunk_offset = 0; + gint datalen = 0; + gint linelen = 0; + gint chunks_decoded = 0; + tvbuff_t *tvb = NULL; + tvbuff_t *new_tvb = NULL; + gint chunked_data_size = 0; + proto_tree *subtree = NULL; + proto_item *ti = NULL; + + if (tvb_ptr == NULL || *tvb_ptr == NULL) { + return 0; + } + + tvb = *tvb_ptr; + + datalen = tvb_reported_length_remaining(tvb, offset); + + if (tree) { + ti = proto_tree_add_text(tree, tvb, offset, datalen, + "HTTP chunked response"); + subtree = proto_item_add_subtree(ti, ett_http_chunked_response); + } + + + while (datalen != 0) { + proto_item *chunk_ti = NULL; + proto_tree *chunk_subtree = NULL; + tvbuff_t *data_tvb = NULL; + gchar *c = NULL; + + linelen = tvb_find_line_end(tvb, offset, -1, &chunk_offset, TRUE); + + if (linelen <= 0) { + /* Can't get the chunk size line */ + break; + } + + chunk_string = tvb_get_string(tvb, offset, linelen); + + if (chunk_string == NULL) { + /* Can't get the chunk size line */ + break; + } + + c = chunk_string; + + /* + * We don't care about the extensions. + */ + if ((c = strchr(c, ';'))) { + *c = '\0'; + } + + if (sscanf(chunk_string, "%x", &chunk_size) != 1) { + g_free(chunk_string); + break; + } + + g_free(chunk_string); + + + if (chunk_size > datalen) { + /* + * The chunk size is more than what's in the tvbuff, + * so either the user hasn't enabled decoding, or all + * of the segments weren't captured. + */ + chunk_size = datalen; + }/* else if (new_tvb == NULL) { + new_tvb = tvb_new_composite(); + } + + + + if (new_tvb != NULL && chunk_size != 0) { + tvbuff_t *chunk_tvb = NULL; + + chunk_tvb = tvb_new_subset(tvb, chunk_offset, + chunk_size, datalen); + + tvb_composite_append(new_tvb, chunk_tvb); + + } + */ + + chunked_data_size += chunk_size; + + if (chunk_size != 0) { + guint8 *raw_data = g_malloc(chunked_data_size); + gint raw_len = 0; + + if (new_tvb != NULL) { + raw_len = tvb_length_remaining(new_tvb, 0); + tvb_memcpy(new_tvb, raw_data, 0, raw_len); + + tvb_free(new_tvb); + } + + tvb_memcpy(tvb, (guint8 *)(raw_data + raw_len), + chunk_offset, chunk_size); + + new_tvb = tvb_new_real_data(raw_data, + chunked_data_size, chunked_data_size); + tvb_set_free_cb(new_tvb, g_free); + + } + + if (subtree) { + if (chunk_size == 0) { + chunk_ti = proto_tree_add_text(subtree, tvb, + offset, + chunk_offset - offset + chunk_size + 2, + "Data chunk (last chunk)"); + } else { + chunk_ti = proto_tree_add_text(subtree, tvb, + offset, + chunk_offset - offset + chunk_size + 2, + "Data chunk (%u octets)", chunk_size); + } + + chunk_subtree = proto_item_add_subtree(chunk_ti, + ett_http_chunk_data); + + proto_tree_add_text(chunk_subtree, tvb, offset, + chunk_offset - offset, "Chunk size: %u octets", + chunk_size); + + data_tvb = tvb_new_subset(tvb, chunk_offset, chunk_size, + datalen); + + + if (chunk_size > 0) { + call_dissector(data_handle, data_tvb, pinfo, + chunk_subtree); + } + + proto_tree_add_text(chunk_subtree, tvb, chunk_offset + + chunk_size, 2, "Chunk boundary"); + } + + chunks_decoded++; + offset = chunk_offset + chunk_size + 2; + datalen = tvb_reported_length_remaining(tvb, offset); + } + + if (new_tvb != NULL) { + + /* Placeholder for the day that composite tvbuffer's will work. + tvb_composite_finalize(new_tvb); + / * tvb_set_reported_length(new_tvb, chunked_data_size); * / + */ + + /* + * XXX - Don't free this, since the tvbuffer that was passed + * may be used if the data spans multiple frames and reassembly + * isn't enabled. + * + tvb_free(*tvb_ptr); + */ + *tvb_ptr = new_tvb; + + } else { + /* + * We didn't create a new tvb, so don't allow sub dissectors + * try to decode the non-existant entity body. + */ + chunks_decoded = -1; + } + + return chunks_decoded; + +} + + +/* + * XXX - this won't handle HTTP 0.9 replies, but they're all data + * anyway. + */ +static int +is_http_request_or_reply(const gchar *data, int linelen, http_type_t *type, + RequestDissector *req_dissector, int *req_strlen) +{ + int isHttpRequestOrReply = FALSE; + int prefix_len = 0; + + /* + * From RFC 2774 - An HTTP Extension Framework + * + * Support the command prefix that identifies the presence of + * a "mandatory" header. + */ + if (linelen >= 2 && strncmp(data, "M-", 2) == 0) { + data += 2; + linelen -= 2; + prefix_len = 2; + } + + /* + * From draft-cohen-gena-client-01.txt, available from the uPnP forum: + * NOTIFY, SUBSCRIBE, UNSUBSCRIBE + * + * From draft-ietf-dasl-protocol-00.txt, a now vanished Microsoft draft: + * SEARCH + */ + if (linelen >= 5 && strncmp(data, "HTTP/", 5) == 0) { + *type = HTTP_RESPONSE; + isHttpRequestOrReply = TRUE; /* response */ + if (req_dissector) { + *req_dissector = basic_response_dissector; + *req_strlen = linelen - 5; + } + } else { + const guchar * ptr = (const guchar *)data; + int index = 0; + + /* Look for the space following the Method */ + while (index < linelen) { + if (*ptr == ' ') + break; + else { + ptr++; + index++; + } + } + + /* Check the methods that have same length */ + switch (index) { + + case 3: + if (strncmp(data, "GET", index) == 0 || + strncmp(data, "PUT", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + else if (strncmp(data, "ICY", index) == 0) { + *type = HTTP_RESPONSE; + isHttpRequestOrReply = TRUE; + } + break; + + case 4: + if (strncmp(data, "COPY", index) == 0 || + strncmp(data, "HEAD", index) == 0 || + strncmp(data, "LOCK", index) == 0 || + strncmp(data, "MOVE", index) == 0 || + strncmp(data, "POLL", index) == 0 || + strncmp(data, "POST", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + break; + + case 5: + if (strncmp(data, "BCOPY", index) == 0 || + strncmp(data, "BMOVE", index) == 0 || + strncmp(data, "MKCOL", index) == 0 || + strncmp(data, "TRACE", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + break; + + case 6: + if (strncmp(data, "DELETE", index) == 0 || + strncmp(data, "SEARCH", index) == 0 || + strncmp(data, "UNLOCK", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + else if (strncmp(data, "NOTIFY", index) == 0) { + *type = HTTP_NOTIFICATION; + isHttpRequestOrReply = TRUE; + } + break; + + case 7: + if (strncmp(data, "BDELETE", index) == 0 || + strncmp(data, "CONNECT", index) == 0 || + strncmp(data, "OPTIONS", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + break; + + case 8: + if (strncmp(data, "PROPFIND", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + break; + + case 9: + if (strncmp(data, "SUBSCRIBE", index) == 0) { + *type = HTTP_NOTIFICATION; + isHttpRequestOrReply = TRUE; + } else if (strncmp(data, "PROPPATCH", index) == 0 || + strncmp(data, "BPROPFIND", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + break; + + case 10: + if (strncmp(data, "BPROPPATCH", index) == 0) { + *type = HTTP_REQUEST; + isHttpRequestOrReply = TRUE; + } + break; + + case 11: + if (strncmp(data, "UNSUBSCRIBE", index) == 0) { + *type = HTTP_NOTIFICATION; + isHttpRequestOrReply = TRUE; + } + break; + + default: + break; + } + + if (isHttpRequestOrReply && req_dissector) { + *req_dissector = basic_request_dissector; + *req_strlen = index + prefix_len; + } + if (isHttpRequestOrReply && req_dissector) { + if (!stat_info->request_method) + stat_info->request_method = g_malloc( index+1 ); + strncpy( stat_info->request_method, data, index); + stat_info->request_method[index] = '\0'; + } + } + + return isHttpRequestOrReply; +} + +/* + * Process headers. + */ +typedef struct { + char *name; + gint *hf; + int special; +} header_info; + +#define HDR_NO_SPECIAL 0 +#define HDR_AUTHORIZATION 1 +#define HDR_AUTHENTICATE 2 +#define HDR_CONTENT_TYPE 3 +#define HDR_CONTENT_LENGTH 4 +#define HDR_CONTENT_ENCODING 5 +#define HDR_TRANSFER_ENCODING 6 + +static const header_info headers[] = { + { "Authorization", &hf_http_authorization, HDR_AUTHORIZATION }, + { "Proxy-Authorization", &hf_http_proxy_authorization, HDR_AUTHORIZATION }, + { "Proxy-Authenticate", &hf_http_proxy_authenticate, HDR_AUTHENTICATE }, + { "WWW-Authenticate", &hf_http_www_authenticate, HDR_AUTHENTICATE }, + { "Content-Type", &hf_http_content_type, HDR_CONTENT_TYPE }, + { "Content-Length", &hf_http_content_length, HDR_CONTENT_LENGTH }, + { "Content-Encoding", &hf_http_content_encoding, HDR_CONTENT_ENCODING }, + { "Transfer-Encoding", &hf_http_transfer_encoding, HDR_TRANSFER_ENCODING }, +}; + +static void +process_header(tvbuff_t *tvb, int offset, int next_offset, + const guchar *line, int linelen, int colon_offset, + packet_info *pinfo, proto_tree *tree, headers_t *eh_ptr) +{ + int len; + int line_end_offset; + int header_len; + gint hf_index; + guchar c; + int value_offset; + int value_len; + char *value; + char *p; + guchar *up; + proto_item *hdr_item; + int i; + + len = next_offset - offset; + line_end_offset = offset + linelen; + header_len = colon_offset - offset; + hf_index = find_header_hf_value(tvb, offset, header_len); + + if (hf_index == -1) { + /* + * Not a header we know anything about. Just put it into + * the tree as text. + */ + if (tree) { + proto_tree_add_text(tree, tvb, offset, len, + "%s", format_text(line, len)); + } + } else { + /* + * Skip whitespace after the colon. + */ + value_offset = colon_offset + 1; + while (value_offset < line_end_offset + && ((c = line[value_offset - offset]) == ' ' || c == '\t')) + value_offset++; + + /* + * Fetch the value. + */ + value_len = line_end_offset - value_offset; + value = g_malloc(value_len + 1); + memcpy(value, &line[value_offset - offset], value_len); + value[value_len] = '\0'; + CLEANUP_PUSH(g_free, value); + + /* + * Add it to the protocol tree as a particular field, + * but display the line as is. + */ + if (tree) { + hdr_item = proto_tree_add_string_format(tree, + *headers[hf_index].hf, tvb, offset, len, + value, "%s", format_text(line, len)); + } else + hdr_item = NULL; + + /* + * Do any special processing that particular headers + * require. + */ + switch (headers[hf_index].special) { + + case HDR_AUTHORIZATION: + if (check_auth_ntlmssp(hdr_item, tvb, pinfo, value)) + break; /* dissected NTLMSSP */ + check_auth_basic(hdr_item, tvb, value); + break; + + case HDR_AUTHENTICATE: + check_auth_ntlmssp(hdr_item, tvb, pinfo, value); + break; + + case HDR_CONTENT_TYPE: + if (eh_ptr->content_type != NULL) + g_free(eh_ptr->content_type); + eh_ptr->content_type = g_malloc(value_len + 1); + for (i = 0; i < value_len; i++) { + c = value[i]; + if (c == ';' || isspace(c)) { + /* + * End of subtype - either + * white space or a ";" + * separating the subtype from + * a parameter. + */ + break; + } + + /* + * Map the character to lower case; + * content types are case-insensitive. + */ + eh_ptr->content_type[i] = tolower(c); + } + eh_ptr->content_type[i] = '\0'; + /* + * Now find the start of the optional parameters; + * skip the optional white space and the semicolon + * if this has not been done before. + */ + i++; + while (i < value_len) { + c = value[i]; + if (c == ';' || isspace(c)) + /* Skip till start of parameters */ + i++; + else + break; + } + if (i < value_len) + eh_ptr->content_type_parameters = value + i; + else + eh_ptr->content_type_parameters = NULL; + break; + + case HDR_CONTENT_LENGTH: + eh_ptr->content_length = strtol(value, &p, 10); + up = (guchar *)p; + if (eh_ptr->content_length < 0 || p == value || + (*up != '\0' && !isspace(*up))) + eh_ptr->content_length = -1; /* not valid */ + break; + + case HDR_CONTENT_ENCODING: + if (eh_ptr->content_encoding != NULL) + g_free(eh_ptr->content_encoding); + eh_ptr->content_encoding = g_malloc(value_len + 1); + memcpy(eh_ptr->content_encoding, value, value_len); + eh_ptr->content_encoding[value_len] = '\0'; + break; + + case HDR_TRANSFER_ENCODING: + if (eh_ptr->transfer_encoding != NULL) + g_free(eh_ptr->transfer_encoding); + eh_ptr->transfer_encoding = g_malloc(value_len + 1); + memcpy(eh_ptr->transfer_encoding, value, value_len); + eh_ptr->transfer_encoding[value_len] = '\0'; + break; + } + + /* + * Free the value, by calling and popping the cleanup + * handler for it. + */ + CLEANUP_CALL_AND_POP; + } +} + +/* Returns index of header tag in headers */ +static gint +find_header_hf_value(tvbuff_t *tvb, int offset, guint header_len) +{ + guint i; + + for (i = 0; i < array_length(headers); i++) { + if (header_len == strlen(headers[i].name) && + tvb_strncaseeql(tvb, offset, + headers[i].name, header_len) == 0) + return i; + } + + return -1; +} + +/* + * Dissect Microsoft's abomination called NTLMSSP over HTTP. + */ +static gboolean +check_auth_ntlmssp(proto_item *hdr_item, tvbuff_t *tvb, packet_info *pinfo, + gchar *value) +{ + static const char *ntlm_headers[] = { + "NTLM ", + "Negotiate ", + NULL + }; + const char **header; + size_t hdrlen; + proto_tree *hdr_tree; + + /* + * Check for NTLM credentials and challenge; those can + * occur with WWW-Authenticate. + */ + for (header = &ntlm_headers[0]; *header != NULL; header++) { + hdrlen = strlen(*header); + if (strncmp(value, *header, hdrlen) == 0) { + if (hdr_item != NULL) { + hdr_tree = proto_item_add_subtree(hdr_item, + ett_http_ntlmssp); + } else + hdr_tree = NULL; + value += hdrlen; + dissect_http_ntlmssp(tvb, pinfo, hdr_tree, value); + return TRUE; + } + } + return FALSE; +} + +/* + * Dissect HTTP Basic authorization. + */ +static gboolean +check_auth_basic(proto_item *hdr_item, tvbuff_t *tvb, gchar *value) +{ + static const char *basic_headers[] = { + "Basic ", + NULL + }; + const char **header; + size_t hdrlen; + proto_tree *hdr_tree; + size_t len; + + for (header = &basic_headers[0]; *header != NULL; header++) { + hdrlen = strlen(*header); + if (strncmp(value, *header, hdrlen) == 0) { + if (hdr_item != NULL) { + hdr_tree = proto_item_add_subtree(hdr_item, + ett_http_ntlmssp); + } else + hdr_tree = NULL; + value += hdrlen; + + len = epan_base64_decode(value); + value[len] = '\0'; + proto_tree_add_string(hdr_tree, hf_http_basic, tvb, + 0, 0, value); + + return TRUE; + } + } + return FALSE; +} + +static void +dissect_http(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + int offset = 0; + int len; + + while (tvb_reported_length_remaining(tvb, offset) != 0) { + len = dissect_http_message(tvb, offset, pinfo, tree); + if (len == -1) + break; + offset += len; + + /* + * OK, we've set the Protocol and Info columns for the + * first HTTP message; make the columns non-writable, + * so that we don't change it for subsequent HTTP messages. + */ + col_set_writable(pinfo->cinfo, FALSE); + } +} + +static void +dissect_http_udp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + dissect_http_message(tvb, 0, pinfo, tree); +} + +void +proto_register_http(void) +{ + static hf_register_info hf[] = { + { &hf_http_notification, + { "Notification", "http.notification", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "TRUE if HTTP notification", HFILL }}, + { &hf_http_response, + { "Response", "http.response", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "TRUE if HTTP response", HFILL }}, + { &hf_http_request, + { "Request", "http.request", + FT_BOOLEAN, BASE_NONE, NULL, 0x0, + "TRUE if HTTP request", HFILL }}, + { &hf_http_basic, + { "Credentials", "http.authbasic", + FT_STRING, BASE_NONE, NULL, 0x0, "", HFILL }}, + { &hf_http_request_method, + { "Request Method", "http.request.method", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Request Method", HFILL }}, + { &hf_http_response_code, + { "Response Code", "http.response.code", + FT_UINT16, BASE_DEC, NULL, 0x0, + "HTTP Response Code", HFILL }}, + { &hf_http_authorization, + { "Authorization", "http.authorization", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Authorization header", HFILL }}, + { &hf_http_proxy_authenticate, + { "Proxy-Authenticate", "http.proxy_authenticate", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Proxy-Authenticate header", HFILL }}, + { &hf_http_proxy_authorization, + { "Proxy-Authorization", "http.proxy_authorization", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Proxy-Authorization header", HFILL }}, + { &hf_http_www_authenticate, + { "WWW-Authenticate", "http.www_authenticate", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP WWW-Authenticate header", HFILL }}, + { &hf_http_content_type, + { "Content-Type", "http.content_type", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Content-Type header", HFILL }}, + { &hf_http_content_length, + { "Content-Length", "http.content_length", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Content-Length header", HFILL }}, + { &hf_http_content_encoding, + { "Content-Encoding", "http.content_encoding", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Content-Encoding header", HFILL }}, + { &hf_http_transfer_encoding, + { "Transfer-Encoding", "http.transfer_encoding", + FT_STRING, BASE_NONE, NULL, 0x0, + "HTTP Transfer-Encoding header", HFILL }}, + }; + static gint *ett[] = { + &ett_http, + &ett_http_ntlmssp, + &ett_http_request, + &ett_http_chunked_response, + &ett_http_chunk_data, + &ett_http_encoded_entity, + }; + module_t *http_module; + + proto_http = proto_register_protocol("Hypertext Transfer Protocol", + "HTTP", "http"); + proto_register_field_array(proto_http, hf, array_length(hf)); + proto_register_subtree_array(ett, array_length(ett)); + http_module = prefs_register_protocol(proto_http, NULL); + prefs_register_bool_preference(http_module, "desegment_headers", + "Desegment all HTTP headers spanning multiple TCP segments", + "Whether the HTTP dissector should desegment all headers " + "of a request spanning multiple TCP segments", + &http_desegment_headers); + prefs_register_bool_preference(http_module, "desegment_body", + "Desegment HTTP bodies spanning multiple TCP segments", + "Whether the HTTP dissector should use the " + "\"Content-length:\" value, if present, to desegment " + "the body of a request spanning multiple TCP segments, " + "and desegment chunked data spanning multiple TCP segments", + &http_desegment_body); + prefs_register_bool_preference(http_module, "dechunk_body", + "Reassemble chunked transfer-coded bodies", + "Whether to reassemble bodies of entities that are transfered " + "using the \"Transfer-Encoding: chunked\" method", + &http_dechunk_body); +#ifdef HAVE_LIBZ + prefs_register_bool_preference(http_module, "decompress_body", + "Uncompress entity bodies", + "Whether to uncompress entity bodies that are compressed " + "using \"Content-Encoding: \"", + &http_decompress_body); +#endif + + http_handle = create_dissector_handle(dissect_http, proto_http); + + /* + * Dissectors shouldn't register themselves in this table; + * instead, they should call "http_dissector_add()", and + * we'll register the port number they specify as a port + * for HTTP, and register them in our subdissector table. + * + * This only works for protocols such as IPP that run over + * HTTP on a specific non-HTTP port. + */ + port_subdissector_table = register_dissector_table("http.port", + "TCP port for protocols using HTTP", FT_UINT16, BASE_DEC); + + /* + * Dissectors can register themselves in this table. + * It's just "media_type", not "http.content_type", because + * it's an Internet media type, usable by other protocols as well. + */ + media_type_subdissector_table = + register_dissector_table("media_type", + "Internet media type", FT_STRING, BASE_NONE); + + /* + * Heuristic dissectors SHOULD register themselves in + * this table using the standard heur_dissector_add() + * function. + */ + register_heur_dissector_list("http", &heur_subdissector_list); + + /* + * Register for tapping + */ + http_tap = register_tap("http"); +} + +/* + * Called by dissectors for protocols that run atop HTTP/TCP. + */ +void +http_dissector_add(guint32 port, dissector_handle_t handle) +{ + /* + * Register ourselves as the handler for that port number + * over TCP. + */ + dissector_add("tcp.port", port, http_handle); + + /* + * And register them in *our* table for that port. + */ + dissector_add("http.port", port, handle); +} + +void +proto_reg_handoff_http(void) +{ + dissector_handle_t http_udp_handle; + + data_handle = find_dissector("data"); + media_handle = find_dissector("media"); + + dissector_add("tcp.port", TCP_PORT_HTTP, http_handle); + dissector_add("tcp.port", TCP_ALT_PORT_HTTP, http_handle); + dissector_add("tcp.port", TCP_PORT_PROXY_HTTP, http_handle); + dissector_add("tcp.port", TCP_PORT_PROXY_ADMIN_HTTP, http_handle); + dissector_add("tcp.port", TCP_PORT_HKP, http_handle); + + /* + * XXX - is there anything to dissect in the body of an SSDP + * request or reply? I.e., should there be an SSDP dissector? + */ + dissector_add("tcp.port", TCP_PORT_SSDP, http_handle); + http_udp_handle = create_dissector_handle(dissect_http_udp, proto_http); + dissector_add("udp.port", UDP_PORT_SSDP, http_udp_handle); + + ntlmssp_handle = find_dissector("ntlmssp"); +} + +/* + * Content-Type: message/http + */ + +static gint proto_message_http = -1; +static gint ett_message_http = -1; +static dissector_handle_t message_http_handle; + +static void +dissect_message_http(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) +{ + proto_tree *subtree; + proto_item *ti; + gint offset = 0, next_offset; + gint len; + + if (check_col(pinfo->cinfo, COL_INFO)) + col_append_str(pinfo->cinfo, COL_INFO, " (message/http)"); + if (tree) { + ti = proto_tree_add_item(tree, proto_message_http, + tvb, 0, -1, FALSE); + subtree = proto_item_add_subtree(ti, ett_message_http); + while (tvb_reported_length_remaining(tvb, offset) != 0) { + len = tvb_find_line_end(tvb, offset, + tvb_ensure_length_remaining(tvb, offset), + &next_offset, FALSE); + if (len == -1) + break; + proto_tree_add_text(subtree, tvb, offset, next_offset - offset, + "%s", tvb_format_text(tvb, offset, len)); + offset = next_offset; + } + } +} + +void +proto_register_message_http(void) +{ + static gint *ett[] = { + &ett_message_http, + }; + + proto_message_http = proto_register_protocol( + "Media Type: message/http", + "message/http", + "message-http" + ); + proto_register_subtree_array(ett, array_length(ett)); + message_http_handle = create_dissector_handle(dissect_message_http, + proto_message_http); +} + +void +proto_reg_handoff_message_http(void) +{ + message_http_handle = create_dissector_handle(dissect_message_http, + proto_message_http); + + dissector_add_string("media_type", "message/http", message_http_handle); +} |