aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Mann <mmann78@netscape.net>2016-01-02 08:58:08 -0500
committerMichael Mann <mmann78@netscape.net>2016-01-03 13:36:18 +0000
commit1fed5fef9a455171323e08f46f2dd8c1b834df79 (patch)
tree14f04a9c27e1362385e11fff70f3cc5b90137611
parentca736cc7b49650764340bf420f834ddbd92da60b (diff)
Add HTTP Follow stream
This automatically detects and decompresses HTTP along a TCP stream through the use of taps. Bug: 3528 Change-Id: I8ab832d509700d0da8eabf3c3e514d8511c598d3 Reviewed-on: https://code.wireshark.org/review/13009 Petri-Dish: Michael Mann <mmann78@netscape.net> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Michael Mann <mmann78@netscape.net>
-rw-r--r--epan/dissectors/packet-http.c14
-rw-r--r--epan/follow.c14
-rw-r--r--epan/follow.h3
-rw-r--r--ui/follow.h3
-rw-r--r--ui/gtk/CMakeLists.txt1
-rw-r--r--ui/gtk/Makefile.common2
-rw-r--r--ui/gtk/follow_http.c313
-rw-r--r--ui/gtk/follow_http.h44
-rw-r--r--ui/gtk/follow_ssl.c2
-rw-r--r--ui/gtk/follow_stream.c4
-rw-r--r--ui/gtk/follow_stream.h1
-rw-r--r--ui/gtk/follow_tcp.c2
-rw-r--r--ui/gtk/follow_udp.c2
-rw-r--r--ui/gtk/main_menubar.c22
-rw-r--r--ui/qt/follow_stream_dialog.cpp125
-rw-r--r--ui/qt/follow_stream_dialog.h5
-rw-r--r--ui/qt/main_window.h1
-rw-r--r--ui/qt/main_window.ui9
-rw-r--r--ui/qt/main_window_slots.cpp9
-rw-r--r--ui/qt/packet_list.cpp1
-rw-r--r--ui/qt/proto_tree.cpp1
21 files changed, 557 insertions, 21 deletions
diff --git a/epan/dissectors/packet-http.c b/epan/dissectors/packet-http.c
index d7b40b341c..6632e094d7 100644
--- a/epan/dissectors/packet-http.c
+++ b/epan/dissectors/packet-http.c
@@ -60,6 +60,7 @@ void proto_reg_handoff_message_http(void);
static int http_tap = -1;
static int http_eo_tap = -1;
+static int http_follow_tap = -1;
static int proto_http = -1;
static int proto_http2 = -1;
@@ -1181,6 +1182,11 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
}
}
+ /* Give the follw tap what we've currently dissected */
+ if(have_tap_listener(http_follow_tap)) {
+ tap_queue_packet(http_follow_tap, pinfo, tvb_new_subset_length(tvb, 0, offset));
+ }
+
reported_datalen = tvb_reported_length_remaining(tvb, offset);
datalen = tvb_captured_length_remaining(tvb, offset);
@@ -1444,6 +1450,13 @@ dissect_http_message(tvbuff_t *tvb, int offset, packet_info *pinfo,
tap_queue_packet(http_eo_tap, pinfo, eo_info);
}
+ /* Save values for the Export Object GUI feature if we have
+ * an active listener to process it (which happens when
+ * the export object window is open). */
+ if(have_tap_listener(http_follow_tap)) {
+ tap_queue_packet(http_follow_tap, pinfo, next_tvb);
+ }
+
/*
* Do subdissector checks.
*
@@ -3497,6 +3510,7 @@ proto_register_http(void)
*/
http_tap = register_tap("http"); /* HTTP statistics tap */
http_eo_tap = register_tap("http_eo"); /* HTTP Export Object tap */
+ http_follow_tap = register_tap("http_follow"); /* HTTP Follow tap */
}
/*
diff --git a/epan/follow.c b/epan/follow.c
index 75ddc020f1..89e6519193 100644
--- a/epan/follow.c
+++ b/epan/follow.c
@@ -86,7 +86,7 @@ follow_stats(follow_stats_t* stats)
chance that two streams could intersect, but not a
very good one */
gchar*
-build_follow_conv_filter( packet_info *pi ) {
+build_follow_conv_filter( packet_info *pi, const char* append_filter ) {
char* buf;
int len;
conversation_t *conv=NULL;
@@ -122,7 +122,11 @@ build_follow_conv_filter( packet_info *pi ) {
/* TCP over IPv4/6 */
tcpd=get_tcp_conversation_data(conv, pi);
if (tcpd) {
- buf = g_strdup_printf("tcp.stream eq %d", tcpd->stream);
+ if (append_filter == NULL) {
+ buf = g_strdup_printf("tcp.stream eq %d", tcpd->stream);
+ } else {
+ buf = g_strdup_printf("((tcp.stream eq %d) && (%s))", tcpd->stream, append_filter);
+ }
stream_to_follow[TCP_STREAM] = tcpd->stream;
if (pi->net_src.type == AT_IPv4) {
len = 4;
@@ -142,7 +146,11 @@ build_follow_conv_filter( packet_info *pi ) {
/* UDP over IPv4/6 */
udpd=get_udp_conversation_data(conv, pi);
if (udpd) {
- buf = g_strdup_printf("udp.stream eq %d", udpd->stream);
+ if (append_filter == NULL) {
+ buf = g_strdup_printf("udp.stream eq %d", udpd->stream);
+ } else {
+ buf = g_strdup_printf("((udp.stream eq %d) && (%s))", udpd->stream, append_filter);
+ }
stream_to_follow[UDP_STREAM] = udpd->stream;
if (pi->net_src.type == AT_IPv4) {
len = 4;
diff --git a/epan/follow.h b/epan/follow.h
index 4e6b748840..7eefe2270e 100644
--- a/epan/follow.h
+++ b/epan/follow.h
@@ -54,11 +54,12 @@ typedef struct _tcp_stream_chunk {
/** Build a follow filter based on the current packet's conversation.
*
* @param packet_info [in] The current packet.
+ * @param append_filter [in] Optional filter to && (AND) to generated one.
* @return A filter that specifies the conversation. Must be g_free()d
* the caller.
*/
WS_DLL_PUBLIC
-gchar* build_follow_conv_filter( packet_info * packet_info);
+gchar* build_follow_conv_filter( packet_info * packet_info, const char* append_filter);
/** Build a follow filter based on the current TCP/UDP stream index.
* follow_index() must be called prior to calling this.
diff --git a/ui/follow.h b/ui/follow.h
index 1ece30ebf1..815e7b49a1 100644
--- a/ui/follow.h
+++ b/ui/follow.h
@@ -44,7 +44,8 @@ typedef struct {
typedef enum {
FOLLOW_TCP,
FOLLOW_SSL,
- FOLLOW_UDP
+ FOLLOW_UDP,
+ FOLLOW_HTTP
} follow_type_t;
/* Show Stream */
diff --git a/ui/gtk/CMakeLists.txt b/ui/gtk/CMakeLists.txt
index 4501f2d32d..b32db52c87 100644
--- a/ui/gtk/CMakeLists.txt
+++ b/ui/gtk/CMakeLists.txt
@@ -53,6 +53,7 @@ set(WIRESHARK_GTK_SRC
filter_utils.c
find_dlg.c
firewall_dlg.c
+ follow_http.c
follow_ssl.c
follow_stream.c
follow_tcp.c
diff --git a/ui/gtk/Makefile.common b/ui/gtk/Makefile.common
index 04e739985f..ffdb84e707 100644
--- a/ui/gtk/Makefile.common
+++ b/ui/gtk/Makefile.common
@@ -72,6 +72,7 @@ WIRESHARK_COMMON_GTK_SRC = \
filter_utils.c \
find_dlg.c \
firewall_dlg.c \
+ follow_http.c \
follow_ssl.c \
follow_stream.c \
follow_tcp.c \
@@ -199,6 +200,7 @@ noinst_HEADERS = \
filter_utils.h \
find_dlg.h \
firewall_dlg.h \
+ follow_http.h \
follow_ssl.h \
follow_stream.h \
follow_tcp.h \
diff --git a/ui/gtk/follow_http.c b/ui/gtk/follow_http.c
new file mode 100644
index 0000000000..dced654025
--- /dev/null
+++ b/ui/gtk/follow_http.c
@@ -0,0 +1,313 @@
+/* follow_http.c
+ * HTTP specific routines for following traffic streams
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include "config.h"
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include <epan/addr_resolv.h>
+#include <epan/epan_dissect.h>
+#include <epan/follow.h>
+#include <epan/tap.h>
+
+#include <ui/simple_dialog.h>
+#include <wsutil/utf8_entities.h>
+
+#include "gtkglobals.h"
+#include "ui/gtk/follow_stream.h"
+#include "ui/gtk/keys.h"
+#include "ui/gtk/main.h"
+#include "ui/gtk/follow_http.h"
+
+static gboolean
+http_queue_packet_data(void *tapdata, packet_info *pinfo,
+ epan_dissect_t *edt _U_, const void *data)
+{
+ follow_record_t *follow_record;
+ follow_info_t *follow_info = (follow_info_t *)tapdata;
+ tvbuff_t *next_tvb = (tvbuff_t *)data;
+
+ follow_record = g_new(follow_record_t,1);
+
+ follow_record->data = g_byte_array_sized_new(tvb_captured_length(next_tvb));
+ follow_record->data = g_byte_array_append(follow_record->data,
+ tvb_get_ptr(next_tvb, 0, -1),
+ tvb_captured_length(next_tvb));
+
+ if (follow_info->client_port == 0) {
+ follow_info->client_port = pinfo->srcport;
+ copy_address(&follow_info->client_ip, &pinfo->src);
+ }
+
+ if (addresses_equal(&follow_info->client_ip, &pinfo->src) && follow_info->client_port == pinfo->srcport)
+ follow_record->is_server = FALSE;
+ else
+ follow_record->is_server = TRUE;
+
+ /* update stream counter */
+ follow_info->bytes_written[follow_record->is_server] += follow_record->data->len;
+
+ follow_info->payload = g_list_append(follow_info->payload, follow_record);
+ return FALSE;
+}
+
+/* Follow the HTTP stream, if any, to which the last packet that we called
+ a dissection routine on belongs (this might be the most recently
+ selected packet, or it might be the last packet in the file). */
+void
+follow_http_stream_cb(GtkWidget *w _U_, gpointer data _U_)
+{
+ GtkWidget *filter_te, *filter_cm;
+ gchar *follow_filter;
+ const gchar *previous_filter;
+ int filter_out_filter_len, previous_filter_len;
+ const char *hostname0, *hostname1;
+ char *port0, *port1;
+ gchar *server_to_client_string = NULL;
+ gchar *client_to_server_string = NULL;
+ gchar *both_directions_string = NULL;
+ follow_stats_t stats;
+ follow_info_t *follow_info;
+ GString *msg;
+ gboolean is_http = FALSE;
+
+ is_http = proto_is_frame_protocol(cfile.edt->pi.layers, "http");
+
+ if (!is_http) {
+ simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+ "Error following stream. Please make\n"
+ "sure you have a UDP packet selected.");
+ return;
+ }
+
+ follow_info = g_new0(follow_info_t, 1);
+ follow_info->follow_type = FOLLOW_HTTP;
+
+ /* Create a new filter that matches all packets in the HTTP stream,
+ and set the display filter entry accordingly */
+ follow_filter = build_follow_conv_filter(&cfile.edt->pi, "http");
+ if (!follow_filter)
+ {
+ simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+ "Error creating filter for this stream.\n"
+ "A network layer header is needed");
+ g_free(follow_info);
+ return;
+ }
+
+ /* Set the display filter entry accordingly */
+ filter_cm = (GtkWidget *)g_object_get_data(G_OBJECT(top_level), E_DFILTER_CM_KEY);
+ filter_te = gtk_bin_get_child(GTK_BIN(filter_cm));
+
+ /* needed in follow_filter_out_stream(), is there a better way? */
+ follow_info->filter_te = filter_te;
+
+ /* save previous filter, const since we're not supposed to alter */
+ previous_filter =
+ (const gchar *)gtk_entry_get_text(GTK_ENTRY(filter_te));
+
+ /* allocate our new filter. API claims g_malloc terminates program on failure */
+ /* my calc for max alloc needed is really +10 but when did a few extra bytes hurt ? */
+ previous_filter_len = previous_filter?(int)strlen(previous_filter):0;
+ filter_out_filter_len = (int)strlen(follow_filter) + previous_filter_len + 16;
+ follow_info->filter_out_filter = (gchar *)g_malloc(filter_out_filter_len);
+
+ /* append the negation */
+ if(previous_filter_len) {
+ g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
+ "%s and !(%s)", previous_filter, follow_filter);
+ } else {
+ g_snprintf(follow_info->filter_out_filter, filter_out_filter_len,
+ "!(%s)", follow_filter);
+ }
+
+ /* data will be passed via tap callback*/
+ msg = register_tap_listener("http_follow", follow_info, follow_filter,
+ 0, NULL, http_queue_packet_data, NULL);
+ if (msg) {
+ simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
+ "Can't register http_follow tap: %s\n",
+ msg->str);
+ g_free(follow_info->filter_out_filter);
+ g_free(follow_info);
+ g_free(follow_filter);
+ return;
+ }
+
+ gtk_entry_set_text(GTK_ENTRY(filter_te), follow_filter);
+
+ /* Run the display filter so it goes in effect - even if it's the
+ same as the previous display filter. */
+ main_filter_packets(&cfile, follow_filter, TRUE);
+
+ /* Free the filter string, as we're done with it. */
+ g_free(follow_filter);
+
+ remove_tap_listener(follow_info);
+
+ /* Stream to show */
+ follow_stats(&stats);
+
+ if (stats.is_ipv6) {
+ struct e_in6_addr ipaddr;
+ memcpy(&ipaddr, stats.ip_address[0], 16);
+ hostname0 = get_hostname6(&ipaddr);
+ memcpy(&ipaddr, stats.ip_address[1], 16);
+ hostname1 = get_hostname6(&ipaddr);
+ } else {
+ guint32 ipaddr;
+ memcpy(&ipaddr, stats.ip_address[0], 4);
+ hostname0 = get_hostname(ipaddr);
+ memcpy(&ipaddr, stats.ip_address[1], 4);
+ hostname1 = get_hostname(ipaddr);
+ }
+
+ port0 = tcp_port_to_display(NULL, stats.port[0]);
+ port1 = tcp_port_to_display(NULL, stats.port[1]);
+
+ follow_info->is_ipv6 = stats.is_ipv6;
+
+ /* Both Stream Directions */
+ both_directions_string = g_strdup_printf("Entire conversation (%u bytes)", follow_info->bytes_written[0] + follow_info->bytes_written[1]);
+
+ if ((follow_info->client_port == stats.port[0]) &&
+ ((stats.is_ipv6 && (memcmp(follow_info->client_ip.data, stats.ip_address[0], 16) == 0)) ||
+ (!stats.is_ipv6 && (memcmp(follow_info->client_ip.data, stats.ip_address[0], 4) == 0)))) {
+ server_to_client_string =
+ g_strdup_printf("%s:%s " UTF8_RIGHTWARDS_ARROW " %s:%s (%u bytes)",
+ hostname0, port0,
+ hostname1, port1,
+ follow_info->bytes_written[0]);
+
+ client_to_server_string =
+ g_strdup_printf("%s:%s " UTF8_RIGHTWARDS_ARROW " %s:%s (%u bytes)",
+ hostname1, port1,
+ hostname0, port0,
+ follow_info->bytes_written[1]);
+ } else {
+ server_to_client_string =
+ g_strdup_printf("%s:%s " UTF8_RIGHTWARDS_ARROW " %s:%s (%u bytes)",
+ hostname1, port1,
+ hostname0, port0,
+ follow_info->bytes_written[0]);
+
+ client_to_server_string =
+ g_strdup_printf("%s:%s " UTF8_RIGHTWARDS_ARROW " %s:%s (%u bytes)",
+ hostname0, port0,
+ hostname1, port1,
+ follow_info->bytes_written[1]);
+ }
+
+ follow_stream("Follow HTTP Stream", follow_info, both_directions_string,
+ server_to_client_string, client_to_server_string);
+
+ wmem_free(NULL, port0);
+ wmem_free(NULL, port1);
+ g_free(both_directions_string);
+ g_free(server_to_client_string);
+ g_free(client_to_server_string);
+}
+
+#define FLT_BUF_SIZE 1024
+
+/*
+ * XXX - the routine pointed to by "print_line_fcn_p" doesn't get handed lines,
+ * it gets handed bufferfuls. That's fine for "follow_write_raw()"
+ * and "follow_add_to_gtk_text()", but, as "follow_print_text()" calls
+ * the "print_line()" routine from "print.c", and as that routine might
+ * genuinely expect to be handed a line (if, for example, it's using
+ * some OS or desktop environment's printing API, and that API expects
+ * to be handed lines), "follow_print_text()" should probably accumulate
+ * lines in a buffer and hand them "print_line()". (If there's a
+ * complete line in a buffer - i.e., there's nothing of the line in
+ * the previous buffer or the next buffer - it can just hand that to
+ * "print_line()" after filtering out non-printables, as an
+ * optimization.)
+ *
+ * This might or might not be the reason why C arrays display
+ * correctly but get extra blank lines very other line when printed.
+ */
+frs_return_t
+follow_read_http_stream(follow_info_t *follow_info,
+ gboolean (*print_line_fcn_p)(char *, size_t, gboolean, void *),
+ void *arg)
+{
+ guint32 global_client_pos = 0, global_server_pos = 0;
+ guint32 server_packet_count = 0;
+ guint32 client_packet_count = 0;
+ guint32 *global_pos;
+ gboolean skip;
+ GList* cur;
+ frs_return_t frs_return;
+ follow_record_t *follow_record;
+ char *buffer;
+
+
+ for (cur = follow_info->payload; cur; cur = g_list_next(cur)) {
+ follow_record = (follow_record_t *)cur->data;
+ skip = FALSE;
+ if (!follow_record->is_server) {
+ global_pos = &global_client_pos;
+ if(follow_info->show_stream == FROM_SERVER) {
+ skip = TRUE;
+ }
+ } else {
+ global_pos = &global_server_pos;
+ if (follow_info->show_stream == FROM_CLIENT) {
+ skip = TRUE;
+ }
+ }
+
+ if (!skip) {
+ buffer = (char *)g_memdup(follow_record->data->data,
+ follow_record->data->len);
+
+ frs_return = follow_show(follow_info, print_line_fcn_p,
+ buffer,
+ follow_record->data->len,
+ follow_record->is_server, arg,
+ global_pos,
+ &server_packet_count,
+ &client_packet_count);
+ g_free(buffer);
+ if(frs_return == FRS_PRINT_ERROR)
+ return frs_return;
+ }
+ }
+
+ return FRS_OK;
+}
+
+/*
+ * Editor modelines - http://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/ui/gtk/follow_http.h b/ui/gtk/follow_http.h
new file mode 100644
index 0000000000..011fc353f4
--- /dev/null
+++ b/ui/gtk/follow_http.h
@@ -0,0 +1,44 @@
+/* follow_http.h
+ * HTTP specific routines for following traffic streams
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __FOLLOW_HTTP_H__
+#define __FOLLOW_HTTP_H__
+
+/* Follow the HTTP stream, if any, to which the last packet that we called
+ a dissection routine on belongs (this might be the most recently
+ selected packet, or it might be the last packet in the file). */
+void follow_http_stream_cb(GtkWidget * w, gpointer data _U_);
+
+#endif /* __FOLLOW_HTTP_H__ */
+
+/*
+ * Editor modelines - http://www.wireshark.org/tools/modelines.html
+ *
+ * Local variables:
+ * c-basic-offset: 4
+ * tab-width: 8
+ * indent-tabs-mode: nil
+ * End:
+ *
+ * vi: set shiftwidth=4 tabstop=8 expandtab:
+ * :indentSize=4:tabSize=8:noTabs=true:
+ */
diff --git a/ui/gtk/follow_ssl.c b/ui/gtk/follow_ssl.c
index 22569f1692..7c30b94f41 100644
--- a/ui/gtk/follow_ssl.c
+++ b/ui/gtk/follow_ssl.c
@@ -160,7 +160,7 @@ follow_ssl_stream_cb(GtkWidget * w _U_, gpointer data _U_)
/* Create a new filter that matches all packets in the SSL stream,
and set the display filter entry accordingly */
reset_tcp_reassembly();
- follow_filter = build_follow_conv_filter(&cfile.edt->pi);
+ follow_filter = build_follow_conv_filter(&cfile.edt->pi, NULL);
if (!follow_filter)
{
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
diff --git a/ui/gtk/follow_stream.c b/ui/gtk/follow_stream.c
index f30e6648cf..4a8d4e7b91 100644
--- a/ui/gtk/follow_stream.c
+++ b/ui/gtk/follow_stream.c
@@ -87,6 +87,9 @@ follow_read_stream(follow_info_t *follow_info,
case FOLLOW_SSL :
return follow_read_ssl_stream(follow_info, print_line_fcn_p, arg);
+ case FOLLOW_HTTP :
+ return follow_read_http_stream(follow_info, print_line_fcn_p, arg);
+
default :
g_assert_not_reached();
return (frs_return_t)0;
@@ -865,6 +868,7 @@ follow_destroy_cb(GtkWidget *w, gpointer data _U_)
break;
case FOLLOW_UDP :
+ case FOLLOW_HTTP :
for(cur = follow_info->payload; cur; cur = g_list_next(cur))
if(cur->data) {
follow_record = (follow_record_t *)cur->data;
diff --git a/ui/gtk/follow_stream.h b/ui/gtk/follow_stream.h
index 60d3283de3..df8c77da46 100644
--- a/ui/gtk/follow_stream.h
+++ b/ui/gtk/follow_stream.h
@@ -70,6 +70,7 @@ frs_return_t follow_show(follow_info_t *follow_info,
gboolean follow_add_to_gtk_text(char *buffer, size_t nchars, gboolean is_server,
void *arg);
+frs_return_t follow_read_http_stream(follow_info_t *follow_info, gboolean (*print_line)(char *, size_t, gboolean, void *), void *arg);
frs_return_t follow_read_tcp_stream(follow_info_t *follow_info, gboolean (*print_line)(char *, size_t, gboolean, void *), void *arg);
frs_return_t follow_read_udp_stream(follow_info_t *follow_info, gboolean (*print_line)(char *, size_t, gboolean, void *), void *arg);
frs_return_t follow_read_ssl_stream(follow_info_t *follow_info, gboolean (*print_line)(char *, size_t, gboolean, void *), void *arg);
diff --git a/ui/gtk/follow_tcp.c b/ui/gtk/follow_tcp.c
index 369a756b3e..111b5de0f1 100644
--- a/ui/gtk/follow_tcp.c
+++ b/ui/gtk/follow_tcp.c
@@ -112,7 +112,7 @@ follow_tcp_stream_cb(GtkWidget * w _U_, gpointer data _U_)
/* Create a new filter that matches all packets in the TCP stream,
and set the display filter entry accordingly */
reset_tcp_reassembly();
- follow_filter = build_follow_conv_filter(&cfile.edt->pi);
+ follow_filter = build_follow_conv_filter(&cfile.edt->pi, NULL);
if (!follow_filter) {
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
"Error creating filter for this stream.\n"
diff --git a/ui/gtk/follow_udp.c b/ui/gtk/follow_udp.c
index ae6f362e49..960fb2a662 100644
--- a/ui/gtk/follow_udp.c
+++ b/ui/gtk/follow_udp.c
@@ -106,7 +106,7 @@ follow_udp_stream_cb(GtkWidget *w _U_, gpointer data _U_)
/* Create a new filter that matches all packets in the UDP stream,
and set the display filter entry accordingly */
- follow_filter = build_follow_conv_filter(&cfile.edt->pi);
+ follow_filter = build_follow_conv_filter(&cfile.edt->pi, NULL);
if (!follow_filter)
{
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
diff --git a/ui/gtk/main_menubar.c b/ui/gtk/main_menubar.c
index 8da4c80299..c31cb335ac 100644
--- a/ui/gtk/main_menubar.c
+++ b/ui/gtk/main_menubar.c
@@ -65,6 +65,7 @@
#include "ui/gtk/summary_dlg.h"
#include "ui/gtk/prefs_dlg.h"
#include "ui/gtk/packet_win.h"
+#include "ui/gtk/follow_http.h"
#include "ui/gtk/follow_tcp.h"
#include "ui/gtk/follow_udp.h"
#include "ui/gtk/follow_ssl.h"
@@ -1002,6 +1003,7 @@ static const char *ui_desc_menubar =
" <menuitem name='FollowTCPStream' action='/Analyze/FollowTCPStream'/>\n"
" <menuitem name='FollowUDPStream' action='/Analyze/FollowUDPStream'/>\n"
" <menuitem name='FollowSSLStream' action='/Analyze/FollowSSLStream'/>\n"
+" <menuitem name='FollowHTTPStream' action='/Analyze/FollowHTTPStream'/>\n"
" <menuitem name='ExpertInfo' action='/Analyze/ExpertInfo'/>\n"
" <menu name= 'ConversationFilterMenu' action='/Analyze/ConversationFilter'>\n"
" <placeholder name='Filters'/>\n"
@@ -1436,6 +1438,7 @@ static const GtkActionEntry main_menu_bar_entries[] = {
{ "/Analyze/FollowTCPStream", NULL, "Follow TCP Stream", NULL, NULL, G_CALLBACK(follow_tcp_stream_cb) },
{ "/Analyze/FollowUDPStream", NULL, "Follow UDP Stream", NULL, NULL, G_CALLBACK(follow_udp_stream_cb) },
{ "/Analyze/FollowSSLStream", NULL, "Follow SSL Stream", NULL, NULL, G_CALLBACK(follow_ssl_stream_cb) },
+ { "/Analyze/FollowHTTPStream", NULL, "Follow HTTP Stream", NULL, NULL, G_CALLBACK(follow_http_stream_cb) },
{ "/Analyze/ExpertInfo", WIRESHARK_STOCK_EXPERT_INFO, "Expert _Info", NULL, NULL, G_CALLBACK(expert_comp_dlg_launch) },
@@ -2201,6 +2204,7 @@ static const char *ui_desc_packet_list_menu_popup =
" <menuitem name='FollowTCPStream' action='/Follow TCP Stream'/>\n"
" <menuitem name='FollowUDPStream' action='/Follow UDP Stream'/>\n"
" <menuitem name='FollowSSLStream' action='/Follow SSL Stream'/>\n"
+" <menuitem name='FollowHTTPStream' action='/Follow HTTP Stream'/>\n"
" <separator/>\n"
" <menu name= 'Copy' action='/Copy'>\n"
" <menuitem name='SummaryTxt' action='/Copy/SummaryTxt'/>\n"
@@ -2265,6 +2269,7 @@ static const GtkActionEntry packet_list_menu_popup_action_entries[] = {
{ "/Follow TCP Stream", NULL, "Follow TCP Stream", NULL, NULL, G_CALLBACK(follow_tcp_stream_cb) },
{ "/Follow UDP Stream", NULL, "Follow UDP Stream", NULL, NULL, G_CALLBACK(follow_udp_stream_cb) },
{ "/Follow SSL Stream", NULL, "Follow SSL Stream", NULL, NULL, G_CALLBACK(follow_ssl_stream_cb) },
+ { "/Follow HTTP Stream", NULL, "Follow HTTP Stream", NULL, NULL, G_CALLBACK(follow_http_stream_cb) },
{ "/Copy", NULL, "Copy", NULL, NULL, NULL },
{ "/Copy/SummaryTxt", NULL, "Summary (Text)", NULL, NULL, G_CALLBACK(packet_list_menu_copy_sum_txt) },
@@ -2329,6 +2334,7 @@ static const char *ui_desc_tree_view_menu_popup =
" <menuitem name='FollowTCPStream' action='/Follow TCP Stream'/>\n"
" <menuitem name='FollowUDPStream' action='/Follow UDP Stream'/>\n"
" <menuitem name='FollowSSLStream' action='/Follow SSL Stream'/>\n"
+" <menuitem name='FollowHTTPStream' action='/Follow HTTP Stream'/>\n"
" <separator/>\n"
" <menu name= 'Copy' action='/Copy'>\n"
" <menuitem name='Description' action='/Copy/Description'/>\n"
@@ -2387,6 +2393,7 @@ static const GtkActionEntry tree_view_menu_popup_action_entries[] = {
{ "/Follow TCP Stream", NULL, "Follow TCP Stream", NULL, NULL, G_CALLBACK(follow_tcp_stream_cb) },
{ "/Follow UDP Stream", NULL, "Follow UDP Stream", NULL, NULL, G_CALLBACK(follow_udp_stream_cb) },
{ "/Follow SSL Stream", NULL, "Follow SSL Stream", NULL, NULL, G_CALLBACK(follow_ssl_stream_cb) },
+ { "/Follow HTTP Stream", NULL, "Follow HTTP Stream", NULL, NULL, G_CALLBACK(follow_http_stream_cb) },
{ "/Copy", NULL, "Copy", NULL, NULL, NULL },
{ "/Copy/Description", NULL, "Description", NULL, NULL, G_CALLBACK(tree_view_menu_copy_desc) },
@@ -4533,7 +4540,7 @@ set_menus_for_selected_packet(capture_file *cf)
gboolean properties = FALSE;
const char *abbrev = NULL;
char *prev_abbrev;
- gboolean is_ip = FALSE, is_tcp = FALSE, is_udp = FALSE, is_sctp = FALSE, is_ssl = FALSE, is_lte_rlc = FALSE;
+ gboolean is_ip = FALSE, is_tcp = FALSE, is_udp = FALSE, is_sctp = FALSE, is_ssl = FALSE, is_lte_rlc = FALSE, is_http = FALSE;
/* Making the menu context-sensitive allows for easier selection of the
desired item and has the added benefit, with large captures, of
@@ -4556,8 +4563,10 @@ set_menus_for_selected_packet(capture_file *cf)
than one time reference frame or the current frame isn't a
time reference frame). (XXX - why check frame_selected?) */
if (cf->edt)
+ {
proto_get_frame_protocols(cf->edt->pi.layers, &is_ip, &is_tcp, &is_udp, &is_sctp, &is_ssl, NULL, &is_lte_rlc);
-
+ is_http = proto_is_frame_protocol(cf->edt->pi.layers, "http");
+ }
if (cf->edt && cf->edt->tree) {
GPtrArray *ga;
header_field_info *hfinfo;
@@ -4663,11 +4672,16 @@ set_menus_for_selected_packet(capture_file *cf)
frame_selected ? is_udp : FALSE);
set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/FollowSSLStream",
frame_selected ? is_ssl : FALSE);
+ set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/FollowHTTPStream",
+ frame_selected ? is_http : FALSE);
+
set_menu_sensitivity(ui_manager_tree_view_menu, "/TreeViewPopup/FollowSSLStream",
frame_selected ? is_ssl : FALSE);
-
set_menu_sensitivity(ui_manager_tree_view_menu, "/TreeViewPopup/FollowUDPStream",
frame_selected ? is_udp : FALSE);
+ set_menu_sensitivity(ui_manager_tree_view_menu, "/TreeViewPopup/FollowHTTPStream",
+ frame_selected ? is_http : FALSE);
+
set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/ColorizeConversation",
frame_selected);
set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/DecodeAs",
@@ -4704,6 +4718,8 @@ set_menus_for_selected_packet(capture_file *cf)
frame_selected ? is_udp : FALSE);
set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/AnalyzeMenu/FollowSSLStream",
frame_selected ? is_ssl : FALSE);
+ set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/AnalyzeMenu/FollowHTTPStream",
+ frame_selected ? is_http : FALSE);
set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/AnalyzeMenu/DecodeAs",
frame_selected && decode_as_ok());
set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/ViewMenu/NameResolution/ResolveName",
diff --git a/ui/qt/follow_stream_dialog.cpp b/ui/qt/follow_stream_dialog.cpp
index cf020ad00e..dec1a0576e 100644
--- a/ui/qt/follow_stream_dialog.cpp
+++ b/ui/qt/follow_stream_dialog.cpp
@@ -252,9 +252,9 @@ void FollowStreamDialog::filterOut()
close();
}
-void FollowStreamDialog::on_cbDirections_currentIndexChanged(int index)
+void FollowStreamDialog::on_cbDirections_currentIndexChanged(int idx)
{
- switch(index)
+ switch(idx)
{
case 0 :
follow_info_.show_stream = BOTH_HOSTS;
@@ -272,10 +272,10 @@ void FollowStreamDialog::on_cbDirections_currentIndexChanged(int index)
readStream();
}
-void FollowStreamDialog::on_cbCharset_currentIndexChanged(int index)
+void FollowStreamDialog::on_cbCharset_currentIndexChanged(int idx)
{
- if (index < 0) return;
- follow_info_.show_type = static_cast<show_type_t>(ui->cbCharset->itemData(index).toInt());
+ if (idx < 0) return;
+ follow_info_.show_type = static_cast<show_type_t>(ui->cbCharset->itemData(idx).toInt());
readStream();
}
@@ -361,6 +361,10 @@ FollowStreamDialog::readStream()
ret = readSslStream();
break;
+ case FOLLOW_HTTP :
+ ret = readHttpStream();
+ break;
+
default :
g_assert_not_reached();
ret = (frs_return_t)0;
@@ -465,6 +469,39 @@ ssl_queue_packet_data(void *tapdata, packet_info *pinfo, epan_dissect_t *, const
return FALSE;
}
+//Copy from ui/gtk/follow_http.c
+static gboolean
+http_queue_packet_data(void *tapdata, packet_info *pinfo,
+ epan_dissect_t *, const void *data)
+{
+ follow_record_t *follow_record;
+ follow_info_t *follow_info = (follow_info_t *)tapdata;
+ tvbuff_t *next_tvb = (tvbuff_t *)data;
+
+ follow_record = g_new(follow_record_t,1);
+
+ follow_record->data = g_byte_array_sized_new(tvb_captured_length(next_tvb));
+ follow_record->data = g_byte_array_append(follow_record->data,
+ tvb_get_ptr(next_tvb, 0, -1),
+ tvb_captured_length(next_tvb));
+
+ if (follow_info->client_port == 0) {
+ follow_info->client_port = pinfo->srcport;
+ copy_address(&follow_info->client_ip, &pinfo->src);
+ }
+
+ if (addresses_equal(&follow_info->client_ip, &pinfo->src) && follow_info->client_port == pinfo->srcport)
+ follow_record->is_server = FALSE;
+ else
+ follow_record->is_server = TRUE;
+
+ /* update stream counter */
+ follow_info->bytes_written[follow_record->is_server] += follow_record->data->len;
+
+ follow_info->payload = g_list_append(follow_info->payload, follow_record);
+ return FALSE;
+}
+
/*
* XXX - the routine pointed to by "print_line_fcn_p" doesn't get handed lines,
* it gets handed bufferfuls. That's fine for "follow_write_raw()"
@@ -519,6 +556,52 @@ FollowStreamDialog::readSslStream()
return FRS_OK;
}
+/* XXX - Currently the same as readUdpStream() */
+frs_return_t
+FollowStreamDialog::readHttpStream()
+{
+ guint32 global_client_pos = 0, global_server_pos = 0;
+ guint32 *global_pos;
+ gboolean skip;
+ GList* cur;
+ frs_return_t frs_return;
+ follow_record_t *follow_record;
+ char *buffer;
+
+ for (cur = follow_info_.payload; cur; cur = g_list_next(cur)) {
+ follow_record = (follow_record_t *)cur->data;
+ skip = FALSE;
+ if (!follow_record->is_server) {
+ global_pos = &global_client_pos;
+ if(follow_info_.show_stream == FROM_SERVER) {
+ skip = TRUE;
+ }
+ } else {
+ global_pos = &global_server_pos;
+ if (follow_info_.show_stream == FROM_CLIENT) {
+ skip = TRUE;
+ }
+ }
+
+ if (!skip) {
+ buffer = (char *)g_memdup(follow_record->data->data,
+ follow_record->data->len);
+
+ frs_return = showBuffer(
+ buffer,
+ follow_record->data->len,
+ follow_record->is_server,
+ follow_record->packet_num,
+ global_pos);
+ g_free(buffer);
+ if(frs_return == FRS_PRINT_ERROR)
+ return frs_return;
+ }
+ }
+
+ return FRS_OK;
+}
+
void
FollowStreamDialog::followStream()
{
@@ -856,7 +939,7 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
follow_stats_t stats;
tcp_stream_chunk sc;
size_t nchars;
- gboolean is_tcp = FALSE, is_udp = FALSE;
+ gboolean is_tcp = FALSE, is_udp = FALSE, is_http = FALSE;
resetStream();
@@ -873,6 +956,7 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
}
proto_get_frame_protocols(cap_file_.capFile()->edt->pi.layers, NULL, &is_tcp, &is_udp, NULL, NULL, NULL, NULL);
+ is_http = proto_is_frame_protocol(cap_file_.capFile()->edt->pi.layers, "http");
switch (follow_type_)
{
@@ -897,6 +981,12 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
return false;
}
break;
+ case FOLLOW_HTTP:
+ if (!is_http) {
+ QMessageBox::warning(this, tr("Error following stream."), tr("Please make sure you have a HTTP packet selected."));
+ return false;
+ }
+ break;
}
if (follow_type_ == FOLLOW_TCP || follow_type_ == FOLLOW_SSL)
@@ -912,7 +1002,11 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
follow_filter = gchar_free_to_qstring(
build_follow_index_filter((follow_type_ == FOLLOW_TCP) ? TCP_STREAM : UDP_STREAM));
} else {
- follow_filter = gchar_free_to_qstring(build_follow_conv_filter(&cap_file_.capFile()->edt->pi));
+ if (follow_type_ == FOLLOW_HTTP) {
+ follow_filter = gchar_free_to_qstring(build_follow_conv_filter(&cap_file_.capFile()->edt->pi, "http"));
+ } else {
+ follow_filter = gchar_free_to_qstring(build_follow_conv_filter(&cap_file_.capFile()->edt->pi, NULL));
+ }
}
if (follow_filter.isEmpty()) {
QMessageBox::warning(this,
@@ -1006,6 +1100,14 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
return false;
}
break;
+ case FOLLOW_HTTP:
+ /* data will be passed via tap callback*/
+ if (!registerTapListener("http_follow", &follow_info_,
+ follow_filter.toUtf8().constData(),
+ 0, NULL, http_queue_packet_data, NULL)) {
+ return false;
+ }
+ break;
}
beginRetapPackets();
@@ -1022,6 +1124,7 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
break;
case FOLLOW_UDP:
case FOLLOW_SSL:
+ case FOLLOW_HTTP:
removeTapListeners();
break;
}
@@ -1097,6 +1200,7 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
switch (follow_type_)
{
case FOLLOW_TCP:
+ case FOLLOW_HTTP:
port0 = tcp_port_to_display(NULL, stats.port[0]);
port1 = tcp_port_to_display(NULL, stats.port[1]);
break;
@@ -1231,6 +1335,13 @@ bool FollowStreamDialog::follow(QString previous_filter, bool use_stream_index)
format_size_unit_bytes|format_size_prefix_si)));
setWindowSubtitle(tr("Follow SSL Stream (%1)").arg(follow_filter));
break;
+ case FOLLOW_HTTP:
+ both_directions_string = QString("Entire conversation (%1)")
+ .arg(gchar_free_to_qstring(format_size(
+ stats.bytes_written[0] + stats.bytes_written[1],
+ format_size_unit_bytes|format_size_prefix_si)));
+ setWindowSubtitle(tr("Follow HTTP Stream (%1)").arg(follow_filter));
+ break;
}
ui->cbDirections->clear();
diff --git a/ui/qt/follow_stream_dialog.h b/ui/qt/follow_stream_dialog.h
index 86041e7192..2d82a83ea7 100644
--- a/ui/qt/follow_stream_dialog.h
+++ b/ui/qt/follow_stream_dialog.h
@@ -79,8 +79,8 @@ protected:
void keyPressEvent(QKeyEvent *event);
private slots:
- void on_cbCharset_currentIndexChanged(int index);
- void on_cbDirections_currentIndexChanged(int index);
+ void on_cbCharset_currentIndexChanged(int idx);
+ void on_cbDirections_currentIndexChanged(int idx);
void on_bFind_clicked();
void on_leFind_returnPressed();
@@ -113,6 +113,7 @@ private:
frs_return_t readTcpStream();
frs_return_t readUdpStream();
frs_return_t readSslStream();
+ frs_return_t readHttpStream();
void followStream();
void addText(QString text, gboolean is_from_server, guint32 packet_num);
diff --git a/ui/qt/main_window.h b/ui/qt/main_window.h
index f7ffcfbabe..aacbb46aa3 100644
--- a/ui/qt/main_window.h
+++ b/ui/qt/main_window.h
@@ -463,6 +463,7 @@ private slots:
void on_actionAnalyzeFollowTCPStream_triggered();
void on_actionAnalyzeFollowUDPStream_triggered();
void on_actionAnalyzeFollowSSLStream_triggered();
+ void on_actionAnalyzeFollowHTTPStream_triggered();
void statCommandExpertInfo(const char *, void *);
void on_actionAnalyzeExpertInfo_triggered();
diff --git a/ui/qt/main_window.ui b/ui/qt/main_window.ui
index 9c5bf183f0..4a9db3e4b0 100644
--- a/ui/qt/main_window.ui
+++ b/ui/qt/main_window.ui
@@ -386,6 +386,7 @@
<addaction name="actionAnalyzeFollowTCPStream"/>
<addaction name="actionAnalyzeFollowUDPStream"/>
<addaction name="actionAnalyzeFollowSSLStream"/>
+ <addaction name="actionAnalyzeFollowHTTPStream"/>
</widget>
<widget class="QMenu" name="menuConversationFilter">
<property name="title">
@@ -1699,6 +1700,14 @@
<string>SSL Stream</string>
</property>
</action>
+ <action name="actionAnalyzeFollowHTTPStream">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>HTTP Stream</string>
+ </property>
+ </action>
<action name="actionStatisticsTcpStreamTcptrace">
<property name="text">
<string>Time Sequence (tcptrace)</string>
diff --git a/ui/qt/main_window_slots.cpp b/ui/qt/main_window_slots.cpp
index 2cc5755626..a564ae2626 100644
--- a/ui/qt/main_window_slots.cpp
+++ b/ui/qt/main_window_slots.cpp
@@ -1046,7 +1046,7 @@ void MainWindow::recentActionTriggered() {
void MainWindow::setMenusForSelectedPacket()
{
- gboolean is_ip = FALSE, is_tcp = FALSE, is_udp = FALSE, is_sctp = FALSE, is_ssl = FALSE, is_rtp = FALSE, is_lte_rlc = FALSE;
+ gboolean is_ip = FALSE, is_tcp = FALSE, is_udp = FALSE, is_sctp = FALSE, is_ssl = FALSE, is_rtp = FALSE, is_lte_rlc = FALSE, is_http = FALSE;
/* Making the menu context-sensitive allows for easier selection of the
desired item and has the added benefit, with large captures, of
@@ -1100,6 +1100,7 @@ void MainWindow::setMenusForSelectedPacket()
proto_get_frame_protocols(capture_file_.capFile()->edt->pi.layers,
&is_ip, &is_tcp, &is_udp, &is_sctp,
&is_ssl, &is_rtp, &is_lte_rlc);
+ is_http = proto_is_frame_protocol(capture_file_.capFile()->edt->pi.layers, "http");
}
}
@@ -1148,6 +1149,7 @@ void MainWindow::setMenusForSelectedPacket()
main_ui_->actionAnalyzeFollowTCPStream->setEnabled(is_tcp);
main_ui_->actionAnalyzeFollowUDPStream->setEnabled(is_udp);
main_ui_->actionAnalyzeFollowSSLStream->setEnabled(is_ssl);
+ main_ui_->actionAnalyzeFollowHTTPStream->setEnabled(is_http);
foreach (QAction *cc_action, cc_actions) {
cc_action->setEnabled(frame_selected);
@@ -2671,6 +2673,11 @@ void MainWindow::on_actionAnalyzeFollowSSLStream_triggered()
openFollowStreamDialog(FOLLOW_SSL);
}
+void MainWindow::on_actionAnalyzeFollowHTTPStream_triggered()
+{
+ openFollowStreamDialog(FOLLOW_HTTP);
+}
+
void MainWindow::openSCTPAllAssocsDialog()
{
SCTPAllAssocsDialog *sctp_dialog = new SCTPAllAssocsDialog(this, capture_file_.capFile());
diff --git a/ui/qt/packet_list.cpp b/ui/qt/packet_list.cpp
index 0f5d57d694..11d7e2e585 100644
--- a/ui/qt/packet_list.cpp
+++ b/ui/qt/packet_list.cpp
@@ -326,6 +326,7 @@ PacketList::PacketList(QWidget *parent) :
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTCPStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowUDPStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowSSLStream"));
+ submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
ctx_menu_.addSeparator();
diff --git a/ui/qt/proto_tree.cpp b/ui/qt/proto_tree.cpp
index 577cf1f8c9..a4fcd3458e 100644
--- a/ui/qt/proto_tree.cpp
+++ b/ui/qt/proto_tree.cpp
@@ -209,6 +209,7 @@ ProtoTree::ProtoTree(QWidget *parent) :
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowTCPStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowUDPStream"));
submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowSSLStream"));
+ submenu->addAction(window()->findChild<QAction *>("actionAnalyzeFollowHTTPStream"));
ctx_menu_.addSeparator();
main_menu_item = window()->findChild<QMenu *>("menuEditCopy");