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