]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Bug 2374: Support ICY / ICEcast / SHOUTcast streaming protocol.
authorAmos Jeffries <squid3@treenet.co.nz>
Wed, 4 Nov 2009 06:59:59 +0000 (19:59 +1300)
committerAmos Jeffries <squid3@treenet.co.nz>
Wed, 4 Nov 2009 06:59:59 +0000 (19:59 +1300)
This makes Squid accept the ICY protocol replies and pass them on to the client.

doc/release-notes/release-3.1.sgml
src/HttpReply.cc
src/HttpStatusLine.cc
src/HttpStatusLine.h
src/URLScheme.cc
src/client_side_reply.cc
src/enums.h
src/http.cc
src/tests/testHttpReply.cc

index 295a759fe760a829cca2dac4a8dfcdd454ef419f..73568588047badcc7f3f1e889960dc5c19abd7d5 100644 (file)
@@ -1597,6 +1597,9 @@ This section gives an account of those changes in three categories:
        <tag>redirector_bypass</tag>
        <p>Replaced by <em>url_rewrite_bypass</em>
 
+       <tag>upgrade_http0.9</tag>
+       <p>ICY protocol streaming support added natively.
+
        <tag>zph_local</tag>
        <p>Replaced by <em>qos_flows local-hit=</em>
 
@@ -1800,9 +1803,6 @@ This section gives an account of those changes in three categories:
        <tag>update_headers</tag>
        <p>Not yet ported from 2.7
 
-       <tag>upgrade_http0.9</tag>
-       <p>Not yet ported from 2.7
-
        <tag>zero_buffers</tag>
        <p>Not yet ported from 2.7
 
index ca7b537c22834863e4c85db2210ab280c74cc3f8..72b29313d3a4f555b08f4d0f29a56250790dd81a 100644 (file)
@@ -458,24 +458,32 @@ HttpReply::sanityCheckStartLine(MemBuf *buf, const size_t hdr_len, http_status *
         return false;
     }
 
+    int pos;
     // catch missing or mismatched protocol identifier
-    if (protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
-        debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix << ") in '" << buf->content() << "'");
-        *error = HTTP_INVALID_HEADER;
-        return false;
-    }
+    // allow special-case for ICY protocol (non-HTTP identifier) in response to faked HTTP request.
+    if (strncmp(buf->content(), "ICY", 3) == 0) {
+        protoPrefix = "ICY";
+        pos = protoPrefix.psize();
+    } else {
+
+        if (protoPrefix.cmp(buf->content(), protoPrefix.size()) != 0) {
+            debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol prefix (" << protoPrefix << ") in '" << buf->content() << "'");
+            *error = HTTP_INVALID_HEADER;
+            return false;
+        }
 
-    // catch missing or negative status value (negative '-' is not a digit)
-    int pos = protoPrefix.psize();
+        // catch missing or negative status value (negative '-' is not a digit)
+        pos = protoPrefix.psize();
 
-    // skip arbitrary number of digits and a dot in the verion portion
-    while ( pos <= buf->contentSize() && (*(buf->content()+pos) == '.' || xisdigit(*(buf->content()+pos)) ) ) ++pos;
+        // skip arbitrary number of digits and a dot in the verion portion
+        while ( pos <= buf->contentSize() && (*(buf->content()+pos) == '.' || xisdigit(*(buf->content()+pos)) ) ) ++pos;
 
-    // catch missing version info
-    if (pos == protoPrefix.psize()) {
-        debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix << "/1.0) in '" << buf->content() << "'");
-        *error = HTTP_INVALID_HEADER;
-        return false;
+        // catch missing version info
+        if (pos == protoPrefix.psize()) {
+            debugs(58, 3, "HttpReply::sanityCheckStartLine: missing protocol version numbers (ie. " << protoPrefix << "/1.0) in '" << buf->content() << "'");
+            *error = HTTP_INVALID_HEADER;
+            return false;
+        }
     }
 
     // skip arbitrary number of spaces...
index 1e5aeb738e1c964e985370dbfe83cfccac5ba541..09ee37d272de1b6ba7c40023d3f8e5ad9f7b4c77 100644 (file)
@@ -39,6 +39,7 @@
 /* local constants */
 /* AYJ: see bug 2469 - RFC2616 confirms stating 'SP characters' plural! */
 const char *HttpStatusLineFormat = "HTTP/%d.%d %3d %s\r\n";
+const char *IcyStatusLineFormat = "ICY %3d %s\r\n";
 
 void
 httpStatusLineInit(HttpStatusLine * sline)
@@ -59,17 +60,31 @@ void
 httpStatusLineSet(HttpStatusLine * sline, HttpVersion version, http_status status, const char *reason)
 {
     assert(sline);
+    sline->protocol = PROTO_HTTP;
     sline->version = version;
     sline->status = status;
     /* Note: no xstrdup for 'reason', assumes constant 'reasons' */
     sline->reason = reason;
 }
 
-/** write HTTP version and status structures into a Packer buffer for output as HTTP status line. */
+/**
+ * Write HTTP version and status structures into a Packer buffer for output as HTTP status line.
+ * Special exemption made for ICY response status lines.
+ */
 void
 httpStatusLinePackInto(const HttpStatusLine * sline, Packer * p)
 {
     assert(sline && p);
+
+    /* handle ICY protocol status line specially. Pass on the bad format. */
+    if (sline->protocol == PROTO_ICY) {
+        debugs(57, 9, "packing sline " << sline << " using " << p << ":");
+        debugs(57, 9, "FORMAT=" << IcyStatusLineFormat );
+        debugs(57, 9, "ICY " << sline->status << " " << (sline->reason ? sline->reason : httpStatusString(sline->status)) );
+        packerPrintf(p, IcyStatusLineFormat, sline->status, httpStatusLineReason(sline));
+        return;
+    }
+
     debugs(57, 9, "packing sline " << sline << " using " << p << ":");
     debug(57, 9) (HttpStatusLineFormat, sline->version.major,
                   sline->version.minor, sline->status,
@@ -91,17 +106,22 @@ httpStatusLineParse(HttpStatusLine * sline, const String &protoPrefix, const cha
     // XXX: HttpMsg::parse() has a similar check but is using
     // casesensitive comparison (which is required by HTTP errata?)
 
-    if (protoPrefix.caseCmp(start, protoPrefix.size()) != 0)
-        return 0;
+    if (protoPrefix.cmp("ICY", 3) == 0) {
+        debugs(57, 3, "httpStatusLineParse: Invalid HTTP identifier. Detected ICY protocol istead.");
+        sline->protocol = PROTO_ICY;
+        start += protoPrefix.size();
+    } else if (protoPrefix.caseCmp(start, protoPrefix.size()) == 0) {
 
-    start += protoPrefix.size();
+        start += protoPrefix.size();
 
-    if (!xisdigit(*start))
-        return 0;
+        if (!xisdigit(*start))
+            return 0;
 
-    if (sscanf(start, "%d.%d", &sline->version.major, &sline->version.minor) != 2) {
-        debugs(57, 7, "httpStatusLineParse: Invalid HTTP identifier.");
-    }
+        if (sscanf(start, "%d.%d", &sline->version.major, &sline->version.minor) != 2) {
+            debugs(57, 7, "httpStatusLineParse: Invalid HTTP identifier.");
+        }
+    } else
+        return 0;
 
     if (!(start = strchr(start, ' ')))
         return 0;
index 73c06efb5059c803bbbc1ebfc2e2ff1b340bb11f..6d969fe0d04be31c81f606c161c0a5f285fe0291 100644 (file)
@@ -39,20 +39,32 @@ class String;
 /* for SQUIDCEXTERN */
 #include "config.h"
 
-/* for http_status */
+/* for http_status and protocol_t */
 #include "enums.h"
 
-/* for class variables */
 #include "HttpVersion.h"
+#include "SquidString.h"
 
+/**
+ * Holds the values parsed from an HTTP reply status line.
+ *
+ * For example: HTTP/1.1 200 Okay
+ */
 class HttpStatusLine
 {
-
 public:
     /* public, read only */
-    HttpVersion version;
-    const char *reason;                /**< points to a _constant_ string (default or supplied), never free()d */
-    http_status status;
+
+    /**
+     * By rights protocol name should be a constant "HTTP", with no need for this field to exist.
+     * However there are protocols which violate HTTP by sending their wn custom formats
+     * back with other protocol names (ICY streaming format being the current major problem)
+     */
+    protocol_t protocol;
+
+    HttpVersion version;     ///< breakdown of protocol version labels: 0.9 1.0 1.1
+    http_status status;      ///< status code. ie 200 404
+    const char *reason;             ///< points to a _constant_ string (default or supplied), never free()d */
 };
 
 /* init/clean */
index fcbc8395c8eedd8bc4cff543e88a1e9942e330ef..5c3aba354f85f16ce7543b3ec15b9965868774a2 100644 (file)
@@ -52,6 +52,6 @@ const char *ProtocolStr[] = {
     "whois",
     "internal",
     "https",
+    "icy",
     "TOTAL"
 };
-
index e9a1d0cad982360fa32ae04a357b3f733cc53702..9f252cbfaf3777555fa4335bd7575add27e82295 100644 (file)
@@ -1440,8 +1440,10 @@ clientReplyContext::cloneReply()
 
     reply = HTTPMSGLOCK(rep);
 
-    /* enforce 1.0 reply version */
-    reply->sline.version = HttpVersion(1,0);
+    if (reply->sline.protocol == PROTO_HTTP) {
+        /* enforce 1.0 reply version (but only on real HTTP traffic) */
+        reply->sline.version = HttpVersion(1,0);
+    }
 
     /* do header conversions */
     buildReplyHeader();
index c3bfb0c3aa600a88f54e36347a4f621768063507..473d91e2f94c3a0d17529d04d889bacbeca89ee8 100644 (file)
@@ -254,6 +254,7 @@ typedef enum {
     PROTO_WHOIS,
     PROTO_INTERNAL,
     PROTO_HTTPS,
+    PROTO_ICY,
     PROTO_MAX
 } protocol_t;
 
index d1fbe8bd1bf1c68e0275ec961fdba7de2ce355bb..acc9bed00b1e53af83748defec28420d5ed4f3b0 100644 (file)
@@ -678,7 +678,7 @@ HttpStateData::processReplyHeader()
     HttpReply *newrep = new HttpReply;
     const bool parsed = newrep->parse(readBuf, eof, &error);
 
-    if (!parsed && readBuf->contentSize() > 5 && strncmp(readBuf->content(), "HTTP/", 5) != 0) {
+    if (!parsed && readBuf->contentSize() > 5 && strncmp(readBuf->content(), "HTTP/", 5) != 0 && strncmp(readBuf->content(), "ICY", 3) != 0) {
         MemBuf *mb;
         HttpReply *tmprep = new HttpReply;
         tmprep->sline.version = HttpVersion(1, 0);
@@ -715,7 +715,7 @@ HttpStateData::processReplyHeader()
     }
 
     flags.chunked = 0;
-    if (newrep->header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ',')) {
+    if (newrep->sline.protocol == PROTO_HTTP && newrep->header.hasListMember(HDR_TRANSFER_ENCODING, "chunked", ',')) {
         flags.chunked = 1;
         httpChunkDecoder = new ChunkedCodingParser;
     }
index ea064276f4228a20b9a9f45e92663cc80f81676f..837e9e31491930b95056093ff4f7204a876252de 100644 (file)
@@ -99,6 +99,17 @@ testHttpReply::testSanityCheckFirstLine()
     error = HTTP_STATUS_NONE;
 #endif
 
+    // valid ICY protocol status line
+    input.append("ICY 200 Okay\n\n", 18);
+    hdr_len = headersEnd(input.content(),input.contentSize());
+    CPPUNIT_ASSERT( engine.sanityCheckStartLine(&input, hdr_len, &error) );
+    CPPUNIT_ASSERT_EQUAL(error, HTTP_STATUS_NONE);
+    input.reset();
+    error = HTTP_STATUS_NONE;
+    /* NP: the engine saves details about the protocol. even when being reset :( */
+    engine.protoPrefix="HTTP/";
+    engine.reset();
+
     // empty status line
     input.append("\n\n", 2);
     hdr_len = headersEnd(input.content(),input.contentSize());
@@ -209,15 +220,4 @@ testHttpReply::testSanityCheckFirstLine()
     CPPUNIT_ASSERT_EQUAL(error, HTTP_INVALID_HEADER);
     input.reset();
     error = HTTP_STATUS_NONE;
-
-    // status line with non-HTTP protocol
-    input.append("ICY/1.1 200 Okay\n\n", 18); /* real case seen */
-    hdr_len = headersEnd(input.content(),input.contentSize());
-    /* NP: for nw ICY is handled as a pass-thru */
-    /*     Squid-3 will ignore it (and mangle the headers as per HTTP). */
-    CPPUNIT_ASSERT(!engine.sanityCheckStartLine(&input, hdr_len, &error) );
-    CPPUNIT_ASSERT_EQUAL(error, HTTP_INVALID_HEADER);
-    input.reset();
-    error = HTTP_STATUS_NONE;
-
 }