Index: gst/multipart/multipartdemux.c
===================================================================
RCS file: /cvs/gstreamer/gst-plugins-good/gst/multipart/multipartdemux.c,v
retrieving revision 1.15
diff -u -r1.15 multipartdemux.c
--- gst/multipart/multipartdemux.c	6 May 2006 00:05:20 -0000	1.15
+++ gst/multipart/multipartdemux.c	28 Jul 2006 10:53:37 -0000
@@ -1,4 +1,5 @@
 /* GStreamer
+ * Copyright (C) 2006 Sjoerd Simons <sjoerd@luon.net>
  * Copyright (C) 2004 Wim Taymans <wim@fluendo.com>
  *
  * gstmultipartdemux.c: multipart stream demuxer
@@ -46,8 +47,8 @@
  * </para>
  * <para>
  * the content in multipart files is separated with a boundary string that can be 
- * configured specifically with the "boundary" property or can be autodetected by
- * setting the "autoscan" property to TRUE.
+ * configured specifically with the "boundary" property otherwise it will be 
+ * autodetected. 
  * </para>
  * </refsect2>
  */
@@ -57,6 +58,7 @@
 #endif
 
 #include <gst/gst.h>
+#include <gst/base/gstadapter.h>
 
 #include <string.h>
 
@@ -69,7 +71,9 @@
 typedef struct _GstMultipartDemux GstMultipartDemux;
 typedef struct _GstMultipartDemuxClass GstMultipartDemuxClass;
 
-#define MAX_LINE_LEN 500
+#define MULTIPART_NEED_MORE_DATA -1
+#define MULTIPART_DATA_ERROR     -2
+#define MULTIPART_DATA_EOS       -3
 
 /* all information needed for one multipart stream */
 typedef struct
@@ -77,11 +81,6 @@
   GstPad *pad;                  /* reference for this pad is held by element we belong to */
 
   gchar *mime;
-
-  guint64 offset;               /* end offset of last buffer */
-  guint64 known_offset;         /* last known offset */
-
-  guint flags;
 }
 GstMultipartPad;
 
@@ -100,17 +99,17 @@
   GSList *srcpads;
   gint numpads;
 
-  gchar *parsing_mime;
-  gchar *buffer;
-  gint maxlen;
-  gint bufsize;
-  gint scanpos;
-  gint lastpos;
+  GstAdapter *adapter;
+
+  /* Header information of the current frame */
+  gboolean header_completed;
+  gchar *boundary;
+  guint boundary_len;
+  gchar *mime_type;
+  gint content_length;
 
-  gchar *prefix;
-  gint prefixLen;
-  gboolean first_frame;
-  gboolean autoscan;
+  /* Index inside the current data when manually looking for the boundary */
+  gint scanpos;
 };
 
 struct _GstMultipartDemuxClass
@@ -126,7 +125,7 @@
 GST_ELEMENT_DETAILS ("Multipart demuxer",
     "Codec/Demuxer",
     "demux multipart streams",
-    "Wim Taymans <wim@fluendo.com>");
+    "Wim Taymans <wim@fluendo.com>, Sjoerd Simons <sjoerd@luon.net>");
 
 
 /* signals and args */
@@ -136,13 +135,11 @@
   LAST_SIGNAL
 };
 
-#define DEFAULT_BOUNDARY	"ThisRandomString"
-#define DEFAULT_AUTOSCAN 	FALSE
+#define DEFAULT_BOUNDARY	NULL
 enum
 {
   PROP_0,
-  PROP_BOUNDARY,
-  PROP_AUTOSCAN
+  PROP_BOUNDARY
       /* FILL ME */
 };
 
@@ -171,6 +168,7 @@
     GValue * value, GParamSpec * pspec);
 
 static void gst_multipart_demux_finalize (GObject * object);
+static void gst_multipart_demux_dispose (GObject * object);
 
 
 GST_BOILERPLATE (GstMultipartDemux, gst_multipart_demux, GstElement,
@@ -196,11 +194,6 @@
           "The boundary string separating data", DEFAULT_BOUNDARY,
           G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
 
-  g_object_class_install_property (gobject_class, PROP_AUTOSCAN,
-      g_param_spec_boolean ("autoscan", "autoscan",
-          "Try to autofind the prefix", DEFAULT_AUTOSCAN,
-          G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
-
 }
 
 static void
@@ -211,6 +204,7 @@
 
   gstelement_class->change_state = gst_multipart_demux_change_state;
   gobject_class->finalize = gst_multipart_demux_finalize;
+  gobject_class->dispose = gst_multipart_demux_dispose;
 }
 
 static void
@@ -225,19 +219,36 @@
   gst_pad_set_chain_function (multipart->sinkpad,
       GST_DEBUG_FUNCPTR (gst_multipart_demux_chain));
 
-  multipart->maxlen = 4096;
+  multipart->adapter = gst_adapter_new();
+  multipart->boundary = NULL;
+  multipart->mime_type = NULL;
+  multipart->content_length = -1;
+  multipart->header_completed = FALSE;
+  multipart->scanpos = 0;
 }
 
+
 static void
 gst_multipart_demux_finalize (GObject * object)
 {
   GstMultipartDemux *demux = GST_MULTIPART_DEMUX (object);
 
-  g_free (demux->prefix);
+  g_free (demux->boundary);
+  g_free (demux->mime_type);
 
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
+static void
+gst_multipart_demux_dispose (GObject * object)
+{
+  GstMultipartDemux *demux = GST_MULTIPART_DEMUX (object);
+
+  g_object_unref (demux->adapter);
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
 static GstMultipartPad *
 gst_multipart_find_pad_by_mime (GstMultipartDemux * demux, gchar * mime,
     gboolean * created)
@@ -292,162 +303,214 @@
   }
 }
 
-static GstFlowReturn
-gst_multipart_demux_chain (GstPad * pad, GstBuffer * buf)
-{
-  GstMultipartDemux *multipart;
-  gint size, matchpos;
-  guchar *data;
-  GstFlowReturn ret = GST_FLOW_OK;
+static gboolean
+get_line_end(const guint8 *data, const guint8 *dataend, guint8 **end, guint8 **next) {
+  guint8 *x;
+  gboolean foundr = FALSE;
+  for (x = (guint8 *)data; x < dataend; x++) {
+    if (*x == '\r') {
+      foundr = TRUE;
+    } else if (*x == '\n') {
+      *end = x - (foundr ?  1 : 0);
+      *next = x + 1;
+      return TRUE;
+    }
+  }
+  return FALSE;
+}
 
-  multipart = GST_MULTIPART_DEMUX (gst_pad_get_parent (pad));
+static gint
+multipart_parse_header(GstMultipartDemux *multipart) {
+  const guint8 *data;
+  const guint8 *dataend;
+  gchar *boundary;
+  int boundary_len;
+  int datalen;
+  guint8 *pos;
+  guint8 *end, *next;
+
+  datalen = gst_adapter_available(multipart->adapter);
+  data = gst_adapter_peek(multipart->adapter, datalen);
+  dataend = data + datalen;
+
+  /* First the boundary */
+  if (!get_line_end(data, dataend, &end, &next))
+    return MULTIPART_NEED_MORE_DATA;
+
+
+  /* Ignore the leading -- */
+  boundary_len = end - data - 2;
+  boundary = (gchar *)data + 2;
+  if (boundary_len  < 1) {
+    GST_DEBUG_OBJECT(multipart, "No boundary available");
+    goto wrong_header;
+  }
+  if (G_UNLIKELY(multipart->boundary == NULL)) {
+    /* First time we see the boundary, copy it */
+    multipart->boundary = g_strndup(boundary, boundary_len);
+    multipart->boundary_len = boundary_len;
+  } else if (G_UNLIKELY(boundary_len != multipart->boundary_len)) {
+    /* Something odd is going on, either the boundary indicated EOS or it's
+     * invalid */
+    if (G_UNLIKELY(
+        boundary_len == multipart->boundary_len + 2 && 
+        !strncmp(boundary, multipart->boundary, multipart->boundary_len) &&
+        !strncmp(boundary + multipart->boundary_len, "--", 2))) {
+      return MULTIPART_DATA_EOS;
+    }
+    GST_DEBUG_OBJECT(multipart, 
+    "Boundary length doesn't match detected boundary (%d <> %d",
+    boundary_len, multipart->boundary_len);
+    goto wrong_header;
+  } else if (G_UNLIKELY(strncmp(boundary, multipart->boundary, boundary_len))) {
+    GST_DEBUG_OBJECT(multipart, "Boundary doesn't match previous boundary");
+    goto wrong_header;
+  }
 
-  data = GST_BUFFER_DATA (buf);
-  size = GST_BUFFER_SIZE (buf);
 
-  /* first make sure our buffer is long enough */
-  if (multipart->bufsize + size > multipart->maxlen) {
-    gint newsize = (multipart->bufsize + size) * 2;
-
-    multipart->buffer = g_realloc (multipart->buffer, newsize);
-    multipart->maxlen = newsize;
-  }
-  /* copy bytes into the buffer */
-  memcpy (multipart->buffer + multipart->bufsize, data, size);
-  multipart->bufsize += size;
-
-  if (multipart->first_frame && multipart->autoscan) {
-    /* find the prefix if this is the first buffer */
-    /* the prefix is like --prefix\r\n */
-    size_t i, start;
-
-    i = 0;
-    start = -1;
-
-    while ((i + 1) < multipart->bufsize) {
-
-      if (-1 == start) {
-        if ((multipart->buffer[i] == '-') && (multipart->buffer[i + 1] == '-')) {
-          start = i + 2;        /* discart -- */
-        }
-      } else {
-        /* look for \r\n or \n\n */
-        if ((multipart->buffer[i] == '\n') ||
-            ((multipart->buffer[i] == '\r') &&
-                (multipart->buffer[i + 1] == '\n'))) {
-
-          /* found first \r\n, the prefix is from 0 to i */
-
-          g_free (multipart->prefix);
-          multipart->prefix =
-              g_strndup (multipart->buffer + start, (i - start));
-          multipart->prefixLen = strlen (multipart->prefix);
-          GST_DEBUG_OBJECT (multipart,
-              "set prefix to [%s]\n", multipart->prefix);
-
-          multipart->first_frame = FALSE;
-          break;
-        }
-      }
+  pos = next;
+  while (get_line_end(pos, dataend, &end, &next)) {
+    guint len = end - pos;
+    if (len == 0) {
+      /* empty line, data starts behind us */
+      GST_DEBUG_OBJECT(multipart, 
+        "Parsed the header - boundary: %s, mime-type: %s, content-length: %d",
+        multipart->boundary, multipart->mime_type, multipart->content_length);
+      return next - data;
+    }
 
-      i++;
+    if (len >= 14 && !g_ascii_strncasecmp("content-type:", (gchar *)pos, 13)) {
+      g_free(multipart->mime_type);
+      multipart->mime_type = g_strndup((gchar *)pos + 14, len - 14);
+    } else if (len >= 15 && 
+               !g_ascii_strncasecmp("content-length:", (gchar *)pos, 15)) {
+      multipart->content_length = g_ascii_strtoull((gchar *)pos + 15, NULL, 10);
+    }
+    pos = next;
+  }
+  GST_DEBUG_OBJECT(multipart, "Need more data for the header");
+  return MULTIPART_NEED_MORE_DATA;
+
+wrong_header:
+  GST_ELEMENT_ERROR(multipart, STREAM, DEMUX, (NULL), 
+                       ("Boundary not found in the multipart header"));
+  return MULTIPART_DATA_ERROR;
+}
+
+static gint
+multipart_find_boundary(GstMultipartDemux *multipart, gint *datalen) {
+  /* Adaptor is positioned at the start of the data */
+  const guint8 *data, *pos;
+  const guint8 *dataend;
+  gint len;
+
+  if (multipart->content_length >= 0) {
+    /* fast path, known content length :) */
+    len = multipart->content_length;
+    if (gst_adapter_available(multipart->adapter) >= len + 2) {
+      *datalen = len;
+      data = gst_adapter_peek(multipart->adapter, len + 1);
+
+      /* If data[len] contains \r then assume a newline is \r\n */
+      if (data[len] == '\r') len += 2;
+      else if (data[len] == '\n') len += 1;
+      /* Don't check if boundary is actually there, but let the header parsing 
+       * bail out if it isn't */
+      return len; 
+    } else {
+      /* need more data */
+      return MULTIPART_NEED_MORE_DATA;
     }
+  }
 
+  len = gst_adapter_available(multipart->adapter);
+  if (len == 0) 
+    return MULTIPART_NEED_MORE_DATA;
+  data = gst_adapter_peek(multipart->adapter, len);
+  dataend = data + len;
+
+  for (pos = data + multipart->scanpos; 
+        pos <= dataend - multipart->boundary_len - 2; pos++) {
+    if (*pos == '-' && pos[1] == '-' && 
+         !strncmp((gchar *)pos + 2, 
+                  multipart->boundary, multipart->boundary_len)) {
+      /* Found the boundary! Check if there was a newline before the boundary*/
+      len = pos - data;
+      if (pos - 2 >  data && pos[-2] == '\r') len -= 2;
+      else if (pos - 1 > data && pos[-1] == '\n') len -= 1;
+      *datalen = len;
+      
+      multipart->scanpos = 0;
+      return pos - data;
+    }
   }
+  multipart->scanpos = pos - data;
+  return MULTIPART_NEED_MORE_DATA; 
+}
+
+static GstFlowReturn
+gst_multipart_demux_chain (GstPad * pad, GstBuffer * buf)
+{
+  GstMultipartDemux *multipart;
+  GstAdapter *adapter;
+  gint size = 1;
 
+  multipart = GST_MULTIPART_DEMUX (gst_pad_get_parent (pad));
+  adapter = multipart->adapter;
 
+  if (GST_BUFFER_FLAG_IS_SET(buf, GST_BUFFER_FLAG_DISCONT)) {
+    gst_adapter_clear(adapter);
+  }
+  gst_adapter_push(adapter, buf);
 
-  /* find \n */
-  while (multipart->scanpos < multipart->bufsize) {
-    if (multipart->buffer[multipart->scanpos] == '\n') {
-      break;
+  while (gst_adapter_available(adapter) > 0) {
+    GstMultipartPad *srcpad;
+    GstBuffer *outbuf;
+    gboolean created; 
+    gint datalen;
+
+    if (G_UNLIKELY(!multipart->header_completed)) {
+      if((size = multipart_parse_header(multipart)) < 0) {
+        goto nodata;
+      } else { 
+        gst_adapter_flush(adapter, size);
+        multipart->header_completed = TRUE;
+      }
     }
-    multipart->scanpos++;
-  }
+    if ((size = multipart_find_boundary(multipart, &datalen)) < 0) {
+      goto nodata;
+    }
+    srcpad =
+      gst_multipart_find_pad_by_mime (multipart,
+              multipart->mime_type, &created);
+    outbuf = gst_adapter_take_buffer(adapter, datalen);
+    gst_adapter_flush(adapter, size - datalen);
+
+    /* Invalidate header info */
+    multipart->header_completed = FALSE;
+    multipart->content_length = -1;
+
+    gst_buffer_set_caps(outbuf, GST_PAD_CAPS(srcpad->pad));
+    if (created) {
+      GstEvent *event;
 
-  /* then scan for the boundary */
-  for (matchpos = 0;
-      multipart->scanpos + multipart->prefixLen + MAX_LINE_LEN - matchpos <
-      multipart->bufsize; multipart->scanpos++) {
-    if (multipart->buffer[multipart->scanpos] == multipart->prefix[matchpos]) {
-
-      matchpos++;
-      if (matchpos == multipart->prefixLen) {
-        int datalen;
-        int i, start;
-        gchar *mime_type;
-
-        multipart->scanpos++;
-
-        start = multipart->scanpos;
-        /* find \n */
-        for (i = 0; i < MAX_LINE_LEN; i++) {
-          if (multipart->buffer[multipart->scanpos] == '\n')
-            break;
-          multipart->scanpos++;
-          matchpos++;
-        }
-        mime_type =
-            g_strndup (multipart->buffer + start, multipart->scanpos - start);
-        multipart->scanpos += 2;
-        matchpos += 3;
-
-        datalen = multipart->scanpos - matchpos;
-        if (datalen > 0 && multipart->parsing_mime) {
-          GstBuffer *outbuf;
-          GstMultipartPad *srcpad;
-          gboolean created = FALSE;
-
-          srcpad =
-              gst_multipart_find_pad_by_mime (multipart,
-              multipart->parsing_mime, &created);
-          if (srcpad != NULL) {
-            ret =
-                gst_pad_alloc_buffer_and_set_caps (srcpad->pad,
-                GST_BUFFER_OFFSET_NONE, datalen, GST_PAD_CAPS (srcpad->pad),
-                &outbuf);
-            if (ret != GST_FLOW_OK) {
-              GST_WARNING_OBJECT (multipart, "failed allocating a %d bytes "
-                  "buffer", datalen);
-            } else {
-              memcpy (GST_BUFFER_DATA (outbuf), multipart->buffer, datalen);
-              if (created) {
-                GstEvent *event;
-
-                /* Push new segment */
-                event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME,
-                    0, -1, 0);
-                if (GST_IS_EVENT (event)) {
-                  gst_pad_push_event (srcpad->pad, event);
-                }
-                GST_BUFFER_TIMESTAMP (outbuf) = 0;
-              } else {
-                GST_BUFFER_TIMESTAMP (outbuf) = -1;
-              }
-              ret = gst_pad_push (srcpad->pad, outbuf);
-            }
-          }
-        }
-        /* move rest downward */
-        multipart->bufsize -= multipart->scanpos;
-        memmove (multipart->buffer, multipart->buffer + multipart->scanpos,
-            multipart->bufsize);
-
-        g_free (multipart->parsing_mime);
-        multipart->parsing_mime = mime_type;
-        multipart->scanpos = 0;
+      /* Push new segment */
+      event = gst_event_new_new_segment (FALSE, 1.0, GST_FORMAT_TIME, 0, -1, 0);
+      if (GST_IS_EVENT (event)) {
+        gst_pad_push_event (srcpad->pad, event);
       }
+      GST_BUFFER_TIMESTAMP (outbuf) = 0;
     } else {
-      matchpos = 0;
+      GST_BUFFER_TIMESTAMP (outbuf) = -1;
     }
-    if (ret != GST_FLOW_OK)
-      break;
+    gst_pad_push (srcpad->pad, outbuf);
   }
 
-  gst_buffer_unref (buf);
+nodata:
   gst_object_unref (multipart);
-
-  return ret;
+  if (G_UNLIKELY(size == MULTIPART_DATA_ERROR)) return GST_FLOW_ERROR;
+  if (G_UNLIKELY(size == MULTIPART_DATA_EOS)) return GST_FLOW_UNEXPECTED;
+  return  GST_FLOW_OK;
 }
 
 static GstStateChangeReturn
@@ -459,23 +522,6 @@
 
   multipart = GST_MULTIPART_DEMUX (element);
 
-  switch (transition) {
-    case GST_STATE_CHANGE_NULL_TO_READY:
-      break;
-    case GST_STATE_CHANGE_READY_TO_PAUSED:
-      multipart->buffer = g_malloc (multipart->maxlen);
-      multipart->first_frame = TRUE;
-      multipart->parsing_mime = NULL;
-      multipart->numpads = 0;
-      multipart->scanpos = 0;
-      multipart->lastpos = 0;
-      break;
-    case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
-      break;
-    default:
-      break;
-  }
-
   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
   if (ret == GST_STATE_CHANGE_FAILURE)
     return ret;
@@ -484,10 +530,12 @@
     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
-      g_free (multipart->parsing_mime);
-      multipart->parsing_mime = NULL;
-      g_free (multipart->buffer);
-      multipart->buffer = NULL;
+      multipart->header_completed = FALSE;
+      g_free (multipart->boundary);
+      multipart->boundary = NULL;
+      g_free (multipart->mime_type);
+      multipart->mime_type = NULL;
+      gst_adapter_clear(multipart->adapter);
       break;
     case GST_STATE_CHANGE_READY_TO_NULL:
       break;
@@ -509,12 +557,12 @@
 
   switch (prop_id) {
     case PROP_BOUNDARY:
-      g_free (filter->prefix);
-      filter->prefix = g_value_dup_string (value);
-      filter->prefixLen = strlen (filter->prefix);
-      break;
-    case PROP_AUTOSCAN:
-      filter->autoscan = g_value_get_boolean (value);
+      /* Not really that usefull anymore as we can reliably autoscan */
+      g_free (filter->boundary);
+      filter->boundary = g_value_dup_string (value);
+      if (filter->boundary != NULL) { 
+        filter->boundary_len = strlen (filter->boundary);
+      }
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
@@ -533,10 +581,7 @@
 
   switch (prop_id) {
     case PROP_BOUNDARY:
-      g_value_set_string (value, filter->prefix);
-      break;
-    case PROP_AUTOSCAN:
-      g_value_set_boolean (value, filter->autoscan);
+      g_value_set_string (value, filter->boundary);
       break;
     default:
       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
