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/minidump.hxx>
11 #include <sal/log.hxx>
12
13 #include <map>
14 #include <fstream>
15 #include <sstream>
16 #include <string>
17
18 #include <curl/curl.h>
19
20 #include <systools/curlinit.hxx>
21
22 #ifdef _WIN32
23 #include <memory>
24 #include <windows.h>
25 #endif
26
27 const char kUserAgent[] = "Breakpad/1.0 (Linux)";
28
readStrings(std::istream & file)29 static std::map<std::string, std::string> readStrings(std::istream& file)
30 {
31 std::map<std::string, std::string> parameters;
32
33 // when file is not readable, the status eof would not be set
34 // better test of state is okay
35 while (file)
36 {
37 std::string line;
38 std::getline(file, line);
39 int sep = line.find('=');
40 if (sep >= 0)
41 {
42 std::string key = line.substr(0, sep);
43 std::string value = line.substr(sep + 1);
44 parameters[key] = value;
45 }
46 }
47
48 return parameters;
49 }
50
51 // Callback to get the response data from server.
WriteCallback(void const * ptr,size_t size,size_t nmemb,void * userp)52 static size_t WriteCallback(void const *ptr, size_t size,
53 size_t nmemb, void *userp)
54 {
55 if (!userp)
56 return 0;
57
58 std::string* response = static_cast<std::string *>(userp);
59 size_t real_size = size * nmemb;
60 response->append(static_cast<char const *>(ptr), real_size);
61 return real_size;
62 }
63
getProperty(const std::string & key,std::string & value,std::map<std::string,std::string> & parameters)64 static void getProperty(const std::string& key, std::string& value,
65 std::map<std::string, std::string>& parameters)
66 {
67 auto itr = parameters.find(key);
68 if (itr != parameters.end())
69 {
70 value = itr->second;
71 parameters.erase(itr);
72 }
73 }
74
generate_json(const std::map<std::string,std::string> & parameters)75 static std::string generate_json(const std::map<std::string, std::string>& parameters)
76 {
77 std::ostringstream stream;
78 stream << "{\n";
79 bool first = true;
80 for (auto itr = parameters.begin(), itrEnd = parameters.end(); itr != itrEnd; ++itr)
81 {
82 if (!first)
83 {
84 stream << ",\n";
85 }
86 first = false;
87 stream << "\"" << itr->first << "\": \"" << itr->second << "\"";
88 }
89 stream << "\n}";
90
91 return stream.str();
92 }
93
uploadContent(std::map<std::string,std::string> & parameters,std::string & response)94 static bool uploadContent(std::map<std::string, std::string>& parameters, std::string& response)
95 {
96 CURL* curl = curl_easy_init();
97 if (!curl)
98 return false;
99
100 ::InitCurl_easy(curl);
101
102 std::string proxy, proxy_user_pwd, ca_certificate_file, file, url, version;
103
104 getProperty("Proxy", proxy, parameters);
105 getProperty("ProxyUserPW", proxy_user_pwd, parameters);
106 getProperty("CAFile", ca_certificate_file, parameters);
107
108 getProperty("DumpFile", file, parameters);
109 getProperty("URL", url, parameters);
110 getProperty("Version", version, parameters);
111 if (url.empty())
112 return false;
113
114 if (file.empty())
115 return false;
116
117 if (version.empty())
118 return false;
119
120 curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
121 curl_easy_setopt(curl, CURLOPT_USERAGENT, kUserAgent);
122 // Set proxy information if necessary.
123 if (!proxy.empty())
124 {
125 curl_easy_setopt(curl, CURLOPT_PROXY, proxy.c_str());
126
127 curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANYSAFE);
128
129 if (!proxy_user_pwd.empty())
130 curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, proxy_user_pwd.c_str());
131 else
132 curl_easy_setopt(curl, CURLOPT_PROXYUSERPWD, ":");
133 }
134
135 if (!ca_certificate_file.empty())
136 curl_easy_setopt(curl, CURLOPT_CAINFO, ca_certificate_file.c_str());
137
138 curl_mime* mime = curl_mime_init(curl);
139 std::string additional_data = generate_json(parameters);
140 curl_mimepart* part = curl_mime_addpart(mime);
141 curl_mime_name(part, "AdditionalData");
142 curl_mime_data(part, additional_data.c_str(), CURL_ZERO_TERMINATED);
143
144 part = curl_mime_addpart(mime);
145 curl_mime_name(part, "Version");
146 curl_mime_data(part, version.c_str(), CURL_ZERO_TERMINATED);
147
148 part = curl_mime_addpart(mime);
149 curl_mime_name(part, "upload_file_minidump");
150 curl_mime_filedata(part, file.c_str());
151
152 curl_easy_setopt(curl, CURLOPT_MIMEPOST, mime);
153
154
155 // Disable 100-continue header.
156 char buf[] = "Expect:";
157 curl_slist* headerlist = nullptr;
158 headerlist = curl_slist_append(headerlist, buf);
159 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
160
161 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
162 std::string response_body;
163 curl_easy_setopt(curl, CURLOPT_WRITEDATA,
164 static_cast<void *>(&response_body));
165
166 // Fail if 400+ is returned from the web server.
167 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
168
169 CURLcode cc = curl_easy_perform(curl);
170 long response_code;
171 curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
172 SAL_WARN_IF(cc != CURLE_OK, "desktop",
173 "Failed to send http request to " <<
174 url.c_str() <<
175 ", error: " <<
176 curl_easy_strerror(cc));
177
178 if (headerlist != nullptr)
179 {
180 curl_slist_free_all(headerlist);
181 }
182
183 response = response_body;
184
185 if( CURLE_OK != cc )
186 {
187 if (response.empty())
188 {
189 response = curl_easy_strerror(cc);
190 }
191 return false;
192 }
193
194 return true;
195 }
196
197 namespace crashreport {
198
readConfig(const std::string & iniPath,std::string * response)199 bool readConfig(const std::string& iniPath, std::string * response)
200 {
201 #if defined _WIN32
202 std::wstring iniPathW;
203 const int nChars = MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, nullptr, 0);
204 auto buf = std::make_unique<wchar_t[]>(nChars);
205 if (MultiByteToWideChar(CP_UTF8, 0, iniPath.c_str(), -1, buf.get(), nChars) != 0)
206 iniPathW = buf.get();
207
208 std::ifstream file = iniPathW.empty() ? std::ifstream(iniPath) : std::ifstream(iniPathW);
209 #else
210 std::ifstream file(iniPath);
211 #endif
212 std::map<std::string, std::string> parameters = readStrings(file);
213
214 // make sure that at least the mandatory parameters are in there
215 if (parameters.find("DumpFile") == parameters.end())
216 {
217 if(response != nullptr)
218 *response = "ini file needs to contain a key DumpFile!";
219 return false;
220 }
221
222 if (parameters.find("Version") == parameters.end())
223 {
224 if (response != nullptr)
225 *response = "ini file needs to contain a key Version!";
226 return false;
227 }
228
229 if (parameters.find("URL") == parameters.end())
230 {
231 if (response != nullptr)
232 *response = "ini file needs to contain a key URL!";
233 return false;
234 }
235
236 if (response != nullptr)
237 return uploadContent(parameters, *response);
238
239 return true;
240 }
241
242 }
243
244 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
245