nghttpx: Rewrite header handling

This commit is contained in:
Tatsuhiro Tsujikawa 2013-08-28 00:09:46 +09:00
parent 89cd2ff479
commit 1f3b96e233
23 changed files with 1023 additions and 279 deletions

View File

@ -1766,6 +1766,16 @@ int nghttp2_submit_window_update(nghttp2_session *session, uint8_t flags,
int32_t stream_id,
int32_t window_size_increment);
/**
* @function
*
* Compares lhs->name with lhs->namelen bytes and rhs->name with
* rhs->namelen bytes. Returns negative integer if lhs->name is found
* to be less than rhs->name; or returns positive integer if lhs->name
* is found to be greater than rhs->name; or returns 0 otherwise.
*/
int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs);
/**
* @function
*

View File

@ -766,6 +766,11 @@ static int nghttp2_nv_name_compar(const void *lhs, const void *rhs)
}
}
int nghttp2_nv_compare_name(const nghttp2_nv *lhs, const nghttp2_nv *rhs)
{
return nghttp2_nv_name_compar(lhs, rhs);
}
void nghttp2_nv_array_sort(nghttp2_nv *nva, size_t nvlen)
{
qsort(nva, nvlen, sizeof(nghttp2_nv), nghttp2_nv_name_compar);

4
src/.gitignore vendored
View File

@ -1,3 +1,7 @@
nghttp
nghttpd
nghttpx
nghttpx-unittest
nghttpx-unittest.log
nghttpx-unittest.trs
test-suite.log

View File

@ -91,16 +91,18 @@ endif # HAVE_SPDYLAY
nghttpx_SOURCES = ${NGHTTPX_SRCS} shrpx.cc shrpx.h
# if HAVE_CUNIT
# check_PROGRAMS += shrpx-unittest
# shrpx_unittest_SOURCES = shrpx-unittest.cc \
# shrpx_ssl_test.cc shrpx_ssl_test.h\
# ${SHRPX_SRCS}
# shrpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\
# -DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
# shrpx_unittest_LDFLAGS = -static @OPENSSL_LIBS@ @LIBEVENT_OPENSSL_LIBS@\
# @SRC_LIBS@ @CUNIT_LIBS@ @TESTS_LIBS@
# TESTS += shrpx-unittest
# endif # HAVE_CUNIT
if HAVE_CUNIT
check_PROGRAMS += nghttpx-unittest
nghttpx_unittest_SOURCES = shrpx-unittest.cc \
shrpx_ssl_test.cc shrpx_ssl_test.h \
shrpx_http_test.cc shrpx_http_test.h \
shrpx_downstream_test.cc shrpx_downstream_test.h \
util_test.cc util_test.h \
${NGHTTPX_SRCS}
nghttpx_unittest_CPPFLAGS = ${AM_CPPFLAGS}\
-DNGHTTP2_TESTS_DIR=\"$(top_srcdir)/tests\"
nghttpx_unittest_LDFLAGS = -static ${AM_LDFLAGS} @CUNIT_LIBS@ @TESTS_LIBS@
TESTS += nghttpx-unittest
endif # HAVE_CUNIT
endif # ENABLE_SRC

View File

@ -29,6 +29,9 @@
#include <openssl/err.h>
/* include test cases' include files here */
#include "shrpx_ssl_test.h"
#include "shrpx_http_test.h"
#include "shrpx_downstream_test.h"
#include "util_test.h"
static int init_suite1(void)
{
@ -65,7 +68,30 @@ int main(int argc, char* argv[])
if(!CU_add_test(pSuite, "ssl_create_lookup_tree",
shrpx::test_shrpx_ssl_create_lookup_tree) ||
!CU_add_test(pSuite, "ssl_cert_lookup_tree_add_cert_from_file",
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file)) {
shrpx::test_shrpx_ssl_cert_lookup_tree_add_cert_from_file) ||
!CU_add_test(pSuite, "http_check_http2_headers",
shrpx::test_http_check_http2_headers) ||
!CU_add_test(pSuite, "http_get_unique_header",
shrpx::test_http_get_unique_header) ||
!CU_add_test(pSuite, "http_get_header",
shrpx::test_http_get_header) ||
!CU_add_test(pSuite, "http_value_lws",
shrpx::test_http_value_lws) ||
!CU_add_test(pSuite, "http_copy_norm_headers_to_nv",
shrpx::test_http_copy_norm_headers_to_nv) ||
!CU_add_test(pSuite, "http_build_http1_headers_from_norm_headers",
shrpx::test_http_build_http1_headers_from_norm_headers) ||
!CU_add_test(pSuite, "downstream_normalize_request_headers",
shrpx::test_downstream_normalize_request_headers) ||
!CU_add_test(pSuite, "downstream_normalize_response_headers",
shrpx::test_downstream_normalize_response_headers) ||
!CU_add_test(pSuite, "downstream_get_norm_request_header",
shrpx::test_downstream_get_norm_request_header) ||
!CU_add_test(pSuite, "downstream_get_norm_response_header",
shrpx::test_downstream_get_norm_response_header) ||
!CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||
!CU_add_test(pSuite, "util_inp_strlower",
shrpx::test_util_inp_strlower)) {
CU_cleanup_registry();
return CU_get_error();
}

View File

@ -157,11 +157,53 @@ void check_connection_close(bool *connection_close,
}
} // namespace
namespace {
auto name_less = [](const Headers::value_type& lhs,
const Headers::value_type& rhs)
{
return lhs.first < rhs.first;
};
} // namespace
namespace {
void normalize_headers(Headers& headers)
{
for(auto& kv : headers) {
util::inp_strlower(kv.first);
}
std::sort(std::begin(headers), std::end(headers), name_less);
}
} // namespace
namespace {
Headers::const_iterator get_norm_header(const Headers& headers,
const std::string& name)
{
auto i = std::lower_bound(std::begin(headers), std::end(headers),
std::make_pair(name, std::string()), name_less);
if(i != std::end(headers) && (*i).first == name) {
return i;
}
return std::end(headers);
}
} // namespace
const Headers& Downstream::get_request_headers() const
{
return request_headers_;
}
void Downstream::normalize_request_headers()
{
normalize_headers(request_headers_);
}
Headers::const_iterator Downstream::get_norm_request_header
(const std::string& name) const
{
return get_norm_header(request_headers_, name);
}
void Downstream::add_request_header(const std::string& name,
const std::string& value)
{
@ -334,6 +376,17 @@ const Headers& Downstream::get_response_headers() const
return response_headers_;
}
void Downstream::normalize_response_headers()
{
normalize_headers(response_headers_);
}
Headers::const_iterator Downstream::get_norm_response_header
(const std::string& name) const
{
return get_norm_header(response_headers_, name);
}
void Downstream::add_response_header(const std::string& name,
const std::string& value)
{

View File

@ -84,6 +84,15 @@ public:
bool http2_upgrade_request() const;
// downstream request API
const Headers& get_request_headers() const;
// Makes key lowercase and sort headers by name using <
void normalize_request_headers();
// Returns iterator pointing to the request header with the name
// |name|. If multiple header have |name| as name, return first
// occurrence from the beginning. If no such header is found,
// returns std::end(get_request_headers()). This function must be
// called after calling normalize_request_headers().
Headers::const_iterator get_norm_request_header
(const std::string& name) const;
void add_request_header(const std::string& name, const std::string& value);
void set_last_request_header_value(const std::string& value);
@ -120,6 +129,15 @@ public:
int get_request_state() const;
// downstream response API
const Headers& get_response_headers() const;
// Makes key lowercase and sort headers by name using <
void normalize_response_headers();
// Returns iterator pointing to the response header with the name
// |name|. If multiple header have |name| as name, return first
// occurrence from the beginning. If no such header is found,
// returns std::end(get_response_headers()). This function must be
// called after calling normalize_response_headers().
Headers::const_iterator get_norm_response_header
(const std::string& name) const;
void add_response_header(const std::string& name, const std::string& value);
void set_last_response_header_value(const std::string& value);

View File

@ -0,0 +1,113 @@
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "shrpx_downstream_test.h"
#include <iostream>
#include <CUnit/CUnit.h>
#include "shrpx_downstream.h"
namespace shrpx {
void test_downstream_normalize_request_headers(void)
{
Downstream d(nullptr, 0, 0);
d.add_request_header("Charlie", "0");
d.add_request_header("Alpha", "1");
d.add_request_header("Delta", "2");
d.add_request_header("BravO", "3");
d.normalize_request_headers();
auto ans = Headers{
{"alpha", "1"},
{"bravo", "3"},
{"charlie", "0"},
{"delta", "2"}
};
CU_ASSERT(ans == d.get_request_headers());
}
void test_downstream_normalize_response_headers(void)
{
Downstream d(nullptr, 0, 0);
d.add_response_header("Charlie", "0");
d.add_response_header("Alpha", "1");
d.add_response_header("Delta", "2");
d.add_response_header("BravO", "3");
d.normalize_response_headers();
auto ans = Headers{
{"alpha", "1"},
{"bravo", "3"},
{"charlie", "0"},
{"delta", "2"}
};
CU_ASSERT(ans == d.get_response_headers());
}
void test_downstream_get_norm_request_header(void)
{
Downstream d(nullptr, 0, 0);
d.add_request_header("alpha", "0");
d.add_request_header("bravo", "1");
d.add_request_header("bravo", "2");
d.add_request_header("charlie", "3");
d.add_request_header("delta", "4");
d.add_request_header("echo", "5");
auto i = d.get_norm_request_header("alpha");
CU_ASSERT(std::make_pair(std::string("alpha"), std::string("0")) == *i);
i = d.get_norm_request_header("bravo");
CU_ASSERT(std::make_pair(std::string("bravo"), std::string("1")) == *i);
i = d.get_norm_request_header("delta");
CU_ASSERT(std::make_pair(std::string("delta"), std::string("4")) == *i);
i = d.get_norm_request_header("echo");
CU_ASSERT(std::make_pair(std::string("echo"), std::string("5")) == *i);
i = d.get_norm_request_header("foxtrot");
CU_ASSERT(i == std::end(d.get_request_headers()));
}
void test_downstream_get_norm_response_header(void)
{
Downstream d(nullptr, 0, 0);
d.add_response_header("alpha", "0");
d.add_response_header("bravo", "1");
d.add_response_header("bravo", "2");
d.add_response_header("charlie", "3");
d.add_response_header("delta", "4");
d.add_response_header("echo", "5");
auto i = d.get_norm_response_header("alpha");
CU_ASSERT(std::make_pair(std::string("alpha"), std::string("0")) == *i);
i = d.get_norm_response_header("bravo");
CU_ASSERT(std::make_pair(std::string("bravo"), std::string("1")) == *i);
i = d.get_norm_response_header("delta");
CU_ASSERT(std::make_pair(std::string("delta"), std::string("4")) == *i);
i = d.get_norm_response_header("echo");
CU_ASSERT(std::make_pair(std::string("echo"), std::string("5")) == *i);
i = d.get_norm_response_header("foxtrot");
CU_ASSERT(i == std::end(d.get_response_headers()));
}
} // namespace shrpx

View File

@ -0,0 +1,37 @@
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SHRPX_DOWNSTREAM_TEST_H
#define SHRPX_DOWNSTREAM_TEST_H
namespace shrpx {
void test_downstream_normalize_request_headers(void);
void test_downstream_normalize_response_headers(void);
void test_downstream_get_norm_request_header(void);
void test_downstream_get_norm_response_header(void);
} // namespace shrpx
#endif // SHRPX_DOWNSTREAM_TEST_H

View File

@ -188,6 +188,188 @@ bool check_http2_allowed_header(const uint8_t *name, size_t namelen)
!util::strieq("upgrade", name, namelen);
}
namespace {
const char *DISALLOWED_HD[] = {
"connection",
"host",
"keep-alive",
"proxy-connection",
"te",
"transfer-encoding",
"upgrade",
};
} // namespace
namespace {
size_t DISALLOWED_HDLEN = sizeof(DISALLOWED_HD)/sizeof(DISALLOWED_HD[0]);
} // namespace
namespace {
const char *IGN_HD[] = {
"connection",
"expect",
"host",
"http2-settings",
"keep-alive",
"proxy-connection",
"te",
"transfer-encoding",
"upgrade",
"via",
"x-forwarded-for",
"x-forwarded-proto",
};
} // namespace
namespace {
size_t IGN_HDLEN = sizeof(IGN_HD)/sizeof(IGN_HD[0]);
} // namespace
namespace {
const char *HTTP1_IGN_HD[] = {
"connection",
"expect",
"http2-settings",
"keep-alive",
"proxy-connection",
"upgrade",
"via",
"x-forwarded-for",
"x-forwarded-proto",
};
} // namespace
namespace {
size_t HTTP1_IGN_HDLEN = sizeof(HTTP1_IGN_HD)/sizeof(HTTP1_IGN_HD[0]);
} // namespace
namespace {
auto nv_name_less = [](const nghttp2_nv& lhs, const nghttp2_nv& rhs)
{
return nghttp2_nv_compare_name(&lhs, &rhs) < 0;
};
} // namespace
bool check_http2_headers(const nghttp2_nv *nva, size_t nvlen)
{
for(size_t i = 0; i < DISALLOWED_HDLEN; ++i) {
nghttp2_nv nv = {(uint8_t*)DISALLOWED_HD[i], nullptr,
(uint16_t)strlen(DISALLOWED_HD[i]), 0};
if(std::binary_search(&nva[0], &nva[nvlen], nv, nv_name_less)) {
return false;
}
}
return true;
}
const nghttp2_nv* get_unique_header(const nghttp2_nv *nva, size_t nvlen,
const char *name)
{
size_t namelen = strlen(name);
nghttp2_nv nv = {(uint8_t*)name, nullptr, (uint16_t)namelen, 0};
auto i = std::lower_bound(&nva[0], &nva[nvlen], nv, nv_name_less);
if(i != &nva[nvlen] && util::streq(i->name, i->namelen,
(const uint8_t*)name, namelen)) {
auto j = i + 1;
if(j == &nva[nvlen] || !util::streq(j->name, j->namelen,
(const uint8_t*)name, namelen)) {
return i;
}
}
return nullptr;
}
const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen,
const char *name)
{
size_t namelen = strlen(name);
nghttp2_nv nv = {(uint8_t*)name, nullptr, (uint16_t)namelen, 0};
auto i = std::lower_bound(&nva[0], &nva[nvlen], nv, nv_name_less);
if(i != &nva[nvlen] && util::streq(i->name, i->namelen,
(const uint8_t*)name, namelen)) {
return i;
}
return nullptr;
}
std::string name_to_str(const nghttp2_nv *nv)
{
return std::string(reinterpret_cast<const char*>(nv->name), nv->namelen);
}
std::string value_to_str(const nghttp2_nv *nv)
{
return std::string(reinterpret_cast<const char*>(nv->value), nv->valuelen);
}
bool value_lws(const nghttp2_nv *nv)
{
for(size_t i = 0; i < nv->valuelen; ++i) {
switch(nv->value[i]) {
case '\t':
case ' ':
continue;
default:
return false;
}
}
return true;
}
size_t copy_norm_headers_to_nv
(const char **nv,
const std::vector<std::pair<std::string, std::string>>& headers)
{
size_t i, j, nvlen = 0;
for(i = 0, j = 0; i < headers.size() && j < IGN_HDLEN;) {
int rv = strcmp(headers[i].first.c_str(), IGN_HD[j]);
if(rv < 0) {
nv[nvlen++] = headers[i].first.c_str();
nv[nvlen++] = headers[i].second.c_str();
++i;
} else if(rv > 0) {
++j;
} else {
++i;
}
}
for(; i < headers.size(); ++i) {
nv[nvlen++] = headers[i].first.c_str();
nv[nvlen++] = headers[i].second.c_str();
}
return nvlen;
}
void build_http1_headers_from_norm_headers
(std::string& hdrs,
const std::vector<std::pair<std::string,
std::string>>& headers)
{
size_t i, j;
for(i = 0, j = 0; i < headers.size() && j < HTTP1_IGN_HDLEN;) {
int rv = strcmp(headers[i].first.c_str(), HTTP1_IGN_HD[j]);
if(rv < 0) {
hdrs += headers[i].first;
http::capitalize(hdrs, hdrs.size()-headers[i].first.size());
hdrs += ": ";
hdrs += headers[i].second;
hdrs += "\r\n";
++i;
} else if(rv > 0) {
++j;
} else {
++i;
}
}
for(; i < headers.size(); ++i) {
hdrs += headers[i].first;
http::capitalize(hdrs, hdrs.size()-headers[i].first.size());
hdrs += ": ";
hdrs += headers[i].second;
hdrs += "\r\n";
}
}
} // namespace http
} // namespace shrpx

View File

@ -26,6 +26,9 @@
#define SHRPX_HTTP_H
#include <string>
#include <vector>
#include <nghttp2/nghttp2.h>
#include "http-parser/http_parser.h"
@ -60,6 +63,45 @@ bool check_http2_allowed_header(const uint8_t *name, size_t namelen);
// assuming |name| is null-terminated string.
bool check_http2_allowed_header(const char *name);
// Checks that headers |nva| including |nvlen| entries do not contain
// disallowed header fields in HTTP/2.0 spec. This function returns
// true if |nva| does not contains such headers.
bool check_http2_headers(const nghttp2_nv *nva, size_t nvlen);
// Returns the pointer to the entry in |nva| which has name |name| and
// the |name| is uinque in the |nva|. If no such entry exist, returns
// nullptr.
const nghttp2_nv* get_unique_header(const nghttp2_nv *nva, size_t nvlen,
const char *name);
// Returns the poiter to the entry in |nva| which has name |name|. If
// more than one entries which have the name |name|, first occurrence
// in |nva| is returned. If no such entry exist, returns nullptr.
const nghttp2_nv* get_header(const nghttp2_nv *nva, size_t nvlen,
const char *name);
// Returns std::string version of nv->name with nv->namelen bytes.
std::string name_to_str(const nghttp2_nv *nv);
// Returns std::string version of nv->value with nv->valuelen bytes.
std::string value_to_str(const nghttp2_nv *nv);
// Returns true if the value of |nv| includes only ' ' (0x20) or '\t'.
bool value_lws(const nghttp2_nv *nv);
// Copies headers in |headers| to |nv|. Certain headers, including
// disallowed headers in HTTP/2.0 spec and headers which require
// special handling (i.e. via), are not copied.
size_t copy_norm_headers_to_nv
(const char **nv,
const std::vector<std::pair<std::string, std::string>>& headers);
// Appends HTTP/1.1 style header lines to |hdrs| from headers in
// |headers|. Certain headers, which requires special handling
// (i.e. via), are not appended.
void build_http1_headers_from_norm_headers
(std::string& hdrs,
const std::vector<std::pair<std::string, std::string>>& headers);
} // namespace http
} // namespace shrpx

View File

@ -192,6 +192,7 @@ void on_frame_recv_callback
downstream->init_response_body_buf();
auto nva = frame->headers.nva;
auto nvlen = frame->headers.nvlen;
if(LOG_ENABLED(INFO)) {
std::stringstream ss;
@ -208,116 +209,58 @@ void on_frame_recv_callback
}
// Assuming that nva is sorted by name.
const char *req_headers[] = {":host", ":method", ":path", ":scheme",
"content-length"};
const size_t req_hdlen = sizeof(req_headers)/sizeof(req_headers[0]);
int req_hdidx[req_hdlen];
memset(req_hdidx, -1, sizeof(req_hdidx));
bool bad_req = false;
{
size_t i, j;
for(i = 0, j = 0; i < frame->headers.nvlen && j < req_hdlen;) {
if(!http::check_http2_allowed_header(nva[i].name, nva[i].namelen)) {
bad_req = true;
break;
}
int rv = util::strcompare(req_headers[j], nva[i].name, nva[i].namelen);
if(rv > 0) {
if(nva[i].namelen > 0 && nva[i].name[0] != ':') {
downstream->add_request_header
(std::string(reinterpret_cast<char*>(nva[i].name),
nva[i].namelen),
std::string(reinterpret_cast<char*>(nva[i].value),
nva[i].valuelen));
}
++i;
} else if(rv < 0) {
++j;
} else {
if(req_hdidx[j] != -1) {
if(LOG_ENABLED(INFO)) {
ULOG(INFO, upstream) << "multiple " << req_headers[j]
<< " found in the request";
}
bad_req = true;
break;
}
req_hdidx[j] = i;
++i;
}
}
if(!bad_req) {
// Here :scheme is optional, because with CONNECT method, it
// is omitted. content-length is mandatory if END_STREAM is
// not set.
for(j = 0; j < 3; ++j) {
if(req_hdidx[j] == -1) {
bad_req = true;
break;
}
}
}
if(!bad_req &&
!util::strieq("CONNECT",
nva[req_hdidx[1]].value,
nva[req_hdidx[1]].valuelen) &&
(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0 &&
req_hdidx[4] == -1) {
// If content-length is missing,
// Downstream::push_upload_data_chunk will fail and
// RST_STREAM will be sent.
bad_req = true;
}
if(!bad_req) {
for(; i < frame->headers.nvlen; ++i) {
if(nva[i].namelen > 0 && nva[i].name[0] != ':') {
downstream->add_request_header
(std::string(reinterpret_cast<char*>(nva[i].name),
nva[i].namelen),
std::string(reinterpret_cast<char*>(nva[i].value),
nva[i].valuelen));
}
}
}
}
if(bad_req) {
if(!http::check_http2_headers(nva, nvlen)) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return;
}
if(req_hdidx[4] != -1) {
downstream->add_request_header
(std::string(reinterpret_cast<char*>(nva[req_hdidx[4]].name),
nva[req_hdidx[4]].namelen),
std::string(reinterpret_cast<char*>(nva[req_hdidx[4]].value),
nva[req_hdidx[4]].valuelen));
for(size_t i = 0; i < nvlen; ++i) {
if(nva[i].namelen > 0 && nva[i].name[0] != ':') {
downstream->add_request_header(http::name_to_str(&nva[i]),
http::value_to_str(&nva[i]));
}
}
std::string host(reinterpret_cast<char*>(nva[req_hdidx[0]].value),
nva[req_hdidx[0]].valuelen);
std::string method(reinterpret_cast<char*>(nva[req_hdidx[1]].value),
nva[req_hdidx[1]].valuelen);
std::string path(reinterpret_cast<char*>(nva[req_hdidx[2]].value),
nva[req_hdidx[2]].valuelen);
auto host = http::get_unique_header(nva, nvlen, ":host");
auto path = http::get_unique_header(nva, nvlen, ":path");
auto method = http::get_unique_header(nva, nvlen, ":method");
auto scheme = http::get_unique_header(nva, nvlen, ":scheme");
bool is_connect = method &&
util::streq("CONNECT", method->value, method->valuelen);
if(!host || !path || !method ||
http::value_lws(host) || http::value_lws(path) ||
http::value_lws(method) ||
(!is_connect && (!scheme || http::value_lws(scheme)))) {
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return;
}
if(!is_connect &&
(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
auto content_length = http::get_header(nva, nvlen, "content-length");
if(!content_length || http::value_lws(content_length)) {
// If content-length is missing,
// Downstream::push_upload_data_chunk will fail and
upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR);
return;
}
}
downstream->set_request_method(method);
downstream->set_request_method(http::value_to_str(method));
// SpdyDownstreamConnection examines request path to find
// scheme. We construct abs URI for spdy_bridge mode as well as
// spdy_proxy mode.
if((get_config()->spdy_proxy || get_config()->spdy_bridge) &&
req_hdidx[3] != -1 && path[0] == '/') {
std::string reqpath(reinterpret_cast<char*>(nva[req_hdidx[3]].value),
nva[req_hdidx[3]].valuelen);
scheme && path->value[0] == '/') {
std::string reqpath(http::value_to_str(scheme));
reqpath += "://";
reqpath += host;
reqpath += path;
reqpath += http::value_to_str(host);
reqpath += http::value_to_str(path);
downstream->set_request_path(reqpath);
} else {
downstream->set_request_path(path);
downstream->set_request_path(http::value_to_str(path));
}
downstream->add_request_header("host", host);
downstream->add_request_header("host", http::value_to_str(host));
downstream->check_upgrade_request();
auto dconn = upstream->get_client_handler()->get_downstream_connection();
@ -908,6 +851,8 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "HTTP response header completed";
}
downstream->normalize_response_headers();
auto end_headers = std::end(downstream->get_response_headers());
size_t nheader = downstream->get_response_headers().size();
// 4 means :status and possible via header field.
const char **nv = new const char*[nheader * 2 + 4 + 1];
@ -917,20 +862,18 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
std::to_string(downstream->get_response_http_status());
nv[hdidx++] = ":status";
nv[hdidx++] = response_status.c_str();
for(Headers::const_iterator i = downstream->get_response_headers().begin();
i != downstream->get_response_headers().end(); ++i) {
if(!http::check_http2_allowed_header((*i).first.c_str())) {
// These are ignored
} else if(!get_config()->no_via &&
util::strieq((*i).first.c_str(), "via")) {
via_value = (*i).second;
hdidx += http::copy_norm_headers_to_nv(&nv[hdidx],
downstream->get_response_headers());
auto via = downstream->get_norm_response_header("via");
if(get_config()->no_via) {
if(via != end_headers) {
nv[hdidx++] = "via";
nv[hdidx++] = (*via).second.c_str();
}
} else {
nv[hdidx++] = (*i).first.c_str();
nv[hdidx++] = (*i).second.c_str();
}
}
if(!get_config()->no_via) {
if(!via_value.empty()) {
if(via != end_headers) {
via_value = (*via).second;
via_value += ", ";
}
via_value += http::create_via_header_value
@ -938,7 +881,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
nv[hdidx++] = "via";
nv[hdidx++] = via_value.c_str();
}
nv[hdidx++] = 0;
nv[hdidx++] = nullptr;
if(LOG_ENABLED(INFO)) {
std::stringstream ss;
for(size_t i = 0; nv[i]; i += 2) {

View File

@ -118,69 +118,26 @@ int HttpDownstreamConnection::push_request_headers()
hdrs += downstream_->get_request_path();
hdrs += " ";
hdrs += "HTTP/1.1\r\n";
std::string connection_upgrade;
std::string via_value;
std::string xff_value;
const Headers& request_headers = downstream_->get_request_headers();
for(Headers::const_iterator i = request_headers.begin();
i != request_headers.end(); ++i) {
if(util::strieq((*i).first.c_str(), "connection")) {
// nghttpx handles HTTP/2.0 upgrade and does not relay it to the
// downstream.
if(util::strifind((*i).second.c_str(), "upgrade") &&
!util::strifind((*i).second.c_str(), "http2-settings")) {
connection_upgrade = (*i).second;
}
continue;
} else if(util::strieq((*i).first.c_str(), "upgrade")) {
// nghttpx handles HTTP/2.0 upgrade and does not relay it to the
// downstream.
if(util::strieq((*i).second.c_str(), NGHTTP2_PROTO_VERSION_ID)) {
continue;
}
} else if(util::strieq((*i).first.c_str(), "x-forwarded-proto") ||
util::strieq((*i).first.c_str(), "keep-alive") ||
util::strieq((*i).first.c_str(), "proxy-connection") ||
util::strieq((*i).first.c_str(), "http2-settings")) {
continue;
} else if(util::strieq((*i).first.c_str(), "via")) {
if(!get_config()->no_via) {
via_value = (*i).second;
continue;
}
} else if(util::strieq((*i).first.c_str(), "x-forwarded-for")) {
xff_value = (*i).second;
continue;
} else if(util::strieq((*i).first.c_str(), "expect")) {
if(util::strifind((*i).second.c_str(), "100-continue")) {
continue;
}
}
hdrs += (*i).first;
http::capitalize(hdrs, hdrs.size()-(*i).first.size());
hdrs += ": ";
hdrs += (*i).second;
http::sanitize_header_value(hdrs, hdrs.size()-(*i).second.size());
hdrs += "\r\n";
}
downstream_->normalize_request_headers();
auto end_headers = std::end(downstream_->get_request_headers());
http::build_http1_headers_from_norm_headers
(hdrs, downstream_->get_request_headers());
if(downstream_->get_request_connection_close()) {
hdrs += "Connection: close\r\n";
} else if(!connection_upgrade.empty()) {
hdrs += "Connection: ";
hdrs += connection_upgrade;
hdrs += "\r\n";
}
auto xff = downstream_->get_norm_request_header("x-forwarded-for");
if(get_config()->add_x_forwarded_for) {
hdrs += "X-Forwarded-For: ";
if(!xff_value.empty()) {
hdrs += xff_value;
if(xff != end_headers) {
hdrs += (*xff).second;
hdrs += ", ";
}
hdrs += downstream_->get_upstream()->get_client_handler()->get_ipaddr();
hdrs += "\r\n";
} else if(!xff_value.empty()) {
} else if(xff != end_headers) {
hdrs += "X-Forwarded-For: ";
hdrs += xff_value;
hdrs += (*xff).second;
hdrs += "\r\n";
}
if(downstream_->get_request_method() != "CONNECT") {
@ -192,10 +149,24 @@ int HttpDownstreamConnection::push_request_headers()
}
hdrs += "\r\n";
}
if(!get_config()->no_via) {
auto expect = downstream_->get_norm_request_header("expect");
if(expect != end_headers &&
util::strifind((*expect).second.c_str(), "100-continue")) {
hdrs += "Expect: ";
hdrs += (*expect).second;
hdrs += "\r\n";
}
auto via = downstream_->get_norm_request_header("via");
if(get_config()->no_via) {
if(via != end_headers) {
hdrs += "Via: ";
if(!via_value.empty()) {
hdrs += via_value;
hdrs += (*via).second;
hdrs += "\r\n";
}
} else {
hdrs += "Via: ";
if(via != end_headers) {
hdrs += (*via).second;
hdrs += ", ";
}
hdrs += http::create_via_header_value(downstream_->get_request_major(),

166
src/shrpx_http_test.cc Normal file
View File

@ -0,0 +1,166 @@
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "shrpx_http_test.h"
#include <iostream>
#include <CUnit/CUnit.h>
#include "shrpx_http.h"
#include "util.h"
using namespace nghttp2;
#define MAKE_NV(K, V) {(uint8_t*)K, (uint8_t*)V, strlen(K), strlen(V)}
namespace shrpx {
void test_http_check_http2_headers(void)
{
nghttp2_nv nv1[] = {MAKE_NV("alpha", "1"),
MAKE_NV("bravo", "2"),
MAKE_NV("upgrade", "http2")};
CU_ASSERT(!http::check_http2_headers(nv1, 3));
nghttp2_nv nv2[] = {MAKE_NV("connection", "1"),
MAKE_NV("delta", "2"),
MAKE_NV("echo", "3")};
CU_ASSERT(!http::check_http2_headers(nv2, 3));
nghttp2_nv nv3[] = {MAKE_NV("alpha", "1"),
MAKE_NV("bravo", "2"),
MAKE_NV("te2", "3")};
CU_ASSERT(http::check_http2_headers(nv3, 3));
}
void test_http_get_unique_header(void)
{
nghttp2_nv nv[] = {MAKE_NV("alpha", "1"),
MAKE_NV("bravo", "2"),
MAKE_NV("bravo", "3"),
MAKE_NV("charlie", "4"),
MAKE_NV("delta", "5"),
MAKE_NV("echo", "6"),};
size_t nvlen = sizeof(nv)/sizeof(nv[0]);
const nghttp2_nv *rv;
rv = http::get_unique_header(nv, nvlen, "delta");
CU_ASSERT(rv != nullptr);
CU_ASSERT(util::streq("delta", rv->name, rv->namelen));
rv = http::get_unique_header(nv, nvlen, "bravo");
CU_ASSERT(rv == nullptr);
rv = http::get_unique_header(nv, nvlen, "foxtrot");
CU_ASSERT(rv == nullptr);
}
void test_http_get_header(void)
{
nghttp2_nv nv[] = {MAKE_NV("alpha", "1"),
MAKE_NV("bravo", "2"),
MAKE_NV("bravo", "3"),
MAKE_NV("charlie", "4"),
MAKE_NV("delta", "5"),
MAKE_NV("echo", "6"),};
size_t nvlen = sizeof(nv)/sizeof(nv[0]);
const nghttp2_nv *rv;
rv = http::get_header(nv, nvlen, "delta");
CU_ASSERT(rv != nullptr);
CU_ASSERT(util::streq("delta", rv->name, rv->namelen));
rv = http::get_header(nv, nvlen, "bravo");
CU_ASSERT(rv != nullptr);
CU_ASSERT(util::streq("bravo", rv->name, rv->namelen));
rv = http::get_header(nv, nvlen, "foxtrot");
CU_ASSERT(rv == nullptr);
}
void test_http_value_lws(void)
{
nghttp2_nv nv[] = {MAKE_NV("0", "alpha"),
MAKE_NV("1", " alpha"),
MAKE_NV("2", ""),
MAKE_NV("3", " "),
MAKE_NV("4", " a ")};
CU_ASSERT(!http::value_lws(&nv[0]));
CU_ASSERT(!http::value_lws(&nv[1]));
CU_ASSERT(http::value_lws(&nv[2]));
CU_ASSERT(http::value_lws(&nv[3]));
CU_ASSERT(!http::value_lws(&nv[4]));
}
namespace {
auto headers = std::vector<std::pair<std::string, std::string>>
{{"alpha", "0"},
{"bravo", "1"},
{"connection", "2"},
{"connection", "3"},
{"delta", "4"},
{"expect", "5"},
{"foxtrot", "6"},
{"tango", "7"},
{"te", "8"},
{"te", "9"},
{"x-forwarded-proto", "10"},
{"x-forwarded-proto", "11"},
{"zulu", "12"}};
} // namespace
void test_http_copy_norm_headers_to_nv(void)
{
const char* nv[30];
size_t nvlen = http::copy_norm_headers_to_nv(nv, headers);
CU_ASSERT(12 == nvlen);
CU_ASSERT(strcmp(nv[0], "alpha") == 0);
CU_ASSERT(strcmp(nv[1], "0") == 0);
CU_ASSERT(strcmp(nv[2], "bravo") == 0);
CU_ASSERT(strcmp(nv[3], "1") == 0);
CU_ASSERT(strcmp(nv[4], "delta") == 0);
CU_ASSERT(strcmp(nv[5], "4") == 0);
CU_ASSERT(strcmp(nv[6], "foxtrot") == 0);
CU_ASSERT(strcmp(nv[7], "6") == 0);
CU_ASSERT(strcmp(nv[8], "tango") == 0);
CU_ASSERT(strcmp(nv[9], "7") == 0);
CU_ASSERT(strcmp(nv[10], "zulu") == 0);
CU_ASSERT(strcmp(nv[11], "12") == 0);
}
void test_http_build_http1_headers_from_norm_headers(void)
{
std::string hdrs;
http::build_http1_headers_from_norm_headers(hdrs, headers);
CU_ASSERT(hdrs ==
"Alpha: 0\r\n"
"Bravo: 1\r\n"
"Delta: 4\r\n"
"Foxtrot: 6\r\n"
"Tango: 7\r\n"
"Te: 8\r\n"
"Te: 9\r\n"
"Zulu: 12\r\n");
}
} // namespace shrpx

39
src/shrpx_http_test.h Normal file
View File

@ -0,0 +1,39 @@
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef SHRPX_HTTP_TEST_H
#define SHRPX_HTTP_TEST_H
namespace shrpx {
void test_http_check_http2_headers(void);
void test_http_get_unique_header(void);
void test_http_get_header(void);
void test_http_value_lws(void);
void test_http_copy_norm_headers_to_nv(void);
void test_http_build_http1_headers_from_norm_headers(void);
} // namespace shrpx
#endif // SHRPX_HTTP_TEST_H

View File

@ -654,8 +654,6 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
if(LOG_ENABLED(INFO)) {
DLOG(INFO, downstream) << "HTTP response header completed";
}
bool connection_upgrade = false;
std::string via_value;
char temp[16];
snprintf(temp, sizeof(temp), "HTTP/%d.%d ",
downstream->get_request_major(),
@ -663,26 +661,10 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
std::string hdrs = temp;
hdrs += http::get_status_string(downstream->get_response_http_status());
hdrs += "\r\n";
for(Headers::const_iterator i = downstream->get_response_headers().begin();
i != downstream->get_response_headers().end(); ++i) {
if(util::strieq((*i).first.c_str(), "connection")) {
if(util::strifind((*i).second.c_str(), "upgrade")) {
connection_upgrade = true;
}
} else if(util::strieq((*i).first.c_str(), "keep-alive") || // HTTP/1.0?
util:: strieq((*i).first.c_str(), "proxy-connection")) {
// These are ignored
} else if(!get_config()->no_via &&
util::strieq((*i).first.c_str(), "via")) {
via_value = (*i).second;
} else {
hdrs += (*i).first;
http::capitalize(hdrs, hdrs.size()-(*i).first.size());
hdrs += ": ";
hdrs += (*i).second;
hdrs += "\r\n";
}
}
downstream->normalize_response_headers();
auto end_headers = std::end(downstream->get_response_headers());
http::build_http1_headers_from_norm_headers
(hdrs, downstream->get_response_headers());
// We check downstream->get_response_connection_close() in case when
// the Content-Length is not available.
@ -692,23 +674,27 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
downstream->get_request_minor() <= 0) {
// We add this header for HTTP/1.0 or HTTP/0.9 clients
hdrs += "Connection: Keep-Alive\r\n";
} else if(connection_upgrade) {
hdrs += "Connection: upgrade\r\n";
}
} else if(!downstream->get_upgraded()) {
} else {
hdrs += "Connection: close\r\n";
}
if(!get_config()->no_via) {
auto via = downstream->get_norm_response_header("via");
if(get_config()->no_via) {
if(via != end_headers) {
hdrs += "Via: ";
if(!via_value.empty()) {
hdrs += via_value;
hdrs += (*via).second;
hdrs += "\r\n";
}
} else {
hdrs += "Via: ";
if(via != end_headers) {
hdrs += (*via).second;
hdrs += ", ";
}
hdrs += http::create_via_header_value
(downstream->get_response_major(), downstream->get_response_minor());
hdrs += "\r\n";
}
hdrs += "\r\n";
if(LOG_ENABLED(INFO)) {
const char *hdrp;

View File

@ -230,6 +230,8 @@ int SpdyDownstreamConnection::push_request_headers()
return 0;
}
size_t nheader = downstream_->get_request_headers().size();
downstream_->normalize_request_headers();
auto end_headers = std::end(downstream_->get_request_headers());
// 10 means :method, :scheme, :path and possible via and
// x-forwarded-for header fields. We rename host header field as
// :host.
@ -280,61 +282,63 @@ int SpdyDownstreamConnection::push_request_headers()
nv[hdidx++] = ":method";
nv[hdidx++] = downstream_->get_request_method().c_str();
bool chunked_encoding = false;
bool content_length = false;
for(Headers::const_iterator i = downstream_->get_request_headers().begin();
i != downstream_->get_request_headers().end(); ++i) {
if(util::strieq((*i).first.c_str(), "transfer-encoding")) {
if(util::strieq((*i).second.c_str(), "chunked")) {
chunked_encoding = true;
hdidx += http::copy_norm_headers_to_nv(&nv[hdidx],
downstream_->get_request_headers());
auto host = downstream_->get_norm_request_header("host");
if(host == end_headers) {
if(LOG_ENABLED(INFO)) {
DCLOG(INFO, this) << "host header field missing";
}
return -1;
}
// Ignore transfer-encoding
continue;
} else if(util::strieq((*i).first.c_str(), "x-forwarded-proto") ||
util::strieq((*i).first.c_str(), "keep-alive") ||
util::strieq((*i).first.c_str(), "connection") ||
util::strieq((*i).first.c_str(), "proxy-connection") ||
util::strieq((*i).first.c_str(), "te") ||
util::strieq((*i).first.c_str(), "upgrade") ||
util::strieq((*i).first.c_str(), "http2-settings")) {
// These are ignored
continue;
} else if(!get_config()->no_via &&
util::strieq((*i).first.c_str(), "via")) {
via_value = (*i).second;
continue;
} else if(util::strieq((*i).first.c_str(), "x-forwarded-for")) {
xff_value = (*i).second;
continue;
} else if(util::strieq((*i).first.c_str(), "expect") &&
util::strifind((*i).second.c_str(), "100-continue")) {
// Ignore
continue;
} else if(util::strieq((*i).first.c_str(), "host")) {
nv[hdidx++] = ":host";
nv[hdidx++] = (*i).second.c_str();
continue;
} else if(util::strieq((*i).first.c_str(), "content-length")) {
nv[hdidx++] = (*host).second.c_str();
bool content_length = false;
if(downstream_->get_norm_request_header("content-length") != end_headers) {
content_length = true;
}
nv[hdidx++] = (*i).first.c_str();
nv[hdidx++] = (*i).second.c_str();
auto expect = downstream_->get_norm_request_header("expect");
if(expect != end_headers &&
util::strifind((*expect).second.c_str(), "100-continue")) {
nv[hdidx++] = "expect";
nv[hdidx++] = (*expect).second.c_str();
}
bool chunked_encoding = false;
auto transfer_encoding =
downstream_->get_norm_request_header("transfer-encoding");
if(transfer_encoding != end_headers &&
util::strieq((*transfer_encoding).second.c_str(), "chunked")) {
chunked_encoding = true;
}
auto xff = downstream_->get_norm_request_header("x-forwarded-for");
if(get_config()->add_x_forwarded_for) {
nv[hdidx++] = "x-forwarded-for";
if(!xff_value.empty()) {
if(xff != end_headers) {
xff_value = (*xff).second;
xff_value += ", ";
}
xff_value += downstream_->get_upstream()->get_client_handler()->
get_ipaddr();
nv[hdidx++] = xff_value.c_str();
} else if(!xff_value.empty()) {
} else if(xff != end_headers) {
nv[hdidx++] = "x-forwarded-for";
nv[hdidx++] = xff_value.c_str();
nv[hdidx++] = (*xff).second.c_str();
}
if(!get_config()->no_via) {
if(!via_value.empty()) {
auto via = downstream_->get_norm_request_header("via");
if(get_config()->no_via) {
if(via != end_headers) {
nv[hdidx++] = "via";
nv[hdidx++] = (*via).second.c_str();
}
} else {
if(via != end_headers) {
via_value = (*via).second;
via_value += ", ";
}
via_value += http::create_via_header_value
@ -342,7 +346,8 @@ int SpdyDownstreamConnection::push_request_headers()
nv[hdidx++] = "via";
nv[hdidx++] = via_value.c_str();
}
nv[hdidx++] = 0;
nv[hdidx++] = nullptr;
if(LOG_ENABLED(INFO)) {
std::stringstream ss;
for(size_t i = 0; nv[i]; i += 2) {

View File

@ -746,35 +746,10 @@ void on_frame_recv_callback
break;
}
auto nva = frame->headers.nva;
std::string status, content_length;
for(size_t i = 0; i < frame->headers.nvlen; ++i) {
if(!http::check_http2_allowed_header(nva[i].name, nva[i].namelen)) {
status.clear();
break;
}
if(util::strieq(":status", nva[i].name, nva[i].namelen)) {
status.assign(reinterpret_cast<char*>(nva[i].value),
nva[i].valuelen);
auto code = strtoul(status.c_str(), nullptr, 10);
downstream->set_response_http_status(code);
} else if(nva[i].namelen > 0 && nva[i].name[0] != ':') {
if(util::strieq("content-length", nva[i].name, nva[i].namelen)) {
content_length.assign(reinterpret_cast<char*>(nva[i].value),
nva[i].valuelen);
}
downstream->add_response_header
(std::string(reinterpret_cast<char*>(nva[i].name),
nva[i].namelen),
std::string(reinterpret_cast<char*>(nva[i].value),
nva[i].valuelen));
}
}
// Just assume it is HTTP/1.1. But we really consider to say 2.0
// here.
downstream->set_response_major(1);
downstream->set_response_minor(1);
auto nvlen = frame->headers.nvlen;
if(status.empty()) {
// Assuming that nva is sorted by name.
if(!http::check_http2_headers(nva, nvlen)) {
nghttp2_submit_rst_stream(session, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET);
@ -782,7 +757,31 @@ void on_frame_recv_callback
return;
}
if(content_length.empty() && downstream->get_request_method() != "HEAD" &&
for(size_t i = 0; i < nvlen; ++i) {
if(nva[i].namelen > 0 && nva[i].name[0] != ':') {
downstream->add_response_header(http::name_to_str(&nva[i]),
http::value_to_str(&nva[i]));
}
}
auto status = http::get_unique_header(nva, nvlen, ":status");
if(!status || http::value_lws(status)) {
nghttp2_submit_rst_stream(session, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
downstream->set_response_state(Downstream::MSG_RESET);
call_downstream_readcb(spdy, downstream);
return;
}
downstream->set_response_http_status
(strtoul(http::value_to_str(status).c_str(), nullptr, 10));
// Just assume it is HTTP/1.1. But we really consider to say 2.0
// here.
downstream->set_response_major(1);
downstream->set_response_minor(1);
auto content_length = http::get_header(nva, nvlen, "content-length");
if(!content_length && downstream->get_request_method() != "HEAD" &&
downstream->get_request_method() != "CONNECT") {
unsigned int status;
status = downstream->get_response_http_status();

View File

@ -184,7 +184,7 @@ void on_ctrl_recv_callback
return;
}
// Require content-length if FIN flag is not set.
if(!util::strieq("CONNECT", method) &&
if(strcmp("CONNECT", method) == 0 &&
(frame->syn_stream.hd.flags & SPDYLAY_CTRL_FLAG_FIN) == 0 &&
!content_length) {
upstream->rst_stream(downstream, SPDYLAY_PROTOCOL_ERROR);

View File

@ -171,6 +171,24 @@ bool strieq(const char *a, const uint8_t *b, size_t bn)
return !*a && b == blast;
}
bool streq(const char *a, const uint8_t *b, size_t bn)
{
if(!a || !b) {
return false;
}
const uint8_t *blast = b + bn;
for(; *a && b != blast && *a == *b; ++a, ++b);
return !*a && b == blast;
}
bool streq(const uint8_t *a, size_t alen, const uint8_t *b, size_t blen)
{
if(alen != blen) {
return false;
}
return memcmp(a, b, alen) == 0;
}
int strcompare(const char *a, const uint8_t *b, size_t bn)
{
assert(a && b);
@ -269,6 +287,15 @@ void to_base64(std::string& token68str)
return;
}
void inp_strlower(std::string& s)
{
for(auto i = std::begin(s); i != std::end(s); ++i) {
if('A' <= *i && *i <= 'Z') {
*i = (*i) - 'A' + 'a';
}
}
}
} // namespace util
} // namespace nghttp2

View File

@ -299,6 +299,10 @@ bool strieq(const char *a, const char *b);
bool strieq(const char *a, const uint8_t *b, size_t n);
bool streq(const char *a, const uint8_t *b, size_t bn);
bool streq(const uint8_t *a, size_t alen, const uint8_t *b, size_t blen);
bool strifind(const char *a, const char *b);
char upcase(char c);
@ -344,6 +348,9 @@ inline char lowcase(char c)
return tbl[static_cast<unsigned char>(c)];
}
// Lowercase |s| in place.
void inp_strlower(std::string& s);
template<typename T>
std::string utos(T n)
{

74
src/util_test.cc Normal file
View File

@ -0,0 +1,74 @@
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "util_test.h"
#include <iostream>
#include <CUnit/CUnit.h>
#include "util.h"
using namespace nghttp2;
namespace shrpx {
void test_util_streq(void)
{
CU_ASSERT(util::streq("alpha", (const uint8_t*)"alpha", 5));
CU_ASSERT(util::streq("alpha", (const uint8_t*)"alphabravo", 5));
CU_ASSERT(!util::streq("alpha", (const uint8_t*)"alphabravo", 6));
CU_ASSERT(!util::streq("alphabravo", (const uint8_t*)"alpha", 5));
CU_ASSERT(!util::streq("alpha", (const uint8_t*)"alphA", 5));
CU_ASSERT(!util::streq("", (const uint8_t*)"a", 1));
CU_ASSERT(util::streq("", (const uint8_t*)"", 0));
CU_ASSERT(!util::streq("alpha", (const uint8_t*)"", 0));
CU_ASSERT(util::streq((const uint8_t*)"alpha", 5,
(const uint8_t*)"alpha", 5));
CU_ASSERT(!util::streq((const uint8_t*)"alpha", 4,
(const uint8_t*)"alpha", 5));
CU_ASSERT(!util::streq((const uint8_t*)"alpha", 5,
(const uint8_t*)"alpha", 4));
CU_ASSERT(!util::streq((const uint8_t*)"alpha", 5,
(const uint8_t*)"alphA", 5));
CU_ASSERT(util::streq(nullptr, 0, nullptr, 0));
}
void test_util_inp_strlower(void)
{
std::string a("alPha");
util::inp_strlower(a);
CU_ASSERT("alpha" == a);
a = "ALPHA123BRAVO";
util::inp_strlower(a);
CU_ASSERT("alpha123bravo" == a);
a = "";
util::inp_strlower(a);
CU_ASSERT("" == a);
}
} // namespace shrpx

35
src/util_test.h Normal file
View File

@ -0,0 +1,35 @@
/*
* nghttp2 - HTTP/2.0 C Library
*
* Copyright (c) 2013 Tatsuhiro Tsujikawa
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef UTIL_TEST_H
#define UTIL_TEST_H
namespace shrpx {
void test_util_streq(void);
void test_util_inp_strlower(void);
} // namespace shrpx
#endif // UTIL_TEST_H