1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * soup-content-sniffer.c
5 * Copyright (C) 2009, 2013 Gustavo Noronha Silva.
7 * This code implements the following specification:
9 * http://mimesniff.spec.whatwg.org/ as of 11 June 2013
18 #include "soup-content-sniffer.h"
20 #include "soup-content-processor.h"
21 #include "soup-content-sniffer-stream.h"
22 #include "soup-message-private.h"
27 * SECTION:soup-content-sniffer
28 * @short_description: Content sniffing for SoupSession
30 * A #SoupContentSniffer tries to detect the actual content type of
31 * the files that are being downloaded by looking at some of the data
32 * before the #SoupMessage emits its #SoupMessage::got-headers signal.
33 * #SoupContentSniffer implements #SoupSessionFeature, so you can add
34 * content sniffing to a session with soup_session_add_feature() or
35 * soup_session_add_feature_by_type().
40 static void soup_content_sniffer_session_feature_init (SoupSessionFeatureInterface *feature_interface, gpointer interface_data);
42 static SoupContentProcessorInterface *soup_content_sniffer_default_content_processor_interface;
43 static void soup_content_sniffer_content_processor_init (SoupContentProcessorInterface *interface, gpointer interface_data);
46 G_DEFINE_TYPE_WITH_CODE (SoupContentSniffer, soup_content_sniffer, G_TYPE_OBJECT,
47 G_IMPLEMENT_INTERFACE (SOUP_TYPE_SESSION_FEATURE,
48 soup_content_sniffer_session_feature_init)
49 G_IMPLEMENT_INTERFACE (SOUP_TYPE_CONTENT_PROCESSOR,
50 soup_content_sniffer_content_processor_init))
54 soup_content_sniffer_content_processor_wrap_input (SoupContentProcessor *processor,
55 GInputStream *base_stream,
59 return g_object_new (SOUP_TYPE_CONTENT_SNIFFER_STREAM,
60 "base-stream", base_stream,
62 "sniffer", SOUP_CONTENT_SNIFFER (processor),
67 soup_content_sniffer_content_processor_init (SoupContentProcessorInterface *processor_interface,
68 gpointer interface_data)
70 soup_content_sniffer_default_content_processor_interface =
71 g_type_default_interface_peek (SOUP_TYPE_CONTENT_PROCESSOR);
73 processor_interface->processing_stage = SOUP_STAGE_BODY_DATA;
74 processor_interface->wrap_input = soup_content_sniffer_content_processor_wrap_input;
78 soup_content_sniffer_init (SoupContentSniffer *content_sniffer)
84 const guchar *pattern;
86 const char *sniffed_type;
87 } SoupContentSnifferMediaPattern;
90 sniff_media (SoupContentSniffer *sniffer,
92 SoupContentSnifferMediaPattern table[],
95 const guchar *resource = (const guchar *)buffer->data;
96 int resource_length = MIN (512, buffer->length);
99 for (i = 0; i < table_length; i++) {
100 SoupContentSnifferMediaPattern *type_row = &(table[i]);
103 if (resource_length < type_row->pattern_length)
106 for (j = 0; j < type_row->pattern_length; j++) {
107 if ((type_row->mask[j] & resource[j]) != type_row->pattern[j])
111 /* This means our comparison above matched completely */
112 if (j == type_row->pattern_length)
113 return g_strdup (type_row->sniffed_type);
119 /* This table is based on the MIMESNIFF spec;
120 * See 6.1 Matching an image type pattern
122 static SoupContentSnifferMediaPattern image_types_table[] = {
124 /* Windows icon signature. */
125 { (const guchar *)"\xFF\xFF\xFF\xFF",
126 (const guchar *)"\x00\x00\x01\x00",
130 /* Windows cursor signature. */
131 { (const guchar *)"\xFF\xFF\xFF\xFF",
132 (const guchar *)"\x00\x00\x02\x00",
137 { (const guchar *)"\xFF\xFF",
138 (const guchar *)"BM",
143 { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF",
144 (const guchar *)"GIF87a",
148 { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF",
149 (const guchar *)"GIF89a",
154 { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF",
155 (const guchar *)"RIFF\x00\x00\x00\x00WEBPVP",
160 { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
161 (const guchar *)"\x89PNG\x0D\x0A\x1A\x0A",
166 { (const guchar *)"\xFF\xFF\xFF",
167 (const guchar *)"\xFF\xD8\xFF",
173 sniff_images (SoupContentSniffer *sniffer, SoupBuffer *buffer)
175 return sniff_media (sniffer,
178 G_N_ELEMENTS (image_types_table));
181 /* This table is based on the MIMESNIFF spec;
182 * See 6.2 Matching an audio or video type pattern
184 static SoupContentSnifferMediaPattern audio_video_types_table[] = {
185 { (const guchar *)"\xFF\xFF\xFF\xFF",
186 (const guchar *)"\x1A\x45\xDF\xA3",
190 { (const guchar *)"\xFF\xFF\xFF\xFF",
191 (const guchar *)".snd",
196 { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
197 (const guchar *)"FORM\0\0\0\0AIFF",
201 { (const guchar *)"\xFF\xFF\xFF",
202 (const guchar *)"ID3",
206 { (const guchar *)"\xFF\xFF\xFF\xFF\xFF",
207 (const guchar *)"OggS\0",
211 { (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
212 (const guchar *)"MThd\x00\x00\x00\x06",
216 { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
217 (const guchar *)"RIFF\x00\x00\x00\x00AVI ",
221 { (const guchar *)"\xFF\xFF\xFF\xFF\x00\x00\x00\x00\xFF\xFF\xFF\xFF",
222 (const guchar *)"RIFF\x00\x00\x00\x00WAVE",
228 sniff_mp4 (SoupContentSniffer *sniffer, SoupBuffer *buffer)
230 const char *resource = (const char *)buffer->data;
231 int resource_length = MIN (512, buffer->length);
232 guint32 box_size = *((guint32*)resource);
235 #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
236 box_size = ((box_size >> 24) |
237 ((box_size << 8) & 0x00FF0000) |
238 ((box_size >> 8) & 0x0000FF00) |
242 if (resource_length < 12 || resource_length < box_size || box_size % 4 != 0)
245 if (!g_str_has_prefix (resource + 4, "ftyp"))
248 if (!g_str_has_prefix (resource + 8, "mp4"))
251 for (i = 16; i < box_size && i < resource_length; i = i + 4) {
252 if (g_str_has_prefix (resource + i, "mp4"))
260 sniff_audio_video (SoupContentSniffer *sniffer, SoupBuffer *buffer)
264 sniffed_type = sniff_media (sniffer,
266 audio_video_types_table,
267 G_N_ELEMENTS (audio_video_types_table));
269 if (sniffed_type != NULL)
272 if (sniff_mp4 (sniffer, buffer))
273 return g_strdup ("video/mp4");
278 /* This table is based on the MIMESNIFF spec;
279 * See 7.1 Identifying a resource with an unknown MIME type
282 /* @has_ws is TRUE if @pattern contains "generic" whitespace */
284 /* @has_tag_termination is TRUE if we should check for a tag-terminating
285 * byte (0x20 " " or 0x3E ">") after the pattern match.
287 gboolean has_tag_termination;
289 const guchar *pattern;
290 guint pattern_length;
291 const char *sniffed_type;
293 } SoupContentSnifferPattern;
296 /* When has_ws is TRUE, spaces in the pattern will indicate where insignificant space
297 * is allowed. Those spaces are marked with \x00 on the mask.
299 static SoupContentSnifferPattern types_table[] = {
300 /* Scriptable types. */
303 (const guchar *)"\x00\xFF\xFF\xDF\xDF\xDF\xDF\xDF\xDF\xDF\xFF\xDF\xDF\xDF\xDF",
304 (const guchar *)" <!DOCTYPE HTML",
310 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF",
311 (const guchar *)" <HTML",
317 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF",
318 (const guchar *)" <HEAD",
324 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF\xDF",
325 (const guchar *)" <SCRIPT",
331 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF\xDF",
332 (const guchar *)" <IFRAME",
338 (const guchar *)"\x00\xFF\xDF\xFF",
339 (const guchar *)" <H1",
345 (const guchar *)"\x00\xFF\xDF\xDF\xDF",
346 (const guchar *)" <DIV",
352 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF",
353 (const guchar *)" <FONT",
359 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF",
360 (const guchar *)" <TABLE",
366 (const guchar *)"\x00\xFF\xDF",
367 (const guchar *)" <A",
373 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF",
374 (const guchar *)" <STYLE",
380 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF\xDF",
381 (const guchar *)" <TITLE",
387 (const guchar *)"\x00\xFF\xDF",
388 (const guchar *)" <B",
394 (const guchar *)"\x00\xFF\xDF\xDF\xDF\xDF",
395 (const guchar *)" <BODY",
401 (const guchar *)"\x00\xFF\xDF\xDF",
402 (const guchar *)" <BR",
408 (const guchar *)"\x00\xFF\xDF",
409 (const guchar *)" <P",
415 (const guchar *)"\x00\xFF\xFF\xFF\xFF",
416 (const guchar *)" <!--",
422 (const guchar *)"\x00\xFF\xFF\xFF\xFF\xFF",
423 (const guchar *)" <?xml",
429 (const guchar *)"\xFF\xFF\xFF\xFF\xFF",
430 (const guchar *)"%PDF-",
435 /* Non-scriptable types. */
437 (const guchar *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",
438 (const guchar *)"%!PS-Adobe-",
440 "application/postscript",
443 { FALSE, FALSE, /* UTF-16BE BOM */
444 (const guchar *)"\xFF\xFF\x00\x00",
445 (const guchar *)"\xFE\xFF\x00\x00",
450 { FALSE, FALSE, /* UTF-16LE BOM */
451 (const guchar *)"\xFF\xFF\x00\x00",
452 (const guchar *)"\xFF\xFE\x00\x00",
457 { FALSE, FALSE, /* UTF-8 BOM */
458 (const guchar *)"\xFF\xFF\xFF\x00",
459 (const guchar *)"\xEF\xBB\xBF\x00",
465 /* Whether a given byte looks like it might be part of binary content.
466 * Source: HTML5 spec; borrowed from the Chromium mime sniffer code,
467 * which is BSD-licensed
469 static char byte_looks_binary[] = {
470 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, /* 0x00 - 0x0F */
471 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, /* 0x10 - 0x1F */
472 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2F */
473 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3F */
474 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4F */
475 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5F */
476 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6F */
477 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7F */
478 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8F */
479 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9F */
480 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0 - 0xAF */
481 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xB0 - 0xBF */
482 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xC0 - 0xCF */
483 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0 - 0xDF */
484 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xE0 - 0xEF */
485 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0 - 0xFF */
488 #if ENABLE(TIZEN_TV_DISABLE_MIME_SNIFF)
489 gboolean soup_Disable_Mime_Sniff = FALSE;
491 soup_content_mime_sniff_set (gboolean gDisableMimeSniff)
493 soup_Disable_Mime_Sniff = gDisableMimeSniff;
497 /* HTML5: 2.7.4 Content-Type sniffing: unknown type */
499 sniff_unknown (SoupContentSniffer *sniffer, SoupBuffer *buffer,
500 gboolean sniff_scriptable)
502 char *sniffed_type = NULL;
503 const guchar *resource = (const guchar *)buffer->data;
504 int resource_length = MIN (512, buffer->length);
507 #if ENABLE(TIZEN_TV_DISABLE_MIME_SNIFF)
508 if (soup_Disable_Mime_Sniff && !sniff_scriptable){
509 return g_strdup ("text/plain");
512 for (i = 0; i < G_N_ELEMENTS (types_table); i++) {
513 SoupContentSnifferPattern *type_row = &(types_table[i]);
515 if (!sniff_scriptable && type_row->scriptable)
518 if (type_row->has_ws) {
519 int index_stream = 0;
520 int index_pattern = 0;
521 gboolean skip_row = FALSE;
523 while ((index_stream < resource_length) &&
524 (index_pattern <= type_row->pattern_length)) {
525 /* Skip insignificant white space ("WS" in the spec) */
526 if (type_row->pattern[index_pattern] == ' ') {
527 if (resource[index_stream] == '\x09' ||
528 resource[index_stream] == '\x0a' ||
529 resource[index_stream] == '\x0c' ||
530 resource[index_stream] == '\x0d' ||
531 resource[index_stream] == '\x20')
536 if ((type_row->mask[index_pattern] & resource[index_stream]) != type_row->pattern[index_pattern]) {
548 if (index_pattern > type_row->pattern_length) {
549 if (type_row->has_tag_termination &&
550 resource[index_stream] != '\x20' &&
551 resource[index_stream] != '\x3E')
554 return g_strdup (type_row->sniffed_type);
559 if (resource_length < type_row->pattern_length)
562 for (j = 0; j < type_row->pattern_length; j++) {
563 if ((type_row->mask[j] & resource[j]) != type_row->pattern[j])
567 /* This means our comparison above matched completely */
568 if (j == type_row->pattern_length)
569 return g_strdup (type_row->sniffed_type);
573 sniffed_type = sniff_images (sniffer, buffer);
575 if (sniffed_type != NULL)
578 sniffed_type = sniff_audio_video (sniffer, buffer);
580 if (sniffed_type != NULL)
583 for (i = 0; i < resource_length; i++) {
584 if (byte_looks_binary[resource[i]])
585 return g_strdup ("application/octet-stream");
588 #if ENABLE(TIZEN_TV_DISABLE_MIME_SNIFF)
589 /* Refer to Orsay's implementation, modify the default value from "text/plain" to "text/html". */
590 return g_strdup ("text/html");
592 return g_strdup ("text/plain");
596 /* MIMESNIFF: 7.2 Sniffing a mislabeled binary resource */
598 sniff_text_or_binary (SoupContentSniffer *sniffer, SoupBuffer *buffer)
600 const guchar *resource = (const guchar *)buffer->data;
601 int resource_length = MIN (512, buffer->length);
602 gboolean looks_binary = FALSE;
605 /* 2. Detecting UTF-16BE, UTF-16LE BOMs means it's text/plain */
606 if (resource_length >= 2) {
607 if ((resource[0] == 0xFE && resource[1] == 0xFF) ||
608 (resource[0] == 0xFF && resource[1] == 0xFE))
609 return g_strdup ("text/plain");
613 if (resource_length >= 3) {
614 if (resource[0] == 0xEF && resource[1] == 0xBB && resource[2] == 0xBF)
615 return g_strdup ("text/plain");
618 /* 4. Look to see if any of the first n bytes looks binary */
619 for (i = 0; i < resource_length; i++) {
620 if (byte_looks_binary[resource[i]]) {
627 return g_strdup ("text/plain");
629 /* 5. Execute 7.1 Identifying a resource with an unknown MIME type.
630 * TODO: sniff-scriptable needs to be unset.
632 return sniff_unknown (sniffer, buffer, TRUE);
636 skip_insignificant_space (const char *resource, int *pos, int resource_length)
638 while ((resource[*pos] == '\x09') ||
639 (resource[*pos] == '\x20') ||
640 (resource[*pos] == '\x0A') ||
641 (resource[*pos] == '\x0D')) {
644 if (*pos > resource_length)
652 sniff_feed_or_html (SoupContentSniffer *sniffer, SoupBuffer *buffer)
654 const char *resource = (const char *)buffer->data;
655 int resource_length = MIN (512, buffer->length);
658 if (resource_length < 3)
661 /* Skip a leading UTF-8 BOM */
662 if (resource[0] == 0xEF && resource[1] == 0xBB && resource[2] == 0xBF)
666 if (pos > resource_length)
669 if (skip_insignificant_space (resource, &pos, resource_length))
672 if (resource[pos] != '<')
673 return g_strdup ("text/html");
677 if ((pos + 2) > resource_length)
681 if (g_str_has_prefix (resource + pos, "!--")) {
684 if ((pos + 2) > resource_length)
687 while (!g_str_has_prefix (resource + pos, "-->")) {
690 if ((pos + 2) > resource_length)
699 if (pos > resource_length)
702 if (resource[pos] == '!') {
706 if (pos > resource_length)
708 } while (resource[pos] != '>');
713 } else if (resource[pos] == '?') {
717 if ((pos + 1) > resource_length)
719 } while (!g_str_has_prefix (resource + pos, "?>"));
726 if ((pos + 3) > resource_length)
729 if (g_str_has_prefix (resource + pos, "rss"))
730 return g_strdup ("application/rss+xml");
732 if ((pos + 4) > resource_length)
735 if (g_str_has_prefix (resource + pos, "feed"))
736 return g_strdup ("application/atom+xml");
738 if ((pos + 7) > resource_length)
741 if (g_str_has_prefix (resource + pos, "rdf:RDF")) {
744 if (skip_insignificant_space (resource, &pos, resource_length))
747 if ((pos + 32) > resource_length)
750 if (g_str_has_prefix (resource + pos, "xmlns=\"http://purl.org/rss/1.0/\"")) {
753 if (skip_insignificant_space (resource, &pos, resource_length))
756 if ((pos + 55) > resource_length)
759 if (g_str_has_prefix (resource + pos, "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\""))
760 return g_strdup ("application/rss+xml");
763 if ((pos + 55) > resource_length)
766 if (g_str_has_prefix (resource + pos, "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"")) {
769 if (skip_insignificant_space (resource, &pos, resource_length))
772 if ((pos + 32) > resource_length)
775 if (g_str_has_prefix (resource + pos, "xmlns=\"http://purl.org/rss/1.0/\""))
776 return g_strdup ("application/rss+xml");
781 return g_strdup ("text/html");
785 soup_content_sniffer_real_sniff (SoupContentSniffer *sniffer, SoupMessage *msg,
786 SoupBuffer *buffer, GHashTable **params)
788 const char *content_type;
789 const char *x_content_type_options;
790 char *sniffed_type = NULL;
791 gboolean no_sniff = FALSE;
793 content_type = soup_message_headers_get_content_type (msg->response_headers, params);
795 /* MIMESNIFF: 7 Determining the sniffed MIME type of a resource. */
797 x_content_type_options = soup_message_headers_get_one (msg->response_headers, "X-Content-Type-Options");
798 if (!g_strcmp0 (x_content_type_options, "nosniff"))
801 /* 1. Unknown/undefined supplied type with sniff-scritable = !nosniff. */
802 if ((content_type == NULL) ||
803 !g_ascii_strcasecmp (content_type, "unknown/unknown") ||
804 !g_ascii_strcasecmp (content_type, "application/unknown") ||
805 !g_ascii_strcasecmp (content_type, "*/*"))
806 return sniff_unknown (sniffer, buffer, !no_sniff);
808 /* 2. If nosniff is specified in X-Content-Type-Options use the supplied MIME type. */
810 return g_strdup (content_type);
812 /* 3. check-for-apache-bug */
813 if ((content_type != NULL) &&
814 (g_str_equal (content_type, "text/plain") ||
815 g_str_equal (content_type, "text/plain; charset=ISO-8859-1") ||
816 g_str_equal (content_type, "text/plain; charset=iso-8859-1") ||
817 g_str_equal (content_type, "text/plain; charset=UTF-8")))
818 return sniff_text_or_binary (sniffer, buffer);
820 /* 4. XML types sent by the server are always used. */
821 if (g_str_has_suffix (content_type, "+xml") ||
822 !g_ascii_strcasecmp (content_type, "text/xml") ||
823 !g_ascii_strcasecmp (content_type, "application/xml"))
824 return g_strdup (content_type);
826 /* 5. Distinguish feed from HTML. */
827 if (!g_ascii_strcasecmp (content_type, "text/html"))
828 return sniff_feed_or_html (sniffer, buffer);
832 if (!g_ascii_strncasecmp (content_type, "image/", 6)) {
833 sniffed_type = sniff_images (sniffer, buffer);
834 if (sniffed_type != NULL)
836 return g_strdup (content_type);
839 /* 7. Audio and video types. */
840 if (!g_ascii_strncasecmp (content_type, "audio/", 6) ||
841 !g_ascii_strncasecmp (content_type, "video/", 6) ||
842 !g_ascii_strcasecmp (content_type, "application/ogg")) {
843 sniffed_type = sniff_audio_video (sniffer, buffer);
844 if (sniffed_type != NULL)
846 return g_strdup (content_type);
849 /* If we got text/plain, use text_or_binary */
850 if (g_str_equal (content_type, "text/plain")) {
851 return sniff_text_or_binary (sniffer, buffer);
854 return g_strdup (content_type);
858 soup_content_sniffer_real_get_buffer_size (SoupContentSniffer *sniffer)
864 soup_content_sniffer_got_headers_cb (SoupMessage *msg, SoupContentSniffer *sniffer)
866 SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
868 priv->bytes_for_sniffing = soup_content_sniffer_get_buffer_size (sniffer);
872 soup_content_sniffer_request_queued (SoupSessionFeature *feature,
873 SoupSession *session,
876 SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
878 priv->sniffer = g_object_ref (feature);
879 g_signal_connect (msg, "got-headers",
880 G_CALLBACK (soup_content_sniffer_got_headers_cb),
885 soup_content_sniffer_request_unqueued (SoupSessionFeature *feature,
886 SoupSession *session,
889 SoupMessagePrivate *priv = SOUP_MESSAGE_GET_PRIVATE (msg);
891 g_object_unref (priv->sniffer);
892 priv->sniffer = NULL;
894 g_signal_handlers_disconnect_by_func (msg, soup_content_sniffer_got_headers_cb, feature);
898 soup_content_sniffer_class_init (SoupContentSnifferClass *content_sniffer_class)
900 content_sniffer_class->sniff = soup_content_sniffer_real_sniff;
901 content_sniffer_class->get_buffer_size = soup_content_sniffer_real_get_buffer_size;
905 soup_content_sniffer_session_feature_init (SoupSessionFeatureInterface *feature_interface,
906 gpointer interface_data)
908 feature_interface->request_queued = soup_content_sniffer_request_queued;
909 feature_interface->request_unqueued = soup_content_sniffer_request_unqueued;
913 * soup_content_sniffer_new:
915 * Creates a new #SoupContentSniffer.
917 * Returns: a new #SoupContentSniffer
922 soup_content_sniffer_new ()
924 return g_object_new (SOUP_TYPE_CONTENT_SNIFFER, NULL);
928 * soup_content_sniffer_sniff:
929 * @sniffer: a #SoupContentSniffer
930 * @msg: the message to sniff
931 * @buffer: a buffer containing the start of @msg's response body
932 * @params: (element-type utf8 utf8) (out) (transfer full) (allow-none): return
933 * location for Content-Type parameters (eg, "charset"), or %NULL
935 * Sniffs @buffer to determine its Content-Type. The result may also
936 * be influenced by the Content-Type declared in @msg's response
939 * Return value: the sniffed Content-Type of @buffer; this will never be %NULL,
940 * but may be "application/octet-stream".
945 soup_content_sniffer_sniff (SoupContentSniffer *sniffer,
946 SoupMessage *msg, SoupBuffer *buffer,
949 g_return_val_if_fail (SOUP_IS_CONTENT_SNIFFER (sniffer), NULL);
950 g_return_val_if_fail (SOUP_IS_MESSAGE (msg), NULL);
951 g_return_val_if_fail (buffer != NULL, NULL);
953 return SOUP_CONTENT_SNIFFER_GET_CLASS (sniffer)->sniff (sniffer, msg, buffer, params);
957 * soup_content_sniffer_get_buffer_size:
958 * @sniffer: a #SoupContentSniffer
960 * Gets the number of bytes @sniffer needs in order to properly sniff
963 * Return value: the number of bytes to sniff
968 soup_content_sniffer_get_buffer_size (SoupContentSniffer *sniffer)
970 g_return_val_if_fail (SOUP_IS_CONTENT_SNIFFER (sniffer), 0);
972 return SOUP_CONTENT_SNIFFER_GET_CLASS (sniffer)->get_buffer_size (sniffer);