From: Amos Jeffries Date: Mon, 2 Nov 2009 04:43:19 +0000 (+1300) Subject: Bug 2374: Support ICY / ICEcast / SHOUTcast streaming protocol. X-Git-Tag: SQUID_3_2_0_1~629 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=e77d7ef081a0455864c70ca65846180e6e15d165;p=thirdparty%2Fsquid.git Bug 2374: Support ICY / ICEcast / SHOUTcast streaming protocol. This makes Squid accept the ICY protocol replies and pass them on to the client. --- diff --git a/doc/release-notes/release-3.1.sgml b/doc/release-notes/release-3.1.sgml index 0d480f8bb1..cb350f48ea 100644 --- a/doc/release-notes/release-3.1.sgml +++ b/doc/release-notes/release-3.1.sgml @@ -1585,6 +1585,9 @@ This section gives an account of those changes in three categories: redirector_bypass

Replaced by url_rewrite_bypass + upgrade_http0.9 +

ICY protocol streaming support added natively. + zph_local

Replaced by qos_flows local-hit= @@ -1788,9 +1791,6 @@ This section gives an account of those changes in three categories: update_headers

Not yet ported from 2.7 - upgrade_http0.9 -

Not yet ported from 2.7 - zero_buffers

Not yet ported from 2.7 diff --git a/src/HttpReply.cc b/src/HttpReply.cc index ca7b537c22..b12e144e4a 100644 --- a/src/HttpReply.cc +++ b/src/HttpReply.cc @@ -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... diff --git a/src/HttpStatusLine.cc b/src/HttpStatusLine.cc index 8ed8a4686b..e221d148ff 100644 --- a/src/HttpStatusLine.cc +++ b/src/HttpStatusLine.cc @@ -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; diff --git a/src/HttpStatusLine.h b/src/HttpStatusLine.h index 73c06efb50..6d969fe0d0 100644 --- a/src/HttpStatusLine.h +++ b/src/HttpStatusLine.h @@ -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 */ diff --git a/src/URLScheme.cc b/src/URLScheme.cc index fcbc8395c8..5c3aba354f 100644 --- a/src/URLScheme.cc +++ b/src/URLScheme.cc @@ -52,6 +52,6 @@ const char *ProtocolStr[] = { "whois", "internal", "https", + "icy", "TOTAL" }; - diff --git a/src/client_side_reply.cc b/src/client_side_reply.cc index e9a1d0cad9..9f252cbfaf 100644 --- a/src/client_side_reply.cc +++ b/src/client_side_reply.cc @@ -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(); diff --git a/src/enums.h b/src/enums.h index ee36d8ba93..e06a933f55 100644 --- a/src/enums.h +++ b/src/enums.h @@ -152,6 +152,7 @@ typedef enum { PROTO_WHOIS, PROTO_INTERNAL, PROTO_HTTPS, + PROTO_ICY, PROTO_MAX } protocol_t; diff --git a/src/http.cc b/src/http.cc index 209968217c..248fa17146 100644 --- a/src/http.cc +++ b/src/http.cc @@ -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); diff --git a/src/tests/testHttpReply.cc b/src/tests/testHttpReply.cc index ea064276f4..55a0472ad8 100644 --- a/src/tests/testHttpReply.cc +++ b/src/tests/testHttpReply.cc @@ -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; - }