Autopsy  4.10.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
Chrome.java
Go to the documentation of this file.
1 /*
2  *
3  * Autopsy Forensic Browser
4  *
5  * Copyright 2012-2019 Basis Technology Corp.
6  *
7  * Copyright 2012 42six Solutions.
8  *
9  * Project Contact/Architect: carrier <at> sleuthkit <dot> org
10  *
11  * Licensed under the Apache License, Version 2.0 (the "License");
12  * you may not use this file except in compliance with the License.
13  * You may obtain a copy of the License at
14  *
15  * http://www.apache.org/licenses/LICENSE-2.0
16  *
17  * Unless required by applicable law or agreed to in writing, software
18  * distributed under the License is distributed on an "AS IS" BASIS,
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  * See the License for the specific language governing permissions and
21  * limitations under the License.
22  */
23 package org.sleuthkit.autopsy.recentactivity;
24 
25 import com.google.gson.JsonArray;
26 import com.google.gson.JsonElement;
27 import com.google.gson.JsonIOException;
28 import com.google.gson.JsonObject;
29 import com.google.gson.JsonParser;
30 import com.google.gson.JsonSyntaxException;
31 import org.openide.util.NbBundle;
34 import java.util.logging.Level;
35 import java.util.*;
36 import java.io.File;
37 import java.io.FileNotFoundException;
38 import java.io.FileReader;
39 import java.io.IOException;
40 import org.apache.commons.io.FilenameUtils;
41 import org.openide.util.NbBundle.Messages;
50 import org.sleuthkit.datamodel.AbstractFile;
51 import org.sleuthkit.datamodel.Account;
52 import org.sleuthkit.datamodel.BlackboardArtifact;
53 import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
54 import org.sleuthkit.datamodel.BlackboardAttribute;
55 import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
56 import org.sleuthkit.datamodel.Content;
57 import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
58 import org.sleuthkit.datamodel.TskCoreException;
59 import org.sleuthkit.datamodel.TskData;
60 
64 class Chrome extends Extract {
65 
66  private static final String HISTORY_QUERY = "SELECT urls.url, urls.title, urls.visit_count, urls.typed_count, " //NON-NLS
67  + "last_visit_time, urls.hidden, visits.visit_time, (SELECT urls.url FROM urls WHERE urls.id=visits.url) AS from_visit, visits.transition FROM urls, visits WHERE urls.id = visits.url"; //NON-NLS
68  private static final String COOKIE_QUERY = "SELECT name, value, host_key, expires_utc,last_access_utc, creation_utc FROM cookies"; //NON-NLS
69  private static final String DOWNLOAD_QUERY = "SELECT full_path, url, start_time, received_bytes FROM downloads"; //NON-NLS
70  private static final String DOWNLOAD_QUERY_V30 = "SELECT current_path AS full_path, url, start_time, received_bytes FROM downloads, downloads_url_chains WHERE downloads.id=downloads_url_chains.id"; //NON-NLS
71  private static final String LOGIN_QUERY = "SELECT origin_url, username_value, date_created, signon_realm from logins"; //NON-NLS
72  private static final String AUTOFILL_QUERY = "SELECT name, value, count, date_created " +
73  " FROM autofill, autofill_dates " +
74  " WHERE autofill.pair_id = autofill_dates.pair_id"
75  ; //NON-NLS
76  private static final String AUTOFILL_QUERY_V8X = "SELECT name, value, count, date_created, date_last_used from autofill"; //NON-NLS
77  private static final String WEBFORM_ADDRESS_QUERY = "SELECT first_name, middle_name, last_name, address_line_1, address_line_2, city, state, zipcode, country_code, number, email, date_modified " +
78  " FROM autofill_profiles, autofill_profile_names, autofill_profile_emails, autofill_profile_phones" +
79  " WHERE autofill_profiles.guid = autofill_profile_names.guid AND autofill_profiles.guid = autofill_profile_emails.guid AND autofill_profiles.guid = autofill_profile_phones.guid";
80 
81  private static final String WEBFORM_ADDRESS_QUERY_V8X = "SELECT first_name, middle_name, last_name, full_name, street_address, city, state, zipcode, country_code, number, email, date_modified, use_date, use_count" +
82  " FROM autofill_profiles, autofill_profile_names, autofill_profile_emails, autofill_profile_phones" +
83  " WHERE autofill_profiles.guid = autofill_profile_names.guid AND autofill_profiles.guid = autofill_profile_emails.guid AND autofill_profiles.guid = autofill_profile_phones.guid";
84  private final Logger logger = Logger.getLogger(this.getClass().getName());
85  private Content dataSource;
86  private IngestJobContext context;
87 
88  @Messages({
89  "Progress_Message_Chrome_History=Chrome History",
90  "Progress_Message_Chrome_Bookmarks=Chrome Bookmarks",
91  "Progress_Message_Chrome_Cookies=Chrome Cookies",
92  "Progress_Message_Chrome_Downloads=Chrome Downloads",
93  "Progress_Message_Chrome_FormHistory=Chrome Form History",
94  "Progress_Message_Chrome_AutoFill=Chrome Auto Fill",
95  "Progress_Message_Chrome_Logins=Chrome Logins",
96  "Progress_Message_Chrome_Cache=Chrome Cache",
97  })
98 
99  Chrome() {
100  moduleName = NbBundle.getMessage(Chrome.class, "Chrome.moduleName");
101  }
102 
103  @Override
104  public void process(Content dataSource, IngestJobContext context, DataSourceIngestModuleProgress progressBar) {
105  this.dataSource = dataSource;
106  this.context = context;
107  dataFound = false;
108 
109  progressBar.progress(Bundle.Progress_Message_Chrome_History());
110  this.getHistory();
111 
112  progressBar.progress(Bundle.Progress_Message_Chrome_Bookmarks());
113  this.getBookmark();
114 
115  progressBar.progress(Bundle.Progress_Message_Chrome_Cookies());
116  this.getCookie();
117 
118  progressBar.progress(Bundle.Progress_Message_Chrome_Logins());
119  this.getLogins();
120 
121  progressBar.progress(Bundle.Progress_Message_Chrome_AutoFill());
122  this.getAutofill();
123 
124  progressBar.progress(Bundle.Progress_Message_Chrome_Downloads());
125  this.getDownload();
126 
127  progressBar.progress(Bundle.Progress_Message_Chrome_Cache());
128  ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context, progressBar);
129  chromeCacheExtractor.getCaches();
130  }
131 
135  private void getHistory() {
136  FileManager fileManager = currentCase.getServices().getFileManager();
137  List<AbstractFile> historyFiles;
138  try {
139  historyFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS
140  } catch (TskCoreException ex) {
141  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errGettingFiles");
142  logger.log(Level.SEVERE, msg, ex);
143  this.addErrorMessage(this.getName() + ": " + msg);
144  return;
145  }
146 
147  // get only the allocated ones, for now
148  List<AbstractFile> allocatedHistoryFiles = new ArrayList<>();
149  for (AbstractFile historyFile : historyFiles) {
150  if (historyFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
151  allocatedHistoryFiles.add(historyFile);
152  }
153  }
154 
155  // log a message if we don't have any allocated history files
156  if (allocatedHistoryFiles.isEmpty()) {
157  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.couldntFindAnyFiles");
158  logger.log(Level.INFO, msg);
159  return;
160  }
161 
162  dataFound = true;
163  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
164  int j = 0;
165  while (j < historyFiles.size()) {
166  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + historyFiles.get(j).getName() + j + ".db"; //NON-NLS
167  final AbstractFile historyFile = historyFiles.get(j++);
168  if (historyFile.getSize() == 0) {
169  continue;
170  }
171  try {
172  ContentUtils.writeToFile(historyFile, new File(temps), context::dataSourceIngestIsCancelled);
173  } catch (ReadContentInputStreamException ex) {
174  logger.log(Level.WARNING, String.format("Error reading Chrome web history artifacts file '%s' (id=%d).",
175  historyFile.getName(), historyFile.getId()), ex); //NON-NLS
176  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errAnalyzingFile",
177  this.getName(), historyFile.getName()));
178  continue;
179  } catch (IOException ex) {
180  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome web history artifacts file '%s' (id=%d).",
181  temps, historyFile.getName(), historyFile.getId()), ex); //NON-NLS
182  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errAnalyzingFile",
183  this.getName(), historyFile.getName()));
184  continue;
185  }
186  File dbFile = new File(temps);
187  if (context.dataSourceIngestIsCancelled()) {
188  dbFile.delete();
189  break;
190  }
191  List<HashMap<String, Object>> tempList;
192  tempList = this.dbConnect(temps, HISTORY_QUERY);
193  logger.log(Level.INFO, "{0}- Now getting history from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
194  for (HashMap<String, Object> result : tempList) {
195  Collection<BlackboardAttribute> bbattributes = new ArrayList<BlackboardAttribute>();
196  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
197  RecentActivityExtracterModuleFactory.getModuleName(),
198  ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS
199  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
200  RecentActivityExtracterModuleFactory.getModuleName(),
201  (Long.valueOf(result.get("last_visit_time").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
202  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REFERRER,
203  RecentActivityExtracterModuleFactory.getModuleName(),
204  ((result.get("from_visit").toString() != null) ? result.get("from_visit").toString() : ""))); //NON-NLS
205  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
206  RecentActivityExtracterModuleFactory.getModuleName(),
207  ((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
208  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
209  RecentActivityExtracterModuleFactory.getModuleName(),
210  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
211  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
212  RecentActivityExtracterModuleFactory.getModuleName(),
213  (NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS
214 
215  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY, historyFile, bbattributes);
216  if (bbart != null) {
217  bbartifacts.add(bbart);
218  }
219  }
220  dbFile.delete();
221  }
222 
223  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
224  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
225  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
226  }
227 
231  private void getBookmark() {
232  FileManager fileManager = currentCase.getServices().getFileManager();
233  List<AbstractFile> bookmarkFiles;
234  try {
235  bookmarkFiles = fileManager.findFiles(dataSource, "Bookmarks", "Chrome"); //NON-NLS
236  } catch (TskCoreException ex) {
237  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errGettingFiles");
238  logger.log(Level.SEVERE, msg, ex);
239  this.addErrorMessage(this.getName() + ": " + msg);
240  return;
241  }
242 
243  if (bookmarkFiles.isEmpty()) {
244  logger.log(Level.INFO, "Didn't find any Chrome bookmark files."); //NON-NLS
245  return;
246  }
247 
248  dataFound = true;
249  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
250  int j = 0;
251 
252  while (j < bookmarkFiles.size()) {
253  AbstractFile bookmarkFile = bookmarkFiles.get(j++);
254  if (bookmarkFile.getSize() == 0) {
255  continue;
256  }
257  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS
258  try {
259  ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled);
260  } catch (ReadContentInputStreamException ex) {
261  logger.log(Level.WARNING, String.format("Error reading Chrome bookmark artifacts file '%s' (id=%d).",
262  bookmarkFile.getName(), bookmarkFile.getId()), ex); //NON-NLS
263  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile",
264  this.getName(), bookmarkFile.getName()));
265  continue;
266  } catch (IOException ex) {
267  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome bookmark artifacts file '%s' (id=%d).",
268  temps, bookmarkFile.getName(), bookmarkFile.getId()), ex); //NON-NLS
269  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile",
270  this.getName(), bookmarkFile.getName()));
271  continue;
272  }
273 
274  logger.log(Level.INFO, "{0}- Now getting Bookmarks from {1}", new Object[]{moduleName, temps}); //NON-NLS
275  File dbFile = new File(temps);
276  if (context.dataSourceIngestIsCancelled()) {
277  dbFile.delete();
278  break;
279  }
280 
281  FileReader tempReader;
282  try {
283  tempReader = new FileReader(temps);
284  } catch (FileNotFoundException ex) {
285  logger.log(Level.SEVERE, "Error while trying to read into the Bookmarks for Chrome.", ex); //NON-NLS
286  this.addErrorMessage(
287  NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzeFile", this.getName(),
288  bookmarkFile.getName()));
289  continue;
290  }
291 
292  final JsonParser parser = new JsonParser();
293  JsonElement jsonElement;
294  JsonObject jElement, jRoot, jBookmark;
295  JsonArray jBookmarkArray;
296 
297  try {
298  jsonElement = parser.parse(tempReader);
299  jElement = jsonElement.getAsJsonObject();
300  jRoot = jElement.get("roots").getAsJsonObject(); //NON-NLS
301  jBookmark = jRoot.get("bookmark_bar").getAsJsonObject(); //NON-NLS
302  jBookmarkArray = jBookmark.getAsJsonArray("children"); //NON-NLS
303  } catch (JsonIOException | JsonSyntaxException | IllegalStateException ex) {
304  logger.log(Level.WARNING, "Error parsing Json from Chrome Bookmark.", ex); //NON-NLS
305  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile3",
306  this.getName(), bookmarkFile.getName()));
307  continue;
308  }
309 
310  for (JsonElement result : jBookmarkArray) {
311  JsonObject address = result.getAsJsonObject();
312  if (address == null) {
313  continue;
314  }
315  JsonElement urlEl = address.get("url"); //NON-NLS
316  String url;
317  if (urlEl != null) {
318  url = urlEl.getAsString();
319  } else {
320  url = "";
321  }
322  String name;
323  JsonElement nameEl = address.get("name"); //NON-NLS
324  if (nameEl != null) {
325  name = nameEl.getAsString();
326  } else {
327  name = "";
328  }
329  Long date;
330  JsonElement dateEl = address.get("date_added"); //NON-NLS
331  if (dateEl != null) {
332  date = dateEl.getAsLong();
333  } else {
334  date = Long.valueOf(0);
335  }
336  String domain = NetworkUtils.extractDomain(url);
337  try {
338  BlackboardArtifact bbart = bookmarkFile.newArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
339  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
340  //TODO Revisit usage of deprecated constructor as per TSK-583
341  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
342  RecentActivityExtracterModuleFactory.getModuleName(), url));
343  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
344  RecentActivityExtracterModuleFactory.getModuleName(), name));
345  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
346  RecentActivityExtracterModuleFactory.getModuleName(), (date / 1000000) - Long.valueOf("11644473600")));
347  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
348  RecentActivityExtracterModuleFactory.getModuleName(),
349  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
350  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
351  RecentActivityExtracterModuleFactory.getModuleName(), domain));
352  bbart.addAttributes(bbattributes);
353 
354  // index the artifact for keyword search
355  this.indexArtifact(bbart);
356  bbartifacts.add(bbart);
357  } catch (TskCoreException ex) {
358  logger.log(Level.SEVERE, "Error while trying to insert Chrome bookmark artifact{0}", ex); //NON-NLS
359  this.addErrorMessage(
360  NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile4",
361  this.getName(), bookmarkFile.getName()));
362  }
363  }
364  dbFile.delete();
365  }
366 
367  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
368  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
369  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bbartifacts));
370  }
371 
375  private void getCookie() {
376 
377  FileManager fileManager = currentCase.getServices().getFileManager();
378  List<AbstractFile> cookiesFiles;
379  try {
380  cookiesFiles = fileManager.findFiles(dataSource, "Cookies", "Chrome"); //NON-NLS
381  } catch (TskCoreException ex) {
382  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errGettingFiles");
383  logger.log(Level.SEVERE, msg, ex);
384  this.addErrorMessage(this.getName() + ": " + msg);
385  return;
386  }
387 
388  if (cookiesFiles.isEmpty()) {
389  logger.log(Level.INFO, "Didn't find any Chrome cookies files."); //NON-NLS
390  return;
391  }
392 
393  dataFound = true;
394  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
395  int j = 0;
396  while (j < cookiesFiles.size()) {
397  AbstractFile cookiesFile = cookiesFiles.get(j++);
398  if (cookiesFile.getSize() == 0) {
399  continue;
400  }
401  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS
402  try {
403  ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled);
404  } catch (ReadContentInputStreamException ex) {
405  logger.log(Level.WARNING, String.format("Error reading Chrome cookie artifacts file '%s' (id=%d).",
406  cookiesFile.getName(), cookiesFile.getId()), ex); //NON-NLS
407  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errAnalyzeFile",
408  this.getName(), cookiesFile.getName()));
409  continue;
410  } catch (IOException ex) {
411  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome cookie artifacts file '%s' (id=%d).",
412  temps, cookiesFile.getName(), cookiesFile.getId()), ex); //NON-NLS
413  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errAnalyzeFile",
414  this.getName(), cookiesFile.getName()));
415  continue;
416  }
417  File dbFile = new File(temps);
418  if (context.dataSourceIngestIsCancelled()) {
419  dbFile.delete();
420  break;
421  }
422 
423  List<HashMap<String, Object>> tempList = this.dbConnect(temps, COOKIE_QUERY);
424  logger.log(Level.INFO, "{0}- Now getting cookies from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
425  for (HashMap<String, Object> result : tempList) {
426  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
427  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
428  RecentActivityExtracterModuleFactory.getModuleName(),
429  ((result.get("host_key").toString() != null) ? result.get("host_key").toString() : ""))); //NON-NLS
430  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME,
431  RecentActivityExtracterModuleFactory.getModuleName(),
432  (Long.valueOf(result.get("last_access_utc").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
433 
434  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
435  RecentActivityExtracterModuleFactory.getModuleName(),
436  ((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
437  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
438  RecentActivityExtracterModuleFactory.getModuleName(),
439  ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
440  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
441  RecentActivityExtracterModuleFactory.getModuleName(),
442  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
443  String domain = result.get("host_key").toString(); //NON-NLS
444  domain = domain.replaceFirst("^\\.+(?!$)", "");
445  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
446  RecentActivityExtracterModuleFactory.getModuleName(), domain));
447 
448  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_COOKIE, cookiesFile, bbattributes);
449  if (bbart != null) {
450  bbartifacts.add(bbart);
451  }
452  }
453 
454  dbFile.delete();
455  }
456 
457  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
458  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
459  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, bbartifacts));
460  }
461 
465  private void getDownload() {
466  FileManager fileManager = currentCase.getServices().getFileManager();
467  List<AbstractFile> downloadFiles;
468  try {
469  downloadFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS
470  } catch (TskCoreException ex) {
471  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errGettingFiles");
472  logger.log(Level.SEVERE, msg, ex);
473  this.addErrorMessage(this.getName() + ": " + msg);
474  return;
475  }
476 
477  if (downloadFiles.isEmpty()) {
478  logger.log(Level.INFO, "Didn't find any Chrome download files."); //NON-NLS
479  return;
480  }
481 
482  dataFound = true;
483  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
484  int j = 0;
485  while (j < downloadFiles.size()) {
486  AbstractFile downloadFile = downloadFiles.get(j++);
487  if (downloadFile.getSize() == 0) {
488  continue;
489  }
490  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
491  try {
492  ContentUtils.writeToFile(downloadFile, new File(temps), context::dataSourceIngestIsCancelled);
493  } catch (ReadContentInputStreamException ex) {
494  logger.log(Level.WARNING, String.format("Error reading Chrome download artifacts file '%s' (id=%d).",
495  downloadFile.getName(), downloadFile.getId()), ex); //NON-NLS
496  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errAnalyzeFiles1",
497  this.getName(), downloadFile.getName()));
498  continue;
499  } catch (IOException ex) {
500  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome download artifacts file '%s' (id=%d).",
501  temps, downloadFile.getName(), downloadFile.getId()), ex); //NON-NLS
502  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errAnalyzeFiles1",
503  this.getName(), downloadFile.getName()));
504  continue;
505  }
506  File dbFile = new File(temps);
507  if (context.dataSourceIngestIsCancelled()) {
508  dbFile.delete();
509  break;
510  }
511 
512  List<HashMap<String, Object>> tempList;
513 
514  if (isChromePreVersion30(temps)) {
515  tempList = this.dbConnect(temps, DOWNLOAD_QUERY);
516  } else {
517  tempList = this.dbConnect(temps, DOWNLOAD_QUERY_V30);
518  }
519 
520  logger.log(Level.INFO, "{0}- Now getting downloads from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
521  for (HashMap<String, Object> result : tempList) {
522  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
523  String fullPath = result.get("full_path").toString(); //NON-NLS
524  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
525  RecentActivityExtracterModuleFactory.getModuleName(), fullPath));
526  long pathID = Util.findID(dataSource, fullPath);
527  if (pathID != -1) {
528  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
529  NbBundle.getMessage(this.getClass(),
530  "Chrome.parentModuleName"), pathID));
531  }
532  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
533  RecentActivityExtracterModuleFactory.getModuleName(),
534  ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS
535  //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "Recent Activity", ((result.get("url").toString() != null) ? EscapeUtil.decodeURL(result.get("url").toString()) : "")));
536  Long time = (Long.valueOf(result.get("start_time").toString()) / 1000000) - Long.valueOf("11644473600"); //NON-NLS
537 
538  //TODO Revisit usage of deprecated constructor as per TSK-583
539  //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "Recent Activity", "Last Visited", time));
540  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
541  RecentActivityExtracterModuleFactory.getModuleName(), time));
542  String domain = NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : ""); //NON-NLS
543  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
544  RecentActivityExtracterModuleFactory.getModuleName(), domain));
545  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
546  RecentActivityExtracterModuleFactory.getModuleName(),
547  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
548 
549  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes);
550  if (bbart != null) {
551  bbartifacts.add(bbart);
552  }
553 
554  // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it..
555  try {
556  for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(fullPath), FilenameUtils.getPath(fullPath))) {
557  BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
558  downloadSourceArt.addAttributes(createDownloadSourceAttributes(result.get("url").toString()));
559 
560  bbartifacts.add(downloadSourceArt);
561  break;
562  }
563  } catch (TskCoreException ex) {
564  logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'", fullPath), ex); //NON-NLS
565  }
566  }
567 
568  dbFile.delete();
569  }
570 
571  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
572  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
573  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
574  }
575 
579  private void getLogins() {
580 
581  FileManager fileManager = currentCase.getServices().getFileManager();
582  List<AbstractFile> loginDataFiles;
583  try {
584  loginDataFiles = fileManager.findFiles(dataSource, "Login Data", "Chrome"); //NON-NLS
585  } catch (TskCoreException ex) {
586  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errGettingFiles");
587  logger.log(Level.SEVERE, msg, ex);
588  this.addErrorMessage(this.getName() + ": " + msg);
589  return;
590  }
591 
592  if (loginDataFiles.isEmpty()) {
593  logger.log(Level.INFO, "Didn't find any Chrome Login Data files."); //NON-NLS
594  return;
595  }
596 
597  dataFound = true;
598  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
599  int j = 0;
600  while (j < loginDataFiles.size()) {
601  AbstractFile loginDataFile = loginDataFiles.get(j++);
602  if (loginDataFile.getSize() == 0) {
603  continue;
604  }
605  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS
606  try {
607  ContentUtils.writeToFile(loginDataFile, new File(temps), context::dataSourceIngestIsCancelled);
608  } catch (ReadContentInputStreamException ex) {
609  logger.log(Level.WARNING, String.format("Error reading Chrome login artifacts file '%s' (id=%d).",
610  loginDataFile.getName(), loginDataFile.getId()), ex); //NON-NLS
611  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
612  this.getName(), loginDataFile.getName()));
613  continue;
614  } catch (IOException ex) {
615  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome login artifacts file '%s' (id=%d).",
616  temps, loginDataFile.getName(), loginDataFile.getId()), ex); //NON-NLS
617  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
618  this.getName(), loginDataFile.getName()));
619  continue;
620  }
621  File dbFile = new File(temps);
622  if (context.dataSourceIngestIsCancelled()) {
623  dbFile.delete();
624  break;
625  }
626  List<HashMap<String, Object>> tempList = this.dbConnect(temps, LOGIN_QUERY);
627  logger.log(Level.INFO, "{0}- Now getting login information from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
628  for (HashMap<String, Object> result : tempList) {
629  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
630 
631  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
632  RecentActivityExtracterModuleFactory.getModuleName(),
633  ((result.get("origin_url").toString() != null) ? result.get("origin_url").toString() : ""))); //NON-NLS
634 
635 
636  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
637  RecentActivityExtracterModuleFactory.getModuleName(),
638  (Long.valueOf(result.get("date_created").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
639 
640  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED,
641  RecentActivityExtracterModuleFactory.getModuleName(),
642  (NetworkUtils.extractDomain((result.get("origin_url").toString() != null) ? result.get("origin_url").toString() : "")))); //NON-NLS
643 
644  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
645  RecentActivityExtracterModuleFactory.getModuleName(),
646  ((result.get("username_value").toString() != null) ? result.get("username_value").toString().replaceAll("'", "''") : ""))); //NON-NLS
647 
648  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
649  RecentActivityExtracterModuleFactory.getModuleName(),
650  ((result.get("signon_realm").toString() != null) ? result.get("signon_realm").toString() : ""))); //NON-NLS
651 
652  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, loginDataFile, bbattributes);
653  if (bbart != null) {
654  this.indexArtifact(bbart);
655  bbartifacts.add(bbart);
656  }
657  }
658 
659  dbFile.delete();
660  }
661  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
662  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
663  BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, bbartifacts));
664 
665  }
666 
671  private void getAutofill() {
672 
673  FileManager fileManager = currentCase.getServices().getFileManager();
674  List<AbstractFile> webDataFiles;
675  try {
676  webDataFiles = fileManager.findFiles(dataSource, "Web Data", "Chrome"); //NON-NLS
677  } catch (TskCoreException ex) {
678  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getAutofills.errMsg.errGettingFiles");
679  logger.log(Level.SEVERE, msg, ex);
680  this.addErrorMessage(this.getName() + ": " + msg);
681  return;
682  }
683 
684  if (webDataFiles.isEmpty()) {
685  logger.log(Level.INFO, "Didn't find any Chrome Web Data files."); //NON-NLS
686  return;
687  }
688 
689  dataFound = true;
690  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
691  int j = 0;
692  while (j < webDataFiles.size()) {
693  AbstractFile webDataFile = webDataFiles.get(j++);
694  if (webDataFile.getSize() == 0) {
695  continue;
696  }
697  String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS
698  try {
699  ContentUtils.writeToFile(webDataFile, new File(tempFilePath), context::dataSourceIngestIsCancelled);
700  } catch (ReadContentInputStreamException ex) {
701  logger.log(Level.WARNING, String.format("Error reading Chrome Autofill artifacts file '%s' (id=%d).",
702  webDataFile.getName(), webDataFile.getId()), ex); //NON-NLS
703  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getAutofill.errMsg.errAnalyzingFiles",
704  this.getName(), webDataFile.getName()));
705  continue;
706  } catch (IOException ex) {
707  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome Web data file '%s' (id=%d).",
708  tempFilePath, webDataFile.getName(), webDataFile.getId()), ex); //NON-NLS
709  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
710  this.getName(), webDataFile.getName()));
711  continue;
712  }
713  File dbFile = new File(tempFilePath);
714  if (context.dataSourceIngestIsCancelled()) {
715  dbFile.delete();
716  break;
717  }
718 
719  // The DB schema is little different in schema version 8x vs older versions
720  boolean isSchemaV8X = Util.checkColumn("date_created", "autofill", tempFilePath);
721 
722  // get form autofill artifacts
723  bbartifacts.addAll(getFormAutofillArtifacts(webDataFile, tempFilePath, isSchemaV8X));
724  // get form address atifacts
725  bbartifacts.addAll(getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X));
726 
727  dbFile.delete();
728  }
729  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
730  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
731  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, bbartifacts));
732 
733  }
734 
744  private Collection<BlackboardArtifact> getFormAutofillArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) {
745 
746  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
747 
748  // The DB Schema is little different in version 8x vs older versions
749  String autoFillquery = (isSchemaV8X) ? AUTOFILL_QUERY_V8X
750  : AUTOFILL_QUERY;
751 
752  List<HashMap<String, Object>> autofills = this.dbConnect(dbFilePath, autoFillquery);
753  logger.log(Level.INFO, "{0}- Now getting Autofill information from {1} with {2}artifacts identified.", new Object[]{moduleName, dbFilePath, autofills.size()}); //NON-NLS
754  for (HashMap<String, Object> result : autofills) {
755  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
756 
757  // extract all common attributes
758  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
759  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
760  ((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
761 
762  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
763  RecentActivityExtracterModuleFactory.getModuleName(),
764  ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
765 
766  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
767  RecentActivityExtracterModuleFactory.getModuleName(),
768  (Integer.valueOf(result.get("count").toString())))); //NON-NLS
769 
770  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
771  RecentActivityExtracterModuleFactory.getModuleName(),
772  Long.valueOf(result.get("date_created").toString()))); //NON-NLS
773 
774  // get schema version specific attributes
775  if (isSchemaV8X) {
776  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
777  RecentActivityExtracterModuleFactory.getModuleName(),
778  Long.valueOf(result.get("date_last_used").toString()))); //NON-NLS
779  }
780 
781  // Add an artifact
782  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, webDataFile, bbattributes);
783  if (bbart != null) {
784  this.indexArtifact(bbart);
785  bbartifacts.add(bbart);
786  }
787  }
788 
789  // return all extracted artifacts
790  return bbartifacts;
791  }
792 
802  private Collection<BlackboardArtifact> getFormAddressArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) {
803  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
804 
805  String webformAddressQuery = (isSchemaV8X) ? WEBFORM_ADDRESS_QUERY_V8X
806  : WEBFORM_ADDRESS_QUERY;
807 
808  // Get Web form addresses
809  List<HashMap<String, Object>> addresses = this.dbConnect(dbFilePath, webformAddressQuery);
810  logger.log(Level.INFO, "{0}- Now getting Web form addresses from {1} with {2}artifacts identified.", new Object[]{moduleName, dbFilePath, addresses.size()}); //NON-NLS
811  for (HashMap<String, Object> result : addresses) {
812  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
813 
814  // get name fields
815  String first_name = result.get("first_name").toString() != null ? result.get("first_name").toString() : "";
816  String middle_name = result.get("middle_name").toString() != null ? result.get("middle_name").toString() : "";
817  String last_name = result.get("last_name").toString() != null ? result.get("last_name").toString() : "";
818 
819  // get email and phone
820  String email_Addr = result.get("email").toString() != null ? result.get("email").toString() : "";
821  String phone_number = result.get("number").toString() != null ? result.get("number").toString() : "";
822 
823  // Get the address fields
824  String city = result.get("city").toString() != null ? result.get("city").toString() : "";
825  String state = result.get("state").toString() != null ? result.get("state").toString() : "";
826  String zipcode = result.get("zipcode").toString() != null ? result.get("zipcode").toString() : "";
827  String country_code = result.get("country_code").toString() != null ? result.get("country_code").toString() : "";
828 
829  // schema version specific fields
830  String full_name = "";
831  String street_address = "";
832  long date_modified = 0;
833  int use_count = 0;
834  long use_date = 0;
835 
836  if (isSchemaV8X) {
837  full_name = result.get("full_name").toString() != null ? result.get("full_name").toString() : "";
838  street_address = result.get("street_address").toString() != null ? result.get("street_address").toString() : "";
839  date_modified = result.get("date_modified").toString() != null ? Long.valueOf(result.get("date_modified").toString()) : 0;
840  use_count = result.get("use_count").toString() != null ? Integer.valueOf(result.get("use_count").toString()) : 0;
841  use_date = result.get("use_date").toString() != null ? Long.valueOf(result.get("use_date").toString()) : 0;
842  } else {
843  String address_line_1 = result.get("address_line_1").toString() != null ? result.get("street_address").toString() : "";
844  String address_line_2 = result.get("address_line_2").toString() != null ? result.get("address_line_2").toString() : "";
845  street_address = String.join(" ", address_line_1, address_line_2);
846  }
847 
848  // If an email address is found, create an account instance for it
849  if (email_Addr != null && !email_Addr.isEmpty()) {
850  try {
851  Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email_Addr, NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), webDataFile);
852  } catch (NoCurrentCaseException | TskCoreException ex) {
853  logger.log(Level.SEVERE, String.format("Error creating email account instance for '%s' from Chrome WebData file '%s' .",
854  email_Addr, webDataFile.getName()), ex); //NON-NLS
855  }
856  }
857  // If a phone number is found, create an account instance for it
858  if (phone_number != null && !phone_number.isEmpty()) {
859  try {
860  Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, phone_number, NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), webDataFile);
861  } catch (NoCurrentCaseException | TskCoreException ex) {
862  logger.log(Level.SEVERE, String.format("Error creating phone account instance for '%s' from Chrome WebData file '%s' .",
863  phone_number, webDataFile.getName()), ex); //NON-NLS
864  }
865  }
866 
867  // Create atrributes from extracted fields
868  if (full_name == null || full_name.isEmpty()) {
869  full_name = String.join(" ", first_name, middle_name, last_name);
870  }
871  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME_PERSON,
872  RecentActivityExtracterModuleFactory.getModuleName(),
873  full_name)); //NON-NLS
874 
875  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL,
876  RecentActivityExtracterModuleFactory.getModuleName(),
877  email_Addr)); //NON-NLS
878 
879  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
880  RecentActivityExtracterModuleFactory.getModuleName(),
881  phone_number)); //NON-NLS
882 
883  String locationAddress = String.join(", ", street_address, city, state, zipcode, country_code);
884  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LOCATION,
885  RecentActivityExtracterModuleFactory.getModuleName(),
886  locationAddress)); //NON-NLS
887 
888  if (date_modified > 0) {
889  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED,
890  RecentActivityExtracterModuleFactory.getModuleName(),
891  date_modified)); //NON-NLS
892  }
893 
894  if (use_count > 0 ){
895  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
896  RecentActivityExtracterModuleFactory.getModuleName(),
897  use_count)); //NON-NLS
898  }
899 
900  if (use_date > 0) {
901  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
902  RecentActivityExtracterModuleFactory.getModuleName(),
903  use_date)); //NON-NLS
904  }
905 
906  // Create artifact
907  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS, webDataFile, bbattributes);
908  if (bbart != null) {
909  this.indexArtifact(bbart);
910  bbartifacts.add(bbart);
911  }
912  }
913 
914  // return all extracted artifacts
915  return bbartifacts;
916  }
917 
918  private boolean isChromePreVersion30(String temps) {
919  String query = "PRAGMA table_info(downloads)"; //NON-NLS
920  List<HashMap<String, Object>> columns = this.dbConnect(temps, query);
921  for (HashMap<String, Object> col : columns) {
922  if (col.get("name").equals("url")) { //NON-NLS
923  return true;
924  }
925  }
926 
927  return false;
928  }
929 }

Copyright © 2012-2018 Basis Technology. Generated on: Fri Mar 22 2019
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.