]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Bug 2374: Support ICY / ICEcast / SHOUTcast streaming protocol.
authorAmos Jeffries <squid3@treenet.co.nz>
Mon, 2 Nov 2009 04:43:19 +0000 (17:43 +1300)
committerAmos Jeffries <squid3@treenet.co.nz>
Mon, 2 Nov 2009 04:43:19 +0000 (17:43 +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 0d480f8bb16ef015c749f11c20e21fe2d3acca70..cb350f48ea5ea39ad99016c9d568d8ddb51ae03a 100644 (file)
@@ -1585,6 +1585,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>
 
@@ -1788,9 +1791,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..b12e144e4ae5fe9c578f182ff115c998957f7738 100644 (file)
@@ -458,24 +458,33 @@ 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 {
 
-    // catch missing or negative status value (negative '-' is not a digit)
-    int pos = protoPrefix.psize();
+        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;
+        }
 
-    // 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 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;
 
-    // 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 8ed8a4686b5f03704fa0d02649fc048a478ec97c..e221d148ff426a3d75679017c54ab69503fbee5e 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 << ":");
     debugs(57, 9, "FORMAT=" << HttpStatusLineFormat );
     debugs(57, 9, "HTTP/" << sline->version.major << "." << sline->version.minor <<
@@ -91,17 +106,24 @@ 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 ee36d8ba93532f38520051e088e75dcdd3ae3c80..e06a933f559fa9ac9466057e80329e840d70008b 100644 (file)
@@ -152,6 +152,7 @@ typedef enum {
     PROTO_WHOIS,
     PROTO_INTERNAL,
     PROTO_HTTPS,
+    PROTO_ICY,
     PROTO_MAX
 } protocol_t;
 
index 209968217c34ab167c0fce355dfbedc5f9a863ce..248fa171468de7500fde203b6cbc102a55a25387 100644 (file)
@@ -681,7 +681,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);
index ea064276f4228a20b9a9f45e92663cc80f81676f..55a0472ad8235666a25065314f3d0bb8b80a8cb6 100644 (file)
@@ -99,6 +99,14 @@ 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;
+
     // empty status line
     input.append("\n\n", 2);
     hdr_len = headersEnd(input.content(),input.contentSize());
@@ -209,15 +217,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;
-
 }