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 "nsWindowsShellService.h"
7 #include "nsIServiceManager.h"
8 #include "nsICategoryManager.h"
9 #include "nsNativeCharsetUtils.h"
10 #include "nsIPrefService.h"
11 #include "windows.h"
12 #include "shellapi.h"
13 #include "nsIFile.h"
14 #include "nsDirectoryServiceDefs.h"
15 #include "nsUnicharUtils.h"
16 #include "nsComponentManagerUtils.h"
17 #include "nsServiceManagerUtils.h"
18 #include "nsIProperties.h"
19 #include "nsString.h"
20
21 #ifdef _WIN32_WINNT
22 # undef _WIN32_WINNT
23 #endif
24 #define _WIN32_WINNT 0x0600
25 #define INITGUID
26 #include <shlobj.h>
27
28 #include <mbstring.h>
29
30 #ifndef MAX_BUF
31 # define MAX_BUF 4096
32 #endif
33
34 #define REG_FAILED(val) (val != ERROR_SUCCESS)
35
NS_IMPL_ISUPPORTS(nsWindowsShellService,nsIShellService,nsIToolkitShellService)36 NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIShellService,
37 nsIToolkitShellService)
38
39 static nsresult OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName,
40 HKEY* aKey) {
41 const nsString& flatName = PromiseFlatString(aKeyName);
42
43 DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey);
44 switch (res) {
45 case ERROR_SUCCESS:
46 break;
47 case ERROR_ACCESS_DENIED:
48 return NS_ERROR_FILE_ACCESS_DENIED;
49 case ERROR_FILE_NOT_FOUND:
50 return NS_ERROR_NOT_AVAILABLE;
51 }
52
53 return NS_OK;
54 }
55
56 ///////////////////////////////////////////////////////////////////////////////
57 // Default Mail Registry Settings
58 ///////////////////////////////////////////////////////////////////////////////
59
60 typedef enum {
61 NO_SUBSTITUTION = 0x00,
62 APP_PATH_SUBSTITUTION = 0x01
63 } SettingFlags;
64
65 // APP_REG_NAME_MAIL and APP_REG_NAME_NEWS should be kept in synch with
66 // AppRegNameMail and AppRegNameNews in the installer file: defines.nsi.in
67 #define APP_REG_NAME_MAIL L"Thunderbird"
68 #define APP_REG_NAME_NEWS L"Thunderbird (News)"
69 #define APP_REG_NAME_CALENDAR L"Thunderbird (Calendar)"
70 #define CLS_EML "ThunderbirdEML"
71 #define CLS_MAILTOURL "Thunderbird.Url.mailto"
72 #define CLS_MIDURL "Thunderbird.Url.mid"
73 #define CLS_NEWSURL "Thunderbird.Url.news"
74 #define CLS_FEEDURL "Thunderbird.Url.feed"
75 #define CLS_WEBCALURL "Thunderbird.Url.webcal"
76 #define CLS_ICS "ThunderbirdICS"
77 #define SOP "\\shell\\open\\command"
78 #define VAL_OPEN "\"%APPPATH%\" \"%1\""
79 #define VAL_MAIL_OPEN "\"%APPPATH%\" -osint -mail \"%1\""
80 #define VAL_COMPOSE_OPEN "\"%APPPATH%\" -osint -compose \"%1\""
81
82 #define MAKE_KEY_NAME1(PREFIX, MID) PREFIX MID
83
84 static SETTING gMailSettings[] = {
85 // File Extension Class
86 {".eml", "", CLS_EML, NO_SUBSTITUTION},
87
88 // File Extension Class
89 {MAKE_KEY_NAME1(CLS_EML, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
90
91 // Protocol Handler Class - for Vista and above
92 {MAKE_KEY_NAME1(CLS_MAILTOURL, SOP), "", VAL_COMPOSE_OPEN,
93 APP_PATH_SUBSTITUTION},
94 {MAKE_KEY_NAME1(CLS_MIDURL, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
95
96 // Protocol Handlers
97 {MAKE_KEY_NAME1("mailto", SOP), "", VAL_COMPOSE_OPEN,
98 APP_PATH_SUBSTITUTION},
99 {MAKE_KEY_NAME1("mid", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
100 };
101
102 static SETTING gNewsSettings[] = {
103 // Protocol Handler Class - for Vista and above
104 {MAKE_KEY_NAME1(CLS_NEWSURL, SOP), "", VAL_MAIL_OPEN,
105 APP_PATH_SUBSTITUTION},
106
107 // Protocol Handlers
108 {MAKE_KEY_NAME1("news", SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION},
109 {MAKE_KEY_NAME1("nntp", SOP), "", VAL_MAIL_OPEN, APP_PATH_SUBSTITUTION},
110 };
111
112 static SETTING gCalendarSettings[] = {
113 // File Extension Class
114 {".ics", "", CLS_ICS, NO_SUBSTITUTION},
115
116 // File Extension Class
117 {MAKE_KEY_NAME1(CLS_ICS, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
118
119 // Protocol Handlers
120 {MAKE_KEY_NAME1(CLS_WEBCALURL, SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
121 {MAKE_KEY_NAME1("webcal", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
122 {MAKE_KEY_NAME1("webcals", SOP), "", VAL_OPEN, APP_PATH_SUBSTITUTION},
123 };
124
GetHelperPath(nsAutoString & aPath)125 nsresult GetHelperPath(nsAutoString& aPath) {
126 nsresult rv;
127 nsCOMPtr<nsIProperties> directoryService =
128 do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
129 NS_ENSURE_SUCCESS(rv, rv);
130
131 nsCOMPtr<nsIFile> appHelper;
132 rv = directoryService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, NS_GET_IID(nsIFile),
133 getter_AddRefs(appHelper));
134 NS_ENSURE_SUCCESS(rv, rv);
135
136 rv = appHelper->Append(u"uninstall"_ns);
137 NS_ENSURE_SUCCESS(rv, rv);
138
139 rv = appHelper->Append(u"helper.exe"_ns);
140 NS_ENSURE_SUCCESS(rv, rv);
141
142 return appHelper->GetPath(aPath);
143 }
144
LaunchHelper(nsAutoString & aPath,nsAutoString & aParams)145 nsresult LaunchHelper(nsAutoString& aPath, nsAutoString& aParams) {
146 SHELLEXECUTEINFOW executeInfo = {0};
147
148 executeInfo.cbSize = sizeof(SHELLEXECUTEINFOW);
149 executeInfo.hwnd = NULL;
150 executeInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
151 executeInfo.lpDirectory = NULL;
152 executeInfo.lpFile = aPath.get();
153 executeInfo.lpParameters = aParams.get();
154 executeInfo.nShow = SW_SHOWNORMAL;
155
156 if (ShellExecuteExW(&executeInfo))
157 // Block until the program exits
158 WaitForSingleObject(executeInfo.hProcess, INFINITE);
159 else
160 return NS_ERROR_ABORT;
161
162 // We're going to ignore errors here since there's nothing we can do about
163 // them, and helper.exe seems to return non-zero ret on success.
164 return NS_OK;
165 }
166
Init()167 nsresult nsWindowsShellService::Init() {
168 WCHAR appPath[MAX_BUF];
169 if (!::GetModuleFileNameW(0, appPath, MAX_BUF)) return NS_ERROR_FAILURE;
170
171 // Convert the path to a long path since GetModuleFileNameW returns the path
172 // that was used to launch the app which is not necessarily a long path.
173 if (!::GetLongPathNameW(appPath, appPath, MAX_BUF)) return NS_ERROR_FAILURE;
174
175 mAppLongPath = appPath;
176
177 return NS_OK;
178 }
179
nsWindowsShellService()180 nsWindowsShellService::nsWindowsShellService() : mCheckedThisSession(false) {}
181
182 NS_IMETHODIMP
IsDefaultClient(bool aStartupCheck,uint16_t aApps,bool * aIsDefaultClient)183 nsWindowsShellService::IsDefaultClient(bool aStartupCheck, uint16_t aApps,
184 bool* aIsDefaultClient) {
185 // If this is the first mail window, maintain internal state that we've
186 // checked this session (so that subsequent window opens don't show the
187 // default client dialog).
188 if (aStartupCheck) mCheckedThisSession = true;
189
190 *aIsDefaultClient = true;
191
192 // for each type,
193 if (aApps & nsIShellService::MAIL) {
194 *aIsDefaultClient &=
195 TestForDefault(gMailSettings, sizeof(gMailSettings) / sizeof(SETTING));
196 // Only check if this app is default on Vista if the previous checks
197 // indicate that this app is the default.
198 if (*aIsDefaultClient)
199 IsDefaultClientVista(nsIShellService::MAIL, aIsDefaultClient);
200 }
201 if (aApps & nsIShellService::NEWS) {
202 *aIsDefaultClient &=
203 TestForDefault(gNewsSettings, sizeof(gNewsSettings) / sizeof(SETTING));
204 // Only check if this app is default on Vista if the previous checks
205 // indicate that this app is the default.
206 if (*aIsDefaultClient)
207 IsDefaultClientVista(nsIShellService::NEWS, aIsDefaultClient);
208 }
209 if (aApps & nsIShellService::CALENDAR) {
210 *aIsDefaultClient &= TestForDefault(
211 gCalendarSettings, sizeof(gCalendarSettings) / sizeof(SETTING));
212 // Only check if this app is default on Vista if the previous checks
213 // indicate that this app is the default.
214 if (*aIsDefaultClient)
215 IsDefaultClientVista(nsIShellService::CALENDAR, aIsDefaultClient);
216 }
217 // RSS / feed protocol shell integration is not working so return true
218 // until it is fixed (bug 445823).
219 if (aApps & nsIShellService::RSS) *aIsDefaultClient &= true;
220
221 return NS_OK;
222 }
223
224 NS_IMETHODIMP
SetDefaultClient(bool aForAllUsers,uint16_t aApps)225 nsWindowsShellService::SetDefaultClient(bool aForAllUsers, uint16_t aApps) {
226 nsAutoString appHelperPath;
227 if (NS_FAILED(GetHelperPath(appHelperPath))) return NS_ERROR_FAILURE;
228
229 nsAutoString params;
230 if (aForAllUsers) {
231 params.AppendLiteral(" /SetAsDefaultAppGlobal");
232 } else {
233 params.AppendLiteral(" /SetAsDefaultAppUser");
234 if (aApps & nsIShellService::MAIL) params.AppendLiteral(" Mail");
235
236 if (aApps & nsIShellService::NEWS) params.AppendLiteral(" News");
237
238 if (aApps & nsIShellService::CALENDAR) params.AppendLiteral(" Calendar");
239 }
240
241 return LaunchHelper(appHelperPath, params);
242 }
243
244 NS_IMETHODIMP
GetShouldCheckDefaultClient(bool * aResult)245 nsWindowsShellService::GetShouldCheckDefaultClient(bool* aResult) {
246 if (mCheckedThisSession) {
247 *aResult = false;
248 return NS_OK;
249 }
250
251 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
252 return prefs->GetBoolPref("mail.shell.checkDefaultClient", aResult);
253 }
254
255 NS_IMETHODIMP
SetShouldCheckDefaultClient(bool aShouldCheck)256 nsWindowsShellService::SetShouldCheckDefaultClient(bool aShouldCheck) {
257 nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
258 return prefs->SetBoolPref("mail.shell.checkDefaultClient", aShouldCheck);
259 }
260
261 /* helper routine. Iterate over the passed in settings object. */
TestForDefault(SETTING aSettings[],int32_t aSize)262 bool nsWindowsShellService::TestForDefault(SETTING aSettings[], int32_t aSize) {
263 bool isDefault = true;
264 char16_t currValue[MAX_BUF];
265 SETTING* end = aSettings + aSize;
266 for (SETTING* settings = aSettings; settings < end; ++settings) {
267 NS_ConvertUTF8toUTF16 dataLongPath(settings->valueData);
268 NS_ConvertUTF8toUTF16 key(settings->keyName);
269 NS_ConvertUTF8toUTF16 value(settings->valueName);
270 if (settings->flags & APP_PATH_SUBSTITUTION) {
271 int32_t offset = dataLongPath.Find("%APPPATH%");
272 dataLongPath.Replace(offset, 9, mAppLongPath);
273 }
274
275 ::ZeroMemory(currValue, sizeof(currValue));
276 HKEY theKey;
277 nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, key, &theKey);
278 if (NS_FAILED(rv)) {
279 // Key doesn't exist
280 isDefault = false;
281 break;
282 }
283
284 DWORD len = sizeof currValue;
285 DWORD result = ::RegQueryValueExW(theKey, value.get(), NULL, NULL,
286 (LPBYTE)currValue, &len);
287 // Close the key we opened.
288 ::RegCloseKey(theKey);
289 if (REG_FAILED(result) ||
290 !dataLongPath.Equals(currValue, nsCaseInsensitiveStringComparator)) {
291 // Key wasn't set, or was set to something else (something else became the
292 // default client)
293 isDefault = false;
294 break;
295 }
296 } // for each registry key we want to look at
297
298 return isDefault;
299 }
300
IsDefaultClientVista(uint16_t aApps,bool * aIsDefaultClient)301 bool nsWindowsShellService::IsDefaultClientVista(uint16_t aApps,
302 bool* aIsDefaultClient) {
303 IApplicationAssociationRegistration* pAAR;
304
305 HRESULT hr = CoCreateInstance(
306 CLSID_ApplicationAssociationRegistration, NULL, CLSCTX_INPROC,
307 IID_IApplicationAssociationRegistration, (void**)&pAAR);
308
309 if (SUCCEEDED(hr)) {
310 BOOL isDefaultMail = true;
311 BOOL isDefaultNews = true;
312 BOOL isDefaultCalendar = true;
313 if (aApps & nsIShellService::MAIL)
314 pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_MAIL,
315 &isDefaultMail);
316 if (aApps & nsIShellService::NEWS)
317 pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_NEWS,
318 &isDefaultNews);
319 if (aApps & nsIShellService::CALENDAR)
320 pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE, APP_REG_NAME_CALENDAR,
321 &isDefaultCalendar);
322
323 *aIsDefaultClient = isDefaultNews && isDefaultMail && isDefaultCalendar;
324
325 pAAR->Release();
326 return true;
327 }
328 return false;
329 }
330