Autopsy  4.11.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  if (context.dataSourceIngestIsCancelled()) {
112  return;
113  }
114 
115  progressBar.progress(Bundle.Progress_Message_Chrome_Bookmarks());
116  this.getBookmark();
117  if (context.dataSourceIngestIsCancelled()) {
118  return;
119  }
120 
121  progressBar.progress(Bundle.Progress_Message_Chrome_Cookies());
122  this.getCookie();
123  if (context.dataSourceIngestIsCancelled()) {
124  return;
125  }
126 
127  progressBar.progress(Bundle.Progress_Message_Chrome_Logins());
128  this.getLogins();
129  if (context.dataSourceIngestIsCancelled()) {
130  return;
131  }
132 
133  progressBar.progress(Bundle.Progress_Message_Chrome_AutoFill());
134  this.getAutofill();
135  if (context.dataSourceIngestIsCancelled()) {
136  return;
137  }
138 
139  progressBar.progress(Bundle.Progress_Message_Chrome_Downloads());
140  this.getDownload();
141  if (context.dataSourceIngestIsCancelled()) {
142  return;
143  }
144 
145  progressBar.progress(Bundle.Progress_Message_Chrome_Cache());
146  ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context, progressBar);
147  chromeCacheExtractor.getCaches();
148  }
149 
153  private void getHistory() {
154  FileManager fileManager = currentCase.getServices().getFileManager();
155  List<AbstractFile> historyFiles;
156  try {
157  historyFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS
158  } catch (TskCoreException ex) {
159  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errGettingFiles");
160  logger.log(Level.SEVERE, msg, ex);
161  this.addErrorMessage(this.getName() + ": " + msg);
162  return;
163  }
164 
165  // get only the allocated ones, for now
166  List<AbstractFile> allocatedHistoryFiles = new ArrayList<>();
167  for (AbstractFile historyFile : historyFiles) {
168  if (historyFile.isMetaFlagSet(TskData.TSK_FS_META_FLAG_ENUM.ALLOC)) {
169  allocatedHistoryFiles.add(historyFile);
170  }
171  }
172 
173  // log a message if we don't have any allocated history files
174  if (allocatedHistoryFiles.isEmpty()) {
175  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.couldntFindAnyFiles");
176  logger.log(Level.INFO, msg);
177  return;
178  }
179 
180  dataFound = true;
181  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
182  int j = 0;
183  while (j < historyFiles.size()) {
184  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + historyFiles.get(j).getName() + j + ".db"; //NON-NLS
185  final AbstractFile historyFile = historyFiles.get(j++);
186  if (historyFile.getSize() == 0) {
187  continue;
188  }
189  try {
190  ContentUtils.writeToFile(historyFile, new File(temps), context::dataSourceIngestIsCancelled);
191  } catch (ReadContentInputStreamException ex) {
192  logger.log(Level.WARNING, String.format("Error reading Chrome web history artifacts file '%s' (id=%d).",
193  historyFile.getName(), historyFile.getId()), ex); //NON-NLS
194  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errAnalyzingFile",
195  this.getName(), historyFile.getName()));
196  continue;
197  } catch (IOException ex) {
198  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome web history artifacts file '%s' (id=%d).",
199  temps, historyFile.getName(), historyFile.getId()), ex); //NON-NLS
200  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errAnalyzingFile",
201  this.getName(), historyFile.getName()));
202  continue;
203  }
204  File dbFile = new File(temps);
205  if (context.dataSourceIngestIsCancelled()) {
206  dbFile.delete();
207  break;
208  }
209  List<HashMap<String, Object>> tempList;
210  tempList = this.dbConnect(temps, HISTORY_QUERY);
211  logger.log(Level.INFO, "{0}- Now getting history from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
212  for (HashMap<String, Object> result : tempList) {
213  Collection<BlackboardAttribute> bbattributes = new ArrayList<BlackboardAttribute>();
214  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
215  RecentActivityExtracterModuleFactory.getModuleName(),
216  ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS
217  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
218  RecentActivityExtracterModuleFactory.getModuleName(),
219  (Long.valueOf(result.get("last_visit_time").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
220  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REFERRER,
221  RecentActivityExtracterModuleFactory.getModuleName(),
222  ((result.get("from_visit").toString() != null) ? result.get("from_visit").toString() : ""))); //NON-NLS
223  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
224  RecentActivityExtracterModuleFactory.getModuleName(),
225  ((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
226  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
227  RecentActivityExtracterModuleFactory.getModuleName(),
228  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
229  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
230  RecentActivityExtracterModuleFactory.getModuleName(),
231  (NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS
232 
233  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY, historyFile, bbattributes);
234  if (bbart != null) {
235  bbartifacts.add(bbart);
236  }
237  }
238  dbFile.delete();
239  }
240 
241  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
242  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
243  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
244  }
245 
249  private void getBookmark() {
250  FileManager fileManager = currentCase.getServices().getFileManager();
251  List<AbstractFile> bookmarkFiles;
252  try {
253  bookmarkFiles = fileManager.findFiles(dataSource, "Bookmarks", "Chrome"); //NON-NLS
254  } catch (TskCoreException ex) {
255  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errGettingFiles");
256  logger.log(Level.SEVERE, msg, ex);
257  this.addErrorMessage(this.getName() + ": " + msg);
258  return;
259  }
260 
261  if (bookmarkFiles.isEmpty()) {
262  logger.log(Level.INFO, "Didn't find any Chrome bookmark files."); //NON-NLS
263  return;
264  }
265 
266  dataFound = true;
267  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
268  int j = 0;
269 
270  while (j < bookmarkFiles.size()) {
271  AbstractFile bookmarkFile = bookmarkFiles.get(j++);
272  if (bookmarkFile.getSize() == 0) {
273  continue;
274  }
275  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS
276  try {
277  ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled);
278  } catch (ReadContentInputStreamException ex) {
279  logger.log(Level.WARNING, String.format("Error reading Chrome bookmark artifacts file '%s' (id=%d).",
280  bookmarkFile.getName(), bookmarkFile.getId()), ex); //NON-NLS
281  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile",
282  this.getName(), bookmarkFile.getName()));
283  continue;
284  } catch (IOException ex) {
285  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome bookmark artifacts file '%s' (id=%d).",
286  temps, bookmarkFile.getName(), bookmarkFile.getId()), ex); //NON-NLS
287  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile",
288  this.getName(), bookmarkFile.getName()));
289  continue;
290  }
291 
292  logger.log(Level.INFO, "{0}- Now getting Bookmarks from {1}", new Object[]{moduleName, temps}); //NON-NLS
293  File dbFile = new File(temps);
294  if (context.dataSourceIngestIsCancelled()) {
295  dbFile.delete();
296  break;
297  }
298 
299  FileReader tempReader;
300  try {
301  tempReader = new FileReader(temps);
302  } catch (FileNotFoundException ex) {
303  logger.log(Level.SEVERE, "Error while trying to read into the Bookmarks for Chrome.", ex); //NON-NLS
304  this.addErrorMessage(
305  NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzeFile", this.getName(),
306  bookmarkFile.getName()));
307  continue;
308  }
309 
310  final JsonParser parser = new JsonParser();
311  JsonElement jsonElement;
312  JsonObject jElement, jRoot, jBookmark;
313  JsonArray jBookmarkArray;
314 
315  try {
316  jsonElement = parser.parse(tempReader);
317  jElement = jsonElement.getAsJsonObject();
318  jRoot = jElement.get("roots").getAsJsonObject(); //NON-NLS
319  jBookmark = jRoot.get("bookmark_bar").getAsJsonObject(); //NON-NLS
320  jBookmarkArray = jBookmark.getAsJsonArray("children"); //NON-NLS
321  } catch (JsonIOException | JsonSyntaxException | IllegalStateException ex) {
322  logger.log(Level.WARNING, "Error parsing Json from Chrome Bookmark.", ex); //NON-NLS
323  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile3",
324  this.getName(), bookmarkFile.getName()));
325  continue;
326  }
327 
328  for (JsonElement result : jBookmarkArray) {
329  JsonObject address = result.getAsJsonObject();
330  if (address == null) {
331  continue;
332  }
333  JsonElement urlEl = address.get("url"); //NON-NLS
334  String url;
335  if (urlEl != null) {
336  url = urlEl.getAsString();
337  } else {
338  url = "";
339  }
340  String name;
341  JsonElement nameEl = address.get("name"); //NON-NLS
342  if (nameEl != null) {
343  name = nameEl.getAsString();
344  } else {
345  name = "";
346  }
347  Long date;
348  JsonElement dateEl = address.get("date_added"); //NON-NLS
349  if (dateEl != null) {
350  date = dateEl.getAsLong();
351  } else {
352  date = Long.valueOf(0);
353  }
354  String domain = NetworkUtils.extractDomain(url);
355  try {
356  BlackboardArtifact bbart = bookmarkFile.newArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK);
357  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
358  //TODO Revisit usage of deprecated constructor as per TSK-583
359  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
360  RecentActivityExtracterModuleFactory.getModuleName(), url));
361  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
362  RecentActivityExtracterModuleFactory.getModuleName(), name));
363  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
364  RecentActivityExtracterModuleFactory.getModuleName(), (date / 1000000) - Long.valueOf("11644473600")));
365  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
366  RecentActivityExtracterModuleFactory.getModuleName(),
367  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
368  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
369  RecentActivityExtracterModuleFactory.getModuleName(), domain));
370  bbart.addAttributes(bbattributes);
371 
372  // index the artifact for keyword search
373  this.indexArtifact(bbart);
374  bbartifacts.add(bbart);
375  } catch (TskCoreException ex) {
376  logger.log(Level.SEVERE, "Error while trying to insert Chrome bookmark artifact{0}", ex); //NON-NLS
377  this.addErrorMessage(
378  NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errAnalyzingFile4",
379  this.getName(), bookmarkFile.getName()));
380  }
381  }
382  dbFile.delete();
383  }
384 
385  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
386  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
387  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bbartifacts));
388  }
389 
393  private void getCookie() {
394 
395  FileManager fileManager = currentCase.getServices().getFileManager();
396  List<AbstractFile> cookiesFiles;
397  try {
398  cookiesFiles = fileManager.findFiles(dataSource, "Cookies", "Chrome"); //NON-NLS
399  } catch (TskCoreException ex) {
400  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errGettingFiles");
401  logger.log(Level.SEVERE, msg, ex);
402  this.addErrorMessage(this.getName() + ": " + msg);
403  return;
404  }
405 
406  if (cookiesFiles.isEmpty()) {
407  logger.log(Level.INFO, "Didn't find any Chrome cookies files."); //NON-NLS
408  return;
409  }
410 
411  dataFound = true;
412  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
413  int j = 0;
414  while (j < cookiesFiles.size()) {
415  AbstractFile cookiesFile = cookiesFiles.get(j++);
416  if (cookiesFile.getSize() == 0) {
417  continue;
418  }
419  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS
420  try {
421  ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled);
422  } catch (ReadContentInputStreamException ex) {
423  logger.log(Level.WARNING, String.format("Error reading Chrome cookie artifacts file '%s' (id=%d).",
424  cookiesFile.getName(), cookiesFile.getId()), ex); //NON-NLS
425  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errAnalyzeFile",
426  this.getName(), cookiesFile.getName()));
427  continue;
428  } catch (IOException ex) {
429  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome cookie artifacts file '%s' (id=%d).",
430  temps, cookiesFile.getName(), cookiesFile.getId()), ex); //NON-NLS
431  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errAnalyzeFile",
432  this.getName(), cookiesFile.getName()));
433  continue;
434  }
435  File dbFile = new File(temps);
436  if (context.dataSourceIngestIsCancelled()) {
437  dbFile.delete();
438  break;
439  }
440 
441  List<HashMap<String, Object>> tempList = this.dbConnect(temps, COOKIE_QUERY);
442  logger.log(Level.INFO, "{0}- Now getting cookies from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
443  for (HashMap<String, Object> result : tempList) {
444  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
445  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
446  RecentActivityExtracterModuleFactory.getModuleName(),
447  ((result.get("host_key").toString() != null) ? result.get("host_key").toString() : ""))); //NON-NLS
448  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME,
449  RecentActivityExtracterModuleFactory.getModuleName(),
450  (Long.valueOf(result.get("last_access_utc").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
451 
452  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
453  RecentActivityExtracterModuleFactory.getModuleName(),
454  ((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
455  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
456  RecentActivityExtracterModuleFactory.getModuleName(),
457  ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
458  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
459  RecentActivityExtracterModuleFactory.getModuleName(),
460  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
461  String domain = result.get("host_key").toString(); //NON-NLS
462  domain = domain.replaceFirst("^\\.+(?!$)", "");
463  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
464  RecentActivityExtracterModuleFactory.getModuleName(), domain));
465 
466  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_COOKIE, cookiesFile, bbattributes);
467  if (bbart != null) {
468  bbartifacts.add(bbart);
469  }
470  }
471 
472  dbFile.delete();
473  }
474 
475  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
476  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
477  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, bbartifacts));
478  }
479 
483  private void getDownload() {
484  FileManager fileManager = currentCase.getServices().getFileManager();
485  List<AbstractFile> downloadFiles;
486  try {
487  downloadFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS
488  } catch (TskCoreException ex) {
489  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errGettingFiles");
490  logger.log(Level.SEVERE, msg, ex);
491  this.addErrorMessage(this.getName() + ": " + msg);
492  return;
493  }
494 
495  if (downloadFiles.isEmpty()) {
496  logger.log(Level.INFO, "Didn't find any Chrome download files."); //NON-NLS
497  return;
498  }
499 
500  dataFound = true;
501  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
502  int j = 0;
503  while (j < downloadFiles.size()) {
504  AbstractFile downloadFile = downloadFiles.get(j++);
505  if (downloadFile.getSize() == 0) {
506  continue;
507  }
508  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
509  try {
510  ContentUtils.writeToFile(downloadFile, new File(temps), context::dataSourceIngestIsCancelled);
511  } catch (ReadContentInputStreamException ex) {
512  logger.log(Level.WARNING, String.format("Error reading Chrome download artifacts file '%s' (id=%d).",
513  downloadFile.getName(), downloadFile.getId()), ex); //NON-NLS
514  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errAnalyzeFiles1",
515  this.getName(), downloadFile.getName()));
516  continue;
517  } catch (IOException ex) {
518  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome download artifacts file '%s' (id=%d).",
519  temps, downloadFile.getName(), downloadFile.getId()), ex); //NON-NLS
520  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errAnalyzeFiles1",
521  this.getName(), downloadFile.getName()));
522  continue;
523  }
524  File dbFile = new File(temps);
525  if (context.dataSourceIngestIsCancelled()) {
526  dbFile.delete();
527  break;
528  }
529 
530  List<HashMap<String, Object>> tempList;
531 
532  if (isChromePreVersion30(temps)) {
533  tempList = this.dbConnect(temps, DOWNLOAD_QUERY);
534  } else {
535  tempList = this.dbConnect(temps, DOWNLOAD_QUERY_V30);
536  }
537 
538  logger.log(Level.INFO, "{0}- Now getting downloads from {1} with {2} artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
539  for (HashMap<String, Object> result : tempList) {
540  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
541  String fullPath = result.get("full_path").toString(); //NON-NLS
542  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
543  RecentActivityExtracterModuleFactory.getModuleName(), fullPath));
544  long pathID = Util.findID(dataSource, fullPath);
545  if (pathID != -1) {
546  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
547  NbBundle.getMessage(this.getClass(),
548  "Chrome.parentModuleName"), pathID));
549  }
550  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
551  RecentActivityExtracterModuleFactory.getModuleName(),
552  ((result.get("url").toString() != null) ? result.get("url").toString() : ""))); //NON-NLS
553  //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "Recent Activity", ((result.get("url").toString() != null) ? EscapeUtil.decodeURL(result.get("url").toString()) : "")));
554  Long time = (Long.valueOf(result.get("start_time").toString()) / 1000000) - Long.valueOf("11644473600"); //NON-NLS
555 
556  //TODO Revisit usage of deprecated constructor as per TSK-583
557  //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "Recent Activity", "Last Visited", time));
558  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
559  RecentActivityExtracterModuleFactory.getModuleName(), time));
560  String domain = NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : ""); //NON-NLS
561  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
562  RecentActivityExtracterModuleFactory.getModuleName(), domain));
563  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
564  RecentActivityExtracterModuleFactory.getModuleName(),
565  NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
566 
567  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes);
568  if (bbart != null) {
569  bbartifacts.add(bbart);
570  }
571 
572  // find the downloaded file and create a TSK_DOWNLOAD_SOURCE for it..
573  try {
574  for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(fullPath), FilenameUtils.getPath(fullPath))) {
575  BlackboardArtifact downloadSourceArt = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_DOWNLOAD_SOURCE);
576  downloadSourceArt.addAttributes(createDownloadSourceAttributes(result.get("url").toString()));
577 
578  bbartifacts.add(downloadSourceArt);
579  break;
580  }
581  } catch (TskCoreException ex) {
582  logger.log(Level.SEVERE, String.format("Error creating download source artifact for file '%s'", fullPath), ex); //NON-NLS
583  }
584  }
585 
586  dbFile.delete();
587  }
588 
589  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
590  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
591  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
592  }
593 
597  private void getLogins() {
598 
599  FileManager fileManager = currentCase.getServices().getFileManager();
600  List<AbstractFile> loginDataFiles;
601  try {
602  loginDataFiles = fileManager.findFiles(dataSource, "Login Data", "Chrome"); //NON-NLS
603  } catch (TskCoreException ex) {
604  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errGettingFiles");
605  logger.log(Level.SEVERE, msg, ex);
606  this.addErrorMessage(this.getName() + ": " + msg);
607  return;
608  }
609 
610  if (loginDataFiles.isEmpty()) {
611  logger.log(Level.INFO, "Didn't find any Chrome Login Data files."); //NON-NLS
612  return;
613  }
614 
615  dataFound = true;
616  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
617  int j = 0;
618  while (j < loginDataFiles.size()) {
619  AbstractFile loginDataFile = loginDataFiles.get(j++);
620  if (loginDataFile.getSize() == 0) {
621  continue;
622  }
623  String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS
624  try {
625  ContentUtils.writeToFile(loginDataFile, new File(temps), context::dataSourceIngestIsCancelled);
626  } catch (ReadContentInputStreamException ex) {
627  logger.log(Level.WARNING, String.format("Error reading Chrome login artifacts file '%s' (id=%d).",
628  loginDataFile.getName(), loginDataFile.getId()), ex); //NON-NLS
629  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
630  this.getName(), loginDataFile.getName()));
631  continue;
632  } catch (IOException ex) {
633  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome login artifacts file '%s' (id=%d).",
634  temps, loginDataFile.getName(), loginDataFile.getId()), ex); //NON-NLS
635  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
636  this.getName(), loginDataFile.getName()));
637  continue;
638  }
639  File dbFile = new File(temps);
640  if (context.dataSourceIngestIsCancelled()) {
641  dbFile.delete();
642  break;
643  }
644  List<HashMap<String, Object>> tempList = this.dbConnect(temps, LOGIN_QUERY);
645  logger.log(Level.INFO, "{0}- Now getting login information from {1} with {2}artifacts identified.", new Object[]{moduleName, temps, tempList.size()}); //NON-NLS
646  for (HashMap<String, Object> result : tempList) {
647  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
648 
649  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
650  RecentActivityExtracterModuleFactory.getModuleName(),
651  ((result.get("origin_url").toString() != null) ? result.get("origin_url").toString() : ""))); //NON-NLS
652 
653 
654  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
655  RecentActivityExtracterModuleFactory.getModuleName(),
656  (Long.valueOf(result.get("date_created").toString()) / 1000000) - Long.valueOf("11644473600"))); //NON-NLS
657 
658  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED,
659  RecentActivityExtracterModuleFactory.getModuleName(),
660  (NetworkUtils.extractDomain((result.get("origin_url").toString() != null) ? result.get("origin_url").toString() : "")))); //NON-NLS
661 
662  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_USER_NAME,
663  RecentActivityExtracterModuleFactory.getModuleName(),
664  ((result.get("username_value").toString() != null) ? result.get("username_value").toString().replaceAll("'", "''") : ""))); //NON-NLS
665 
666  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
667  RecentActivityExtracterModuleFactory.getModuleName(),
668  ((result.get("signon_realm").toString() != null) ? result.get("signon_realm").toString() : ""))); //NON-NLS
669 
670  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, loginDataFile, bbattributes);
671  if (bbart != null) {
672  this.indexArtifact(bbart);
673  bbartifacts.add(bbart);
674  }
675  }
676 
677  dbFile.delete();
678  }
679  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
680  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
681  BlackboardArtifact.ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, bbartifacts));
682 
683  }
684 
689  private void getAutofill() {
690 
691  FileManager fileManager = currentCase.getServices().getFileManager();
692  List<AbstractFile> webDataFiles;
693  try {
694  webDataFiles = fileManager.findFiles(dataSource, "Web Data", "Chrome"); //NON-NLS
695  } catch (TskCoreException ex) {
696  String msg = NbBundle.getMessage(this.getClass(), "Chrome.getAutofills.errMsg.errGettingFiles");
697  logger.log(Level.SEVERE, msg, ex);
698  this.addErrorMessage(this.getName() + ": " + msg);
699  return;
700  }
701 
702  if (webDataFiles.isEmpty()) {
703  logger.log(Level.INFO, "Didn't find any Chrome Web Data files."); //NON-NLS
704  return;
705  }
706 
707  dataFound = true;
708  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
709  int j = 0;
710  while (j < webDataFiles.size()) {
711  AbstractFile webDataFile = webDataFiles.get(j++);
712  if (webDataFile.getSize() == 0) {
713  continue;
714  }
715  String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS
716  try {
717  ContentUtils.writeToFile(webDataFile, new File(tempFilePath), context::dataSourceIngestIsCancelled);
718  } catch (ReadContentInputStreamException ex) {
719  logger.log(Level.WARNING, String.format("Error reading Chrome Autofill artifacts file '%s' (id=%d).",
720  webDataFile.getName(), webDataFile.getId()), ex); //NON-NLS
721  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getAutofill.errMsg.errAnalyzingFiles",
722  this.getName(), webDataFile.getName()));
723  continue;
724  } catch (IOException ex) {
725  logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Chrome Web data file '%s' (id=%d).",
726  tempFilePath, webDataFile.getName(), webDataFile.getId()), ex); //NON-NLS
727  this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errAnalyzingFiles",
728  this.getName(), webDataFile.getName()));
729  continue;
730  }
731  File dbFile = new File(tempFilePath);
732  if (context.dataSourceIngestIsCancelled()) {
733  dbFile.delete();
734  break;
735  }
736 
737  // The DB schema is little different in schema version 8x vs older versions
738  boolean isSchemaV8X = Util.checkColumn("date_created", "autofill", tempFilePath);
739 
740  // get form autofill artifacts
741  bbartifacts.addAll(getFormAutofillArtifacts(webDataFile, tempFilePath, isSchemaV8X));
742  // get form address atifacts
743  bbartifacts.addAll(getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X));
744 
745  dbFile.delete();
746  }
747  IngestServices.getInstance().fireModuleDataEvent(new ModuleDataEvent(
748  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
749  BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, bbartifacts));
750 
751  }
752 
762  private Collection<BlackboardArtifact> getFormAutofillArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) {
763 
764  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
765 
766  // The DB Schema is little different in version 8x vs older versions
767  String autoFillquery = (isSchemaV8X) ? AUTOFILL_QUERY_V8X
768  : AUTOFILL_QUERY;
769 
770  List<HashMap<String, Object>> autofills = this.dbConnect(dbFilePath, autoFillquery);
771  logger.log(Level.INFO, "{0}- Now getting Autofill information from {1} with {2}artifacts identified.", new Object[]{moduleName, dbFilePath, autofills.size()}); //NON-NLS
772  for (HashMap<String, Object> result : autofills) {
773  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
774 
775  // extract all common attributes
776  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
777  NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"),
778  ((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
779 
780  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
781  RecentActivityExtracterModuleFactory.getModuleName(),
782  ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
783 
784  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
785  RecentActivityExtracterModuleFactory.getModuleName(),
786  (Integer.valueOf(result.get("count").toString())))); //NON-NLS
787 
788  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
789  RecentActivityExtracterModuleFactory.getModuleName(),
790  Long.valueOf(result.get("date_created").toString()))); //NON-NLS
791 
792  // get schema version specific attributes
793  if (isSchemaV8X) {
794  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
795  RecentActivityExtracterModuleFactory.getModuleName(),
796  Long.valueOf(result.get("date_last_used").toString()))); //NON-NLS
797  }
798 
799  // Add an artifact
800  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, webDataFile, bbattributes);
801  if (bbart != null) {
802  this.indexArtifact(bbart);
803  bbartifacts.add(bbart);
804  }
805  }
806 
807  // return all extracted artifacts
808  return bbartifacts;
809  }
810 
820  private Collection<BlackboardArtifact> getFormAddressArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) {
821  Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
822 
823  String webformAddressQuery = (isSchemaV8X) ? WEBFORM_ADDRESS_QUERY_V8X
824  : WEBFORM_ADDRESS_QUERY;
825 
826  // Get Web form addresses
827  List<HashMap<String, Object>> addresses = this.dbConnect(dbFilePath, webformAddressQuery);
828  logger.log(Level.INFO, "{0}- Now getting Web form addresses from {1} with {2}artifacts identified.", new Object[]{moduleName, dbFilePath, addresses.size()}); //NON-NLS
829  for (HashMap<String, Object> result : addresses) {
830  Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
831 
832  // get name fields
833  String first_name = result.get("first_name").toString() != null ? result.get("first_name").toString() : "";
834  String middle_name = result.get("middle_name").toString() != null ? result.get("middle_name").toString() : "";
835  String last_name = result.get("last_name").toString() != null ? result.get("last_name").toString() : "";
836 
837  // get email and phone
838  String email_Addr = result.get("email").toString() != null ? result.get("email").toString() : "";
839  String phone_number = result.get("number").toString() != null ? result.get("number").toString() : "";
840 
841  // Get the address fields
842  String city = result.get("city").toString() != null ? result.get("city").toString() : "";
843  String state = result.get("state").toString() != null ? result.get("state").toString() : "";
844  String zipcode = result.get("zipcode").toString() != null ? result.get("zipcode").toString() : "";
845  String country_code = result.get("country_code").toString() != null ? result.get("country_code").toString() : "";
846 
847  // schema version specific fields
848  String full_name = "";
849  String street_address = "";
850  long date_modified = 0;
851  int use_count = 0;
852  long use_date = 0;
853 
854  if (isSchemaV8X) {
855  full_name = result.get("full_name").toString() != null ? result.get("full_name").toString() : "";
856  street_address = result.get("street_address").toString() != null ? result.get("street_address").toString() : "";
857  date_modified = result.get("date_modified").toString() != null ? Long.valueOf(result.get("date_modified").toString()) : 0;
858  use_count = result.get("use_count").toString() != null ? Integer.valueOf(result.get("use_count").toString()) : 0;
859  use_date = result.get("use_date").toString() != null ? Long.valueOf(result.get("use_date").toString()) : 0;
860  } else {
861  String address_line_1 = result.get("address_line_1").toString() != null ? result.get("street_address").toString() : "";
862  String address_line_2 = result.get("address_line_2").toString() != null ? result.get("address_line_2").toString() : "";
863  street_address = String.join(" ", address_line_1, address_line_2);
864  }
865 
866  // If an email address is found, create an account instance for it
867  if (email_Addr != null && !email_Addr.isEmpty()) {
868  try {
869  Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.EMAIL, email_Addr, NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), webDataFile);
870  } catch (NoCurrentCaseException | TskCoreException ex) {
871  logger.log(Level.SEVERE, String.format("Error creating email account instance for '%s' from Chrome WebData file '%s' .",
872  email_Addr, webDataFile.getName()), ex); //NON-NLS
873  }
874  }
875  // If a phone number is found, create an account instance for it
876  if (phone_number != null && !phone_number.isEmpty()) {
877  try {
878  Case.getCurrentCaseThrows().getSleuthkitCase().getCommunicationsManager().createAccountFileInstance(Account.Type.PHONE, phone_number, NbBundle.getMessage(this.getClass(), "Chrome.parentModuleName"), webDataFile);
879  } catch (NoCurrentCaseException | TskCoreException ex) {
880  logger.log(Level.SEVERE, String.format("Error creating phone account instance for '%s' from Chrome WebData file '%s' .",
881  phone_number, webDataFile.getName()), ex); //NON-NLS
882  }
883  }
884 
885  // Create atrributes from extracted fields
886  if (full_name == null || full_name.isEmpty()) {
887  full_name = String.join(" ", first_name, middle_name, last_name);
888  }
889  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME_PERSON,
890  RecentActivityExtracterModuleFactory.getModuleName(),
891  full_name)); //NON-NLS
892 
893  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL,
894  RecentActivityExtracterModuleFactory.getModuleName(),
895  email_Addr)); //NON-NLS
896 
897  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PHONE_NUMBER,
898  RecentActivityExtracterModuleFactory.getModuleName(),
899  phone_number)); //NON-NLS
900 
901  String locationAddress = String.join(", ", street_address, city, state, zipcode, country_code);
902  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LOCATION,
903  RecentActivityExtracterModuleFactory.getModuleName(),
904  locationAddress)); //NON-NLS
905 
906  if (date_modified > 0) {
907  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED,
908  RecentActivityExtracterModuleFactory.getModuleName(),
909  date_modified)); //NON-NLS
910  }
911 
912  if (use_count > 0 ){
913  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
914  RecentActivityExtracterModuleFactory.getModuleName(),
915  use_count)); //NON-NLS
916  }
917 
918  if (use_date > 0) {
919  bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
920  RecentActivityExtracterModuleFactory.getModuleName(),
921  use_date)); //NON-NLS
922  }
923 
924  // Create artifact
925  BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_FORM_ADDRESS, webDataFile, bbattributes);
926  if (bbart != null) {
927  this.indexArtifact(bbart);
928  bbartifacts.add(bbart);
929  }
930  }
931 
932  // return all extracted artifacts
933  return bbartifacts;
934  }
935 
936  private boolean isChromePreVersion30(String temps) {
937  String query = "PRAGMA table_info(downloads)"; //NON-NLS
938  List<HashMap<String, Object>> columns = this.dbConnect(temps, query);
939  for (HashMap<String, Object> col : columns) {
940  if (col.get("name").equals("url")) { //NON-NLS
941  return true;
942  }
943  }
944 
945  return false;
946  }
947 }

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