multipartdemux: Better parsing of Content-Type multipart header
Submitted by Jose Aladro
Link to original bug (#754483)
Description
Dear maintainer:
The parsing of the "Content-Type:" multipart header in multipartdemux assumes 1 space must follow the colon in every chunk's header or it will parse the type incorrectly.
I used an Approx APPIP01P2P IP camera (http://approx.es/APPIP01P2P).
HTTP Request
GET /videostream.cgi?user=XXX&pwd=XXX HTTP/1.1
HTTP Response
HTTP/1.1 200 OK
Date: Wed Sep 2 16:27:08 2015
Server: GoAhead-Webs
Accept-Ranges: bytes
Connection: close
Content-Type: multipart/x-mixed-replace;boundary=object-ipcamera <-- this is parsed OK
--object-ipcamera
Content-Type:image/jpeg <-- this is not parsed properly
Content-Length:54264
......JFIF...
...
--object-ipcamera
Content-Type:image/jpeg
Content-Length:54150
......JFIF...
... and so on
What happens
The chunks are identified by multipartdemux as "mage/jpeg" (missing the "i"), assuming a space must follow the colon in the header. For that reason, any capsfilter for 'image/jpeg' or any jpegparse or jpegdec element further in the pipeline will not link.
Note: Some buggy IP cameras may not format this header properly, as in this casse, but i.e. VLC 2.2.0 and Firefox 31.8.0 don't have any trouble playing that multipart content.
How to reproduce (useless, see the caps = mage/jpeg)
$ gst-launch-1.0 -e -v --gst-debug=*:1 souphttpsrc location="http://192.168.4.37/videostream.cgi?user=asus&pwd=asus" do-timestamp=true ! multipartdemux ! identity silent=false ! fakesink
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = event ******* (identity0:sink) E (type: caps (12814), GstEventCaps, caps=(GstCaps)mage/jpeg;) 0xe3a180
/GstPipeline:pipeline0/GstIdentity:identity0.GstPad:src: caps = mage/jpeg
/GstPipeline:pipeline0/GstFakeSink:fakesink0.GstPad:sink: caps = mage/jpeg
/GstPipeline:pipeline0/GstIdentity:identity0.GstPad:sink: caps = mage/jpeg
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = event ******* (identity0:sink) E (type: segment (17934), GstEventSegment, segment=(GstSegment)"GstSegment, flags=(GstSegmentFlags)GST_SEGMENT_FLAG_NONE, rate=(double)1, applied-rate=(double)1, format=(GstFormat)GST_FORMAT_TIME, base=(guint64)0, offset=(guint64)0, start=(guint64)0, stop=(guint64)18446744073709551615, time=(guint64)0, position=(guint64)0, duration=(guint64)18446744073709551615;";) 0xe3a240
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = event ******* (identity0:sink) E (type: tag (20510), GstTagList-global, taglist=(taglist)"taglist,\ container-format=(string)Multipart;";) 0xe3a300
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = chain ******* (identity0:sink) (52276 bytes, dts: none, pts:none, duration: none, offset: -1, offset_end: -1, flags: 00000040 discont ) 0xf35b30
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = chain ******* (identity0:sink) (52776 bytes, dts: none, pts:none, duration: none, offset: -1, offset_end: -1, flags: 00000000 ) 0xe41070
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = chain ******* (identity0:sink) (53112 bytes, dts: none, pts:0:00:00.580902326, duration: none, offset: -1, offset_end: -1, flags: 00000000 ) 0xe414b0
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = chain ******* (identity0:sink) (53160 bytes, dts: none, pts:0:00:01.642750430, duration: none, offset: -1, offset_end: -1, flags: 00000000 ) 0xf35c40
^
Chandling interrupt.
How to reproduce (added jpegparse, not-linked error)
$ gst-launch-1.0 -e -v --gst-debug=*:1 souphttpsrc location="http://192.168.xxx.xxx/videostream.cgi?user=xxx&pwd=xxx" do-timestamp=true ! multipartdemux ! jpegparse ! identity silent=false ! fakesink
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
ERROR: from element /GstPipeline:pipeline0/GstSoupHTTPSrc:souphttpsrc0: Internal data flow error.
Additional debug info:
gstbasesrc.c(2865): gst_base_src_loop (): /GstPipeline:pipeline0/GstSoupHTTPSrc:souphttpsrc0:
streaming task paused, reason not-linked (-1)
ERROR: pipeline doesn't want to preroll.
Setting pipeline to NULL ...
/GstPipeline:pipeline0/GstMultipartDemux:multipartdemux0.GstPad:src_0: caps = NULL
Freeing pipeline ...
GStreamer version
I've tested it with gst-launch-1.0 (GStreamer 1.2.4) but the relevant code is similar/the same in the master trunk (1.4?) at:
http://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/multipart/multipartdemux.c
Relevant code
http://cgit.freedesktop.org/gstreamer/gst-plugins-good/tree/gst/multipart/multipartdemux.c:477
---cut---
if (len >= 14 && !g_ascii_strncasecmp ("content-type:", (gchar *) pos, 13)) {
guint mime_len;
/* only take the mime type up to the first ; if any. After ; there can be
* properties that we don't handle yet. */
mime_len = get_mime_len (pos + 14, len - 14);
g_free (multipart->mime_type);
multipart->mime_type = g_ascii_strdown ((gchar *) pos + 14, mime_len);
} else if (len >= 15 &&
---cut---
This piece checks for header length >= 14 chars, compares the first 13 and assumes the actual type starts at char 14, which is not always true.
Possible solutions
Allow zero or more whitespace after the colon.
Solution that works for me
Consider both cases: 1 and zero spaces after the colon. For 14-char pattern, read after char #14
. For 13-char pattern, read after char #13
.
(code recompiled with "apt-get build-dep ...", "apt-get source ..." and "fakeroot debian/rules binary" stuff)
---cut---
if (len >= 14 && !g_ascii_strncasecmp ("content-type: ", (gchar *) pos, 14)) {
guint mime_len;
/* only take the mime type up to the first ; if any. After ; there can be
* properties that we don't handle yet. */
mime_len = get_mime_len (pos + 14, len - 14);
g_free (multipart->mime_type);
multipart->mime_type = g_ascii_strdown ((gchar *) pos + 14, mime_len);
} else if (len >= 13 && !g_ascii_strncasecmp ("content-type:", (gchar *) pos, 13)) {
guint mime_len;
/* only take the mime type up to the first ; if any. After ; there can be
* properties that we don't handle yet. */
mime_len = get_mime_len (pos + 13, len - 13);
g_free (multipart->mime_type);
multipart->mime_type = g_ascii_strdown ((gchar *) pos + 13, mime_len);
} else if (len >= 15 &&
---cut---
Result with patch applied
$ gst-launch-1.0 -e -v --gst-debug=*:1 souphttpsrc location="http://192.168.xxx.xxx/videostream.cgi?user=xxx&pwd=xxx" do-timestamp=true ! multipartdemux ! jpegparse ! identity silent=false ! fakesink
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
/GstPipeline:pipeline0/GstJpegParse:jpegparse0.GstPad:sink: caps = image/jpeg
/GstPipeline:pipeline0/GstJpegParse:jpegparse0.GstPad:src: caps = image/jpeg, parsed=(boolean)true, format=(string)UYVY, interlaced=(boolean)false, width=(int)640, height=(int)480, framerate=(fraction)1/1
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = event ******* (identity0:sink) E (type: caps (12814), GstEventCaps, caps=(GstCaps)image/jpeg, parsed=(boolean)true, format=(string)UYVY, interlaced=(boolean)false, width=(int)640, height=(int)480, framerate=(fraction)1/1;) 0x21f5300
/GstPipeline:pipeline0/GstIdentity:identity0.GstPad:src: caps = image/jpeg, parsed=(boolean)true, format=(string)UYVY, interlaced=(boolean)false, width=(int)640, height=(int)480, framerate=(fraction)1/1
/GstPipeline:pipeline0/GstFakeSink:fakesink0.GstPad:sink: caps = image/jpeg, parsed=(boolean)true, format=(string)UYVY, interlaced=(boolean)false, width=(int)640, height=(int)480, framerate=(fraction)1/1
/GstPipeline:pipeline0/GstIdentity:identity0.GstPad:sink: caps = image/jpeg, parsed=(boolean)true, format=(string)UYVY, interlaced=(boolean)false, width=(int)640, height=(int)480, framerate=(fraction)1/1
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = event ******* (identity0:sink) E (type: segment (17934), GstEventSegment, segment=(GstSegment)"GstSegment, flags=(GstSegmentFlags)GST_SEGMENT_FLAG_NONE, rate=(double)1, applied-rate=(double)1, format=(GstFormat)GST_FORMAT_TIME, base=(guint64)0, offset=(guint64)0, start=(guint64)0, stop=(guint64)18446744073709551615, time=(guint64)0, position=(guint64)0, duration=(guint64)18446744073709551615;";) 0x21f5400
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = event ******* (identity0:sink) E (type: tag (20510), GstTagList-stream, taglist=(taglist)"taglist,\ container-format=(string)Multipart;";) 0x21f5460
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = chain ******* (identity0:sink) (51172 bytes, dts: none, pts:none, duration: none, offset: -1, offset_end: -1, flags: 00000040 discont ) 0x23b2ac0
Pipeline is PREROLLED ...
Setting pipeline to PLAYING ...
New clock: GstSystemClock
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = chain ******* (identity0:sink) (50988 bytes, dts: none, pts:none, duration: none, offset: -1, offset_end: -1, flags: 00000000 ) 0x21fa000
/GstPipeline:pipeline0/GstIdentity:identity0: last-message = chain ******* (identity0:sink) (50944 bytes, dts: none, pts:0:00:01.007713812, duration: none, offset: -1, offset_end: -1, flags: 00000000 ) 0x21fa330
^
Chandling interrupt.
Could someone add this patch (or a more elegant one) to the development trunk?
Thanks. Sorry for the long post.
Jose
Version: 1.2.4