link_header_util.cc 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright 2016 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/link_header_util/link_header_util.h"
  5. #include "base/strings/string_util.h"
  6. #include "net/http/http_util.h"
  7. namespace link_header_util {
  8. namespace {
  9. // A variation of base::StringTokenizer and net::HttpUtil::ValuesIterator.
  10. // Takes the parsing of StringTokenizer and adds support for quoted strings that
  11. // are quoted by matching <> (and does not support escaping in those strings).
  12. // Also has the behavior of ValuesIterator where it strips whitespace from all
  13. // values and only outputs non-empty values.
  14. // Only supports ',' as separator and supports "" and <> as quote chars.
  15. class ValueTokenizer {
  16. public:
  17. ValueTokenizer(std::string::const_iterator begin,
  18. std::string::const_iterator end)
  19. : token_begin_(begin), token_end_(begin), end_(end) {}
  20. std::string::const_iterator token_begin() const { return token_begin_; }
  21. std::string::const_iterator token_end() const { return token_end_; }
  22. bool GetNext() {
  23. while (GetNextInternal()) {
  24. net::HttpUtil::TrimLWS(&token_begin_, &token_end_);
  25. // Only return non-empty values.
  26. if (token_begin_ != token_end_)
  27. return true;
  28. }
  29. return false;
  30. }
  31. private:
  32. // Updates token_begin_ and token_end_ to point to the (possibly empty) next
  33. // token. Returns false if end-of-string was reached first.
  34. bool GetNextInternal() {
  35. // First time this is called token_end_ points to the first character in the
  36. // input. Every other time token_end_ points to the delimiter at the end of
  37. // the last returned token (which could be the end of the string).
  38. // End of string, return false.
  39. if (token_end_ == end_)
  40. return false;
  41. // Skip past the delimiter.
  42. if (*token_end_ == ',')
  43. ++token_end_;
  44. // Make token_begin_ point to the beginning of the next token, and search
  45. // for the end of the token in token_end_.
  46. token_begin_ = token_end_;
  47. // Set to true if we're currently inside a quoted string.
  48. bool in_quote = false;
  49. // Set to true if we're currently inside a quoted string, and have just
  50. // encountered an escape character. In this case a closing quote will be
  51. // ignored.
  52. bool in_escape = false;
  53. // If currently in a quoted string, this is the character that (when not
  54. // escaped) indicates the end of the string.
  55. char quote_close_char = '\0';
  56. // If currently in a quoted string, this is set to true if it is possible to
  57. // escape the closing quote using '\'.
  58. bool quote_allows_escape = false;
  59. while (token_end_ != end_) {
  60. char c = *token_end_;
  61. if (in_quote) {
  62. if (in_escape) {
  63. in_escape = false;
  64. } else if (quote_allows_escape && c == '\\') {
  65. in_escape = true;
  66. } else if (c == quote_close_char) {
  67. in_quote = false;
  68. }
  69. } else {
  70. if (c == ',')
  71. break;
  72. if (c == '"' || c == '<') {
  73. in_quote = true;
  74. quote_close_char = (c == '<' ? '>' : c);
  75. quote_allows_escape = (c != '<');
  76. }
  77. }
  78. ++token_end_;
  79. }
  80. return true;
  81. }
  82. std::string::const_iterator token_begin_;
  83. std::string::const_iterator token_end_;
  84. std::string::const_iterator end_;
  85. };
  86. // Parses the URL part of a Link header. When successful |url_begin| points
  87. // to the beginning of the url, |url_end| points to the end of the url and
  88. // |params_begin| points to the first character after the '>' character at the
  89. // end of the url.
  90. bool ExtractURL(std::string::const_iterator begin,
  91. std::string::const_iterator end,
  92. std::string::const_iterator* url_begin,
  93. std::string::const_iterator* url_end,
  94. std::string::const_iterator* params_begin) {
  95. // Extract the URL part (everything between '<' and first '>' character).
  96. if (*begin != '<')
  97. return false;
  98. ++begin;
  99. *url_begin = begin;
  100. *url_end = std::find(begin, end, '>');
  101. // Fail if we did not find a '>'.
  102. if (*url_end == end)
  103. return false;
  104. *params_begin = *url_end;
  105. // Skip the '>' at the end of the URL.
  106. ++*params_begin;
  107. // Trim whitespace from the URL.
  108. net::HttpUtil::TrimLWS(url_begin, url_end);
  109. return true;
  110. }
  111. } // namespace
  112. std::vector<StringIteratorPair> SplitLinkHeader(const std::string& header) {
  113. std::vector<StringIteratorPair> values;
  114. ValueTokenizer tokenizer(header.begin(), header.end());
  115. while (tokenizer.GetNext()) {
  116. values.push_back(
  117. StringIteratorPair(tokenizer.token_begin(), tokenizer.token_end()));
  118. }
  119. return values;
  120. }
  121. // Parses one link in a link header into its url and parameters.
  122. // A link is of the form "<some-url>; param1=value1; param2=value2".
  123. // Returns false if parsing the link failed, returns true on success. This
  124. // method is more lenient than the RFC. It doesn't fail on things like invalid
  125. // characters in the URL, and also doesn't verify that certain parameters should
  126. // or shouldn't be quoted strings.
  127. // If a parameter occurs more than once in the link, only the first value is
  128. // returned in params as this is the required behavior for all attributes chrome
  129. // currently cares about in link headers.
  130. bool ParseLinkHeaderValue(
  131. std::string::const_iterator begin,
  132. std::string::const_iterator end,
  133. std::string* url,
  134. std::unordered_map<std::string, absl::optional<std::string>>* params) {
  135. // Can't parse an empty string.
  136. if (begin == end)
  137. return false;
  138. // Extract the URL part (everything between '<' and first '>' character).
  139. std::string::const_iterator url_begin;
  140. std::string::const_iterator url_end;
  141. if (!ExtractURL(begin, end, &url_begin, &url_end, &begin))
  142. return false;
  143. *url = std::string(url_begin, url_end);
  144. // Trim any remaining whitespace, and make sure there is a ';' separating
  145. // parameters from the URL.
  146. net::HttpUtil::TrimLWS(&begin, &end);
  147. if (begin != end && *begin != ';')
  148. return false;
  149. // Parse all the parameters.
  150. net::HttpUtil::NameValuePairsIterator params_iterator(
  151. begin, end, ';',
  152. net::HttpUtil::NameValuePairsIterator::Values::NOT_REQUIRED,
  153. net::HttpUtil::NameValuePairsIterator::Quotes::STRICT_QUOTES);
  154. while (params_iterator.GetNext()) {
  155. if (!net::HttpUtil::IsParmName(params_iterator.name_piece()))
  156. return false;
  157. std::string name = base::ToLowerASCII(params_iterator.name_piece());
  158. if (!params_iterator.value_is_quoted() &&
  159. params_iterator.value_piece().empty())
  160. params->insert(std::make_pair(name, absl::nullopt));
  161. else
  162. params->insert(std::make_pair(name, params_iterator.value()));
  163. }
  164. return params_iterator.valid();
  165. }
  166. } // namespace link_header_util