Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(30)

Side by Side Diff: chrome/android/java_staging/src/org/chromium/chrome/browser/crash/MinidumpUploadCallable.java

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package org.chromium.chrome.browser.crash;
6
7 import android.content.Context;
8 import android.content.SharedPreferences;
9 import android.preference.PreferenceManager;
10 import android.util.Log;
11
12 import org.chromium.base.VisibleForTesting;
13 import org.chromium.chrome.browser.preferences.privacy.CrashReportingPermissionM anager;
14 import org.chromium.chrome.browser.preferences.privacy.PrivacyPreferencesManager ;
15 import org.chromium.chrome.browser.util.HttpURLConnectionFactory;
16 import org.chromium.chrome.browser.util.HttpURLConnectionFactoryImpl;
17 import org.chromium.chrome.browser.util.StreamUtil;
18
19 import java.io.BufferedReader;
20 import java.io.ByteArrayOutputStream;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileReader;
24 import java.io.FileWriter;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.net.HttpURLConnection;
29 import java.util.Calendar;
30 import java.util.Locale;
31 import java.util.concurrent.Callable;
32
33 /**
34 * This class tries to upload a minidump to the crash server.
35 *
36 * It is implemented as a Callable<Boolean> and returns true on successful uploa ds,
37 * and false otherwise.
38 */
39 public class MinidumpUploadCallable implements Callable<Boolean> {
40 private static final String TAG = "MinidumpUploadCallable";
41 @VisibleForTesting protected static final int LOG_SIZE_LIMIT_BYTES = 1024 * 1024; // 1MB
42 @VisibleForTesting protected static final int LOG_UPLOAD_LIMIT_PER_DAY = 5;
43
44 @VisibleForTesting
45 protected static final String PREF_LAST_UPLOAD_DAY = "crash_dump_last_upload _day";
46 @VisibleForTesting protected static final String PREF_UPLOAD_COUNT = "crash_ dump_upload_count";
47
48 @VisibleForTesting
49 protected static final String CRASH_URL_STRING = "https://clients2.google.co m/cr/report";
50
51 @VisibleForTesting
52 protected static final String CONTENT_TYPE_TMPL = "multipart/form-data; boun dary=%s";
53
54 private final File mFileToUpload;
55 private final File mLogfile;
56 private final HttpURLConnectionFactory mHttpURLConnectionFactory;
57 private final CrashReportingPermissionManager mPermManager;
58 private final SharedPreferences mSharedPreferences;
59
60 public MinidumpUploadCallable(File fileToUpload, File logfile, Context conte xt) {
61 this(fileToUpload, logfile, new HttpURLConnectionFactoryImpl(),
62 PrivacyPreferencesManager.getInstance(context),
63 PreferenceManager.getDefaultSharedPreferences(context));
64 }
65
66 public MinidumpUploadCallable(File fileToUpload, File logfile,
67 HttpURLConnectionFactory httpURLConnectionFactory,
68 CrashReportingPermissionManager permManager, SharedPreferences share dPreferences) {
69 mFileToUpload = fileToUpload;
70 mLogfile = logfile;
71 mHttpURLConnectionFactory = httpURLConnectionFactory;
72 mPermManager = permManager;
73 mSharedPreferences = sharedPreferences;
74 }
75
76 @Override
77 public Boolean call() {
78 if (!mPermManager.isUploadPermitted()) {
79 Log.i(TAG, "Minidump upload is not permitted");
80 return false;
81 }
82
83 boolean isLimited = mPermManager.isUploadLimited();
84 if (isLimited && !isUploadSizeAndFrequencyAllowed()) {
85 Log.i(TAG, "Minidump cannot currently be uploaded due to constraints ");
86 return false;
87 }
88
89 HttpURLConnection connection =
90 mHttpURLConnectionFactory.createHttpURLConnection(CRASH_URL_STRI NG);
91 if (connection == null) {
92 return false;
93 }
94
95 FileInputStream minidumpInputStream = null;
96 try {
97 if (!configureConnectionForHttpPost(connection)) {
98 return false;
99 }
100 minidumpInputStream = new FileInputStream(mFileToUpload);
101 streamCopy(minidumpInputStream, connection.getOutputStream());
102 boolean status = handleExecutionResponse(connection);
103
104 if (isLimited) updateUploadPrefs();
105 return status;
106 } catch (IOException e) {
107 // For now just log the stack trace.
108 Log.w(TAG, "Error while uploading " + mFileToUpload.getName(), e);
109 return false;
110 } finally {
111 connection.disconnect();
112
113 if (minidumpInputStream != null) {
114 StreamUtil.closeQuietly(minidumpInputStream);
115 }
116 }
117 }
118
119 /**
120 * Configures a HttpURLConnection to send a HTTP POST request for uploading the minidump.
121 *
122 * This also reads the content-type from the minidump file.
123 *
124 * @param connection the HttpURLConnection to configure
125 * @return true if successful.
126 * @throws IOException
127 */
128 private boolean configureConnectionForHttpPost(HttpURLConnection connection)
129 throws IOException {
130 // Read the boundary which we need for the content type.
131 String boundary = readBoundary();
132 if (boundary == null) {
133 return false;
134 }
135
136 connection.setDoOutput(true);
137 connection.setRequestProperty("Connection", "Keep-Alive");
138 connection.setRequestProperty("Content-Type", String.format(CONTENT_TYPE _TMPL, boundary));
139 return true;
140 }
141
142 /**
143 * Reads the HTTP response and cleans up successful uploads.
144 *
145 * @param connection the connection to read the response from
146 * @return true if the upload was successful, false otherwise.
147 * @throws IOException
148 */
149 private Boolean handleExecutionResponse(HttpURLConnection connection) throws IOException {
150 int responseCode = connection.getResponseCode();
151 if (isSuccessful(responseCode)) {
152 String responseContent = getResponseContentAsString(connection);
153 // The crash server returns the crash ID.
154 String id = responseContent != null ? responseContent : "unknown";
155 Log.i(TAG, "Minidump " + mFileToUpload.getName() + " uploaded succes sfully, id: " + id);
156
157 // TODO(acleung): MinidumpUploadService is in charge of renaming whi le this class is
158 // in charge of deleting. We should move all the file system operati ons into
159 // MinidumpUploadService instead.
160 cleanupMinidumpFile();
161
162 try {
163 appendUploadedEntryToLog(id);
164 } catch (IOException ioe) {
165 Log.e(TAG, "Fail to write uploaded entry to log file");
166 }
167 return true;
168 } else {
169 // Log the results of the upload. Note that periodic upload failures aren't bad
170 // because we will need to throttle uploads in the future anyway.
171 String msg = String.format(Locale.US,
172 "Failed to upload %s with code: %d (%s).",
173 mFileToUpload.getName(), responseCode, connection.getRespons eMessage());
174 Log.i(TAG, msg);
175
176 // TODO(acleung): The return status informs us about why an upload m ight be
177 // rejected. The next logical step is to put the reasons in an UMA h istogram.
178 return false;
179 }
180 }
181
182 /**
183 * Records the upload entry to a log file
184 * similar to what is done in chrome/app/breakpad_linux.cc
185 *
186 * @param id The crash ID return from the server.
187 */
188 private void appendUploadedEntryToLog(String id) throws IOException {
189 FileWriter writer = new FileWriter(mLogfile, /* Appending */ true);
190
191 // The log entries are formated like so:
192 // seconds_since_epoch,crash_id
193 StringBuilder sb = new StringBuilder();
194 sb.append(System.currentTimeMillis() / 1000);
195 sb.append(",");
196 sb.append(id);
197 sb.append('\n');
198
199 try {
200 // Since we are writing one line at a time, lets forget about Buffer Writers.
201 writer.write(sb.toString());
202 } finally {
203 writer.close();
204 }
205 }
206
207 /**
208 * Get the boundary from the file, we need it for the content-type.
209 *
210 * @return the boundary if found, else null.
211 * @throws IOException
212 */
213 private String readBoundary() throws IOException {
214 BufferedReader reader = new BufferedReader(new FileReader(mFileToUpload) );
215 String boundary = reader.readLine();
216 reader.close();
217 if (boundary == null || boundary.trim().isEmpty()) {
218 Log.e(TAG, "Ignoring invalid crash dump: '" + mFileToUpload + "'");
219 return null;
220 }
221 boundary = boundary.trim();
222 if (!boundary.startsWith("--") || boundary.length() < 10) {
223 Log.e(TAG, "Ignoring invalidly bound crash dump: '" + mFileToUpload + "'");
224 return null;
225 }
226 boundary = boundary.substring(2); // Remove the initial --
227 return boundary;
228 }
229
230 /**
231 * Mark file we just uploaded for cleanup later.
232 *
233 * We do not immediately delete the file for testing reasons,
234 * but if marking the file fails, we do delete it right away.
235 */
236 private void cleanupMinidumpFile() {
237 if (!CrashFileManager.tryMarkAsUploaded(mFileToUpload)) {
238 Log.w(TAG, "Unable to mark " + mFileToUpload + " as uploaded.");
239 if (!mFileToUpload.delete()) {
240 Log.w(TAG, "Cannot delete " + mFileToUpload);
241 }
242 }
243 }
244
245 /**
246 * Checks whether crash upload satisfies the size and frequency constraints.
247 *
248 * @return whether crash upload satisfies the size and frequency constraints .
249 */
250 private boolean isUploadSizeAndFrequencyAllowed() {
251 // Check upload size constraint.
252 if (mFileToUpload.length() > LOG_SIZE_LIMIT_BYTES) return false;
253
254 // Check upload frequency constraint.
255 // If pref doesn't exist then in both cases default value 0 will be retu rned and comparison
256 // always would be true.
257 if (mSharedPreferences.getInt(PREF_LAST_UPLOAD_DAY, 0) != getCurrentDay( )) return true;
258 return mSharedPreferences.getInt(PREF_UPLOAD_COUNT, 0) < LOG_UPLOAD_LIMI T_PER_DAY;
259 }
260
261 /**
262 * Updates preferences used for determining crash upload constraints.
263 */
264 private void updateUploadPrefs() {
265 SharedPreferences.Editor editor = mSharedPreferences.edit();
266
267 int day = getCurrentDay();
268 int prevCount = mSharedPreferences.getInt(PREF_UPLOAD_COUNT, 0);
269 if (mSharedPreferences.getInt(PREF_LAST_UPLOAD_DAY, 0) != day) {
270 prevCount = 0;
271 }
272 editor.putInt(PREF_LAST_UPLOAD_DAY, day).putInt(PREF_UPLOAD_COUNT, prevC ount + 1).apply();
273 }
274
275 /**
276 * Returns number of current day in a year starting from 1. Overridden in te sts.
277 */
278 protected int getCurrentDay() {
279 return Calendar.getInstance().get(Calendar.YEAR) * 365
280 + Calendar.getInstance().get(Calendar.DAY_OF_YEAR);
281 }
282
283 /**
284 * Returns whether the response code indicates a successful HTTP request.
285 *
286 * @param responseCode the response code
287 * @return true if response code indicates success, false otherwise.
288 */
289 private static boolean isSuccessful(int responseCode) {
290 return responseCode == 200 || responseCode == 201 || responseCode == 202 ;
291 }
292
293 /**
294 * Reads the response from |connection| as a String.
295 *
296 * @param connection the connection to read the response from.
297 * @return the content of the response.
298 * @throws IOException
299 */
300 private static String getResponseContentAsString(HttpURLConnection connectio n)
301 throws IOException {
302 String responseContent = null;
303 ByteArrayOutputStream baos = new ByteArrayOutputStream();
304 streamCopy(connection.getInputStream(), baos);
305 if (baos.size() > 0) {
306 responseContent = baos.toString();
307 }
308 return responseContent;
309 }
310
311 /**
312 * Copies all available data from |inStream| to |outStream|. Closes both
313 * streams when done.
314 *
315 * @param inStream the stream to read
316 * @param outStream the stream to write to
317 * @throws IOException
318 */
319 private static void streamCopy(InputStream inStream,
320 OutputStream outStream) throws IOException {
321 byte[] temp = new byte[4096];
322 int bytesRead = inStream.read(temp);
323 while (bytesRead >= 0) {
324 outStream.write(temp, 0, bytesRead);
325 bytesRead = inStream.read(temp);
326 }
327 inStream.close();
328 outStream.close();
329 }
330 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698