rtsp: Made read_line() support LWS.
authorPeter Kjellerstedt <pkj@axis.com>
Mon, 17 Aug 2009 16:29:17 +0000 (18:29 +0200)
committerPeter Kjellerstedt <pkj@axis.com>
Mon, 24 Aug 2009 11:19:45 +0000 (13:19 +0200)
Rewrote read_line() to support LWS (Line White Space), the method used by
RTSP (and HTTP) to break long lines. Also added support for \r and \n as
line endings (in addition to the official \r\n).

gst-libs/gst/rtsp/gstrtspconnection.c

index 8de8981..3970587 100644 (file)
@@ -180,6 +180,8 @@ struct _GstRTSPConnection
   GstPoll *fdset;
   gchar *ip;
 
+  gint read_ahead;
+
   gchar *initial_buffer;
   gsize initial_buffer_offset;
 
@@ -212,6 +214,13 @@ enum
   STATE_LAST
 };
 
+enum
+{
+  READ_AHEAD_EOH = -1,          /* end of headers */
+  READ_AHEAD_CRLF = -2,
+  READ_AHEAD_CRLFCR = -3
+};
+
 /* a structure for constructing RTSPMessages */
 typedef struct
 {
@@ -1144,6 +1153,12 @@ read_bytes (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
   return GST_RTSP_OK;
 }
 
+/* The code below tries to handle clients using \r, \n or \r\n to indicate the
+ * end of a line. It even does its best to handle clients which mix them (even
+ * though this is a really stupid idea (tm).) It also handles Line White Space
+ * (LWS), where a line end followed by whitespace is considered LWS. This is
+ * the method used in RTSP (and HTTP) to break long lines.
+ */
 static GstRTSPResult
 read_line (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
 {
@@ -1151,23 +1166,111 @@ read_line (GstRTSPConnection * conn, guint8 * buffer, guint * idx, guint size)
     guint8 c;
     gint r;
 
-    r = fill_bytes (conn, &c, 1);
-    if (G_UNLIKELY (r == 0)) {
-      return GST_RTSP_EEOF;
-    } else if (G_UNLIKELY (r < 0)) {
-      if (ERRNO_IS_EAGAIN)
-        return GST_RTSP_EINTR;
-      if (!ERRNO_IS_EINTR)
-        return GST_RTSP_ESYS;
+    if (conn->read_ahead == READ_AHEAD_EOH) {
+      /* the last call to read_line() already determined that we have reached
+       * the end of the headers, so convey that information now */
+      conn->read_ahead = 0;
+      break;
+    } else if (conn->read_ahead == READ_AHEAD_CRLF) {
+      /* the last call to read_line() left off after having read \r\n */
+      c = '\n';
+    } else if (conn->read_ahead == READ_AHEAD_CRLFCR) {
+      /* the last call to read_line() left off after having read \r\n\r */
+      c = '\r';
+    } else if (conn->read_ahead != 0) {
+      /* the last call to read_line() left us with a character to start with */
+      c = (guint8) conn->read_ahead;
+      conn->read_ahead = 0;
     } else {
-      if (c == '\n')            /* end on \n */
-        break;
-      if (c == '\r')            /* ignore \r */
+      /* read the next character */
+      r = fill_bytes (conn, &c, 1);
+      if (G_UNLIKELY (r == 0)) {
+        return GST_RTSP_EEOF;
+      } else if (G_UNLIKELY (r < 0)) {
+        if (ERRNO_IS_EAGAIN)
+          return GST_RTSP_EINTR;
+        if (!ERRNO_IS_EINTR)
+          return GST_RTSP_ESYS;
         continue;
+      }
+    }
+
+    /* special treatment of line endings */
+    if (c == '\r' || c == '\n') {
+      guint8 read_ahead;
+
+    retry:
+      /* need to read ahead one more character to know what to do... */
+      r = fill_bytes (conn, &read_ahead, 1);
+      if (G_UNLIKELY (r == 0)) {
+        return GST_RTSP_EEOF;
+      } else if (G_UNLIKELY (r < 0)) {
+        if (ERRNO_IS_EAGAIN) {
+          /* remember the original character we read and try again next time */
+          if (conn->read_ahead == 0)
+            conn->read_ahead = c;
+          return GST_RTSP_EINTR;
+        }
+        if (!ERRNO_IS_EINTR)
+          return GST_RTSP_ESYS;
+        goto retry;
+      }
 
-      if (G_LIKELY (*idx < size - 1))
-        buffer[(*idx)++] = c;
+      if (read_ahead == ' ' || read_ahead == '\t') {
+        if (conn->read_ahead == READ_AHEAD_CRLFCR) {
+          /* got \r\n\r followed by whitespace, treat it as a normal line
+           * followed by one starting with LWS */
+          conn->read_ahead = read_ahead;
+          break;
+        } else {
+          /* got LWS, change the line ending to a space and continue */
+          c = ' ';
+          conn->read_ahead = read_ahead;
+        }
+      } else if (conn->read_ahead == READ_AHEAD_CRLFCR) {
+        if (read_ahead == '\r' || read_ahead == '\n') {
+          /* got \r\n\r\r or \r\n\r\n, treat it as the end of the headers */
+          conn->read_ahead = READ_AHEAD_EOH;
+          break;
+        } else {
+          /* got \r\n\r followed by something else, this is not really
+           * supported since we have probably just eaten the first character
+           * of the body or the next message, so just ignore the second \r
+           * and live with it... */
+          conn->read_ahead = read_ahead;
+          break;
+        }
+      } else if (conn->read_ahead == READ_AHEAD_CRLF) {
+        if (read_ahead == '\r') {
+          /* got \r\n\r so far, need one more character... */
+          conn->read_ahead = READ_AHEAD_CRLFCR;
+          goto retry;
+        } else if (read_ahead == '\n') {
+          /* got \r\n\n, treat it as the end of the headers */
+          conn->read_ahead = READ_AHEAD_EOH;
+          break;
+        } else {
+          /* found the end of a line, keep read_ahead for the next line */
+          conn->read_ahead = read_ahead;
+          break;
+        }
+      } else if (c == read_ahead) {
+        /* got double \r or \n, treat it as the end of the headers */
+        conn->read_ahead = READ_AHEAD_EOH;
+        break;
+      } else if (c == '\r' && read_ahead == '\n') {
+        /* got \r\n so far, still need more to know what to do... */
+        conn->read_ahead = READ_AHEAD_CRLF;
+        goto retry;
+      } else {
+        /* found the end of a line, keep read_ahead for the next line */
+        conn->read_ahead = read_ahead;
+        break;
+      }
     }
+
+    if (G_LIKELY (*idx < size - 1))
+      buffer[(*idx)++] = c;
   }
   buffer[*idx] = '\0';
 
@@ -1776,10 +1879,6 @@ build_next (GstRTSPBuilder * builder, GstRTSPMessage * message,
           goto done;
 
         /* we have a regular response */
-        if (builder->buffer[0] == '\r') {
-          builder->buffer[0] = '\0';
-        }
-
         if (builder->buffer[0] == '\0') {
           gchar *hdrval;
 
@@ -2156,6 +2255,8 @@ gst_rtsp_connection_close (GstRTSPConnection * conn)
   g_free (conn->ip);
   conn->ip = NULL;
 
+  conn->read_ahead = 0;
+
   g_free (conn->initial_buffer);
   conn->initial_buffer = NULL;
   conn->initial_buffer_offset = 0;