Autopsy 4.22.1
Graphical digital forensics platform for The Sleuth Kit and other tools.
Firefox.java
Go to the documentation of this file.
1/*
2 *
3 * Autopsy Forensic Browser
4 *
5 * Copyright 2012-2021 Basis Technology Corp.
6 *
7 * Copyright 2012 42six Solutions.
8 * Contact: aebadirad <at> 42six <dot> com
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 */
23package org.sleuthkit.autopsy.recentactivity;
24
25import com.google.gson.JsonArray;
26import com.google.gson.JsonElement;
27import com.google.gson.JsonIOException;
28import com.google.gson.JsonObject;
29import com.google.gson.JsonParser;
30import com.google.gson.JsonSyntaxException;
31import java.io.File;
32import java.io.FileNotFoundException;
33import java.io.FileReader;
34import java.io.IOException;
35import java.io.UnsupportedEncodingException;
36import java.net.URLDecoder;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.Collection;
40import java.util.HashMap;
41import java.util.HashSet;
42import java.util.List;
43import java.util.Set;
44import java.util.logging.Level;
45import org.apache.commons.io.FilenameUtils;
46import org.openide.util.NbBundle;
47import org.openide.util.NbBundle.Messages;
48import org.sleuthkit.autopsy.casemodule.Case;
49import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
50import org.sleuthkit.autopsy.casemodule.services.FileManager;
51import org.sleuthkit.autopsy.coreutils.Logger;
52import org.sleuthkit.autopsy.coreutils.NetworkUtils;
53import org.sleuthkit.autopsy.datamodel.ContentUtils;
54import org.sleuthkit.autopsy.ingest.DataSourceIngestModuleProgress;
55import org.sleuthkit.autopsy.ingest.IngestJobContext;
56import org.sleuthkit.datamodel.AbstractFile;
57import org.sleuthkit.datamodel.Blackboard;
58import org.sleuthkit.datamodel.BlackboardArtifact;
59import org.sleuthkit.datamodel.BlackboardAttribute;
60import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
61import org.sleuthkit.datamodel.Content;
62import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
63import org.sleuthkit.datamodel.TskCoreException;
64import org.sleuthkit.datamodel.blackboardutils.WebBrowserArtifactsHelper;
65
66@Messages({
67 "Progress_Message_Firefox_History=Firefox History",
68 "Progress_Message_Firefox_Bookmarks=Firefox Bookmarks",
69 "Progress_Message_Firefox_Cookies=Firefox Cookies",
70 "Progress_Message_Firefox_Downloads=Firefox Downloads",
71 "Progress_Message_Firefox_FormHistory=Firefox Form History",
72 "Progress_Message_Firefox_AutoFill=Firefox Auto Fill"
73})
74
78class Firefox extends Extract {
79
80 private static final Logger logger = Logger.getLogger(Firefox.class.getName());
81 private static final String PLACE_URL_PREFIX = "place:";
82 private static final String HISTORY_QUERY = "SELECT moz_historyvisits.id, url, title, visit_count,(visit_date/1000000) AS visit_date,from_visit,"
83 + "(SELECT url FROM moz_historyvisits history, moz_places places where history.id = moz_historyvisits.from_visit and history.place_id = places.id ) as ref "
84 + "FROM moz_places, moz_historyvisits "
85 + "WHERE moz_places.id = moz_historyvisits.place_id "
86 + "AND hidden = 0"; //NON-NLS
87 private static final String COOKIE_QUERY = "SELECT name,value,host,expiry,(lastAccessed/1000000) AS lastAccessed,(creationTime/1000000) AS creationTime FROM moz_cookies"; //NON-NLS
88 private static final String COOKIE_QUERY_V3 = "SELECT name,value,host,expiry,(lastAccessed/1000000) AS lastAccessed FROM moz_cookies"; //NON-NLS
89 private static final String BOOKMARK_QUERY = "SELECT fk, moz_bookmarks.title, url, (moz_bookmarks.dateAdded/1000000) AS dateAdded FROM moz_bookmarks INNER JOIN moz_places ON moz_bookmarks.fk=moz_places.id"; //NON-NLS
90 private static final String DOWNLOAD_QUERY = "SELECT target, source,(startTime/1000000) AS startTime, maxBytes FROM moz_downloads"; //NON-NLS
91 private static final String DOWNLOAD_QUERY_V24 = "SELECT url, content AS target, (lastModified/1000000) AS lastModified "
92 + " FROM moz_places, moz_annos, moz_anno_attributes "
93 + " WHERE moz_places.id = moz_annos.place_id"
94 + " AND moz_annos.anno_attribute_id = moz_anno_attributes.id"
95 + " AND moz_anno_attributes.name='downloads/destinationFileURI'"; //NON-NLS
96 private static final String FORMHISTORY_QUERY = "SELECT fieldname, value FROM moz_formhistory";
97 private static final String FORMHISTORY_QUERY_V64 = "SELECT fieldname, value, timesUsed, firstUsed, lastUsed FROM moz_formhistory";
98 private Content dataSource;
99 private final IngestJobContext context;
100
101 Firefox(IngestJobContext context) {
102 super(NbBundle.getMessage(Firefox.class, "Firefox.moduleName"), context);
103 this.context = context;
104 }
105
106 @Override
107 public void process(Content dataSource, DataSourceIngestModuleProgress progressBar) {
108 this.dataSource = dataSource;
109 dataFound = false;
110 long ingestJobId = context.getJobId();
111
112 progressBar.progress(Bundle.Progress_Message_Firefox_History());
113 this.getHistory(context.getJobId());
114
115 if (context.dataSourceIngestIsCancelled()) {
116 return;
117 }
118
119 progressBar.progress(Bundle.Progress_Message_Firefox_Bookmarks());
120 this.getBookmark(ingestJobId);
121
122 if (context.dataSourceIngestIsCancelled()) {
123 return;
124 }
125
126 progressBar.progress(Bundle.Progress_Message_Firefox_Downloads());
127 this.getDownload(ingestJobId);
128
129 if (context.dataSourceIngestIsCancelled()) {
130 return;
131 }
132
133 progressBar.progress(Bundle.Progress_Message_Firefox_Cookies());
134 this.getCookie(ingestJobId);
135
136 if (context.dataSourceIngestIsCancelled()) {
137 return;
138 }
139
140 progressBar.progress(Bundle.Progress_Message_Firefox_FormHistory());
141 this.getFormsHistory(ingestJobId);
142
143 if (context.dataSourceIngestIsCancelled()) {
144 return;
145 }
146
147 progressBar.progress(Bundle.Progress_Message_Firefox_AutoFill());
148 this.getAutofillProfiles(ingestJobId);
149 }
150
156 private void getHistory(long ingestJobId) {
157 FileManager fileManager = currentCase.getServices().getFileManager();
158 List<AbstractFile> historyFiles;
159 try {
160 historyFiles = fileManager.findFiles(dataSource, "places.sqlite", "Firefox"); //NON-NLS
161 } catch (TskCoreException ex) {
162 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.errFetchingFiles");
163 logger.log(Level.WARNING, msg);
164 this.addErrorMessage(this.getDisplayName() + ": " + msg);
165 return;
166 }
167
168 if (historyFiles.isEmpty()) {
169 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.noFilesFound");
170 logger.log(Level.INFO, msg);
171 return;
172 }
173
174 dataFound = true;
175 Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
176 int j = 0;
177 for (AbstractFile historyFile : historyFiles) {
178
179 if (context.dataSourceIngestIsCancelled()) {
180 return;
181 }
182
183 if (historyFile.getSize() == 0) {
184 continue;
185 }
186
187 String fileName = historyFile.getName();
188 String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
189 try {
190 ContentUtils.writeToFile(historyFile, new File(temps), context::dataSourceIngestIsCancelled);
191 } catch (ReadContentInputStreamException ex) {
192 logger.log(Level.WARNING, String.format("Error reading Firefox web history artifacts file '%s' (id=%d).",
193 fileName, historyFile.getId()), ex); //NON-NLS
194 this.addErrorMessage(
195 NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.errAnalyzeFile", this.getDisplayName(),
196 fileName));
197 continue;
198 } catch (IOException ex) {
199 logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Firefox web history artifacts file '%s' (id=%d).",
200 temps, fileName, historyFile.getId()), ex); //NON-NLS
201 this.addErrorMessage(
202 NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.errAnalyzeFile", this.getDisplayName(),
203 fileName));
204 continue;
205 }
206 File dbFile = new File(temps);
207 if (context.dataSourceIngestIsCancelled()) {
208 dbFile.delete();
209 break;
210 }
211 List<HashMap<String, Object>> tempList = this.querySQLiteDb(temps, HISTORY_QUERY);
212 logger.log(Level.INFO, "{0} - Now getting history from {1} with {2} artifacts identified.", new Object[]{getDisplayName(), temps, tempList.size()}); //NON-NLS
213 for (HashMap<String, Object> result : tempList) {
214
215 if (context.dataSourceIngestIsCancelled()) {
216 return;
217 }
218
219 String url = result.get("url").toString();
220 String domain = extractDomain(url);
221 try {
222
223 Collection<BlackboardAttribute> bbattributes = createHistoryAttributes(
224 url,
225 Long.valueOf(result.get("visit_date").toString()),
226 result.get("ref").toString(),
227 result.get("title").toString(),
228 NbBundle.getMessage(this.getClass(), "Firefox.moduleName"),
229 domain,
230 null);
231
232
233 bbartifacts.add(createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_HISTORY, historyFile, bbattributes));
234 } catch (TskCoreException ex) {
235 logger.log(Level.SEVERE, String.format("Failed to create TSK_WEB_HISTORY artifact for file %d", historyFile.getId()), ex);
236 }
237 }
238 ++j;
239 dbFile.delete();
240 }
241
242 if (!context.dataSourceIngestIsCancelled()) {
243 postArtifacts(bbartifacts);
244 }
245 }
246
252 private void getBookmark(long ingestJobId) {
253
254 FileManager fileManager = currentCase.getServices().getFileManager();
255 List<AbstractFile> bookmarkFiles;
256 try {
257 bookmarkFiles = fileManager.findFiles(dataSource, "places.sqlite", "Firefox"); //NON-NLS
258 } catch (TskCoreException ex) {
259 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getBookmark.errMsg.errFetchFiles");
260 logger.log(Level.WARNING, msg);
261 this.addErrorMessage(this.getDisplayName() + ": " + msg);
262 return;
263 }
264
265 if (bookmarkFiles.isEmpty()) {
266 logger.log(Level.INFO, "Didn't find any firefox bookmark files."); //NON-NLS
267 return;
268 }
269
270 dataFound = true;
271 Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
272 int j = 0;
273 for (AbstractFile bookmarkFile : bookmarkFiles) {
274 if (bookmarkFile.getSize() == 0) {
275 continue;
276 }
277 String fileName = bookmarkFile.getName();
278 String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
279 try {
280 ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled);
281 } catch (ReadContentInputStreamException ex) {
282 logger.log(Level.WARNING, String.format("Error reading Firefox bookmark artifacts file '%s' (id=%d).",
283 fileName, bookmarkFile.getId()), ex); //NON-NLS
284 this.addErrorMessage(
285 NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.errAnalyzeFile", this.getDisplayName(),
286 fileName));
287 continue;
288 } catch (IOException ex) {
289 logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Firefox bookmark artifacts file '%s' (id=%d).",
290 temps, fileName, bookmarkFile.getId()), ex); //NON-NLS
291 this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Firefox.getBookmark.errMsg.errAnalyzeFile",
292 this.getDisplayName(), fileName));
293 continue;
294 }
295 File dbFile = new File(temps);
296 if (context.dataSourceIngestIsCancelled()) {
297 dbFile.delete();
298 break;
299 }
300 List<HashMap<String, Object>> tempList = this.querySQLiteDb(temps, BOOKMARK_QUERY);
301 logger.log(Level.INFO, "{0} - Now getting bookmarks from {1} with {2} artifacts identified.", new Object[]{getDisplayName(), temps, tempList.size()}); //NON-NLS
302 for (HashMap<String, Object> result : tempList) {
303
304 if (context.dataSourceIngestIsCancelled()) {
305 break;
306 }
307
308 String url = result.get("url").toString();
309
310 Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
311 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
313 ((url != null) ? url : ""))); //NON-NLS
314 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
316 ((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
317 if (Long.valueOf(result.get("dateAdded").toString()) > 0) { //NON-NLS
318 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
320 (Long.valueOf(result.get("dateAdded").toString())))); //NON-NLS
321 }
322 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
324 NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
325 String domain = extractDomain(url);
326 if (domain != null && domain.isEmpty() == false) {
327 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
328 RecentActivityExtracterModuleFactory.getModuleName(), domain)); //NON-NLS
329 }
330
331 try {
332 bbartifacts.add(createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_BOOKMARK, bookmarkFile, bbattributes));
333 } catch (TskCoreException ex) {
334 logger.log(Level.SEVERE, String.format("Failed to create TSK_WEB_BOOKMARK artifact for file %d", bookmarkFile.getId()), ex);
335 }
336 }
337 ++j;
338 dbFile.delete();
339 }
340
341 if (!context.dataSourceIngestIsCancelled()) {
342 postArtifacts(bbartifacts);
343 }
344 }
345
351 private void getCookie(long ingestJobId) {
352 FileManager fileManager = currentCase.getServices().getFileManager();
353 List<AbstractFile> cookiesFiles;
354 try {
355 cookiesFiles = fileManager.findFiles(dataSource, "cookies.sqlite", "Firefox"); //NON-NLS
356 } catch (TskCoreException ex) {
357 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getCookie.errMsg.errFetchFile");
358 logger.log(Level.WARNING, msg);
359 this.addErrorMessage(this.getDisplayName() + ": " + msg);
360 return;
361 }
362
363 if (cookiesFiles.isEmpty()) {
364 logger.log(Level.INFO, "Didn't find any Firefox cookie files."); //NON-NLS
365 return;
366 }
367
368 dataFound = true;
369 Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
370 int j = 0;
371 for (AbstractFile cookiesFile : cookiesFiles) {
372 if (context.dataSourceIngestIsCancelled()) {
373 return;
374 }
375
376 if (cookiesFile.getSize() == 0) {
377 continue;
378 }
379 String fileName = cookiesFile.getName();
380 String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
381 try {
382 ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled);
383 } catch (ReadContentInputStreamException ex) {
384 logger.log(Level.WARNING, String.format("Error reading Firefox cookie artifacts file '%s' (id=%d).",
385 fileName, cookiesFile.getId()), ex); //NON-NLS
386 this.addErrorMessage(
387 NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.errAnalyzeFile", this.getDisplayName(),
388 fileName));
389 continue;
390 } catch (IOException ex) {
391 logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Firefox cookie artifacts file '%s' (id=%d).",
392 temps, fileName, cookiesFile.getId()), ex); //NON-NLS
393 this.addErrorMessage(
394 NbBundle.getMessage(this.getClass(), "Firefox.getCookie.errMsg.errAnalyzeFile", this.getDisplayName(),
395 fileName));
396 continue;
397 }
398 File dbFile = new File(temps);
399 if (context.dataSourceIngestIsCancelled()) {
400 dbFile.delete();
401 break;
402 }
403 boolean checkColumn = Util.checkColumn("creationTime", "moz_cookies", temps); //NON-NLS
404 String query;
405 if (checkColumn) {
406 query = COOKIE_QUERY;
407 } else {
408 query = COOKIE_QUERY_V3;
409 }
410
411 List<HashMap<String, Object>> tempList = this.querySQLiteDb(temps, query);
412 logger.log(Level.INFO, "{0} - Now getting cookies from {1} with {2} artifacts identified.", new Object[]{getDisplayName(), temps, tempList.size()}); //NON-NLS
413 for (HashMap<String, Object> result : tempList) {
414
415 if (context.dataSourceIngestIsCancelled()) {
416 break;
417 }
418
419 String host = result.get("host").toString();
420
421 Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
422 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
424 ((host != null) ? host : ""))); //NON-NLS
425 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
427 (Long.valueOf(result.get("lastAccessed").toString())))); //NON-NLS
428 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
430 ((result.get("name").toString() != null) ? result.get("name").toString() : ""))); //NON-NLS
431 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
433 ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
434 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
436 NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
437
438 if (checkColumn == true) {
439 String value = result.get("creationTime").toString();
440 if(value != null && !value.isEmpty()) {
441 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
443 (Long.valueOf(result.get("creationTime").toString())))); //NON-NLS
444 }
445 }
446 String domain = extractDomain(host);
447 if (domain != null && domain.isEmpty() == false) {
448 domain = domain.replaceFirst("^\\.+(?!$)", "");
449 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
450 RecentActivityExtracterModuleFactory.getModuleName(), domain));
451 }
452
453 try {
454 bbartifacts.add(createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_COOKIE, cookiesFile, bbattributes));
455 } catch (TskCoreException ex) {
456 logger.log(Level.SEVERE, String.format("Failed to create TSK_WEB_COOKIE artifact for file %d", cookiesFile.getId()), ex);
457 }
458 }
459 ++j;
460 dbFile.delete();
461 }
462
463 if (!context.dataSourceIngestIsCancelled()) {
464 postArtifacts(bbartifacts);
465 }
466 }
467
473 private void getDownload(long ingestJobId) {
474 getDownloadPreVersion24(ingestJobId);
475 getDownloadVersion24(ingestJobId);
476 }
477
485 private void getDownloadPreVersion24(long ingestJobId) {
486
487 FileManager fileManager = currentCase.getServices().getFileManager();
488 List<AbstractFile> downloadsFiles;
489 try {
490 downloadsFiles = fileManager.findFiles(dataSource, "downloads.sqlite", "Firefox"); //NON-NLS
491 } catch (TskCoreException ex) {
492 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getDlPre24.errMsg.errFetchFiles");
493 logger.log(Level.WARNING, msg);
494 this.addErrorMessage(this.getDisplayName() + ": " + msg);
495 return;
496 }
497
498 if (downloadsFiles.isEmpty()) {
499 logger.log(Level.INFO, "Didn't find any pre-version-24.0 Firefox download files."); //NON-NLS
500 return;
501 }
502
503 dataFound = true;
504 Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
505 int j = 0;
506 for (AbstractFile downloadsFile : downloadsFiles) {
507 if (downloadsFile.getSize() == 0) {
508 continue;
509 }
510 String fileName = downloadsFile.getName();
511 String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
512 int errors = 0;
513 try {
514 ContentUtils.writeToFile(downloadsFile, new File(temps), context::dataSourceIngestIsCancelled);
515 } catch (ReadContentInputStreamException ex) {
516 logger.log(Level.WARNING, String.format("Error reading Firefox download artifacts file '%s' (id=%d).",
517 fileName, downloadsFile.getId()), ex); //NON-NLS
518 this.addErrorMessage(
519 NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.errAnalyzeFile", this.getDisplayName(),
520 fileName));
521 continue;
522 } catch (IOException ex) {
523 logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Firefox download artifacts file '%s' (id=%d).",
524 temps, fileName, downloadsFile.getId()), ex); //NON-NLS
525 this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Firefox.getDlPre24.errMsg.errAnalyzeFiles",
526 this.getDisplayName(), fileName));
527 continue;
528 }
529 File dbFile = new File(temps);
530 if (context.dataSourceIngestIsCancelled()) {
531 dbFile.delete();
532 break;
533 }
534
535 List<HashMap<String, Object>> tempList = this.querySQLiteDb(temps, DOWNLOAD_QUERY);
536 logger.log(Level.INFO, "{0}- Now getting downloads from {1} with {2} artifacts identified.", new Object[]{getDisplayName(), temps, tempList.size()}); //NON-NLS
537 for (HashMap<String, Object> result : tempList) {
538
539 if (context.dataSourceIngestIsCancelled()) {
540 break;
541 }
542
543 String source = result.get("source").toString();
544
545 Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
546
547 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
549 source)); //NON-NLS
550 //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : "")));
551 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
553 (Long.valueOf(result.get("startTime").toString())))); //NON-NLS
554
555 String target = result.get("target").toString(); //NON-NLS
556 String downloadedFilePath = "";
557 if (target != null) {
558 try {
559 downloadedFilePath = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS
560 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
562 downloadedFilePath));
563 long pathID = Util.findID(dataSource, downloadedFilePath);
564 if (pathID != -1) {
565 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
567 pathID));
568 }
569 } catch (UnsupportedEncodingException ex) {
570 logger.log(Level.SEVERE, "Error decoding Firefox download URL in " + temps, ex); //NON-NLS
571 errors++;
572 }
573 }
574
575 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
577 NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
578 String domain = extractDomain(source);
579 if (domain != null && domain.isEmpty() == false) {
580 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
582 domain)); //NON-NLS
583 }
584 try {
585 BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
586 bbartifacts.add(webDownloadArtifact);
587
588 // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
589 for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource,
590 FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) {
591 bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact));
592 break;
593 }
594 } catch (TskCoreException ex) {
595 logger.log(Level.SEVERE, String.format("Error creating TSK_WEB_DOWNLOAD or TSK_ASSOCIATED_ARTIFACT artifact for file '%d'",
596 downloadsFile.getId()), ex); //NON-NLS
597 }
598
599 }
600 if (errors > 0) {
601 this.addErrorMessage(
602 NbBundle.getMessage(this.getClass(), "Firefox.getDlPre24.errMsg.errParsingArtifacts",
603 this.getDisplayName(), errors));
604 }
605 j++;
606 dbFile.delete();
607 }
608
609 if (!context.dataSourceIngestIsCancelled()) {
610 postArtifacts(bbartifacts);
611 }
612 }
613
621 private void getDownloadVersion24(long ingestJobId) {
622 FileManager fileManager = currentCase.getServices().getFileManager();
623 List<AbstractFile> downloadsFiles;
624 try {
625 downloadsFiles = fileManager.findFiles(dataSource, "places.sqlite", "Firefox"); //NON-NLS
626 } catch (TskCoreException ex) {
627 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getDlV24.errMsg.errFetchFiles");
628 logger.log(Level.WARNING, msg);
629 this.addErrorMessage(this.getDisplayName() + ": " + msg);
630 return;
631 }
632
633 if (downloadsFiles.isEmpty()) {
634 logger.log(Level.INFO, "Didn't find any version-24.0 Firefox download files."); //NON-NLS
635 return;
636 }
637
638 dataFound = true;
639 Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
640 int j = 0;
641 for (AbstractFile downloadsFile : downloadsFiles) {
642 if (downloadsFile.getSize() == 0) {
643 continue;
644 }
645 String fileName = downloadsFile.getName();
646 String temps = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + "-downloads" + j + ".db"; //NON-NLS
647 int errors = 0;
648 try {
649 ContentUtils.writeToFile(downloadsFile, new File(temps), context::dataSourceIngestIsCancelled);
650 } catch (ReadContentInputStreamException ex) {
651 logger.log(Level.WARNING, String.format("Error reading Firefox download artifacts file '%s' (id=%d).",
652 fileName, downloadsFile.getId()), ex); //NON-NLS
653 this.addErrorMessage(
654 NbBundle.getMessage(this.getClass(), "Firefox.getHistory.errMsg.errAnalyzeFile", this.getDisplayName(),
655 fileName));
656 continue;
657 } catch (IOException ex) {
658 logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Firefox download artifacts file '%s' (id=%d).",
659 temps, fileName, downloadsFile.getId()), ex); //NON-NLS
660 this.addErrorMessage(
661 NbBundle.getMessage(this.getClass(), "Firefox.getDlV24.errMsg.errAnalyzeFile", this.getDisplayName(),
662 fileName));
663 continue;
664 }
665 File dbFile = new File(temps);
666 if (context.dataSourceIngestIsCancelled()) {
667 dbFile.delete();
668 break;
669 }
670
671 List<HashMap<String, Object>> tempList = this.querySQLiteDb(temps, DOWNLOAD_QUERY_V24);
672
673 logger.log(Level.INFO, "{0} - Now getting downloads from {1} with {2} artifacts identified.", new Object[]{getDisplayName(), temps, tempList.size()}); //NON-NLS
674 for (HashMap<String, Object> result : tempList) {
675
676 if (context.dataSourceIngestIsCancelled()) {
677 break;
678 }
679
680 String url = result.get("url").toString();
681
682 Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
683
684 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
686 url)); //NON-NLS
687 //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL_DECODED.getTypeID(), "RecentActivity", ((result.get("source").toString() != null) ? EscapeUtil.decodeURL(result.get("source").toString()) : "")));
688 //TODO Revisit usage of deprecated constructor as per TSK-583
689 //bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_LAST_ACCESSED.getTypeID(), "RecentActivity", "Last Visited", (Long.valueOf(result.get("startTime").toString()))));
690
691 String target = result.get("target").toString(); //NON-NLS
692 String downloadedFilePath = "";
693 if (target != null) {
694 try {
695 downloadedFilePath = URLDecoder.decode(target.replaceAll("file:///", ""), "UTF-8"); //NON-NLS
696 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
698 downloadedFilePath));
699 long pathID = Util.findID(dataSource, downloadedFilePath);
700 if (pathID != -1) {
701 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
703 pathID));
704 }
705 } catch (UnsupportedEncodingException ex) {
706 logger.log(Level.SEVERE, "Error decoding Firefox download URL in " + temps, ex); //NON-NLS
707 errors++;
708 }
709 }
710 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
712 Long.valueOf(result.get("lastModified").toString()))); //NON-NLS
713 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
715 NbBundle.getMessage(this.getClass(), "Firefox.moduleName")));
716 String domain = extractDomain(url);
717 if (domain != null && domain.isEmpty() == false) {
718 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
719 RecentActivityExtracterModuleFactory.getModuleName(), domain)); //NON-NLS
720 }
721 try {
722 BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
723 bbartifacts.add(webDownloadArtifact);
724
725 // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
726 for (AbstractFile downloadedFile : currentCase.getSleuthkitCase().getFileManager().findFilesExactNameExactPath(dataSource,
727 FilenameUtils.getName(downloadedFilePath), FilenameUtils.getPath(downloadedFilePath))) {
728 bbartifacts.add(createAssociatedArtifact(downloadedFile, webDownloadArtifact));
729 break;
730 }
731 } catch (TskCoreException ex) {
732 logger.log(Level.SEVERE, String.format("Error creating associated object artifact for file '%s'",
733 downloadedFilePath), ex); //NON-NLS
734 }
735 }
736 if (errors > 0) {
737 this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Firefox.getDlV24.errMsg.errParsingArtifacts",
738 this.getDisplayName(), errors));
739 }
740 j++;
741 dbFile.delete();
742 }
743
744 if (!context.dataSourceIngestIsCancelled()) {
745 postArtifacts(bbartifacts);
746 }
747 }
748
755 private void getFormsHistory(long ingestJobId) {
756 FileManager fileManager = currentCase.getServices().getFileManager();
757 List<AbstractFile> formHistoryFiles;
758
759 // Some fields are just noisy and can me excluded
760 Set<String> excludedFieldNames = new HashSet<>(Arrays.asList(
761 "it", // some kind of timestamp
762 "ts" // some kind of timestamp
763 ));
764
765 try {
766 formHistoryFiles = fileManager.findFiles(dataSource, "formhistory.sqlite", "Firefox"); //NON-NLS
767 } catch (TskCoreException ex) {
768 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getFormsAutofill.errMsg.errFetchingFiles");
769 logger.log(Level.WARNING, msg);
770 this.addErrorMessage(this.getDisplayName() + ": " + msg);
771 return;
772 }
773
774 if (formHistoryFiles.isEmpty()) {
775 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getFormsAutofill.errMsg.noFilesFound");
776 logger.log(Level.INFO, msg);
777 return;
778 }
779
780 dataFound = true;
781 Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
782 int j = 0;
783 for (AbstractFile formHistoryFile : formHistoryFiles) {
784 if (formHistoryFile.getSize() == 0) {
785 continue;
786 }
787
788 String fileName = formHistoryFile.getName();
789 String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "firefox", ingestJobId) + File.separator + fileName + j + ".db"; //NON-NLS
790 try {
791 ContentUtils.writeToFile(formHistoryFile, new File(tempFilePath), context::dataSourceIngestIsCancelled);
792 } catch (ReadContentInputStreamException ex) {
793 logger.log(Level.WARNING, String.format("Error reading Firefox web history artifacts file '%s' (id=%d).",
794 fileName, formHistoryFile.getId()), ex); //NON-NLS
795 this.addErrorMessage(
796 NbBundle.getMessage(this.getClass(), "Firefox.getFormsAutofill.errMsg.errAnalyzeFile", this.getDisplayName(),
797 fileName));
798 continue;
799 } catch (IOException ex) {
800 logger.log(Level.SEVERE, String.format("Error writing temp sqlite db file '%s' for Firefox web history artifacts file '%s' (id=%d).",
801 tempFilePath, fileName, formHistoryFile.getId()), ex); //NON-NLS
802 this.addErrorMessage(
803 NbBundle.getMessage(this.getClass(), "Firefox.getFormsAutofill.errMsg.errAnalyzeFile", this.getDisplayName(),
804 fileName));
805 continue;
806 }
807 File dbFile = new File(tempFilePath);
808 if (context.dataSourceIngestIsCancelled()) {
809 dbFile.delete();
810 break;
811 }
812
813 // The table schema is a little different in newer version of Firefox
814 boolean isFirefoxV64 = Util.checkColumn("timesUsed", "moz_formhistory", tempFilePath);
815 String formHistoryQuery = (isFirefoxV64) ? FORMHISTORY_QUERY_V64 : FORMHISTORY_QUERY;
816
817 List<HashMap<String, Object>> tempList = this.querySQLiteDb(tempFilePath, formHistoryQuery);
818 logger.log(Level.INFO, "{0} - Now getting history from {1} with {2} artifacts identified.", new Object[]{getDisplayName(), tempFilePath, tempList.size()}); //NON-NLS
819 for (HashMap<String, Object> result : tempList) {
820
821 if (context.dataSourceIngestIsCancelled()) {
822 break;
823 }
824
825 Collection<BlackboardAttribute> bbattributes = new ArrayList<>();
826
827 String fieldName = ((result.get("fieldname").toString() != null) ? result.get("fieldname").toString() : "");
828 // filter out unuseful values
829 if (excludedFieldNames.contains(fieldName.toLowerCase())) {
830 continue;
831 }
832
833 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
835 fieldName)); //NON-NLS
836
837 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
839 ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
840
841 // Newer versions of firefox have additional columns
842 if (isFirefoxV64) {
843 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
845 (Long.valueOf(result.get("firstUsed").toString()) / 1000000))); //NON-NLS
846
847 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
849 (Long.valueOf(result.get("lastUsed").toString()) / 1000000))); //NON-NLS
850
851 bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COUNT,
853 (Integer.valueOf(result.get("timesUsed").toString())))); //NON-NLS
854
855 }
856 try {
857 // Add artifact
858 bbartifacts.add(createArtifactWithAttributes(BlackboardArtifact.Type.TSK_WEB_FORM_AUTOFILL, formHistoryFile, bbattributes));
859 } catch (TskCoreException ex) {
860 logger.log(Level.SEVERE, String.format("Failed to create TSK_WEB_FORM_AUTOFILL artifact for file %d", formHistoryFile.getId()), ex);
861 }
862 }
863 ++j;
864 dbFile.delete();
865 }
866
867 if (!context.dataSourceIngestIsCancelled()) {
868 postArtifacts(bbartifacts);
869 }
870 }
871
878 private void getAutofillProfiles(long ingestJobId) {
879 FileManager fileManager = currentCase.getServices().getFileManager();
880 List<AbstractFile> autofillProfilesFiles;
881 try {
882 autofillProfilesFiles = fileManager.findFiles(dataSource, "autofill-profiles.json", "Firefox"); //NON-NLS
883 } catch (TskCoreException ex) {
884 String msg = NbBundle.getMessage(this.getClass(), "Firefox.getAutofillProfiles.errMsg.errGettingFiles");
885 logger.log(Level.SEVERE, msg, ex);
886 this.addErrorMessage(this.getDisplayName() + ": " + msg);
887 return;
888 }
889
890 if (autofillProfilesFiles.isEmpty()) {
891 logger.log(Level.INFO, "Didn't find any Firefox Autofill Profiles files."); //NON-NLS
892 return;
893 }
894
895 dataFound = true;
896 int j = 0;
897 while (j < autofillProfilesFiles.size()) {
898 AbstractFile profileFile = autofillProfilesFiles.get(j++);
899 if (profileFile.getSize() == 0) {
900 continue;
901 }
902 String temps = RAImageIngestModule.getRATempPath(currentCase, "Firefox", ingestJobId) + File.separator + profileFile.getName() + j + ".json"; //NON-NLS
903 try {
904 ContentUtils.writeToFile(profileFile, new File(temps), context::dataSourceIngestIsCancelled);
905 } catch (ReadContentInputStreamException ex) {
906 logger.log(Level.WARNING, String.format("Error reading Firefox Autofill profiles artifacts file '%s' (id=%d).",
907 profileFile.getName(), profileFile.getId()), ex); //NON-NLS
908 this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Firefox.getAutofillProfiles.errMsg.errAnalyzingFile",
909 this.getDisplayName(), profileFile.getName()));
910 continue;
911 } catch (IOException ex) {
912 logger.log(Level.SEVERE, String.format("Error writing temp file '%s' for Firefox Autofill profiles file '%s' (id=%d).",
913 temps, profileFile.getName(), profileFile.getId()), ex); //NON-NLS
914 this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Firefox.getAutofillProfiles.errMsg.errAnalyzingFile",
915 this.getDisplayName(), profileFile.getName()));
916 continue;
917 }
918
919 logger.log(Level.INFO, "{0}- Now getting Bookmarks from {1}", new Object[]{getDisplayName(), temps}); //NON-NLS
920 File dbFile = new File(temps);
921 if (context.dataSourceIngestIsCancelled()) {
922 dbFile.delete();
923 break;
924 }
925
926 FileReader tempReader;
927 try {
928 tempReader = new FileReader(temps);
929 } catch (FileNotFoundException ex) {
930 logger.log(Level.SEVERE, "Error while trying to read the Autofill profiles json file for Firefox.", ex); //NON-NLS
931 this.addErrorMessage(
932 NbBundle.getMessage(this.getClass(), "Firefox.getAutofillProfiles.errMsg.errAnalyzeFile", this.getDisplayName(),
933 profileFile.getName()));
934 continue;
935 }
936
937 JsonObject jsonRootObject;
938 JsonArray jAddressesArray;
939
940 try {
941 jsonRootObject = JsonParser.parseReader(tempReader).getAsJsonObject();
942 jAddressesArray = jsonRootObject.getAsJsonArray("addresses"); //NON-NLS
943 } catch (JsonIOException | JsonSyntaxException | IllegalStateException ex) {
944 logger.log(Level.WARNING, "Error parsing Json for Firefox Autofill profiles.", ex); //NON-NLS
945 this.addErrorMessage(NbBundle.getMessage(this.getClass(), "Firefox.getAutofillProfiles.errMsg.errAnalyzingFile3",
946 this.getDisplayName(), profileFile.getName()));
947 continue;
948 }
949
950 WebBrowserArtifactsHelper helper;
951 try {
952 // Helper to create web form address artifacts.
953 helper = new WebBrowserArtifactsHelper(
955 NbBundle.getMessage(this.getClass(), "Firefox.parentModuleName"),
956 profileFile,
957 ingestJobId
958 );
959 } catch (NoCurrentCaseException ex) {
960 logger.log(Level.SEVERE, "No case open, bailing.", ex); //NON-NLS
961 return;
962 }
963
964 for (JsonElement result : jAddressesArray) {
965 JsonObject address = result.getAsJsonObject();
966 if (address == null) {
967 continue;
968 }
969
970 JsonElement nameEl = address.get("name"); //NON-NLS
971 String name = (nameEl != null) ? nameEl.getAsString() : "";
972
973 JsonElement emailEl = address.get("email"); //NON-NLS
974 String email = (emailEl != null) ? emailEl.getAsString() : "";
975
976 JsonElement telEl = address.get("tel"); //NON-NLS
977 String tel = (telEl != null) ? telEl.getAsString() : "";
978 JsonElement telCountryCodeEl = address.get("tel-country-code"); //NON-NLS
979 String telCountryCode = (telCountryCodeEl != null) ? telCountryCodeEl.getAsString() : "";
980 JsonElement telNationalEl = address.get("tel-national"); //NON-NLS
981 String telNational = (telNationalEl != null) ? telNationalEl.getAsString() : "";
982
983 String phoneNumber = makeTelNumber(tel, telCountryCode, telNational);
984
985 JsonElement createdEl = address.get("timeCreated"); //NON-NLS
986 Long datetimeCreated = (createdEl != null) ? createdEl.getAsLong() / 1000 : Long.valueOf(0);
987 JsonElement lastusedEl = address.get("timeLastUsed"); //NON-NLS
988 Long datetimeLastUsed = (lastusedEl != null) ? lastusedEl.getAsLong() / 1000 : Long.valueOf(0);
989 JsonElement timesUsedEl = address.get("timesUsed"); //NON-NLS
990 Integer timesUsed = (timesUsedEl != null) ? timesUsedEl.getAsShort() : Integer.valueOf(0);
991
992 JsonElement addressLine1El = address.get("address-line1"); //NON-NLS
993 String addressLine1 = (addressLine1El != null) ? addressLine1El.getAsString() : "";
994 JsonElement addressLine2El = address.get("address-line2"); //NON-NLS
995 String addressLine2 = (addressLine2El != null) ? addressLine2El.getAsString() : "";
996 JsonElement addressLine3El = address.get("address-line3"); //NON-NLS
997 String addressLine3 = (addressLine3El != null) ? addressLine3El.getAsString() : "";
998
999 JsonElement postalCodeEl = address.get("postal-code"); //NON-NLS
1000 String postalCode = (postalCodeEl != null) ? postalCodeEl.getAsString() : "";
1001 JsonElement countryEl = address.get("country"); //NON-NLS
1002 String country = (countryEl != null) ? countryEl.getAsString() : "";
1003
1004 String mailingAddress = makeFullAddress(addressLine1, addressLine2, addressLine3, postalCode, country);
1005
1006 try {
1007 helper.addWebFormAddress(name, email, phoneNumber,
1008 mailingAddress, datetimeCreated, datetimeLastUsed, timesUsed);
1009 } catch (TskCoreException | Blackboard.BlackboardException ex) {
1010 logger.log(Level.SEVERE, "Error while trying to insert Firefox Autofill profile artifact{0}", ex); //NON-NLS
1011 this.addErrorMessage(
1012 NbBundle.getMessage(this.getClass(), "Firefox.getAutofillProfiles.errMsg.errAnalyzingFile4",
1013 this.getDisplayName(), profileFile.getName()));
1014 }
1015 }
1016 dbFile.delete();
1017 }
1018 }
1019
1028 private String extractDomain(String url) {
1029 if (url == null || url.isEmpty()) {
1030 return url;
1031 }
1032
1033 if (url.toLowerCase().startsWith(PLACE_URL_PREFIX)) {
1034 /*
1035 * Ignore URLs that begin with the matched text.
1036 */
1037 return null;
1038 }
1039
1040 return NetworkUtils.extractDomain(url);
1041 }
1042
1054 private String makeTelNumber(String tel, String telCountryCode, String telNational) {
1055
1056 if (tel != null && !tel.isEmpty()) {
1057 return tel;
1058 }
1059
1060 if ((telCountryCode != null && !telCountryCode.isEmpty())
1061 && (telNational != null && !telNational.isEmpty())) {
1062 return telCountryCode + telNational;
1063 }
1064
1065 return "";
1066 }
1067
1079 private String makeFullAddress(String addressLine1, String addressLine2, String addressLine3, String postalCode, String country) {
1080 String fullAddress = "";
1081 fullAddress = appendAddressField(fullAddress, addressLine1);
1082 fullAddress = appendAddressField(fullAddress, addressLine2);
1083 fullAddress = appendAddressField(fullAddress, addressLine3);
1084 fullAddress = appendAddressField(fullAddress, postalCode);
1085 fullAddress = appendAddressField(fullAddress, country);
1086
1087 return fullAddress;
1088 }
1089
1099 private String appendAddressField(String address, String addressfield) {
1100
1101 String updatedAddress = address;
1102 if (addressfield != null && !addressfield.isEmpty()) {
1103 if (!updatedAddress.isEmpty()) {
1104 updatedAddress += ", ";
1105 }
1106 updatedAddress += addressfield;
1107 }
1108
1109 return updatedAddress;
1110 }
1111
1112}
synchronized static Logger getLogger(String name)
Definition Logger.java:124
static String extractDomain(String urlString)
static< T > long writeToFile(Content content, java.io.File outputFile, ProgressHandle progress, Future< T > worker, boolean source)

Copyright © 2012-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.