1 // Copyright (c) 2011 The Mozilla Foundation
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are
6 // met:
7 //
8 // * Redistributions of source code must retain the above copyright
9 // notice, this list of conditions and the following disclaimer.
10 // * Redistributions in binary form must reproduce the above
11 // copyright notice, this list of conditions and the following disclaimer
12 // in the documentation and/or other materials provided with the
13 // distribution.
14 // * Neither the name of The Mozilla Foundation nor the names of its
15 // contributors may be used to endorse or promote products derived from
16 // this software without specific prior written permission.
17 //
18 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30 #include "http_symbol_supplier.h"
31
32 #include <algorithm>
33
34 #include <sys/stat.h>
35 #include <sys/time.h>
36 #include <unistd.h>
37
38 #include <errno.h>
39
40 #include "google_breakpad/processor/code_module.h"
41 #include "google_breakpad/processor/system_info.h"
42 #include "processor/logging.h"
43 #include "processor/pathname_stripper.h"
44
45 #ifdef _WIN32
46 # include <direct.h>
47 # include "zlib.h"
48 #else
49 # include <curl/curl.h>
50 #endif
51
52 namespace breakpad_extra {
53
54 using google_breakpad::CodeModule;
55 using google_breakpad::PathnameStripper;
56 using google_breakpad::SystemInfo;
57
file_exists(const string & file_name)58 static bool file_exists(const string& file_name) {
59 struct stat sb;
60 return stat(file_name.c_str(), &sb) == 0;
61 }
62
dirname(const string & path)63 static string dirname(const string& path) {
64 size_t i = path.rfind('/');
65 if (i == string::npos) {
66 return path;
67 }
68 return path.substr(0, i);
69 }
70
71 #ifdef _WIN32
72 # define mkdir_port(d) _mkdir(d)
73 #else
74 # define mkdir_port(d) mkdir(d, 0755)
75 #endif
76
mkdirs(const string & file)77 static bool mkdirs(const string& file) {
78 vector<string> dirs;
79 string dir = dirname(file);
80 while (!file_exists(dir)) {
81 dirs.push_back(dir);
82 string new_dir = dirname(dir);
83 if (new_dir == dir || dir.empty()) {
84 break;
85 }
86 dir = new_dir;
87 }
88 for (auto d = dirs.rbegin(); d != dirs.rend(); ++d) {
89 if (mkdir_port(d->c_str()) != 0) {
90 BPLOG(ERROR) << "Error creating " << *d << ": " << errno;
91 return false;
92 }
93 }
94 return true;
95 }
96
vector_from(const string & front,const vector<string> & rest)97 static vector<string> vector_from(const string& front,
98 const vector<string>& rest) {
99 vector<string> vec(1, front);
100 std::copy(rest.begin(), rest.end(), std::back_inserter(vec));
101 return vec;
102 }
103
HTTPSymbolSupplier(const vector<string> & server_urls,const string & cache_path,const vector<string> & local_paths,const string & tmp_path)104 HTTPSymbolSupplier::HTTPSymbolSupplier(const vector<string>& server_urls,
105 const string& cache_path,
106 const vector<string>& local_paths,
107 const string& tmp_path)
108 : SimpleSymbolSupplier(vector_from(cache_path, local_paths)),
109 server_urls_(server_urls),
110 cache_path_(cache_path),
111 tmp_path_(tmp_path) {
112 #ifdef _WIN32
113 session_ = InternetOpenW(L"Breakpad/1.0", INTERNET_OPEN_TYPE_PRECONFIG,
114 nullptr, nullptr, 0);
115 if (!session_) {
116 BPLOG(INFO) << "HTTPSymbolSupplier: InternetOpenW: Error: "
117 << GetLastError();
118 }
119 #else
120 session_ = curl_easy_init();
121 #endif
122 for (auto i = server_urls_.begin(); i < server_urls_.end(); ++i) {
123 if (*(i->end() - 1) != '/') {
124 i->push_back('/');
125 }
126 }
127 // Remove any trailing slash on tmp_path.
128 if (!tmp_path_.empty() && *(tmp_path_.end() - 1) == '/') {
129 tmp_path_.erase(tmp_path_.end() - 1);
130 }
131 }
132
~HTTPSymbolSupplier()133 HTTPSymbolSupplier::~HTTPSymbolSupplier() {
134 #ifdef _WIN32
135 InternetCloseHandle(session_);
136 #else
137 curl_easy_cleanup(session_);
138 #endif
139 }
140
StoreSymbolStats(const CodeModule * module,const SymbolStats & stats)141 void HTTPSymbolSupplier::StoreSymbolStats(const CodeModule* module,
142 const SymbolStats& stats) {
143 const auto& key =
144 std::make_pair(module->debug_file(), module->debug_identifier());
145 if (symbol_stats_.find(key) == symbol_stats_.end()) {
146 symbol_stats_[key] = stats;
147 }
148 }
149
StoreCacheHit(const CodeModule * module)150 void HTTPSymbolSupplier::StoreCacheHit(const CodeModule* module) {
151 SymbolStats stats = {true, 0.0f};
152 StoreSymbolStats(module, stats);
153 }
154
StoreCacheMiss(const CodeModule * module,float fetch_time)155 void HTTPSymbolSupplier::StoreCacheMiss(const CodeModule* module,
156 float fetch_time) {
157 SymbolStats stats = {false, fetch_time};
158 StoreSymbolStats(module, stats);
159 }
160
GetSymbolFile(const CodeModule * module,const SystemInfo * system_info,string * symbol_file)161 SymbolSupplier::SymbolResult HTTPSymbolSupplier::GetSymbolFile(
162 const CodeModule* module, const SystemInfo* system_info,
163 string* symbol_file) {
164 SymbolSupplier::SymbolResult res =
165 SimpleSymbolSupplier::GetSymbolFile(module, system_info, symbol_file);
166 if (res != SymbolSupplier::NOT_FOUND) {
167 StoreCacheHit(module);
168 return res;
169 }
170
171 if (!FetchSymbolFile(module, system_info)) {
172 return SymbolSupplier::NOT_FOUND;
173 }
174
175 return SimpleSymbolSupplier::GetSymbolFile(module, system_info, symbol_file);
176 }
177
GetSymbolFile(const CodeModule * module,const SystemInfo * system_info,string * symbol_file,string * symbol_data)178 SymbolSupplier::SymbolResult HTTPSymbolSupplier::GetSymbolFile(
179 const CodeModule* module, const SystemInfo* system_info,
180 string* symbol_file, string* symbol_data) {
181 SymbolSupplier::SymbolResult res = SimpleSymbolSupplier::GetSymbolFile(
182 module, system_info, symbol_file, symbol_data);
183 if (res != SymbolSupplier::NOT_FOUND) {
184 StoreCacheHit(module);
185 return res;
186 }
187
188 if (!FetchSymbolFile(module, system_info)) {
189 return SymbolSupplier::NOT_FOUND;
190 }
191
192 return SimpleSymbolSupplier::GetSymbolFile(module, system_info, symbol_file,
193 symbol_data);
194 }
195
GetCStringSymbolData(const CodeModule * module,const SystemInfo * system_info,string * symbol_file,char ** symbol_data,size_t * size)196 SymbolSupplier::SymbolResult HTTPSymbolSupplier::GetCStringSymbolData(
197 const CodeModule* module, const SystemInfo* system_info,
198 string* symbol_file, char** symbol_data, size_t* size) {
199 SymbolSupplier::SymbolResult res = SimpleSymbolSupplier::GetCStringSymbolData(
200 module, system_info, symbol_file, symbol_data, size);
201 if (res != SymbolSupplier::NOT_FOUND) {
202 StoreCacheHit(module);
203 return res;
204 }
205
206 if (!FetchSymbolFile(module, system_info)) {
207 return SymbolSupplier::NOT_FOUND;
208 }
209
210 return SimpleSymbolSupplier::GetCStringSymbolData(
211 module, system_info, symbol_file, symbol_data, size);
212 }
213
214 namespace {
JoinPath(const string & path,const string & sub)215 string JoinPath(const string& path, const string& sub) {
216 if (path[path.length() - 1] == '/') {
217 return path + sub;
218 }
219 return path + "/" + sub;
220 }
221
222 #ifdef _WIN32
URLEncode(HINTERNET session,const string & url)223 string URLEncode(HINTERNET session, const string& url) {
224 string out(url.length() * 3, '\0');
225 DWORD length = out.length();
226 ;
227 if (InternetCanonicalizeUrlA(url.c_str(), &out[0], &length, 0)) {
228 out.resize(length);
229 return out;
230 }
231 return url;
232 }
233
JoinURL(HINTERNET session,const string & url,const string & sub)234 string JoinURL(HINTERNET session, const string& url, const string& sub) {
235 return url + "/" + URLEncode(session, sub);
236 }
237
FetchURLToFile(HINTERNET session,const string & url,const string & file,const string & tmp_path,float * fetch_time)238 bool FetchURLToFile(HINTERNET session, const string& url, const string& file,
239 const string& tmp_path, float* fetch_time) {
240 *fetch_time = 0.0f;
241
242 URL_COMPONENTSA comps = {};
243 comps.dwStructSize = sizeof(URL_COMPONENTSA);
244 comps.dwHostNameLength = static_cast<DWORD>(-1);
245 comps.dwSchemeLength = static_cast<DWORD>(-1);
246 comps.dwUrlPathLength = static_cast<DWORD>(-1);
247 comps.dwExtraInfoLength = static_cast<DWORD>(-1);
248
249 if (!InternetCrackUrlA(url.c_str(), 0, 0, &comps)) {
250 BPLOG(INFO) << "HTTPSymbolSupplier: InternetCrackUrlA: Error: "
251 << GetLastError();
252 return false;
253 }
254
255 DWORD start = GetTickCount();
256 string host(comps.lpszHostName, comps.dwHostNameLength);
257 string path(comps.lpszUrlPath, comps.dwUrlPathLength);
258 HINTERNET conn = InternetConnectA(session, host.c_str(), comps.nPort, nullptr,
259 nullptr, INTERNET_SERVICE_HTTP, 0, 0);
260 if (!conn) {
261 BPLOG(INFO) << "HTTPSymbolSupplier: HttpOpenRequest: Error: "
262 << GetLastError();
263 return false;
264 }
265
266 HINTERNET req = HttpOpenRequestA(conn, "GET", path.c_str(), nullptr, nullptr,
267 nullptr, INTERNET_FLAG_NO_COOKIES, 0);
268 if (!req) {
269 BPLOG(INFO) << "HTTPSymbolSupplier: HttpSendRequest: Error: "
270 << GetLastError();
271 InternetCloseHandle(conn);
272 return false;
273 }
274
275 DWORD status = 0;
276 DWORD size = sizeof(status);
277 if (!HttpSendRequest(req, nullptr, 0, nullptr, 0)) {
278 BPLOG(INFO) << "HTTPSymbolSupplier: HttpSendRequest: Error: "
279 << GetLastError();
280 InternetCloseHandle(req);
281 InternetCloseHandle(conn);
282 return false;
283 }
284
285 if (!HttpQueryInfo(req, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
286 &status, &size, nullptr)) {
287 BPLOG(INFO) << "HTTPSymbolSupplier: HttpQueryInfo: Error: "
288 << GetLastError();
289 InternetCloseHandle(req);
290 InternetCloseHandle(conn);
291 return false;
292 }
293
294 bool do_ungzip = false;
295 // See if the content is gzipped and we need to decompress it.
296 char encoding[32];
297 DWORD encoding_size = sizeof(encoding);
298 if (HttpQueryInfo(req, HTTP_QUERY_CONTENT_ENCODING, encoding, &encoding_size,
299 nullptr) &&
300 strcmp(encoding, "gzip") == 0) {
301 do_ungzip = true;
302 BPLOG(INFO) << "HTTPSymbolSupplier: need to manually un-gzip";
303 }
304
305 bool success = false;
306 if (status == 200) {
307 DWORD bytes = 0;
308 string tempfile(MAX_PATH, '\0');
309 if (GetTempFileNameA(tmp_path.c_str(), "sym", 1, &tempfile[0]) != 0) {
310 tempfile.resize(strlen(tempfile.c_str()));
311 BPLOG(INFO) << "HTTPSymbolSupplier: symbol exists, saving to "
312 << tempfile;
313 FILE* f = fopen(tempfile.c_str(), "wb");
314 while (InternetQueryDataAvailable(req, &bytes, 0, 0) && bytes > 0) {
315 vector<uint8_t> data(bytes);
316 DWORD downloaded = 0;
317 if (InternetReadFile(req, &data[0], bytes, &downloaded)) {
318 fwrite(&data[0], downloaded, 1, f);
319 }
320 }
321 fclose(f);
322 if (do_ungzip) {
323 string gzfile = tempfile + ".gz";
324 MoveFileA(tempfile.c_str(), gzfile.c_str());
325 uint8_t buffer[4096];
326 gzFile g = gzopen(gzfile.c_str(), "r");
327 FILE* f = fopen(tempfile.c_str(), "w");
328 if (g && f) {
329 while (true) {
330 int bytes_read = gzread(g, buffer, sizeof(buffer));
331 if (bytes_read > 0) {
332 fwrite(buffer, bytes_read, 1, f);
333 } else {
334 if (bytes_read == 0) {
335 success = true;
336 }
337 break;
338 }
339 }
340 }
341 if (g) {
342 gzclose(g);
343 }
344 if (f) {
345 fclose(f);
346 }
347 if (!success) {
348 BPLOG(INFO) << "HTTPSymbolSupplier: failed to decompress " << file;
349 }
350 } else {
351 success = true;
352 }
353
354 *fetch_time = GetTickCount() - start;
355
356 if (success) {
357 success = mkdirs(file);
358 if (!success) {
359 BPLOG(INFO) << "HTTPSymbolSupplier: failed to create directories "
360 << "for " << file;
361 } else {
362 success = MoveFileA(tempfile.c_str(), file.c_str());
363 if (!success) {
364 BPLOG(INFO) << "HTTPSymbolSupplier: failed to rename file";
365 unlink(tempfile.c_str());
366 }
367 }
368 }
369 }
370 } else {
371 BPLOG(INFO) << "HTTPSymbolSupplier: HTTP response code: " << status;
372 }
373
374 InternetCloseHandle(req);
375 InternetCloseHandle(conn);
376 return success;
377 }
378
379 #else // !_WIN32
URLEncode(CURL * curl,const string & url)380 string URLEncode(CURL* curl, const string& url) {
381 char* escaped_url_raw =
382 curl_easy_escape(curl, const_cast<char*>(url.c_str()), url.length());
383 if (not escaped_url_raw) {
384 BPLOG(INFO) << "HTTPSymbolSupplier: couldn't escape URL: " << url;
385 return "";
386 }
387 string escaped_url(escaped_url_raw);
388 curl_free(escaped_url_raw);
389 return escaped_url;
390 }
391
JoinURL(CURL * curl,const string & url,const string & sub)392 string JoinURL(CURL* curl, const string& url, const string& sub) {
393 return url + "/" + URLEncode(curl, sub);
394 }
395
FetchURLToFile(CURL * curl,const string & url,const string & file,const string & tmp_path,float * fetch_time)396 bool FetchURLToFile(CURL* curl, const string& url, const string& file,
397 const string& tmp_path, float* fetch_time) {
398 *fetch_time = 0.0f;
399
400 string tempfile = JoinPath(tmp_path, "symbolXXXXXX");
401 int fd = mkstemp(&tempfile[0]);
402 if (fd == -1) {
403 return false;
404 }
405 FILE* f = fdopen(fd, "w");
406
407 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
408 curl_easy_setopt(curl, CURLOPT_ENCODING, "");
409 curl_easy_setopt(curl, CURLOPT_STDERR, stderr);
410 curl_easy_setopt(curl, CURLOPT_WRITEDATA, f);
411 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
412
413 struct timeval t1, t2;
414 gettimeofday(&t1, nullptr);
415 bool result = false;
416 long retcode = -1;
417 if (curl_easy_perform(curl) != 0) {
418 BPLOG(INFO) << "HTTPSymbolSupplier: curl_easy_perform failed";
419 } else if (curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &retcode) != 0) {
420 BPLOG(INFO) << "HTTPSymbolSupplier: curl_easy_getinfo failed";
421 } else if (retcode != 200) {
422 BPLOG(INFO) << "HTTPSymbolSupplier: HTTP response code: " << retcode;
423 } else {
424 BPLOG(INFO) << "HTTPSymbolSupplier: symbol exists, saving to " << file;
425 result = true;
426 }
427 gettimeofday(&t2, nullptr);
428 *fetch_time =
429 (t2.tv_sec - t1.tv_sec) * 1000.0 + (t2.tv_usec - t1.tv_usec) / 1000.0;
430 fclose(f);
431 close(fd);
432
433 if (result) {
434 result = mkdirs(file);
435 if (!result) {
436 BPLOG(INFO) << "HTTPSymbolSupplier: failed to create directories for "
437 << file;
438 }
439 }
440 if (result) {
441 result = 0 == rename(tempfile.c_str(), file.c_str());
442 if (!result) {
443 int e = errno;
444 BPLOG(INFO) << "HTTPSymbolSupplier: failed to rename file, errno=" << e;
445 }
446 }
447
448 if (!result) {
449 unlink(tempfile.c_str());
450 }
451
452 return result;
453 }
454 #endif
455 } // namespace
456
FetchSymbolFile(const CodeModule * module,const SystemInfo * system_info)457 bool HTTPSymbolSupplier::FetchSymbolFile(const CodeModule* module,
458 const SystemInfo* system_info) {
459 if (!session_) {
460 return false;
461 }
462 // Copied from simple_symbol_supplier.cc
463 string debug_file_name = PathnameStripper::File(module->debug_file());
464 if (debug_file_name.empty()) {
465 return false;
466 }
467 string path = debug_file_name;
468 string url = URLEncode(session_, debug_file_name);
469
470 // Append the identifier as a directory name.
471 string identifier = module->debug_identifier();
472 if (identifier.empty()) {
473 return false;
474 }
475 path = JoinPath(path, identifier);
476 url = JoinURL(session_, url, identifier);
477
478 // See if we already attempted to fetch this symbol file.
479 if (SymbolWasError(module, system_info)) {
480 return false;
481 }
482
483 // Transform the debug file name into one ending in .sym. If the existing
484 // name ends in .pdb, strip the .pdb. Otherwise, add .sym to the non-.pdb
485 // name.
486 string debug_file_extension;
487 if (debug_file_name.size() > 4) {
488 debug_file_extension = debug_file_name.substr(debug_file_name.size() - 4);
489 }
490 std::transform(debug_file_extension.begin(), debug_file_extension.end(),
491 debug_file_extension.begin(), tolower);
492 if (debug_file_extension == ".pdb") {
493 debug_file_name = debug_file_name.substr(0, debug_file_name.size() - 4);
494 }
495
496 debug_file_name += ".sym";
497 path = JoinPath(path, debug_file_name);
498 url = JoinURL(session_, url, debug_file_name);
499
500 string full_path = JoinPath(cache_path_, path);
501
502 bool result = false;
503 for (auto server_url = server_urls_.begin(); server_url < server_urls_.end();
504 ++server_url) {
505 string full_url = *server_url + url;
506 float fetch_time;
507 BPLOG(INFO) << "HTTPSymbolSupplier: querying " << full_url;
508 if (FetchURLToFile(session_, full_url, full_path, tmp_path_, &fetch_time)) {
509 StoreCacheMiss(module, fetch_time);
510 result = true;
511 break;
512 }
513 }
514 if (!result) {
515 error_symbols_.insert(
516 std::make_pair(module->debug_file(), module->debug_identifier()));
517 }
518 return result;
519 }
520
GetStats(const CodeModule * module,SymbolStats * stats) const521 bool HTTPSymbolSupplier::GetStats(const CodeModule* module,
522 SymbolStats* stats) const {
523 const auto& found = symbol_stats_.find(
524 std::make_pair(module->debug_file(), module->debug_identifier()));
525 if (found == symbol_stats_.end()) {
526 return false;
527 }
528
529 *stats = found->second;
530 return true;
531 }
532
SymbolWasError(const CodeModule * module,const SystemInfo * system_info)533 bool HTTPSymbolSupplier::SymbolWasError(const CodeModule* module,
534 const SystemInfo* system_info) {
535 return error_symbols_.find(std::make_pair(module->debug_file(),
536 module->debug_identifier())) !=
537 error_symbols_.end();
538 }
539
540 } // namespace breakpad_extra
541