diff --git a/src/http2.cc b/src/http2.cc index 9acf1862..68587216 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -864,6 +864,29 @@ bool check_link_param_empty(const char *first, const char *last, } } // namespace +namespace { +// Returns true if link-param consists of only parmname, and it +// matches string [pat, pat + patlen). +bool check_link_param_without_value(const char *first, const char *last, + const char *pat, size_t patlen) { + if (first + patlen > last) { + return false; + } + + if (first + patlen == last) { + return std::equal(pat, pat + patlen, first, util::CaseCmp()); + } + + switch (*(first + patlen)) { + case ';': + case ',': + return std::equal(pat, pat + patlen, first, util::CaseCmp()); + } + + return false; +} +} // namespace + namespace { std::pair parse_next_link_header_once(const char *first, const char *last) { @@ -900,99 +923,109 @@ parse_next_link_header_once(const char *first, const char *last) { } // we expect link-param - // rel can take several relations using quoted form. - static constexpr char PLP[] = "rel=\""; - static constexpr size_t PLPLEN = sizeof(PLP) - 1; + if (!ign) { + // rel can take several relations using quoted form. + static constexpr char PLP[] = "rel=\""; + static constexpr size_t PLPLEN = sizeof(PLP) - 1; - static constexpr char PLT[] = "preload"; - static constexpr size_t PLTLEN = sizeof(PLT) - 1; - if (first + PLPLEN < last && *(first + PLPLEN - 1) == '"' && - std::equal(PLP, PLP + PLPLEN, first, util::CaseCmp())) { - // we have to search preload in whitespace separated list: - // rel="preload something http://example.org/foo" - first += PLPLEN; - auto start = first; - for (; first != last;) { - if (*first != ' ' && *first != '"') { + static constexpr char PLT[] = "preload"; + static constexpr size_t PLTLEN = sizeof(PLT) - 1; + if (first + PLPLEN < last && *(first + PLPLEN - 1) == '"' && + std::equal(PLP, PLP + PLPLEN, first, util::CaseCmp())) { + // we have to search preload in whitespace separated list: + // rel="preload something http://example.org/foo" + first += PLPLEN; + auto start = first; + for (; first != last;) { + if (*first != ' ' && *first != '"') { + ++first; + continue; + } + + if (start == first) { + return {{{nullptr, nullptr}}, last}; + } + + if (!ok && start + PLTLEN == first && + std::equal(PLT, PLT + PLTLEN, start, util::CaseCmp())) { + ok = true; + } + + if (*first == '"') { + break; + } + first = skip_lws(first, last); + start = first; + } + if (first == last) { + return {{{nullptr, nullptr}}, first}; + } + assert(*first == '"'); + ++first; + if (first == last || *first == ',') { + goto almost_done; + } + if (*first == ';') { ++first; + // parse next link-param continue; } - - if (start == first) { - return {{{nullptr, nullptr}}, last}; + return {{{nullptr, nullptr}}, last}; + } + // we are only interested in rel=preload parameter. Others are + // simply skipped. + static constexpr char PL[] = "rel=preload"; + static constexpr size_t PLLEN = sizeof(PL) - 1; + if (first + PLLEN == last) { + if (std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { + // ok = true; + // this is the end of sequence + return {{{url_first, url_last}}, last}; } - - if (!ok && start + PLTLEN == first && - std::equal(PLT, PLT + PLTLEN, start, util::CaseCmp())) { + } else if (first + PLLEN + 1 <= last) { + switch (*(first + PLLEN)) { + case ',': + if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { + break; + } + // ok = true; + // skip including ',' + first += PLLEN + 1; + return {{{url_first, url_last}}, first}; + case ';': + if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { + break; + } ok = true; + // skip including ';' + first += PLLEN + 1; + // continue parse next link-param + continue; } + } + // we have to reject URI if we have nonempty anchor parameter. + static constexpr char ANCHOR[] = "anchor="; + static constexpr size_t ANCHORLEN = sizeof(ANCHOR) - 1; + if (!ign && !check_link_param_empty(first, last, ANCHOR, ANCHORLEN)) { + ign = true; + } - if (*first == '"') { - break; - } - first = skip_lws(first, last); - start = first; + // reject URI if we have non-empty loadpolicy. This could be + // tightened up to just pick up "next" or "insert". + static constexpr char LOADPOLICY[] = "loadpolicy="; + static constexpr size_t LOADPOLICYLEN = sizeof(LOADPOLICY) - 1; + if (!ign && + !check_link_param_empty(first, last, LOADPOLICY, LOADPOLICYLEN)) { + ign = true; } - if (first == last) { - return {{{nullptr, nullptr}}, first}; - } - assert(*first == '"'); - ++first; - if (first == last || *first == ',') { - goto almost_done; - } - if (*first == ';') { - ++first; - // parse next link-param - continue; - } - return {{{nullptr, nullptr}}, last}; - } - // we are only interested in rel=preload parameter. Others are - // simply skipped. - static constexpr char PL[] = "rel=preload"; - static constexpr size_t PLLEN = sizeof(PL) - 1; - if (first + PLLEN == last) { - if (std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { - // ok = true; - // this is the end of sequence - return {{{url_first, url_last}}, last}; - } - } else if (first + PLLEN + 1 <= last) { - switch (*(first + PLLEN)) { - case ',': - if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { - break; - } - // ok = true; - // skip including ',' - first += PLLEN + 1; - return {{{url_first, url_last}}, first}; - case ';': - if (!std::equal(PL, PL + PLLEN, first, util::CaseCmp())) { - break; - } - ok = true; - // skip including ';' - first += PLLEN + 1; - // continue parse next link-param - continue; - } - } - // we have to reject URI if we have nonempty anchor parameter. - static constexpr char ANCHOR[] = "anchor="; - static constexpr size_t ANCHORLEN = sizeof(ANCHOR) - 1; - if (!ign && !check_link_param_empty(first, last, ANCHOR, ANCHORLEN)) { - ign = true; - } - // reject URI if we have non-empty loadpolicy. This could be - // tightened up to just pick up "next" or "insert". - static constexpr char LOADPOLICY[] = "loadpolicy="; - static constexpr size_t LOADPOLICYLEN = sizeof(LOADPOLICY) - 1; - if (!ign && - !check_link_param_empty(first, last, LOADPOLICY, LOADPOLICYLEN)) { - ign = true; + // reject URI if we have nopush attribute. + static constexpr char NOPUSH[] = "nopush"; + static constexpr size_t NOPUSHLEN = str_size(NOPUSH); + if (!ign && + check_link_param_without_value(first, last, NOPUSH, NOPUSHLEN)) { + ign = true; + } } auto param_first = first; diff --git a/src/http2_test.cc b/src/http2_test.cc index 34f96461..46653686 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -625,6 +625,38 @@ void test_http2_parse_link_header(void) { CU_ASSERT(std::make_pair(&s[36], &s[39]) == res[0].uri); CU_ASSERT(std::make_pair(&s[42 + 14], &s[42 + 17]) == res[1].uri); } + { + // nopush at the end of input + constexpr char s[] = "; rel=preload; nopush"; + auto res = http2::parse_link_header(s, str_size(s)); + CU_ASSERT(0 == res.size()); + } + { + // nopush followed by ';' + constexpr char s[] = "; rel=preload; nopush; foo"; + auto res = http2::parse_link_header(s, str_size(s)); + CU_ASSERT(0 == res.size()); + } + { + // nopush followed by ',' + constexpr char s[] = "; nopush; rel=preload"; + auto res = http2::parse_link_header(s, str_size(s)); + CU_ASSERT(0 == res.size()); + } + { + // string whose prefix is nopush + constexpr char s[] = "; nopushyes; rel=preload"; + auto res = http2::parse_link_header(s, str_size(s)); + CU_ASSERT(1 == res.size()); + CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri); + } + { + // rel=preload twice + constexpr char s[] = "; rel=preload; rel=preload"; + auto res = http2::parse_link_header(s, str_size(s)); + CU_ASSERT(1 == res.size()); + CU_ASSERT(std::make_pair(&s[1], &s[4]) == res[0].uri); + } } void test_http2_path_join(void) {