Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
5 package org.mozilla.gecko.background.healthreport;
7 import java.io.UnsupportedEncodingException;
8 import java.security.NoSuchAlgorithmException;
9 import java.util.Iterator;
10 import java.util.SortedSet;
12 import org.json.JSONException;
13 import org.json.JSONObject;
14 import org.mozilla.apache.commons.codec.binary.Base64;
15 import org.mozilla.gecko.background.common.log.Logger;
16 import org.mozilla.gecko.background.nativecode.NativeCrypto;
18 public abstract class EnvironmentV1 {
19 private static final String LOG_TAG = "GeckoEnvironment";
20 private static final int VERSION = 1;
22 protected final Class<? extends EnvironmentAppender> appenderClass;
24 protected volatile String hash = null;
25 protected volatile int id = -1;
27 public int version = VERSION;
29 // org.mozilla.profile.age.
30 public int profileCreation;
32 // org.mozilla.sysinfo.sysinfo.
33 public int cpuCount;
34 public int memoryMB;
35 public String architecture;
36 public String sysName;
37 public String sysVersion; // Kernel.
39 // geckoAppInfo.
40 public String vendor;
41 public String appName;
42 public String appID;
43 public String appVersion;
44 public String appBuildID;
45 public String platformVersion;
46 public String platformBuildID;
47 public String os;
48 public String xpcomabi;
49 public String updateChannel;
51 // appinfo.
52 public int isBlocklistEnabled;
53 public int isTelemetryEnabled;
55 // org.mozilla.addons.active.
56 public JSONObject addons = null;
58 // org.mozilla.addons.counts.
59 public int extensionCount;
60 public int pluginCount;
61 public int themeCount;
63 /**
64 * We break out this interface in order to allow for testing -- pass in your
65 * own appender that just records strings, for example.
66 */
67 public static abstract class EnvironmentAppender {
68 public abstract void append(String s);
69 public abstract void append(int v);
70 }
72 public static class HashAppender extends EnvironmentAppender {
73 private final StringBuilder builder;
75 public HashAppender() throws NoSuchAlgorithmException {
76 builder = new StringBuilder();
77 }
79 @Override
80 public void append(String s) {
81 builder.append((s == null) ? "null" : s);
82 }
84 @Override
85 public void append(int profileCreation) {
86 append(Integer.toString(profileCreation, 10));
87 }
89 @Override
90 public String toString() {
91 // We *could* use ASCII85… but the savings would be negated by the
92 // inclusion of JSON-unsafe characters like double-quote.
93 final byte[] inputBytes;
94 try {
95 inputBytes = builder.toString().getBytes("UTF-8");
96 } catch (UnsupportedEncodingException e) {
97 Logger.warn(LOG_TAG, "Invalid charset String passed to getBytes", e);
98 return null;
99 }
101 // Note to the security-minded reader: we deliberately use SHA-1 here, not
102 // a stronger hash. These identifiers don't strictly need a cryptographic
103 // hash function, because there is negligible value in attacking the hash.
104 // We use SHA-1 because it's *shorter* -- the exact same reason that Git
105 // chose SHA-1.
106 final byte[] hash = NativeCrypto.sha1(inputBytes);
107 return new Base64(-1, null, false).encodeAsString(hash);
108 }
109 }
111 /**
112 * Ensure that the {@link Environment} has been registered with its
113 * storage layer, and can be used to annotate events.
114 *
115 * It's safe to call this method more than once, and each time you'll
116 * get the same ID.
117 *
118 * @return the integer ID to use in subsequent DB insertions.
119 */
120 public abstract int register();
122 protected EnvironmentAppender getAppender() {
123 EnvironmentAppender appender = null;
124 try {
125 appender = appenderClass.newInstance();
126 } catch (InstantiationException ex) {
127 // Should never happen, but...
128 Logger.warn(LOG_TAG, "Could not compute hash.", ex);
129 } catch (IllegalAccessException ex) {
130 // Should never happen, but...
131 Logger.warn(LOG_TAG, "Could not compute hash.", ex);
132 }
133 return appender;
134 }
136 protected void appendHash(EnvironmentAppender appender) {
137 appender.append(profileCreation);
138 appender.append(cpuCount);
139 appender.append(memoryMB);
140 appender.append(architecture);
141 appender.append(sysName);
142 appender.append(sysVersion);
143 appender.append(vendor);
144 appender.append(appName);
145 appender.append(appID);
146 appender.append(appVersion);
147 appender.append(appBuildID);
148 appender.append(platformVersion);
149 appender.append(platformBuildID);
150 appender.append(os);
151 appender.append(xpcomabi);
152 appender.append(updateChannel);
153 appender.append(isBlocklistEnabled);
154 appender.append(isTelemetryEnabled);
155 appender.append(extensionCount);
156 appender.append(pluginCount);
157 appender.append(themeCount);
159 // We need sorted values.
160 if (addons != null) {
161 appendSortedAddons(getNonIgnoredAddons(), appender);
162 }
163 }
165 /**
166 * Compute the stable hash of the configured environment.
167 *
168 * @return the hash in base34, or null if there was a problem.
169 */
170 public String getHash() {
171 // It's never unset, so we only care about partial reads. volatile is enough.
172 if (hash != null) {
173 return hash;
174 }
176 EnvironmentAppender appender = getAppender();
177 if (appender == null) {
178 return null;
179 }
181 appendHash(appender);
182 return hash = appender.toString();
183 }
185 public EnvironmentV1(Class<? extends EnvironmentAppender> appenderClass) {
186 super();
187 this.appenderClass = appenderClass;
188 }
190 public JSONObject getNonIgnoredAddons() {
191 if (addons == null) {
192 return null;
193 }
194 JSONObject out = new JSONObject();
195 @SuppressWarnings("unchecked")
196 Iterator<String> keys = addons.keys();
197 while (keys.hasNext()) {
198 try {
199 final String key = keys.next();
200 final Object obj = addons.get(key);
201 if (obj != null &&
202 obj instanceof JSONObject &&
203 ((JSONObject) obj).optBoolean("ignore", false)) {
204 continue;
205 }
206 out.put(key, obj);
207 } catch (JSONException ex) {
208 // Do nothing.
209 }
210 }
211 return out;
212 }
214 /**
215 * Take a collection of add-on descriptors, appending a consistent string
216 * to the provided builder.
217 */
218 public static void appendSortedAddons(JSONObject addons, final EnvironmentAppender builder) {
219 final SortedSet<String> keys = HealthReportUtils.sortedKeySet(addons);
221 // For each add-on, produce a consistent, sorted mapping of its descriptor.
222 for (String key : keys) {
223 try {
224 JSONObject addon = addons.getJSONObject(key);
226 // Now produce the output for this add-on.
227 builder.append(key);
228 builder.append("={");
230 for (String addonKey : HealthReportUtils.sortedKeySet(addon)) {
231 builder.append(addonKey);
232 builder.append("==");
233 try {
234 builder.append(addon.get(addonKey).toString());
235 } catch (JSONException e) {
236 builder.append("_e_");
237 }
238 }
240 builder.append("}");
241 } catch (Exception e) {
242 // Muffle.
243 Logger.warn(LOG_TAG, "Invalid add-on for ID " + key);
244 }
245 }
246 }
248 public void setJSONForAddons(byte[] json) throws Exception {
249 setJSONForAddons(new String(json, "UTF-8"));
250 }
252 public void setJSONForAddons(String json) throws Exception {
253 if (json == null || "null".equals(json)) {
254 addons = null;
255 return;
256 }
257 addons = new JSONObject(json);
258 }
260 public void setJSONForAddons(JSONObject json) {
261 addons = json;
262 }
264 /**
265 * Includes ignored add-ons.
266 */
267 public String getNormalizedAddonsJSON() {
268 // We trust that our input will already be normalized. If that assumption
269 // is invalidated, then we'll be sorry.
270 return (addons == null) ? "null" : addons.toString();
271 }
272 }