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 : namespace kasko {
25 :
26 : 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 E : bool ReadResponseData(HttpResponse *response, char* buffer, size_t* count) {
33 E : size_t content_length_header_value = 0;
34 E : bool has_content_length_header = false;
35 :
36 E : size_t buffer_size = *count;
37 E : *count = 0;
38 :
39 : if (!response->GetContentLength(&has_content_length_header,
40 E : &content_length_header_value)) {
41 i : return false;
42 : }
43 :
44 : do {
45 E : size_t single_read_count = buffer_size - *count;
46 E : if (!response->ReadData(buffer + *count, &single_read_count))
47 E : return false;
48 :
49 E : if (single_read_count == 0)
50 E : break;
51 :
52 E : *count += single_read_count;
53 E : } while (*count < buffer_size);
54 :
55 E : bool has_more_data = false;
56 E : if (!response->HasData(&has_more_data))
57 i : return false;
58 E : if (has_more_data) {
59 E : LOG(ERROR) << "Incoming data exceeds anticipated maximum of " << *count
60 : << " bytes.";
61 E : return false;
62 : }
63 :
64 : if (has_content_length_header &&
65 E : (*count != content_length_header_value)) {
66 E : LOG(ERROR) << "Response body length of " << *count
67 : << " differs from content length header value "
68 : << content_length_header_value;
69 E : return false;
70 : }
71 :
72 E : return true;
73 E : }
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 : bool GetCharsetAndMimeType(HttpResponse* response,
80 : base::string16* charset,
81 E : base::string16* mime_type) {
82 E : base::string16 content_type;
83 E : bool has_content_type = false;
84 E : if (!response->GetContentType(&has_content_type, &content_type))
85 i : return false;
86 :
87 E : if (!has_content_type) {
88 E : charset->clear();
89 E : mime_type->clear();
90 E : return true;
91 : }
92 :
93 E : bool had_charset = false;
94 E : base::string16 boundary;
95 E : ParseContentType(content_type, mime_type, charset, &had_charset, &boundary);
96 E : return true;
97 E : }
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 E : bool ReadResponse(HttpResponse* response, base::string16 *response_body) {
105 E : DCHECK(response);
106 E : DCHECK(response_body);
107 :
108 : // We are only expecting a small identifier string.
109 : char buffer[256];
110 E : size_t total_read = sizeof(buffer);
111 E : if (!ReadResponseData(response, buffer, &total_read)) {
112 : // Interpret the partial body (if any) as ASCII for diagnostic output.
113 E : *response_body = base::string16(buffer, buffer + total_read);
114 E : return false;
115 : }
116 :
117 E : base::string16 charset, mime_type;
118 E : if (!GetCharsetAndMimeType(response, &charset, &mime_type)) {
119 : // Interpret the body as ASCII for diagnostic output.
120 i : *response_body = base::string16(buffer, buffer + total_read);
121 i : return false;
122 : }
123 :
124 E : if (charset.empty() || charset == L"utf-8") {
125 E : *response_body = base::UTF8ToUTF16(base::StringPiece(buffer, total_read));
126 E : } else if (charset == L"utf-16") {
127 : *response_body =
128 : base::string16(reinterpret_cast<const base::char16*>(buffer),
129 E : total_read / sizeof(base::char16));
130 E : } else if (charset == L"iso-8859-1" &&
131 E : base::IsStringASCII(base::StringPiece(buffer, total_read))) {
132 : // Although labeled as latin-1, this string is also valid ASCII.
133 E : *response_body = base::ASCIIToUTF16(base::StringPiece(buffer, total_read));
134 E : } else {
135 E : LOG(ERROR) << "Unexpected charset: " << charset;
136 : // Interpret the body as ASCII for diagnostic output.
137 E : *response_body = base::string16(buffer, buffer + total_read);
138 E : return false;
139 : }
140 :
141 E : 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 : if (mime_type != L"text/html" ||
145 E : response_body->find_first_of(L"<>") != base::string16::npos) {
146 E : LOG(ERROR) << "Unexpected MIME type: " << mime_type;
147 E : return false;
148 : }
149 : }
150 :
151 E : return true;
152 E : }
153 :
154 : } // namespace
155 :
156 : bool SendHttpUpload(HttpAgent* agent,
157 : const base::string16& url,
158 : const std::map<base::string16, base::string16>& parameters,
159 : const std::string& upload_file,
160 : const base::string16& file_part_name,
161 : base::string16* response_body,
162 E : uint16_t* response_code) {
163 E : DCHECK(response_body);
164 E : DCHECK(response_code);
165 :
166 E : base::string16 scheme, host, path;
167 E : uint16_t port = 0;
168 E : if (!DecomposeUrl(url, &scheme, &host, &port, &path)) {
169 E : LOG(ERROR) << "Failed to decompose URL: " << url;
170 E : return false;
171 : }
172 :
173 E : bool secure = false;
174 E : if (scheme == L"https") {
175 E : secure = true;
176 E : } else if (scheme != L"http") {
177 E : LOG(ERROR) << "Invalid scheme in URL: " << url;
178 E : return false;
179 : }
180 :
181 E : base::string16 boundary = GenerateMultipartHttpRequestBoundary();
182 : base::string16 content_type_header =
183 E : GenerateMultipartHttpRequestContentTypeHeader(boundary);
184 :
185 : std::string request_body = GenerateMultipartHttpRequestBody(
186 E : parameters, upload_file, file_part_name, boundary);
187 :
188 : scoped_ptr<HttpResponse> response =
189 E : agent->Post(host, port, path, secure, content_type_header, request_body);
190 E : if (!response) {
191 E : LOG(ERROR) << "Request to " << url << " failed.";
192 E : return false;
193 : }
194 :
195 E : uint16_t status_code = 0;
196 E : if (!response->GetStatusCode(&status_code))
197 E : return false;
198 :
199 E : *response_code = status_code;
200 :
201 E : if (status_code != 200) {
202 E : LOG(ERROR) << "Request to " << url << " failed with HTTP status code "
203 : << status_code;
204 E : return false;
205 : }
206 :
207 E : if (!ReadResponse(response.get(), response_body)) {
208 E : if (response_body->length()) {
209 E : LOG(ERROR) << "Failure while reading response body. Possibly truncated "
210 : "response body: " << *response_body;
211 E : } else {
212 i : LOG(ERROR) << "Failure while reading response body.";
213 : }
214 E : return false;
215 : }
216 :
217 E : return true;
218 E : }
219 :
220 : } // namespace kasko
|