static void runHttpHeaderTest(TestRunner& tr) { tr.group("HttpHeader"); tr.test("Bicapitalization"); { // test bicapitalization of http headers const char* tests[] = { "", "", "a", "A", "-", "-", "a--a", "A--A", "-aa-", "-Aa-", "-aa", "-Aa", "aa-", "Aa-", "aaa-zzz", "Aaa-Zzz", "ThIs-a-BICaPitAlized-hEADer", "This-A-Bicapitalized-Header", "Message-ID", "Message-Id", NULL }; for(int i = 0; tests[i] != NULL; i +=2) { char* bic = strdup(tests[i]); HttpHeader::biCapitalize(bic); assertStrCmp(bic, tests[i+1]); free(bic); } } tr.passIfNoException(); tr.test("HttpRequestHeader parse"); { HttpRequestHeader header; header.setDate(); header.setMethod("GET"); header.setPath("/"); header.setVersion("HTTP/1.1"); header.setField("host", "localhost:80"); header.setField("Content-Type", "text/html"); header.setField("Connection", "close"); string date; string expect; expect.append("GET / HTTP/1.1\r\n"); expect.append("Connection: close\r\n"); expect.append("Content-Type: text/html\r\n"); expect.append("Date: "); header.getField("Date", date); expect.append(date); expect.append("\r\n"); expect.append("Host: localhost:80\r\n"); expect.append("\r\n"); string str = header.toString(); assertStrCmp(str.c_str(), expect.c_str()); HttpRequestHeader header2; header2.parse(str); string str2 = header2.toString(); assertStrCmp(str2.c_str(), expect.c_str()); } tr.passIfNoException(); tr.test("HttpResponseHeader parse"); { HttpResponseHeader header; header.setDate(); header.setVersion("HTTP/1.1"); header.setStatus(404, "Not Found"); header.setField("host", "localhost:80"); header.setField("Content-Type", "text/html"); header.setField("Connection", "close"); string date; string expect; expect.append("HTTP/1.1 404 Not Found\r\n"); expect.append("Connection: close\r\n"); expect.append("Content-Type: text/html\r\n"); expect.append("Date: "); header.getField("Date", date); expect.append(date); expect.append("\r\n"); expect.append("Host: localhost:80\r\n"); expect.append("\r\n"); string str = header.toString(); assertStrCmp(str.c_str(), expect.c_str()); HttpResponseHeader header2; header2.parse(str); string str2 = header2.toString(); assertStrCmp(str2.c_str(), expect.c_str()); } tr.passIfNoException(); tr.test("Multiple fields with same name"); { HttpResponseHeader header; header.setDate(); header.setVersion("HTTP/1.1"); header.setStatus(404, "Not Found"); header.setField("host", "localhost:80"); header.setField("Content-Type", "text/html"); header.setField("Connection", "close"); header.addField("Set-Cookie", "cookie1=value1; max-age=0; path=/"); header.addField("Set-Cookie", "cookie2=value2; max-age=0; path=/"); header.addField("Set-Cookie", "cookie3=value3; max-age=0; path=/"); string date; string expect; expect.append("HTTP/1.1 404 Not Found\r\n"); expect.append("Connection: close\r\n"); expect.append("Content-Type: text/html\r\n"); expect.append("Date: "); header.getField("Date", date); expect.append(date); expect.append("\r\n"); expect.append("Host: localhost:80\r\n"); expect.append("Set-Cookie: cookie1=value1; max-age=0; path=/\r\n"); expect.append("Set-Cookie: cookie2=value2; max-age=0; path=/\r\n"); expect.append("Set-Cookie: cookie3=value3; max-age=0; path=/\r\n"); expect.append("\r\n"); string str = header.toString(); assertStrCmp(str.c_str(), expect.c_str()); HttpResponseHeader header2; header2.parse(str); string str2 = header2.toString(); assertStrCmp(str2.c_str(), expect.c_str()); } tr.passIfNoException(); tr.test("hasContentType"); { HttpResponseHeader header; header.clearFields(); header.setField("Content-Type", "text/html"); assert(header.hasContentType("text/html")); header.clearFields(); header.setField("Content-Type", "text/html; params"); assert(header.hasContentType("text/html")); header.clearFields(); header.setField("Content-Type", "text/html; params"); assert(!header.hasContentType("text/plain")); } tr.passIfNoException(); tr.ungroup(); }
void ProxyResourceHandler::operator()(BtpAction* action) { // get request host HttpRequestHeader* hrh = action->getRequest()->getHeader(); string host = hrh->getFieldValue("X-Forwarded-Host"); if(host.length() == 0) { host = hrh->getFieldValue("Host"); } // find a rule Rule* rule = findRule(action, host); if(rule == NULL) { // delegate to rest resource handler RestResourceHandler::operator()(action); } else { // get URL to proxy or redirect to UrlRef url = rule->url; // get url host string urlHost; HttpResponse* res = action->getResponse(); bool secure = res->getConnection()->isSecure(); // if URL has no host, reuse incoming host if(url->getHost().length() == 0) { urlHost = host; } // if URL has no port or uses a default port number, only use URL host else if( (url->getPort() == 0) || (secure && url->getPort() == 443) || (!secure && url->getPort() == 80)) { urlHost = url->getHost(); } // use URL host and port else { urlHost = url->getHostAndPort(); } // handle 0.0.0.0 (any host) by replacing it with the request host if(strncmp(urlHost.c_str(), "0.0.0.0", 8) == 0) { // 0.0.0.0 is 8 chars long urlHost.replace(0, 8, host.substr(0, host.find(':')).c_str()); } // rewrite the request path if it does not match URL path string path = hrh->getPath(); if(strcmp(path.c_str(), url->getPath().c_str()) != 0) { // check for path wildcard if(strcmp(rule->path, "*") == 0) { // since a wildcard is used, prepend the URL path to the // resource (if the url path isn't '/') string urlPath = url->getPath(); if(urlPath.length() > 1) { path.insert(0, url->getPath().c_str()); } } else { // replace the part of the resource that matched the proxy // rule with the rewrite path from the proxy URL path.replace(0, strlen(rule->path), url->getPath().c_str()); } } // do redirect if appropriate if(rule->type == Rule::Redirect) { // set response code HttpResponseHeader* header = res->getHeader(); if(rule->permanent) { header->setStatus(301, "Moved Permanently"); } else { header->setStatus(302, "Found"); } // build new location url bool secure = res->getConnection()->isSecure(); header->setField("Location", StringTools::format("%s://%s%s", secure ? "https" : "http", urlHost.c_str(), path.c_str())); action->sendResult(); } // do proxy else if(rule->type == Rule::Proxy) { // get client-side request HttpRequest* req = action->getRequest(); // do path rewrite hrh->setPath(path.c_str()); // add X-Forwarded headers hrh->appendFieldValue("X-Forwarded-For", req->getConnection()->getRemoteAddress()->toString(true).c_str()); hrh->appendFieldValue("X-Forwarded-Host", hrh->getFieldValue("Host").c_str()); hrh->appendFieldValue("X-Forwarded-Server", SocketTools::getHostname().c_str()); // rewrite host if rule specifies it if(rule->rewriteHost) { hrh->setField("Host", urlHost.c_str()); } // do proxy: MO_CAT_INFO(BM_NODE_CAT, "ProxyResourceHandler proxying %s%s => %s%s", host.c_str(), hrh->getPath(), urlHost.c_str(), path.c_str()); MO_CAT_DEBUG(BM_NODE_CAT, "ProxyResourceHandler request header for %s%s => %s%s:\n%s", host.c_str(), hrh->getPath(), urlHost.c_str(), path.c_str(), hrh->toString().c_str()); // get a connection BtpClient* btpc = mNode->getMessenger()->getBtpClient(); HttpConnection* conn = btpc->createConnection(false, &(*url)); if(conn == NULL) { // send service unavailable HttpResponseHeader* header = action->getResponse()->getHeader(); header->setStatus(503, "Service Unavailable"); string content = "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n" "<html><head>\n" "<title>503 Service Unavailable</title>\n" "</head><body>\n" "<h1>Service Unavailable</h1>\n" "<p>The service was not available.</p>\n" "</body></html>"; ByteBuffer b(content.length()); b.put(content.c_str(), content.length(), false); ByteArrayInputStream bais(&b); action->sendResult(&bais); } else { // proxy the client's request and receive server's header (by // writing it into the client's response header) HttpResponse* res = action->getResponse(); if(_proxyHttp(req->getHeader(), req->getConnection(), conn) && conn->receiveHeader(res->getHeader())) { // proxy the server's response, consider result sent _proxyHttp(res->getHeader(), conn, req->getConnection()); action->setResultSent(true); } // close connection conn->close(); // clean up delete conn; } if(!action->isResultSent()) { // send exception (client's fault if code < 500) ExceptionRef e = Exception::get(); action->sendException(e, e->getCode() < 500); } } } }