1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  */
9 
10 #include <desktop/crashreport.hxx>
11 #include <rtl/bootstrap.hxx>
12 #include <osl/file.hxx>
13 #include <comphelper/processfactory.hxx>
14 #include <ucbhelper/proxydecider.hxx>
15 #include <unotools/bootstrap.hxx>
16 #include <o3tl/char16_t2wchar_t.hxx>
17 #include <desktop/minidump.hxx>
18 
19 #include <config_version.h>
20 #include <config_folders.h>
21 
22 #include <string>
23 
24 
25 #if HAVE_FEATURE_BREAKPAD
26 
27 #include <fstream>
28 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
29 #include <client/linux/handler/exception_handler.h>
30 #elif defined WNT
31 #if defined __clang__
32 #pragma clang diagnostic push
33 #pragma clang diagnostic ignored "-Wmicrosoft-enum-value"
34 #endif
35 #include <client/windows/handler/exception_handler.h>
36 #if defined __clang__
37 #pragma clang diagnostic pop
38 #endif
39 #include <locale>
40 #include <codecvt>
41 #endif
42 
43 osl::Mutex CrashReporter::maMutex;
44 std::unique_ptr<google_breakpad::ExceptionHandler> CrashReporter::mpExceptionHandler;
45 bool CrashReporter::mbInit = false;
46 CrashReporter::vmaKeyValues CrashReporter::maKeyValues;
47 
48 
49 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
dumpCallback(const google_breakpad::MinidumpDescriptor & descriptor,void *,bool succeeded)50 static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* /*context*/, bool succeeded)
51 {
52     CrashReporter::addKeyValue("DumpFile", OStringToOUString(descriptor.path(), RTL_TEXTENCODING_UTF8), CrashReporter::Write);
53     SAL_WARN("desktop", "minidump generated: " << descriptor.path());
54 
55     return succeeded;
56 }
57 #elif defined WNT
dumpCallback(const wchar_t * path,const wchar_t * id,void *,EXCEPTION_POINTERS *,MDRawAssertionInfo *,bool succeeded)58 static bool dumpCallback(const wchar_t* path, const wchar_t* id,
59     void* /*context*/, EXCEPTION_POINTERS* /*exinfo*/,
60     MDRawAssertionInfo* /*assertion*/,
61     bool succeeded)
62 {
63     // TODO: moggi: can we avoid this conversion
64 #ifdef _MSC_VER
65 #pragma warning (disable: 4996)
66 #endif
67     std::wstring_convert<std::codecvt_utf8<wchar_t>> conv1;
68     std::string aPath = conv1.to_bytes(std::wstring(path)) + conv1.to_bytes(std::wstring(id)) + ".dmp";
69     CrashReporter::addKeyValue("DumpFile", OStringToOUString(aPath.c_str(), RTL_TEXTENCODING_UTF8), CrashReporter::AddItem);
70     CrashReporter::addKeyValue("GDIHandles", OUString::number(::GetGuiResources(::GetCurrentProcess(), GR_GDIOBJECTS)), CrashReporter::Write);
71     SAL_WARN("desktop", "minidump generated: " << aPath);
72     return succeeded;
73 }
74 #endif
75 
76 
writeToFile(std::ios_base::openmode Openmode)77 void CrashReporter::writeToFile(std::ios_base::openmode Openmode)
78 {
79     std::ofstream ini_file(getIniFileName(), Openmode);
80 
81     for (auto& keyValue : maKeyValues)
82     {
83         ini_file << OUStringToOString(keyValue.first, RTL_TEXTENCODING_UTF8) << "=";
84         ini_file << OUStringToOString(keyValue.second, RTL_TEXTENCODING_UTF8) << "\n";
85     }
86 
87     maKeyValues.clear();
88     ini_file.close();
89 }
90 
addKeyValue(const OUString & rKey,const OUString & rValue,tAddKeyHandling AddKeyHandling)91 void CrashReporter::addKeyValue(const OUString& rKey, const OUString& rValue, tAddKeyHandling AddKeyHandling)
92 {
93     osl::MutexGuard aGuard(maMutex);
94 
95     if (IsDumpEnable())
96     {
97         if (!rKey.isEmpty())
98             maKeyValues.push_back(mpair(rKey, rValue));
99 
100         if (AddKeyHandling != AddItem)
101         {
102             if (mbInit)
103                 writeToFile(std::ios_base::app);
104             else if (AddKeyHandling == Create)
105                 writeCommonInfo();
106         }
107     }
108 }
109 
writeCommonInfo()110 void CrashReporter::writeCommonInfo()
111 {
112     ucbhelper::InternetProxyDecider proxy_decider(::comphelper::getProcessComponentContext());
113 
114     const OUString protocol = "https";
115     const OUString url = "crashreport.libreoffice.org";
116     const sal_Int32 port = 443;
117 
118     const ucbhelper::InternetProxyServer proxy_server = proxy_decider.getProxy(protocol, url, port);
119 
120     // save the new Keys
121     vmaKeyValues atlast = maKeyValues;
122     // clear the keys, the following Keys should be at the begin
123     maKeyValues.clear();
124 
125     // limit the amount of code that needs to be executed before the crash reporting
126     addKeyValue("ProductName", "LibreOffice", AddItem);
127     addKeyValue("Version", LIBO_VERSION_DOTTED, AddItem);
128     addKeyValue("BuildID", utl::Bootstrap::getBuildIdData(""), AddItem);
129     addKeyValue("URL", protocol + "://" + url + "/submit/", AddItem);
130 
131     if (proxy_server.aName != OUString())
132     {
133         addKeyValue("Proxy", proxy_server.aName + ":" + OUString::number(proxy_server.nPort), AddItem);
134     }
135 
136     // write the new keys at the end
137     maKeyValues.insert(maKeyValues.end(), atlast.begin(), atlast.end());
138 
139     mbInit = true;
140 
141     writeToFile(std::ios_base::trunc);
142 
143     updateMinidumpLocation();
144 }
145 
146 
147 namespace {
148 
getCrashDirectory()149 OUString getCrashDirectory()
150 {
151     OUString aCrashURL;
152     rtl::Bootstrap::get("CrashDirectory", aCrashURL);
153     // Need to convert to URL in case of user-defined path
154     osl::FileBase::getFileURLFromSystemPath(aCrashURL, aCrashURL);
155 
156     if (aCrashURL.isEmpty()) { // Fall back to user profile
157         aCrashURL = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/crash/";
158         rtl::Bootstrap::expandMacros(aCrashURL);
159     }
160 
161     if (!aCrashURL.endsWith("/"))
162         aCrashURL += "/";
163 
164     osl::Directory::create(aCrashURL);
165     OUString aCrashPath;
166     osl::FileBase::getSystemPathFromFileURL(aCrashURL, aCrashPath);
167     return aCrashPath;
168 }
169 
170 }
171 
updateMinidumpLocation()172 void CrashReporter::updateMinidumpLocation()
173 {
174 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
175     OUString aURL = getCrashDirectory();
176     OString aOStringUrl = OUStringToOString(aURL, RTL_TEXTENCODING_UTF8);
177     google_breakpad::MinidumpDescriptor descriptor(aOStringUrl.getStr());
178     mpExceptionHandler->set_minidump_descriptor(descriptor);
179 #elif defined WNT
180     OUString aURL = getCrashDirectory();
181     mpExceptionHandler->set_dump_path(o3tl::toW(aURL.getStr()));
182 #endif
183 }
184 
crashReportInfoExists()185 bool CrashReporter::crashReportInfoExists()
186 {
187     static bool first = true;
188     static bool InfoExist = false;
189 
190     if (first)
191     {
192         first = false;
193         InfoExist = crashreport::readConfig(CrashReporter::getIniFileName(), nullptr);
194     }
195 
196     return InfoExist;
197 }
198 
readSendConfig(std::string & response)199 bool CrashReporter::readSendConfig(std::string& response)
200 {
201     return crashreport::readConfig(CrashReporter::getIniFileName(), &response);
202 }
203 
installExceptionHandler()204 void CrashReporter::installExceptionHandler()
205 {
206     if (!IsDumpEnable())
207         return;
208 #if defined( UNX ) && !defined MACOSX && !defined IOS && !defined ANDROID
209     google_breakpad::MinidumpDescriptor descriptor("/tmp");
210     mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(descriptor, nullptr, dumpCallback, nullptr, true, -1);
211 #elif defined WNT
212     mpExceptionHandler = std::make_unique<google_breakpad::ExceptionHandler>(L".", nullptr, dumpCallback, nullptr, google_breakpad::ExceptionHandler::HANDLER_ALL);
213 #endif
214 }
215 
removeExceptionHandler()216 void CrashReporter::removeExceptionHandler()
217 {
218     mpExceptionHandler.reset();
219 }
220 
221 
222 
IsDumpEnable()223 bool CrashReporter::IsDumpEnable()
224 {
225     OUString sToken;
226     OString  sEnvVar(std::getenv("CRASH_DUMP_ENABLE"));
227     bool     bEnable = true;   // default, always on
228     // read configuration item 'CrashDumpEnable' -> bool on/off
229     if (rtl::Bootstrap::get("CrashDumpEnable", sToken) && sEnvVar.isEmpty())
230     {
231         bEnable = sToken.toBoolean();
232     }
233 
234     return bEnable;
235 }
236 
237 
getIniFileName()238 std::string CrashReporter::getIniFileName()
239 {
240     OUString url = getCrashDirectory() + "dump.ini";
241     OString aUrl = OUStringToOString(url, RTL_TEXTENCODING_UTF8);
242     std::string aRet(aUrl.getStr());
243     return aRet;
244 }
245 
246 
247 #endif //HAVE_FEATURE_BREAKPAD
248 
249 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
250