xref: /aosp_15_r20/external/google-breakpad/src/tools/windows/converter_exe/http_download.cc (revision 9712c20fc9bbfbac4935993a2ca0b3958c5adad2)
1 // Copyright 2019 Google LLC
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //     * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 //     * Neither the name of Google LLC nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>  // Must come first
31 #endif
32 
33 #include <assert.h>
34 #include <stdio.h>
35 #include <Windows.h>
36 #include <WinInet.h>
37 
38 #include <vector>
39 
40 #include "tools/windows/converter_exe/http_download.h"
41 #include "tools/windows/converter_exe/winhttp_client.h"
42 #include "tools/windows/converter_exe/wininet_client.h"
43 
44 namespace crash {
45 static const std::vector<char>::size_type kVectorChunkSize = 4096;  // 4 KB
46 
47 using std::vector;
48 
49 // Class that atuo closes the contained HttpHandle when the object
50 // goes out of scope.
51 class AutoHttpHandle {
52  public:
AutoHttpHandle()53   AutoHttpHandle() : handle_(NULL) {}
AutoHttpHandle(HttpHandle handle)54   explicit AutoHttpHandle(HttpHandle handle) : handle_(handle) {}
~AutoHttpHandle()55   ~AutoHttpHandle() {
56     if (handle_) {
57       InternetCloseHandle(handle_);
58     }
59   }
60 
get()61   HttpHandle get() { return handle_; }
get_handle_addr()62   HttpHandle* get_handle_addr () { return &handle_; }
63 
64  private:
65   HttpHandle handle_;
66 };
67 
68 // Template class for auto releasing the contained pointer when
69 // the object goes out of scope.
70 template<typename T>
71 class AutoPtr {
72  public:
AutoPtr(T * ptr)73   explicit AutoPtr(T* ptr) : ptr_(ptr) {}
~AutoPtr()74   ~AutoPtr() {
75     if (ptr_) {
76       delete ptr_;
77     }
78   }
79 
get()80   T* get() { return ptr_; }
operator ->()81   T* operator -> () { return ptr_; }
82 
83  private:
84   T* ptr_;
85 };
86 
87 // CheckParameters ensures that the parameters in |parameters| are safe for
88 // use in an HTTP URL.  Returns true if they are, false if unsafe characters
89 // are present.
CheckParameters(const map<wstring,wstring> * parameters)90 static bool CheckParameters(const map<wstring, wstring>* parameters) {
91   for (map<wstring, wstring>::const_iterator iterator = parameters->begin();
92        iterator != parameters->end();
93        ++iterator) {
94     const wstring& key = iterator->first;
95     if (key.empty()) {
96       // Disallow empty parameter names.
97       return false;
98     }
99     for (unsigned int i = 0; i < key.size(); ++i) {
100       wchar_t c = key[i];
101       if (c < 32 || c == '"' || c == '?' || c == '&' || c > 127) {
102         return false;
103       }
104     }
105 
106     const wstring& value = iterator->second;
107     for (unsigned int i = 0; i < value.size(); ++i) {
108       wchar_t c = value[i];
109       if (c < 32 || c == '"' || c == '?' || c == '&' || c > 127) {
110         return false;
111       }
112     }
113   }
114 
115   return true;
116 }
117 
CreateHttpClient(const wchar_t * url)118 HttpClient* HTTPDownload::CreateHttpClient(const wchar_t* url) {
119   const TCHAR* kHttpApiPolicyEnvironmentVariable = TEXT("USE_WINHTTP");
120   TCHAR buffer[2] = {0};
121   HttpClient* http_client = NULL;
122 
123   if (::GetEnvironmentVariable(kHttpApiPolicyEnvironmentVariable,
124                                buffer,
125                                sizeof(buffer)/sizeof(buffer[0])) > 0) {
126     fprintf(stdout,
127             "Environment variable [%ws] is set, use WinHttp\n",
128             kHttpApiPolicyEnvironmentVariable);
129     http_client = CreateWinHttpClient(url);
130     if (http_client == NULL) {
131       fprintf(stderr, "WinHttpClient not created, Is the protocol HTTPS? "
132                       "Fall back to WinInet API.\n");
133     }
134   } else {
135     fprintf(stderr,
136             "Environment variable [%ws] is NOT set, use WinInet API\n",
137             kHttpApiPolicyEnvironmentVariable);
138   }
139 
140   if (http_client == NULL) {
141     return CreateWinInetClient(url);
142   }
143 
144   return http_client;
145 }
146 
147 // static
Download(const wstring & url,const map<wstring,wstring> * parameters,string * content,int * status_code)148 bool HTTPDownload::Download(const wstring& url,
149                             const map<wstring, wstring>* parameters,
150                             string *content, int *status_code) {
151   assert(content);
152   AutoPtr<HttpClient> http_client(CreateHttpClient(url.c_str()));
153 
154   if (!http_client.get()) {
155     fprintf(stderr, "Failed to create any http client.\n");
156     return false;
157   }
158 
159   if (status_code) {
160     *status_code = 0;
161   }
162 
163   wchar_t scheme[16] = {0};
164   wchar_t host[256] = {0};
165   wchar_t path[256] = {0};
166   int port = 0;
167   if (!http_client->CrackUrl(url.c_str(),
168                              0,
169                              scheme,
170                              sizeof(scheme)/sizeof(scheme[0]),
171                              host,
172                              sizeof(host)/sizeof(host[0]),
173                              path,
174                              sizeof(path)/sizeof(path[0]),
175                              &port)) {
176     fprintf(stderr,
177             "HTTPDownload::Download: InternetCrackUrl: error %lu for %ws\n",
178             GetLastError(), url.c_str());
179     return false;
180   }
181 
182   bool secure = false;
183   if (_wcsicmp(scheme, L"https") == 0) {
184     secure = true;
185   } else if (wcscmp(scheme, L"http") != 0) {
186     fprintf(stderr,
187             "HTTPDownload::Download: scheme must be http or https for %ws\n",
188             url.c_str());
189     return false;
190   }
191 
192   AutoHttpHandle internet;
193   if (!http_client->Open(NULL,  // user agent
194                          HttpClient::ACCESS_TYPE_PRECONFIG,
195                          NULL,  // proxy name
196                          NULL,  // proxy bypass
197                          internet.get_handle_addr())) {
198     fprintf(stderr,
199             "HTTPDownload::Download: Open: error %lu for %ws\n",
200             GetLastError(), url.c_str());
201     return false;
202   }
203 
204   AutoHttpHandle connection;
205   if (!http_client->Connect(internet.get(),
206                             host,
207                             port,
208                             connection.get_handle_addr())) {
209     fprintf(stderr,
210             "HTTPDownload::Download: InternetConnect: error %lu for %ws\n",
211             GetLastError(), url.c_str());
212     return false;
213   }
214 
215   wstring request_string = path;
216   if (parameters) {
217     // TODO(mmentovai): escape bad characters in parameters instead of
218     // forbidding them.
219     if (!CheckParameters(parameters)) {
220       fprintf(stderr,
221               "HTTPDownload::Download: invalid characters in parameters\n");
222       return false;
223     }
224 
225     bool added_parameter = false;
226     for (map<wstring, wstring>::const_iterator iterator = parameters->begin();
227          iterator != parameters->end();
228          ++iterator) {
229       request_string.append(added_parameter ? L"&" : L"?");
230       request_string.append(iterator->first);
231       request_string.append(L"=");
232       request_string.append(iterator->second);
233       added_parameter = true;
234     }
235   }
236 
237   AutoHttpHandle request;
238   if (!http_client->OpenRequest(connection.get(),
239                                 L"GET",
240                                 request_string.c_str(),
241                                 NULL,    // version
242                                 NULL,    // referer
243                                 secure,
244                                 request.get_handle_addr())) {
245     fprintf(stderr,
246             "HttpClient::OpenRequest: error %lu for %ws, request: %ws\n",
247             GetLastError(), url.c_str(), request_string.c_str());
248     return false;
249   }
250 
251   if (!http_client->SendRequest(request.get(), NULL, 0)) {
252     fprintf(stderr,
253             "HttpClient::SendRequest: error %lu for %ws\n",
254             GetLastError(), url.c_str());
255     return false;
256   }
257 
258   if (!http_client->ReceiveResponse(request.get())) {
259     fprintf(stderr,
260             "HttpClient::ReceiveResponse: error %lu for %ws\n",
261             GetLastError(), url.c_str());
262     return false;
263   }
264 
265   int http_status = 0;
266   if (!http_client->GetHttpStatusCode(request.get(), &http_status)) {
267     fprintf(stderr,
268             "HttpClient::GetHttpStatusCode: error %lu for %ws\n",
269             GetLastError(), url.c_str());
270     return false;
271   }
272   if (http_status != 200) {
273     fprintf(stderr,
274             "HTTPDownload::Download: HTTP status code %d for %ws\n",
275             http_status, url.c_str());
276     return false;
277   }
278 
279   DWORD content_length = 0;
280   vector<char>::size_type buffer_size = 0;
281   http_client->GetContentLength(request.get(), &content_length);
282   if (content_length == HttpClient::kUnknownContentLength) {
283     buffer_size = kVectorChunkSize;
284   } else {
285     buffer_size = content_length;
286   }
287 
288   if (content_length != 0) {
289     vector<char> response_buffer = vector<char>(buffer_size+1);
290     DWORD size_read;
291     DWORD total_read = 0;
292     bool read_result;
293     do {
294       if (content_length == HttpClient::kUnknownContentLength
295           && buffer_size == total_read) {
296         // The content length wasn't specified in the response header, so we
297         // have to keep growing the buffer until we're done reading.
298         buffer_size += kVectorChunkSize;
299         response_buffer.resize(buffer_size);
300       }
301       read_result = !!http_client->ReadData(
302           request.get(),
303           &response_buffer[total_read],
304           static_cast<DWORD>(buffer_size) - total_read,
305           &size_read);
306       total_read += size_read;
307     } while (read_result && (size_read != 0));
308 
309     if (!read_result) {
310       fprintf(stderr,
311               "HttpClient::ReadData: error %lu for %ws\n",
312               GetLastError(),
313               url.c_str());
314       return false;
315     } else if (size_read != 0) {
316       fprintf(stderr,
317               "HttpClient::ReadData: error %lu/%lu for %ws\n",
318               total_read,
319               content_length,
320               url.c_str());
321       return false;
322     }
323     content->assign(&response_buffer[0], total_read);
324   } else {
325     content->clear();
326   }
327   return true;
328 }
329 
330 }  // namespace crash
331