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
5this.EXPORTED_SYMBOLS = ["UpdateUtils"];
6
7const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
8
9Cu.import("resource://gre/modules/AppConstants.jsm");
10Cu.import("resource://gre/modules/Services.jsm");
11Cu.import("resource://gre/modules/XPCOMUtils.jsm");
12Cu.import("resource://gre/modules/NetUtil.jsm");
13Cu.import("resource://gre/modules/Preferences.jsm");
14Cu.import("resource://gre/modules/ctypes.jsm");
15
16const FILE_UPDATE_LOCALE                  = "update.locale";
17const PREF_APP_DISTRIBUTION               = "distribution.id";
18const PREF_APP_DISTRIBUTION_VERSION       = "distribution.version";
19const PREF_APP_B2G_VERSION                = "b2g.version";
20const PREF_APP_UPDATE_CUSTOM              = "app.update.custom";
21const PREF_APP_UPDATE_IMEI_HASH           = "app.update.imei_hash";
22
23
24this.UpdateUtils = {
25  /**
26   * Read the update channel from defaults only.  We do this to ensure that
27   * the channel is tightly coupled with the application and does not apply
28   * to other instances of the application that may use the same profile.
29   *
30   * @param [optional] aIncludePartners
31   *        Whether or not to include the partner bits. Default: true.
32   */
33  getUpdateChannel(aIncludePartners = true) {
34    let channel = AppConstants.MOZ_UPDATE_CHANNEL;
35    let defaults = Services.prefs.getDefaultBranch(null);
36    try {
37      channel = defaults.getCharPref("app.update.channel");
38    } catch (e) {
39      // use default value when pref not found
40    }
41
42    if (aIncludePartners) {
43      try {
44        let partners = Services.prefs.getChildList("app.partner.").sort();
45        if (partners.length) {
46          channel += "-cck";
47          partners.forEach(function (prefName) {
48            channel += "-" + Services.prefs.getCharPref(prefName);
49          });
50        }
51      } catch (e) {
52        Cu.reportError(e);
53      }
54    }
55
56    return channel;
57  },
58
59  get UpdateChannel() {
60    return this.getUpdateChannel();
61  },
62
63  /**
64   * Formats a URL by replacing %...% values with OS, build and locale specific
65   * values.
66   *
67   * @param  url
68   *         The URL to format.
69   * @return The formatted URL.
70   */
71  formatUpdateURL(url) {
72    url = url.replace(/%PRODUCT%/g, Services.appinfo.name);
73    url = url.replace(/%VERSION%/g, Services.appinfo.version);
74    url = url.replace(/%BUILD_ID%/g, Services.appinfo.appBuildID);
75    url = url.replace(/%BUILD_TARGET%/g, Services.appinfo.OS + "_" + this.ABI);
76    url = url.replace(/%OS_VERSION%/g, this.OSVersion);
77    url = url.replace(/%SYSTEM_CAPABILITIES%/g, gSystemCapabilities);
78    if (/%LOCALE%/.test(url)) {
79      url = url.replace(/%LOCALE%/g, this.Locale);
80    }
81    url = url.replace(/%CHANNEL%/g, this.UpdateChannel);
82    url = url.replace(/%PLATFORM_VERSION%/g, Services.appinfo.platformVersion);
83    url = url.replace(/%DISTRIBUTION%/g,
84                      getDistributionPrefValue(PREF_APP_DISTRIBUTION));
85    url = url.replace(/%DISTRIBUTION_VERSION%/g,
86                      getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION));
87    url = url.replace(/%CUSTOM%/g, Preferences.get(PREF_APP_UPDATE_CUSTOM, ""));
88    url = url.replace(/\+/g, "%2B");
89
90    if (AppConstants.platform == "gonk") {
91      let sysLibs = {};
92      Cu.import("resource://gre/modules/systemlibs.js", sysLibs);
93      let productDevice = sysLibs.libcutils.property_get("ro.product.device");
94      let buildType = sysLibs.libcutils.property_get("ro.build.type");
95      url = url.replace(/%PRODUCT_MODEL%/g,
96                        sysLibs.libcutils.property_get("ro.product.model"));
97      if (buildType == "user" || buildType == "userdebug") {
98        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice);
99      } else {
100        url = url.replace(/%PRODUCT_DEVICE%/g, productDevice + "-" + buildType);
101      }
102      url = url.replace(/%B2G_VERSION%/g,
103                        Preferences.get(PREF_APP_B2G_VERSION, null));
104      url = url.replace(/%IMEI%/g,
105                        Preferences.get(PREF_APP_UPDATE_IMEI_HASH, "default"));
106    }
107
108    return url;
109  }
110};
111
112/* Get the distribution pref values, from defaults only */
113function getDistributionPrefValue(aPrefName) {
114  var prefValue = "default";
115
116  try {
117    prefValue = Services.prefs.getDefaultBranch(null).getCharPref(aPrefName);
118  } catch (e) {
119    // use default when pref not found
120  }
121
122  return prefValue;
123}
124
125/**
126 * Gets the locale from the update.locale file for replacing %LOCALE% in the
127 * update url. The update.locale file can be located in the application
128 * directory or the GRE directory with preference given to it being located in
129 * the application directory.
130 */
131XPCOMUtils.defineLazyGetter(UpdateUtils, "Locale", function() {
132  let channel;
133  let locale;
134  for (let res of ['app', 'gre']) {
135    channel = NetUtil.newChannel({
136      uri: "resource://" + res + "/" + FILE_UPDATE_LOCALE,
137      contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_XMLHTTPREQUEST,
138      loadUsingSystemPrincipal: true
139    });
140    try {
141      let inputStream = channel.open2();
142      locale = NetUtil.readInputStreamToString(inputStream, inputStream.available());
143    } catch (e) {}
144    if (locale)
145      return locale.trim();
146  }
147
148  Cu.reportError(FILE_UPDATE_LOCALE + " file doesn't exist in either the " +
149                 "application or GRE directories");
150
151  return null;
152});
153
154/**
155 * Provides adhoc system capability information for application update.
156 */
157XPCOMUtils.defineLazyGetter(this, "gSystemCapabilities", function aus_gSC() {
158  if (AppConstants.platform == "win") {
159    const PF_MMX_INSTRUCTIONS_AVAILABLE = 3; // MMX
160    const PF_XMMI_INSTRUCTIONS_AVAILABLE = 6; // SSE
161    const PF_XMMI64_INSTRUCTIONS_AVAILABLE = 10; // SSE2
162    const PF_SSE3_INSTRUCTIONS_AVAILABLE = 13; // SSE3
163
164    let lib = ctypes.open("kernel32.dll");
165    let IsProcessorFeaturePresent = lib.declare("IsProcessorFeaturePresent",
166                                                ctypes.winapi_abi,
167                                                ctypes.int32_t, /* success */
168                                                ctypes.uint32_t); /* DWORD */
169    let instructionSet = "unknown";
170    try {
171      if (IsProcessorFeaturePresent(PF_SSE3_INSTRUCTIONS_AVAILABLE)) {
172        instructionSet = "SSE3";
173      } else if (IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) {
174        instructionSet = "SSE2";
175      } else if (IsProcessorFeaturePresent(PF_XMMI_INSTRUCTIONS_AVAILABLE)) {
176        instructionSet = "SSE";
177      } else if (IsProcessorFeaturePresent(PF_MMX_INSTRUCTIONS_AVAILABLE)) {
178        instructionSet = "MMX";
179      }
180    } catch (e) {
181      instructionSet = "error";
182      Cu.reportError("Error getting processor instruction set. " +
183                     "Exception: " + e);
184    }
185
186    lib.close();
187    return instructionSet;
188  }
189
190  if (AppConstants == "linux") {
191    let instructionSet = "unknown";
192    if (navigator.cpuHasSSE2) {
193      instructionSet = "SSE2";
194    }
195    return instructionSet;
196  }
197
198  return "NA"
199});
200
201/* Windows only getter that returns the processor architecture. */
202XPCOMUtils.defineLazyGetter(this, "gWinCPUArch", function aus_gWinCPUArch() {
203  // Get processor architecture
204  let arch = "unknown";
205
206  const WORD = ctypes.uint16_t;
207  const DWORD = ctypes.uint32_t;
208
209  // This structure is described at:
210  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
211  const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
212      [
213      {wProcessorArchitecture: WORD},
214      {wReserved: WORD},
215      {dwPageSize: DWORD},
216      {lpMinimumApplicationAddress: ctypes.voidptr_t},
217      {lpMaximumApplicationAddress: ctypes.voidptr_t},
218      {dwActiveProcessorMask: DWORD.ptr},
219      {dwNumberOfProcessors: DWORD},
220      {dwProcessorType: DWORD},
221      {dwAllocationGranularity: DWORD},
222      {wProcessorLevel: WORD},
223      {wProcessorRevision: WORD}
224      ]);
225
226  let kernel32 = false;
227  try {
228    kernel32 = ctypes.open("Kernel32");
229  } catch (e) {
230    Cu.reportError("Unable to open kernel32! Exception: " + e);
231  }
232
233  if (kernel32) {
234    try {
235      let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
236                                                 ctypes.default_abi,
237                                                 ctypes.void_t,
238                                                 SYSTEM_INFO.ptr);
239      let winSystemInfo = SYSTEM_INFO();
240      // Default to unknown
241      winSystemInfo.wProcessorArchitecture = 0xffff;
242
243      GetNativeSystemInfo(winSystemInfo.address());
244      switch (winSystemInfo.wProcessorArchitecture) {
245        case 9:
246          arch = "x64";
247          break;
248        case 6:
249          arch = "IA64";
250          break;
251        case 0:
252          arch = "x86";
253          break;
254      }
255    } catch (e) {
256      Cu.reportError("Error getting processor architecture. " +
257                     "Exception: " + e);
258    } finally {
259      kernel32.close();
260    }
261  }
262
263  return arch;
264});
265
266XPCOMUtils.defineLazyGetter(UpdateUtils, "ABI", function() {
267  let abi = null;
268  try {
269    abi = Services.appinfo.XPCOMABI;
270  }
271  catch (e) {
272    Cu.reportError("XPCOM ABI unknown");
273  }
274
275  if (AppConstants.platform == "macosx") {
276    // Mac universal build should report a different ABI than either macppc
277    // or mactel.
278    let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
279                   getService(Ci.nsIMacUtils);
280
281    if (macutils.isUniversalBinary) {
282      abi += "-u-" + macutils.architecturesInBinary;
283    }
284  } else if (AppConstants.platform == "win") {
285    // Windows build should report the CPU architecture that it's running on.
286    abi += "-" + gWinCPUArch;
287  }
288  return abi;
289});
290
291XPCOMUtils.defineLazyGetter(UpdateUtils, "OSVersion", function() {
292  let osVersion;
293  try {
294    osVersion = Services.sysinfo.getProperty("name") + " " +
295                Services.sysinfo.getProperty("version");
296  }
297  catch (e) {
298    Cu.reportError("OS Version unknown.");
299  }
300
301  if (osVersion) {
302    if (AppConstants.platform == "win") {
303      const BYTE = ctypes.uint8_t;
304      const WORD = ctypes.uint16_t;
305      const DWORD = ctypes.uint32_t;
306      const WCHAR = ctypes.char16_t;
307      const BOOL = ctypes.int;
308
309      // This structure is described at:
310      // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
311      const SZCSDVERSIONLENGTH = 128;
312      const OSVERSIONINFOEXW = new ctypes.StructType('OSVERSIONINFOEXW',
313          [
314          {dwOSVersionInfoSize: DWORD},
315          {dwMajorVersion: DWORD},
316          {dwMinorVersion: DWORD},
317          {dwBuildNumber: DWORD},
318          {dwPlatformId: DWORD},
319          {szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH)},
320          {wServicePackMajor: WORD},
321          {wServicePackMinor: WORD},
322          {wSuiteMask: WORD},
323          {wProductType: BYTE},
324          {wReserved: BYTE}
325          ]);
326
327      // This structure is described at:
328      // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
329      const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
330          [
331          {wProcessorArchitecture: WORD},
332          {wReserved: WORD},
333          {dwPageSize: DWORD},
334          {lpMinimumApplicationAddress: ctypes.voidptr_t},
335          {lpMaximumApplicationAddress: ctypes.voidptr_t},
336          {dwActiveProcessorMask: DWORD.ptr},
337          {dwNumberOfProcessors: DWORD},
338          {dwProcessorType: DWORD},
339          {dwAllocationGranularity: DWORD},
340          {wProcessorLevel: WORD},
341          {wProcessorRevision: WORD}
342          ]);
343
344      let kernel32 = false;
345      try {
346        kernel32 = ctypes.open("Kernel32");
347      } catch (e) {
348        Cu.reportError("Unable to open kernel32! " + e);
349        osVersion += ".unknown (unknown)";
350      }
351
352      if (kernel32) {
353        try {
354          // Get Service pack info
355          try {
356            let GetVersionEx = kernel32.declare("GetVersionExW",
357                                                ctypes.default_abi,
358                                                BOOL,
359                                                OSVERSIONINFOEXW.ptr);
360            let winVer = OSVERSIONINFOEXW();
361            winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;
362
363            if (0 !== GetVersionEx(winVer.address())) {
364              osVersion += "." + winVer.wServicePackMajor +
365                           "." + winVer.wServicePackMinor;
366            } else {
367              Cu.reportError("Unknown failure in GetVersionEX (returned 0)");
368              osVersion += ".unknown";
369            }
370          } catch (e) {
371            Cu.reportError("Error getting service pack information. Exception: " + e);
372            osVersion += ".unknown";
373          }
374        } finally {
375          kernel32.close();
376        }
377
378        // Add processor architecture
379        osVersion += " (" + gWinCPUArch + ")";
380      }
381    }
382
383    try {
384      osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
385    }
386    catch (e) {
387      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
388    }
389    osVersion = encodeURIComponent(osVersion);
390  }
391  return osVersion;
392});
393