=== modified file 'src/client_side.cc'
--- src/client_side.cc	2013-07-26 11:26:04 +0000
+++ src/client_side.cc	2013-08-02 09:49:24 +0000
@@ -81,6 +81,7 @@
 #include "squid.h"
 #include "acl/FilledChecklist.h"
 #include "anyp/PortCfg.h"
+#include "base/StringArea.h"
 #include "base/Subscription.h"
 #include "base/TextException.h"
 #include "CachePeer.h"
@@ -2301,6 +2302,29 @@
         return parseHttpRequestAbort(csd, "error:method-not-allowed");
     }
 
+    /* deny "PRI * HTTP/2.0" via non-intercepted ports */
+    bool isHttp20Magic = (*method_p == Http::METHOD_PRI &&
+                          *http_ver == Http::ProtocolVersion(2,0) &&
+                          memcmp(&hp->buf[hp->hdr_end+1], "SM\r\n\r\n", 6) == 0);
+    if (isHttp20Magic) {
+        /* NOTE: the 'SM\r\n\r\n' string is part of the HTTP/2.0 magic connection header
+         * even though to the HTTP/1 parser it appears to be a payload.
+         * ensure it gets removed before we start handling the real HTTP/2 frames.
+         */
+        memmove(const_cast<char*>(&hp->buf[hp->hdr_end+1]), &hp->buf[hp->hdr_end + 6 +1], hp->bufsiz - hp->hdr_end - 6);
+        hp->bufsiz -= 6;
+
+        // we only support tunneling intercepted HTTP/2.0 traffic for now
+        if (csd->port && !csd->port->flags.isIntercepted()) {
+            hp->request_parse_status = Http::scMethodNotAllowed;
+            return parseHttpRequestAbort(csd, "error:http-2.0-not-supported");
+        }
+    } else if (*method_p == Http::METHOD_PRI || *http_ver == Http::ProtocolVersion(2,0)) {
+        // other uses of PRI method or HTTP/2.0 version moniker are not supported
+        hp->request_parse_status = Http::scMethodNotAllowed;
+        return parseHttpRequestAbort(csd, "error:method-not-allowed");
+    }
+
     if (*method_p == Http::METHOD_NONE) {
         /* XXX need a way to say "this many character length string" */
         debugs(33, DBG_IMPORTANT, "clientParseRequestMethod: Unsupported method in request '" << hp->buf << "'");
@@ -2374,7 +2398,10 @@
      */
     if (csd->transparent()) {
         /* intercept or transparent mode, properly working with no failures */
-        prepareTransparentURL(csd, http, url, req_hdr);
+        if (isHttp20Magic)
+            http->uri = xstrdup(url);
+        else
+            prepareTransparentURL(csd, http, url, req_hdr);
 
     } else if (internalCheck(url)) {
         /* internal URL mode */
@@ -2702,8 +2729,9 @@
 
     /* RFC 2616 section 10.5.6 : handle unsupported HTTP major versions cleanly. */
     /* We currently only support 0.9, 1.0, 1.1 properly */
+    /* We support 2.0 experimentally */
     if ( (http_ver.major == 0 && http_ver.minor != 9) ||
-            (http_ver.major > 1) ) {
+            (http_ver.major > 2) ) {
 
         clientStreamNode *node = context->getClientReplyContext();
         debugs(33, 5, "Unsupported HTTP version discovered. :\n" << HttpParserHdrBuf(hp));
@@ -2856,10 +2884,14 @@
     HTTPMSGLOCK(http->request);
     clientSetKeepaliveFlag(http);
 
-    // Let tunneling code be fully responsible for CONNECT requests
-    if (http->request->method == Http::METHOD_CONNECT) {
+    // Let tunneling code be fully responsible for CONNECT and PRI requests
+    if (http->request->method == Http::METHOD_CONNECT || http->request->method == Http::METHOD_PRI) {
         context->mayUseConnection(true);
         conn->flags.readMore = false;
+
+        // consume header early so that tunnel gets just the body
+        connNoteUseOfBuffer(conn, http->req_sz);
+        notedUseOfBuffer = true;
     }
 
 #if USE_SSL

=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc	2013-07-13 12:19:45 +0000
+++ src/client_side_request.cc	2013-07-15 04:15:51 +0000
@@ -555,6 +555,7 @@
 {
     // IP address validation for Host: failed. Admin wants to ignore them.
     // NP: we do not yet handle CONNECT tunnels well, so ignore for them
+    // NP: we also ignore HTTP/2.0 PRI tunnels, but they are missing Host header entirely.
     if (!Config.onoff.hostStrictVerify && http->request->method != Http::METHOD_CONNECT) {
         debugs(85, 3, "SECURITY ALERT: Host header forgery detected on " << http->getConn()->clientConnection <<
                " (" << A << " does not match " << B << ") on URL: " << urlCanonical(http->request));
@@ -1512,7 +1513,7 @@
 {
     debugs(85, 4, "clientProcessRequest: " << RequestMethodStr(request->method) << " '" << uri << "'");
 
-    if (request->method == Http::METHOD_CONNECT && !redirect.status) {
+    if (!redirect.status && (request->method == Http::METHOD_CONNECT || request->method == Http::METHOD_PRI)) {
 #if USE_SSL
         if (sslBumpNeeded()) {
             sslBumpStart();

=== modified file 'src/http/MethodType.h'
--- src/http/MethodType.h	2012-10-27 00:13:19 +0000
+++ src/http/MethodType.h	2013-07-14 11:14:40 +0000
@@ -75,6 +75,9 @@
     METHOD_UNBIND,
 #endif
 
+    // HTTP/2.0 magic - http://tools.ietf.org/html/draft-ietf-httpbis-http2-04#section-3.5
+    METHOD_PRI,
+
     // Squid extension methods
     METHOD_PURGE,
     METHOD_OTHER,

=== modified file 'src/tunnel.cc'
--- src/tunnel.cc	2013-06-11 08:11:30 +0000
+++ src/tunnel.cc	2013-08-02 10:03:11 +0000
@@ -33,6 +33,8 @@
 
 #include "squid.h"
 #include "acl/FilledChecklist.h"
+#include "base/CbcPointer.h"
+#include "base/StringArea.h"
 #include "base/Vector.h"
 #include "CachePeer.h"
 #include "client_side_request.h"
@@ -99,6 +101,7 @@
 
     bool noConnections() const;
     char *url;
+    CbcPointer<ClientHttpRequest> http;
     HttpRequest::Pointer request;
     Comm::ConnectionList serverDestinations;
 
@@ -224,6 +227,7 @@
 
 TunnelStateData::TunnelStateData() :
         url(NULL),
+        http(),
         request(NULL),
         status_ptr(NULL),
         connectRespBuf(NULL),
@@ -647,6 +651,9 @@
 void
 TunnelStateData::copyRead(Connection &from, IOCB *completion)
 {
+    debugs(26, 8, "Payload " << from.len << " bytes from " << from.conn);
+    debugs(26, 9, "\n----------\n" << StringArea(from.buf, from.len) << "\n----------");
+
     assert(from.len == 0);
     AsyncCall::Pointer call = commCbCall(5,4, "TunnelBlindCopyReadHandler",
                                          CommIoCbPtrFun(completion, this));
@@ -674,11 +681,23 @@
     assert(!tunnelState->waitingForConnectExchange());
     *tunnelState->status_ptr = Http::scOkay;
     if (cbdataReferenceValid(tunnelState)) {
+
         if (!tunnelState->server.len)
             tunnelState->copyRead(tunnelState->server, TunnelStateData::ReadServer);
         else
             tunnelState->copy(tunnelState->server.len, tunnelState->server, tunnelState->client, TunnelStateData::WriteClientDone);
-        tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient);
+
+        // Bug 3371: shovel any payload already pushed into ConnStateData by the client request
+        if (tunnelState->http.valid() && tunnelState->http->getConn() && tunnelState->http->getConn()->in.notYetUsed) {
+            debugs(26, 9, "Tunnel PUSH Payload: \n" << StringArea(tunnelState->http->getConn()->in.buf, tunnelState->http->getConn()->in.notYetUsed) << "\n----------");
+
+            // We just need to ensure the bytes from ConnStateData are in client.buf already
+            memcpy(tunnelState->client.buf, tunnelState->http->getConn()->in.buf, tunnelState->http->getConn()->in.notYetUsed);
+            // NP: readClient() takes care of buffer length accounting.
+            tunnelState->readClient(tunnelState->client.buf, tunnelState->http->getConn()->in.notYetUsed, COMM_OK, 0);
+            tunnelState->http->getConn()->in.notYetUsed = 0; // ConnStateData buffer accounting after the shuffle.
+        } else
+            tunnelState->copyRead(tunnelState->client, TunnelStateData::ReadClient);
     }
 }
 
@@ -708,7 +727,6 @@
 {
     TunnelStateData *tunnelState = (TunnelStateData *)data;
     debugs(26, 3, conn << ", flag=" << flag);
-    assert(tunnelState->waitingForConnectRequest());
 
     if (flag != COMM_OK) {
         *tunnelState->status_ptr = Http::scInternalServerError;
@@ -890,6 +908,7 @@
     tunnelState->server.size_ptr = size_ptr;
     tunnelState->status_ptr = status_ptr;
     tunnelState->client.conn = http->getConn()->clientConnection;
+    tunnelState->http = http;
 
     comm_add_close_handler(tunnelState->client.conn->fd,
                            tunnelClientClosed,
@@ -918,19 +937,25 @@
     flags.proxying = tunnelState->request->flags.proxying;
     MemBuf mb;
     mb.init();
-    mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url);
-    HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(),
-                                          NULL,			/* StoreEntry */
-                                          NULL,			/* AccessLogEntry */
-                                          &hdr_out,
-                                          flags);			/* flags */
-    packerToMemInit(&p, &mb);
-    hdr_out.packInto(&p);
-    hdr_out.clean();
-    packerClean(&p);
-    mb.append("\r\n", 2);
-
-    if (tunnelState->clientExpectsConnectResponse()) {
+
+    if (tunnelState->request->method == Http::METHOD_PRI) {
+        // send full HTTP/2.0 magic connection header to server
+        mb.Printf("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n");
+    } else {
+        // assume CONNECT
+        mb.Printf("CONNECT %s HTTP/1.1\r\n", tunnelState->url);
+        HttpStateData::httpBuildRequestHeader(tunnelState->request.getRaw(), NULL, NULL, &hdr_out, flags);
+        packerToMemInit(&p, &mb);
+        hdr_out.packInto(&p);
+        hdr_out.clean();
+        packerClean(&p);
+        mb.append("\r\n", 2);
+    }
+
+    debugs(11, 2, "Tunnel server REQUEST: " << tunnelState->server.conn << ":\n----------\n" <<
+           StringArea(mb.content(), mb.contentSize()) << "\n----------");
+
+    if (tunnelState->clientExpectsConnectResponse() || tunnelState->request->method == Http::METHOD_PRI) {
         // hack: blindly tunnel peer response (to our CONNECT request) to the client as ours.
         AsyncCall::Pointer writeCall = commCbCall(5,5, "tunnelConnectedWriteDone",
                                        CommIoCbPtrFun(tunnelConnectedWriteDone, tunnelState));
@@ -950,8 +975,6 @@
         // 2*SQUID_TCP_SO_RCVBUF: HttpMsg::parse() zero-terminates, which uses space.
         tunnelState->connectRespBuf->init(SQUID_TCP_SO_RCVBUF, 2*SQUID_TCP_SO_RCVBUF);
         tunnelState->readConnectResponse();
-
-        assert(tunnelState->waitingForConnectExchange());
     }
 
     AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "tunnelTimeout",

=== modified file 'src/url.cc'
--- src/url.cc	2012-12-27 17:58:29 +0000
+++ src/url.cc	2013-07-18 17:28:07 +0000
@@ -243,7 +243,7 @@
             if (sscanf(url, "%[^:]:%d", host, &port) < 1)
                 return NULL;
 
-    } else if ((method == Http::METHOD_OPTIONS || method == Http::METHOD_TRACE) &&
+    } else if ((method == Http::METHOD_OPTIONS || method == Http::METHOD_TRACE || method == Http::METHOD_PRI) &&
                strcmp(url, "*") == 0) {
         protocol = AnyP::PROTO_HTTP;
         port = urlDefaultPort(protocol);
@@ -507,9 +507,22 @@
     if (request->protocol == AnyP::PROTO_URN) {
         snprintf(urlbuf, MAX_URL, "urn:" SQUIDSTRINGPH,
                  SQUIDSTRINGPRINT(request->urlpath));
-    } else if (request->method.id() == Http::METHOD_CONNECT) {
-        snprintf(urlbuf, MAX_URL, "%s:%d", request->GetHost(), request->port);
     } else {
+
+        switch(request->method.id()) {
+        case Http::METHOD_CONNECT:
+            snprintf(urlbuf, MAX_URL, "%s:%d", request->GetHost(), request->port);
+        break;
+
+        case Http::METHOD_OPTIONS:
+        case Http::METHOD_TRACE:
+        case Http::METHOD_PRI:
+            if (request->urlpath == "*")
+                return (request->canonical = xstrdup(request->urlpath.termedBuf()));
+            // else fall through to default
+
+        default:
+        {
         portbuf[0] = '\0';
 
         if (request->port != urlDefaultPort(request->protocol))
@@ -523,6 +536,8 @@
                  request->GetHost(),
                  portbuf,
                  SQUIDSTRINGPRINT(request->urlpath));
+        }
+        }
     }
 
     return (request->canonical = xstrdup(urlbuf));
@@ -543,9 +558,24 @@
     if (request->protocol == AnyP::PROTO_URN) {
         snprintf(buf, MAX_URL, "urn:" SQUIDSTRINGPH,
                  SQUIDSTRINGPRINT(request->urlpath));
-    } else if (request->method.id() == Http::METHOD_CONNECT) {
-        snprintf(buf, MAX_URL, "%s:%d", request->GetHost(), request->port);
     } else {
+
+        switch(request->method.id()) {
+        case Http::METHOD_CONNECT:
+            snprintf(buf, MAX_URL, "%s:%d", request->GetHost(), request->port);
+        break;
+
+        case Http::METHOD_OPTIONS:
+        case Http::METHOD_TRACE:
+        case Http::METHOD_PRI:
+            if (request->urlpath == "*") {
+                memcpy(buf, "*", 2);
+                return buf;
+            }
+            // else fall through to default
+
+        default:
+        {
         portbuf[0] = '\0';
 
         if (request->port != urlDefaultPort(request->protocol))
@@ -576,6 +606,8 @@
         if (Config.onoff.strip_query_terms)
             if ((t = strchr(buf, '?')))
                 *(++t) = '\0';
+        }
+        }
     }
 
     if (stringHasCntl(buf))
@@ -822,6 +854,10 @@
     if (r->method == Http::METHOD_CONNECT)
         return 1;
 
+    // we support HTTP/2.0 magic PRI requests
+    if (r->method == Http::METHOD_PRI)
+        return (r->urlpath == "*");
+
     // we support OPTIONS and TRACE directed at us (with a 501 reply, for now)
     // we also support forwarding OPTIONS and TRACE, except for the *-URI ones
     if (r->method == Http::METHOD_OPTIONS || r->method == Http::METHOD_TRACE)

=== modified file 'tools/squidclient.cc'
--- tools/squidclient.cc	2013-06-03 14:05:16 +0000
+++ tools/squidclient.cc	2013-08-01 23:16:27 +0000
@@ -445,6 +445,9 @@
     if (version[0] == '-' || !version[0]) {
         /* HTTP/0.9, no headers, no version */
         snprintf(msg, BUFSIZ, "%s %s\r\n", method, url);
+    } else if (strcmp(version, "2.0") == 0) {
+        // send HTTP/2.0 magic buffer, then wait for the server response.
+        snprintf(msg, BUFSIZ, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\nSETTINGS-FRAME...\nDONE\r\n");
     } else {
         if (!xisdigit(version[0])) // not HTTP/n.n
             snprintf(msg, BUFSIZ, "%s %s %s\r\n", method, url, version);


