blob: 780c3fd5eff0a0a8723a47bf935a0da3702f5714 [file] [log] [blame]
Andreea Costinas90b71642020-06-12 10:18:25 +02001// Copyright 2020 The Chromium OS 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
5#include "system-proxy/http_util.h"
6
7#include <array>
8#include <string_view>
9
Andreea Costinascc4d54e2020-10-19 15:46:25 +020010#include <curl/curl.h>
11
Andreea Costinas90b71642020-06-12 10:18:25 +020012#include <base/strings/stringprintf.h>
13#include <base/strings/string_split.h>
14#include <base/strings/string_tokenizer.h>
Andreea Costinascc4d54e2020-10-19 15:46:25 +020015#include <base/strings/string_util.h>
Andreea Costinas90b71642020-06-12 10:18:25 +020016
17namespace {
18// The elements in this array are used to identify the end of a HTTP header
19// which should be an empty line. Note: all HTTP header lines end with CRLF.
20// RFC7230, section 3.5 allow LF (without CR) as a valid end of header. HTTP
21// connect requests don't have a body so end of header is end of request.
22static const std::array<std::string, 2> kValidHttpHeaderEnd = {"\r\n\n",
23 "\r\n\r\n"};
24constexpr char kConnectMethod[] = "CONNECT";
25constexpr char kProxyAuthenticate[] = "Proxy-Authenticate:";
26const std::string_view kRealm = "realm=";
27} // namespace
28
29namespace system_proxy {
30
31bool IsEndingWithHttpEmptyLine(const base::StringPiece& http_header_line) {
32 for (const auto& header_end : kValidHttpHeaderEnd) {
33 if (http_header_line.size() > header_end.size() &&
34 std::memcmp(header_end.data(),
35 http_header_line.data() + http_header_line.size() -
36 header_end.size(),
37 header_end.size()) == 0) {
38 return true;
39 }
40 }
41 return false;
42}
43
Andreea Costinas435851b2020-05-25 14:18:41 +020044bool ExtractHTTPRequest(const std::vector<char>& input,
45 std::vector<char>* out_http_request,
46 std::vector<char>* out_remaining_data) {
47 for (const auto& header_end : kValidHttpHeaderEnd) {
48 auto it = std::search(input.begin(), input.end(), header_end.c_str(),
49 header_end.c_str() + header_end.length());
50 if (it == input.end())
51 continue;
52 it += header_end.length();
53 *out_http_request = {input.begin(), it};
54 *out_remaining_data = {it, input.end()};
55 return true;
56 }
57 return false;
58}
59
Andreea Costinas90b71642020-06-12 10:18:25 +020060std::string GetUriAuthorityFromHttpHeader(
61 const base::StringPiece& http_request) {
62 // Request-Line ends with CRLF (RFC2616, section 5.1).
Andreea Costinas435851b2020-05-25 14:18:41 +020063 size_t i = http_request.find("\r\n");
Andreea Costinas90b71642020-06-12 10:18:25 +020064 if (i == base::StringPiece::npos)
65 return std::string();
66 // Elements are delimited by non-breaking space (SP).
67 auto pieces =
68 base::SplitString(http_request.substr(0, i), " ", base::TRIM_WHITESPACE,
69 base::SPLIT_WANT_NONEMPTY);
70 // Request-Line has the format: Method SP Request-URI SP HTTP-Version CRLF.
71 if (pieces.size() < 3)
72 return std::string();
73 if (pieces[0] != kConnectMethod)
74 return std::string();
75
76 return pieces[1];
77}
78
79SchemeRealmPairList ParseAuthChallenge(const base::StringPiece& http_request) {
80 SchemeRealmPairList scheme_realm_pairs;
81 std::string scheme;
82 std::string realm;
83 std::vector<base::StringPiece> header_lines = base::SplitStringPiece(
84 http_request, "\r\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
85
86 for (const auto& line : header_lines) {
87 const std::string request_line = line.as_string();
88 base::StringTokenizer tok_challenge(request_line, " ");
89 tok_challenge.GetNext();
90 if (tok_challenge.token() != kProxyAuthenticate) {
91 continue;
92 }
93
94 tok_challenge.GetNext();
95 scheme = tok_challenge.token();
96 realm = std::string();
97 // Depending on the challenge scheme, the challenge can contain a
98 // comma-separated list of authenticatio parameters. See RFC7235,
99 // section 4.3.
100 base::StringTokenizer tok_realm(tok_challenge.token_end(),
101 request_line.end(), ",");
102 tok_realm.set_quote_chars("\"");
103 while (tok_realm.GetNext()) {
104 int pos = tok_realm.token().find(kRealm);
105 if (pos != std::string::npos) {
106 realm = tok_realm.token().substr(pos + kRealm.size());
107 }
108 }
109 scheme_realm_pairs.push_back(std::make_pair(scheme, realm));
110 }
111
112 return scheme_realm_pairs;
113}
Andreea Costinas90b71642020-06-12 10:18:25 +0200114} // namespace system_proxy