1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include <string>
7
8 #include <direct.h>
9 #include <windows.h>
10 #include <wininet.h>
11
12 namespace PingSender {
13
14 using std::string;
15
16 /**
17 * A helper to automatically close internet handles when they go out of scope
18 */
19 class ScopedHInternet {
20 public:
ScopedHInternet(HINTERNET handle)21 explicit ScopedHInternet(HINTERNET handle) : mHandle(handle) {}
22
~ScopedHInternet()23 ~ScopedHInternet() {
24 if (mHandle) {
25 InternetCloseHandle(mHandle);
26 }
27 }
28
empty()29 bool empty() { return (mHandle == nullptr); }
get()30 HINTERNET get() { return mHandle; }
31
32 private:
33 HINTERNET mHandle;
34 };
35
36 const size_t kUrlComponentsSchemeLength = 256;
37 const size_t kUrlComponentsHostLength = 256;
38 const size_t kUrlComponentsPathLength = 256;
39
40 /**
41 * Post the specified payload to a telemetry server
42 *
43 * @param url The URL of the telemetry server
44 * @param payload The ping payload
45 */
Post(const string & url,const string & payload)46 bool Post(const string& url, const string& payload) {
47 char scheme[kUrlComponentsSchemeLength];
48 char host[kUrlComponentsHostLength];
49 char path[kUrlComponentsPathLength];
50
51 URL_COMPONENTS components = {};
52 components.dwStructSize = sizeof(components);
53 components.lpszScheme = scheme;
54 components.dwSchemeLength = kUrlComponentsSchemeLength;
55 components.lpszHostName = host;
56 components.dwHostNameLength = kUrlComponentsHostLength;
57 components.lpszUrlPath = path;
58 components.dwUrlPathLength = kUrlComponentsPathLength;
59
60 if (!InternetCrackUrl(url.c_str(), url.size(), 0, &components)) {
61 PINGSENDER_LOG("ERROR: Could not separate the URL components\n");
62 return false;
63 }
64
65 if (!IsValidDestination(host)) {
66 PINGSENDER_LOG("ERROR: Invalid destination host '%s'\n", host);
67 return false;
68 }
69
70 ScopedHInternet internet(InternetOpen(kUserAgent,
71 INTERNET_OPEN_TYPE_PRECONFIG,
72 /* lpszProxyName */ NULL,
73 /* lpszProxyBypass */ NULL,
74 /* dwFlags */ 0));
75
76 if (internet.empty()) {
77 PINGSENDER_LOG("ERROR: Could not open wininet internet handle\n");
78 return false;
79 }
80
81 DWORD timeout = static_cast<DWORD>(kConnectionTimeoutMs);
82 bool rv = InternetSetOption(internet.get(), INTERNET_OPTION_CONNECT_TIMEOUT,
83 &timeout, sizeof(timeout));
84 if (!rv) {
85 PINGSENDER_LOG("ERROR: Could not set the connection timeout\n");
86 return false;
87 }
88
89 ScopedHInternet connection(
90 InternetConnect(internet.get(), host, components.nPort,
91 /* lpszUsername */ NULL,
92 /* lpszPassword */ NULL, INTERNET_SERVICE_HTTP,
93 /* dwFlags */ 0,
94 /* dwContext */ NULL));
95
96 if (connection.empty()) {
97 PINGSENDER_LOG("ERROR: Could not connect\n");
98 return false;
99 }
100
101 DWORD flags = ((strcmp(scheme, "https") == 0) ? INTERNET_FLAG_SECURE : 0) |
102 INTERNET_FLAG_NO_COOKIES;
103 ScopedHInternet request(HttpOpenRequest(connection.get(), "POST", path,
104 /* lpszVersion */ NULL,
105 /* lpszReferer */ NULL,
106 /* lplpszAcceptTypes */ NULL, flags,
107 /* dwContext */ NULL));
108
109 if (request.empty()) {
110 PINGSENDER_LOG("ERROR: Could not open HTTP POST request\n");
111 return false;
112 }
113
114 // Build a string containing all the headers.
115 std::string headers = GenerateDateHeader() + "\r\n";
116 headers += kCustomVersionHeader;
117 headers += "\r\n";
118 headers += kContentEncodingHeader;
119
120 rv = HttpSendRequest(request.get(), headers.c_str(), -1L,
121 (LPVOID)payload.c_str(), payload.size());
122 if (!rv) {
123 PINGSENDER_LOG("ERROR: Could not execute HTTP POST request\n");
124 return false;
125 }
126
127 // HttpSendRequest doesn't fail if we hit an HTTP error, so manually check
128 // for errors. Please note that this is not needed on the Linux/MacOS version
129 // of the pingsender, as libcurl already automatically fails on HTTP errors.
130 DWORD statusCode = 0;
131 DWORD bufferLength = sizeof(DWORD);
132 rv = HttpQueryInfo(
133 request.get(),
134 /* dwInfoLevel */ HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER,
135 /* lpvBuffer */ &statusCode,
136 /* lpdwBufferLength */ &bufferLength,
137 /* lpdwIndex */ NULL);
138 if (!rv) {
139 PINGSENDER_LOG("ERROR: Could not get the HTTP status code\n");
140 return false;
141 }
142
143 if (statusCode != 200) {
144 PINGSENDER_LOG("ERROR: Error submitting the HTTP request: code %lu\n",
145 statusCode);
146 return false;
147 }
148
149 return rv;
150 }
151
ChangeCurrentWorkingDirectory(const string & pingPath)152 void ChangeCurrentWorkingDirectory(const string& pingPath) {
153 char fullPath[MAX_PATH + 1] = {};
154 if (!_fullpath(fullPath, pingPath.c_str(), sizeof(fullPath))) {
155 PINGSENDER_LOG("Could not build the full path to the ping\n");
156 return;
157 }
158
159 char drive[_MAX_DRIVE] = {};
160 char dir[_MAX_DIR] = {};
161 if (_splitpath_s(fullPath, drive, sizeof(drive), dir, sizeof(dir), nullptr, 0,
162 nullptr, 0)) {
163 PINGSENDER_LOG("Could not split the current path\n");
164 return;
165 }
166
167 char cwd[MAX_PATH + 1] = {};
168 if (_makepath_s(cwd, sizeof(cwd), drive, dir, nullptr, nullptr)) {
169 PINGSENDER_LOG("Could not assemble the path for the new cwd\n");
170 return;
171 }
172
173 if (_chdir(cwd) == -1) {
174 PINGSENDER_LOG("Could not change the current working directory\n");
175 }
176 }
177
178 } // namespace PingSender
179