1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5var EXPORTED_SYMBOLS = ["UpdateUtils"];
6
7ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
8ChromeUtils.import("resource://gre/modules/Services.jsm");
9ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
10ChromeUtils.import("resource://gre/modules/ctypes.jsm");
11Cu.importGlobalProperties(["fetch"]); /* globals fetch */
12
13ChromeUtils.defineModuleGetter(this, "WindowsRegistry",
14                               "resource://gre/modules/WindowsRegistry.jsm");
15
16const FILE_UPDATE_LOCALE                  = "update.locale";
17const PREF_APP_DISTRIBUTION               = "distribution.id";
18const PREF_APP_DISTRIBUTION_VERSION       = "distribution.version";
19const PREF_APP_UPDATE_CUSTOM              = "app.update.custom";
20
21/**
22 * Pref that stores the time of the last use of OLDJAWS screen reader.
23 */
24const PREF_ACCESSIBILITY_CLIENTS_OLDJAWS_TIMESTAMP = "accessibility.clients.oldjaws.timestamp";
25
26const TIMESPAN_WEEK = 7 * 24 * 60 * 60 * 1000;
27
28var UpdateUtils = {
29  _locale: undefined,
30
31  /**
32   * Read the update channel from defaults only.  We do this to ensure that
33   * the channel is tightly coupled with the application and does not apply
34   * to other instances of the application that may use the same profile.
35   *
36   * @param [optional] aIncludePartners
37   *        Whether or not to include the partner bits. Default: true.
38   */
39  getUpdateChannel(aIncludePartners = true) {
40    let defaults = Services.prefs.getDefaultBranch(null);
41    let channel = defaults.getCharPref("app.update.channel",
42                                       AppConstants.MOZ_UPDATE_CHANNEL);
43
44    if (aIncludePartners) {
45      try {
46        let partners = Services.prefs.getChildList("app.partner.").sort();
47        if (partners.length) {
48          channel += "-cck";
49          partners.forEach(function(prefName) {
50            channel += "-" + Services.prefs.getCharPref(prefName);
51          });
52        }
53      } catch (e) {
54        Cu.reportError(e);
55      }
56    }
57
58    return channel;
59  },
60
61  get UpdateChannel() {
62    return this.getUpdateChannel();
63  },
64
65  /**
66   * Formats a URL by replacing %...% values with OS, build and locale specific
67   * values.
68   *
69   * @param  url
70   *         The URL to format.
71   * @return The formatted URL.
72   */
73  async formatUpdateURL(url) {
74    const locale = await this.getLocale();
75
76    return url.replace(/%(\w+)%/g, (match, name) => {
77      switch (name) {
78        case "PRODUCT":
79          return Services.appinfo.name;
80        case "VERSION":
81          return Services.appinfo.version;
82        case "BUILD_ID":
83          return Services.appinfo.appBuildID;
84        case "BUILD_TARGET":
85          return Services.appinfo.OS + "_" + this.ABI;
86        case "OS_VERSION":
87          return this.OSVersion;
88        case "LOCALE":
89          return locale;
90        case "CHANNEL":
91          return this.UpdateChannel;
92        case "PLATFORM_VERSION":
93          return Services.appinfo.platformVersion;
94        case "SYSTEM_CAPABILITIES":
95          return getSystemCapabilities();
96        case "CUSTOM":
97          return Services.prefs.getStringPref(PREF_APP_UPDATE_CUSTOM, "");
98        case "DISTRIBUTION":
99          return getDistributionPrefValue(PREF_APP_DISTRIBUTION);
100        case "DISTRIBUTION_VERSION":
101          return getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION);
102      }
103      return match;
104    }).replace(/\+/g, "%2B");
105  },
106
107  /**
108   * Gets the locale from the update.locale file for replacing %LOCALE% in the
109   * update url. The update.locale file can be located in the application
110   * directory or the GRE directory with preference given to it being located in
111   * the application directory.
112   */
113  async getLocale() {
114    if (this._locale !== undefined) {
115      return this._locale;
116    }
117
118    for (let res of ["app", "gre"]) {
119      const url = "resource://" + res + "/" + FILE_UPDATE_LOCALE;
120      let data;
121      try {
122        data = await fetch(url);
123      } catch (e) {
124        continue;
125      }
126      const locale = await data.text();
127      if (locale) {
128        return this._locale = locale.trim();
129      }
130    }
131
132    Cu.reportError(FILE_UPDATE_LOCALE + " file doesn't exist in either the " +
133                   "application or GRE directories");
134
135    return this._locale = null;
136  }
137};
138
139/* Get the distribution pref values, from defaults only */
140function getDistributionPrefValue(aPrefName) {
141  return Services.prefs.getDefaultBranch(null).getCharPref(aPrefName, "default");
142}
143
144function getSystemCapabilities() {
145  return "ISET:" + gInstructionSet + ",MEM:" + getMemoryMB() + getJAWS();
146}
147
148/**
149 * Gets the appropriate update url string for whether a JAWS screen reader that
150 * is incompatible with e10s is or was recently present on Windows. For
151 * platforms other than Windows this returns an empty string which is easier for
152 * balrog to detect.
153 */
154function getJAWS() {
155  if (AppConstants.platform != "win") {
156    return "";
157  }
158
159  let oldjaws = false;
160  if (Services.appinfo.shouldBlockIncompatJaws) {
161    // User is using old JAWS screen reader right now.
162    oldjaws = true;
163  } else {
164    // We stored seconds in order to fit within int prefs.
165    const timestamp = Services.prefs.getIntPref(
166      PREF_ACCESSIBILITY_CLIENTS_OLDJAWS_TIMESTAMP, 0) * 1000;
167    // User was using old JAWS screen reader less than a week ago.
168    oldjaws = Date.now() - timestamp < TIMESPAN_WEEK;
169  }
170
171  return ",JAWS:" + (oldjaws ? "1" : "0");
172}
173
174/**
175 * Gets the RAM size in megabytes. This will round the value because sysinfo
176 * doesn't always provide RAM in multiples of 1024.
177 */
178function getMemoryMB() {
179  let memoryMB = "unknown";
180  try {
181    memoryMB = Services.sysinfo.getProperty("memsize");
182    if (memoryMB) {
183      memoryMB = Math.round(memoryMB / 1024 / 1024);
184    }
185  } catch (e) {
186    Cu.reportError("Error getting system info memsize property. " +
187                   "Exception: " + e);
188  }
189  return memoryMB;
190}
191
192/**
193 * Gets the supported CPU instruction set.
194 */
195XPCOMUtils.defineLazyGetter(this, "gInstructionSet", function aus_gIS() {
196  const CPU_EXTENSIONS = ["hasSSE4_2", "hasSSE4_1", "hasSSE4A", "hasSSSE3",
197                          "hasSSE3", "hasSSE2", "hasSSE", "hasMMX",
198                          "hasNEON", "hasARMv7", "hasARMv6"];
199  for (let ext of CPU_EXTENSIONS) {
200    if (Services.sysinfo.getProperty(ext)) {
201      return ext.substring(3);
202    }
203  }
204
205  return "unknown";
206});
207
208/* Windows only getter that returns the processor architecture. */
209XPCOMUtils.defineLazyGetter(this, "gWinCPUArch", function aus_gWinCPUArch() {
210  // Get processor architecture
211  let arch = "unknown";
212
213  const WORD = ctypes.uint16_t;
214  const DWORD = ctypes.uint32_t;
215
216  // This structure is described at:
217  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
218  const SYSTEM_INFO = new ctypes.StructType("SYSTEM_INFO",
219      [
220      {wProcessorArchitecture: WORD},
221      {wReserved: WORD},
222      {dwPageSize: DWORD},
223      {lpMinimumApplicationAddress: ctypes.voidptr_t},
224      {lpMaximumApplicationAddress: ctypes.voidptr_t},
225      {dwActiveProcessorMask: DWORD.ptr},
226      {dwNumberOfProcessors: DWORD},
227      {dwProcessorType: DWORD},
228      {dwAllocationGranularity: DWORD},
229      {wProcessorLevel: WORD},
230      {wProcessorRevision: WORD}
231      ]);
232
233  let kernel32 = false;
234  try {
235    kernel32 = ctypes.open("Kernel32");
236  } catch (e) {
237    Cu.reportError("Unable to open kernel32! Exception: " + e);
238  }
239
240  if (kernel32) {
241    try {
242      let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
243                                                 ctypes.winapi_abi,
244                                                 ctypes.void_t,
245                                                 SYSTEM_INFO.ptr);
246      let winSystemInfo = SYSTEM_INFO();
247      // Default to unknown
248      winSystemInfo.wProcessorArchitecture = 0xffff;
249
250      GetNativeSystemInfo(winSystemInfo.address());
251      switch (winSystemInfo.wProcessorArchitecture) {
252        case 9:
253          arch = "x64";
254          break;
255        case 6:
256          arch = "IA64";
257          break;
258        case 0:
259          arch = "x86";
260          break;
261      }
262    } catch (e) {
263      Cu.reportError("Error getting processor architecture. " +
264                     "Exception: " + e);
265    } finally {
266      kernel32.close();
267    }
268  }
269
270  return arch;
271});
272
273XPCOMUtils.defineLazyGetter(UpdateUtils, "ABI", function() {
274  let abi = null;
275  try {
276    abi = Services.appinfo.XPCOMABI;
277  } catch (e) {
278    Cu.reportError("XPCOM ABI unknown");
279  }
280
281  if (AppConstants.platform == "macosx") {
282    // Mac universal build should report a different ABI than either macppc
283    // or mactel.
284    let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
285                   getService(Ci.nsIMacUtils);
286
287    if (macutils.isUniversalBinary) {
288      abi += "-u-" + macutils.architecturesInBinary;
289    }
290  } else if (AppConstants.platform == "win") {
291    // Windows build should report the CPU architecture that it's running on.
292    abi += "-" + gWinCPUArch;
293  }
294
295  if (AppConstants.ASAN) {
296    // Allow ASan builds to receive their own updates
297    abi += "-asan";
298  }
299
300  return abi;
301});
302
303XPCOMUtils.defineLazyGetter(UpdateUtils, "OSVersion", function() {
304  let osVersion;
305  try {
306    osVersion = Services.sysinfo.getProperty("name") + " " +
307                Services.sysinfo.getProperty("version");
308  } catch (e) {
309    Cu.reportError("OS Version unknown.");
310  }
311
312  if (osVersion) {
313    if (AppConstants.platform == "win") {
314      const BYTE = ctypes.uint8_t;
315      const WORD = ctypes.uint16_t;
316      const DWORD = ctypes.uint32_t;
317      const WCHAR = ctypes.char16_t;
318      const BOOL = ctypes.int;
319
320      // This structure is described at:
321      // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
322      const SZCSDVERSIONLENGTH = 128;
323      const OSVERSIONINFOEXW = new ctypes.StructType("OSVERSIONINFOEXW",
324          [
325          {dwOSVersionInfoSize: DWORD},
326          {dwMajorVersion: DWORD},
327          {dwMinorVersion: DWORD},
328          {dwBuildNumber: DWORD},
329          {dwPlatformId: DWORD},
330          {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
331          {wServicePackMajor: WORD},
332          {wServicePackMinor: WORD},
333          {wSuiteMask: WORD},
334          {wProductType: BYTE},
335          {wReserved: BYTE}
336          ]);
337
338      let kernel32 = false;
339      try {
340        kernel32 = ctypes.open("Kernel32");
341      } catch (e) {
342        Cu.reportError("Unable to open kernel32! " + e);
343        osVersion += ".unknown (unknown)";
344      }
345
346      if (kernel32) {
347        try {
348          // Get Service pack info
349          try {
350            let GetVersionEx = kernel32.declare("GetVersionExW",
351                                                ctypes.winapi_abi,
352                                                BOOL,
353                                                OSVERSIONINFOEXW.ptr);
354            let winVer = OSVERSIONINFOEXW();
355            winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
356
357            if (0 !== GetVersionEx(winVer.address())) {
358              osVersion += "." + winVer.wServicePackMajor +
359                           "." + winVer.wServicePackMinor +
360                           "." + winVer.dwBuildNumber;
361            } else {
362              Cu.reportError("Unknown failure in GetVersionEX (returned 0)");
363              osVersion += ".unknown";
364            }
365          } catch (e) {
366            Cu.reportError("Error getting service pack information. Exception: " + e);
367            osVersion += ".unknown";
368          }
369
370          if (Services.vc.compare(Services.sysinfo.getProperty("version"), "10") >= 0) {
371            const WINDOWS_UBR_KEY_PATH = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
372            let ubr = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
373                                                 WINDOWS_UBR_KEY_PATH, "UBR",
374                                                 Ci.nsIWindowsRegKey.WOW64_64);
375            osVersion += (ubr !== undefined) ? "." + ubr : ".unknown";
376          }
377        } finally {
378          kernel32.close();
379        }
380
381        // Add processor architecture
382        osVersion += " (" + gWinCPUArch + ")";
383      }
384    }
385
386    try {
387      osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
388    } catch (e) {
389      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
390    }
391    osVersion = encodeURIComponent(osVersion);
392  }
393  return osVersion;
394});
395