1 : // Copyright 2014 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "base/logging.h"
16 : #include "base/strings/string_util.h"
17 : #include "base/strings/utf_string_conversions.h"
18 :
19 : #include "syzygy/common/com_utils.h"
20 : #include "syzygy/kasko/http_agent.h"
21 : #include "syzygy/kasko/http_response.h"
22 : #include "syzygy/kasko/internet_helpers.h"
23 :
24 m : namespace kasko {
25 :
26 m : namespace {
27 :
28 : // Reads up to |count| bytes of raw response body into |buffer|. Returns true if
29 : // the entire response body is successfully read. Regardless of success or
30 : // failure, |*count| will be assigned the number of bytes read (between 0 and
31 : // the original value of |*count|).
32 m : bool ReadResponseData(HttpResponse *response, char* buffer, size_t* count) {
33 m : size_t content_length_header_value = 0;
34 m : bool has_content_length_header = false;
35 :
36 m : size_t buffer_size = *count;
37 m : *count = 0;
38 :
39 m : if (!response->GetContentLength(&has_content_length_header,
40 m : &content_length_header_value)) {
41 m : return false;
42 m : }
43 :
44 m : do {
45 m : size_t single_read_count = buffer_size - *count;
46 m : if (!response->ReadData(buffer + *count, &single_read_count))
47 m : return false;
48 :
49 m : if (single_read_count == 0)
50 m : break;
51 :
52 m : *count += single_read_count;
53 m : } while (*count < buffer_size);
54 :
55 m : bool has_more_data = false;
56 m : if (!response->HasData(&has_more_data))
57 m : return false;
58 m : if (has_more_data) {
59 m : LOG(ERROR) << "Incoming data exceeds anticipated maximum of " << *count
60 m : << " bytes.";
61 m : return false;
62 m : }
63 :
64 m : if (has_content_length_header &&
65 m : (*count != content_length_header_value)) {
66 m : LOG(ERROR) << "Response body length of " << *count
67 m : << " differs from content length header value "
68 m : << content_length_header_value;
69 m : return false;
70 m : }
71 :
72 m : return true;
73 m : }
74 :
75 : // Reads and parses the Content-Type header from |response| and stores the
76 : // resulting character set and MIME type in |charset| and |mime_type|. |charset|
77 : // and |mime_type| will be empty if they are not present. Returns true if the
78 : // header is absent or present and successfully parsed.
79 m : bool GetCharsetAndMimeType(HttpResponse* response,
80 m : base::string16* charset,
81 m : base::string16* mime_type) {
82 m : base::string16 content_type;
83 m : bool has_content_type = false;
84 m : if (!response->GetContentType(&has_content_type, &content_type))
85 m : return false;
86 :
87 m : if (!has_content_type) {
88 m : charset->clear();
89 m : mime_type->clear();
90 m : return true;
91 m : }
92 :
93 m : bool had_charset = false;
94 m : base::string16 boundary;
95 m : ParseContentType(content_type, mime_type, charset, &had_charset, &boundary);
96 m : return true;
97 m : }
98 :
99 : // Reads the response body and stores it in |response_body|. Does character set
100 : // conversion if necessary. Returns true if the entire response body is
101 : // successfully read. Regardless of success or failure, |*response_body| will
102 : // contain a best effort interpretation of the partially or fully read response
103 : // body.
104 m : bool ReadResponse(HttpResponse* response, base::string16 *response_body) {
105 m : DCHECK(response);
106 m : DCHECK(response_body);
107 :
108 : // We are only expecting a small identifier string.
109 m : char buffer[256];
110 m : size_t total_read = sizeof(buffer);
111 m : if (!ReadResponseData(response, buffer, &total_read)) {
112 : // Interpret the partial body (if any) as ASCII for diagnostic output.
113 m : *response_body = base::string16(buffer, buffer + total_read);
114 m : return false;
115 m : }
116 :
117 m : base::string16 charset, mime_type;
118 m : if (!GetCharsetAndMimeType(response, &charset, &mime_type)) {
119 : // Interpret the body as ASCII for diagnostic output.
120 m : *response_body = base::string16(buffer, buffer + total_read);
121 m : return false;
122 m : }
123 :
124 m : if (charset.empty() || charset == L"utf-8") {
125 m : *response_body = base::UTF8ToUTF16(base::StringPiece(buffer, total_read));
126 m : } else if (charset == L"utf-16") {
127 m : *response_body =
128 m : base::string16(reinterpret_cast<const base::char16*>(buffer),
129 m : total_read / sizeof(base::char16));
130 m : } else if (charset == L"iso-8859-1" &&
131 m : base::IsStringASCII(base::StringPiece(buffer, total_read))) {
132 : // Although labeled as latin-1, this string is also valid ASCII.
133 m : *response_body = base::ASCIIToUTF16(base::StringPiece(buffer, total_read));
134 m : } else {
135 m : LOG(ERROR) << "Unexpected charset: " << charset;
136 : // Interpret the body as ASCII for diagnostic output.
137 m : *response_body = base::string16(buffer, buffer + total_read);
138 m : return false;
139 m : }
140 :
141 m : if (!mime_type.empty() && mime_type != L"text/plain") {
142 : // If the body is labeled text/html but is clearly not HTML we will treat it
143 : // as text/plain.
144 m : if (mime_type != L"text/html" ||
145 m : response_body->find_first_of(L"<>") != base::string16::npos) {
146 m : LOG(ERROR) << "Unexpected MIME type: " << mime_type;
147 m : return false;
148 m : }
149 m : }
150 :
151 m : return true;
152 m : }
153 :
154 m : } // namespace
155 :
156 m : bool SendHttpUpload(HttpAgent* agent,
157 m : const base::string16& url,
158 m : const std::map<base::string16, base::string16>& parameters,
159 m : const std::string& upload_file,
160 m : const base::string16& file_part_name,
161 m : base::string16* response_body,
162 m : uint16_t* response_code) {
163 m : DCHECK(response_body);
164 m : DCHECK(response_code);
165 :
166 m : base::string16 scheme, host, path;
167 m : uint16_t port = 0;
168 m : if (!DecomposeUrl(url, &scheme, &host, &port, &path)) {
169 m : LOG(ERROR) << "Failed to decompose URL: " << url;
170 m : return false;
171 m : }
172 :
173 m : bool secure = false;
174 m : if (scheme == L"https") {
175 m : secure = true;
176 m : } else if (scheme != L"http") {
177 m : LOG(ERROR) << "Invalid scheme in URL: " << url;
178 m : return false;
179 m : }
180 :
181 m : base::string16 boundary = GenerateMultipartHttpRequestBoundary();
182 m : base::string16 content_type_header =
183 m : GenerateMultipartHttpRequestContentTypeHeader(boundary);
184 :
185 m : std::string request_body = GenerateMultipartHttpRequestBody(
186 m : parameters, upload_file, file_part_name, boundary);
187 :
188 m : std::unique_ptr<HttpResponse> response =
189 m : agent->Post(host, port, path, secure, content_type_header, request_body);
190 m : if (!response) {
191 m : LOG(ERROR) << "Request to " << url << " failed.";
192 m : return false;
193 m : }
194 :
195 m : uint16_t status_code = 0;
196 m : if (!response->GetStatusCode(&status_code))
197 m : return false;
198 :
199 m : *response_code = status_code;
200 :
201 m : if (status_code != 200) {
202 m : LOG(ERROR) << "Request to " << url << " failed with HTTP status code "
203 m : << status_code;
204 m : return false;
205 m : }
206 :
207 m : if (!ReadResponse(response.get(), response_body)) {
208 m : if (response_body->length()) {
209 m : LOG(ERROR) << "Failure while reading response body. Possibly truncated "
210 m : "response body: " << *response_body;
211 m : } else {
212 m : LOG(ERROR) << "Failure while reading response body.";
213 m : }
214 m : return false;
215 m : }
216 :
217 m : return true;
218 m : }
219 :
220 m : } // namespace kasko
|