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