From 4776cff74ca35dcfa1ebb9dae829a002f6afd2f1 Mon Sep 17 00:00:00 2001 From: Amos Jeffries Date: Wed, 4 Nov 2009 19:59:59 +1300 Subject: [PATCH] Bug 2374: Support ICY / ICEcast / SHOUTcast streaming protocol. This makes Squid accept the ICY protocol replies and pass them on to the client. --- doc/release-notes/release-3.1.sgml | 6 ++--- src/HttpReply.cc | 36 +++++++++++++++++----------- src/HttpStatusLine.cc | 38 +++++++++++++++++++++++------- src/HttpStatusLine.h | 24 ++++++++++++++----- src/URLScheme.cc | 2 +- src/client_side_reply.cc | 6 +++-- src/enums.h | 1 + src/http.cc | 4 ++-- src/tests/testHttpReply.cc | 22 ++++++++--------- 9 files changed, 91 insertions(+), 48 deletions(-) diff --git a/doc/release-notes/release-3.1.sgml b/doc/release-notes/release-3.1.sgml index 295a759fe7..7356858804 100644 --- a/doc/release-notes/release-3.1.sgml +++ b/doc/release-notes/release-3.1.sgml @@ -1597,6 +1597,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= @@ -1800,9 +1803,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..72b29313d3 100644 --- a/src/HttpReply.cc +++ b/src/HttpReply.cc @@ -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... diff --git a/src/HttpStatusLine.cc b/src/HttpStatusLine.cc index 1e5aeb738e..09ee37d272 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 << ":"); 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; 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 c3bfb0c3aa..473d91e2f9 100644 --- a/src/enums.h +++ b/src/enums.h @@ -254,6 +254,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 d1fbe8bd1b..acc9bed00b 100644 --- a/src/http.cc +++ b/src/http.cc @@ -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; } diff --git a/src/tests/testHttpReply.cc b/src/tests/testHttpReply.cc index ea064276f4..837e9e3149 100644 --- a/src/tests/testHttpReply.cc +++ b/src/tests/testHttpReply.cc @@ -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; - } -- 2.47.3