23 package org.sleuthkit.autopsy.recentactivity;
26 import java.io.IOException;
27 import java.io.UnsupportedEncodingException;
28 import java.net.URLDecoder;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.logging.Level;
35 import org.openide.util.NbBundle;
53 class Firefox
extends Extract {
55 private static final Logger logger = Logger.getLogger(Firefox.class.getName());
56 private static final String historyQuery =
"SELECT moz_historyvisits.id,url,title,visit_count,(visit_date/1000000) AS visit_date,from_visit,(SELECT url FROM moz_places WHERE id=moz_historyvisits.from_visit) as ref FROM moz_places, moz_historyvisits WHERE moz_places.id = moz_historyvisits.place_id AND hidden = 0";
57 private static final String cookieQuery =
"SELECT name,value,host,expiry,(lastAccessed/1000000) AS lastAccessed,(creationTime/1000000) AS creationTime FROM moz_cookies";
58 private static final String cookieQueryV3 =
"SELECT name,value,host,expiry,(lastAccessed/1000000) AS lastAccessed FROM moz_cookies";
59 private static final String bookmarkQuery =
"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";
60 private static final String downloadQuery =
"SELECT target, source,(startTime/1000000) AS startTime, maxBytes FROM moz_downloads";
61 private static final String downloadQueryVersion24 =
"SELECT url, content AS target, (lastModified/1000000) AS lastModified FROM moz_places, moz_annos WHERE moz_places.id = moz_annos.place_id AND moz_annos.anno_attribute_id = 3";
62 private final IngestServices services = IngestServices.getInstance();
63 private Content dataSource;
64 private IngestJobContext context;
67 moduleName = NbBundle.getMessage(Firefox.class,
"Firefox.moduleName");
71 public void process(Content dataSource, IngestJobContext context) {
72 this.dataSource = dataSource;
73 this.context = context;
81 private void getHistory() {
82 FileManager fileManager = currentCase.getServices().getFileManager();
83 List<AbstractFile> historyFiles;
85 historyFiles = fileManager.findFiles(dataSource,
"places.sqlite",
"Firefox");
86 }
catch (TskCoreException ex) {
87 String msg = NbBundle.getMessage(this.getClass(),
"Firefox.getHistory.errMsg.errFetchingFiles");
88 logger.log(Level.WARNING, msg);
89 this.addErrorMessage(this.getName() +
": " + msg);
93 if (historyFiles.isEmpty()) {
94 String msg = NbBundle.getMessage(this.getClass(),
"Firefox.getHistory.errMsg.noFilesFound");
95 logger.log(Level.INFO, msg);
100 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
102 for (AbstractFile historyFile : historyFiles) {
103 if (historyFile.getSize() == 0) {
107 String fileName = historyFile.getName();
108 String temps = RAImageIngestModule.getRATempPath(currentCase,
"firefox") + File.separator + fileName + j +
".db";
110 ContentUtils.writeToFile(historyFile,
new File(temps), context::dataSourceIngestIsCancelled);
111 }
catch (IOException ex) {
112 logger.log(Level.SEVERE,
"Error writing the sqlite db for firefox web history artifacts.{0}", ex);
113 this.addErrorMessage(
114 NbBundle.getMessage(
this.getClass(),
"Firefox.getHistory.errMsg.errAnalyzeFile", this.getName(),
118 File dbFile =
new File(temps);
119 if (context.dataSourceIngestIsCancelled()) {
123 List<HashMap<String, Object>> tempList = this.dbConnect(temps, historyQuery);
124 logger.log(Level.INFO,
"{0} - Now getting history from {1} with {2} artifacts identified.",
new Object[]{moduleName, temps, tempList.size()});
125 for (HashMap<String, Object> result : tempList) {
126 Collection<BlackboardAttribute> bbattributes =
new ArrayList<>();
127 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
128 NbBundle.getMessage(
this.getClass(),
129 "Firefox.parentModuleName.noSpace"),
130 ((result.get(
"url").toString() != null) ? result.get(
"url").toString() :
"")));
132 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
133 NbBundle.getMessage(
this.getClass(),
134 "Firefox.parentModuleName.noSpace"),
135 (Long.valueOf(result.get(
"visit_date").toString()))));
136 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_REFERRER,
137 NbBundle.getMessage(
this.getClass(),
138 "Firefox.parentModuleName.noSpace"),
139 ((result.get(
"ref").toString() != null) ? result.get(
"ref").toString() :
"")));
140 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
141 NbBundle.getMessage(
this.getClass(),
142 "Firefox.parentModuleName.noSpace"),
143 ((result.get(
"title").toString() != null) ? result.get(
"title").toString() :
"")));
144 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
145 NbBundle.getMessage(
this.getClass(),
146 "Firefox.parentModuleName.noSpace"),
147 NbBundle.getMessage(
this.getClass(),
"Firefox.moduleName")));
148 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
149 NbBundle.getMessage(
this.getClass(),
150 "Firefox.parentModuleName.noSpace"), (Util.extractDomain((result.get(
"url").toString() != null) ? result.get(
"url").toString() :
""))));
152 BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_HISTORY, historyFile, bbattributes);
154 bbartifacts.add(bbart);
161 services.fireModuleDataEvent(
new ModuleDataEvent(
162 NbBundle.getMessage(
this.getClass(),
"Firefox.parentModuleName"),
163 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_HISTORY, bbartifacts));
169 private void getBookmark() {
171 FileManager fileManager = currentCase.getServices().getFileManager();
172 List<AbstractFile> bookmarkFiles;
174 bookmarkFiles = fileManager.findFiles(dataSource,
"places.sqlite",
"Firefox");
175 }
catch (TskCoreException ex) {
176 String msg = NbBundle.getMessage(this.getClass(),
"Firefox.getBookmark.errMsg.errFetchFiles");
177 logger.log(Level.WARNING, msg);
178 this.addErrorMessage(this.getName() +
": " + msg);
182 if (bookmarkFiles.isEmpty()) {
183 logger.log(Level.INFO,
"Didn't find any firefox bookmark files.");
188 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
190 for (AbstractFile bookmarkFile : bookmarkFiles) {
191 if (bookmarkFile.getSize() == 0) {
194 String fileName = bookmarkFile.getName();
195 String temps = RAImageIngestModule.getRATempPath(currentCase,
"firefox") + File.separator + fileName + j +
".db";
197 ContentUtils.writeToFile(bookmarkFile,
new File(temps), context::dataSourceIngestIsCancelled);
198 }
catch (IOException ex) {
199 logger.log(Level.SEVERE,
"Error writing the sqlite db for firefox bookmark artifacts.{0}", ex);
200 this.addErrorMessage(NbBundle.getMessage(
this.getClass(),
"Firefox.getBookmark.errMsg.errAnalyzeFile",
201 this.getName(), fileName));
204 File dbFile =
new File(temps);
205 if (context.dataSourceIngestIsCancelled()) {
209 List<HashMap<String, Object>> tempList = this.dbConnect(temps, bookmarkQuery);
210 logger.log(Level.INFO,
"{0} - Now getting bookmarks from {1} with {2} artifacts identified.",
new Object[]{moduleName, temps, tempList.size()});
211 for (HashMap<String, Object> result : tempList) {
213 Collection<BlackboardAttribute> bbattributes =
new ArrayList<>();
214 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
215 NbBundle.getMessage(
this.getClass(),
216 "Firefox.parentModuleName.noSpace"),
217 ((result.get(
"url").toString() != null) ? result.get(
"url").toString() :
"")));
218 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_TITLE,
219 NbBundle.getMessage(
this.getClass(),
220 "Firefox.parentModuleName.noSpace"),
221 ((result.get(
"title").toString() != null) ? result.get(
"title").toString() :
"")));
222 if (Long.valueOf(result.get(
"dateAdded").toString()) > 0) {
223 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
224 NbBundle.getMessage(
this.getClass(),
225 "Firefox.parentModuleName.noSpace"),
226 (Long.valueOf(result.get(
"dateAdded").toString()))));
228 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
229 NbBundle.getMessage(
this.getClass(),
230 "Firefox.parentModuleName.noSpace"),
231 NbBundle.getMessage(
this.getClass(),
"Firefox.moduleName")));
232 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
233 NbBundle.getMessage(
this.getClass(),
234 "Firefox.parentModuleName.noSpace"),
235 (Util.extractDomain((result.get(
"url").toString() != null) ? result.get(
"url").toString() :
""))));
237 BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bookmarkFile, bbattributes);
239 bbartifacts.add(bbart);
246 services.fireModuleDataEvent(
new ModuleDataEvent(
247 NbBundle.getMessage(
this.getClass(),
"Firefox.parentModuleName"),
248 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_BOOKMARK, bbartifacts));
254 private void getCookie() {
255 FileManager fileManager = currentCase.getServices().getFileManager();
256 List<AbstractFile> cookiesFiles;
258 cookiesFiles = fileManager.findFiles(dataSource,
"cookies.sqlite",
"Firefox");
259 }
catch (TskCoreException ex) {
260 String msg = NbBundle.getMessage(this.getClass(),
"Firefox.getCookie.errMsg.errFetchFile");
261 logger.log(Level.WARNING, msg);
262 this.addErrorMessage(this.getName() +
": " + msg);
266 if (cookiesFiles.isEmpty()) {
267 logger.log(Level.INFO,
"Didn't find any Firefox cookie files.");
272 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
274 for (AbstractFile cookiesFile : cookiesFiles) {
275 if (cookiesFile.getSize() == 0) {
278 String fileName = cookiesFile.getName();
279 String temps = RAImageIngestModule.getRATempPath(currentCase,
"firefox") + File.separator + fileName + j +
".db";
281 ContentUtils.writeToFile(cookiesFile,
new File(temps), context::dataSourceIngestIsCancelled);
282 }
catch (IOException ex) {
283 logger.log(Level.SEVERE,
"Error writing the sqlite db for firefox cookie artifacts.{0}", ex);
284 this.addErrorMessage(
285 NbBundle.getMessage(
this.getClass(),
"Firefox.getCookie.errMsg.errAnalyzeFile", this.getName(),
289 File dbFile =
new File(temps);
290 if (context.dataSourceIngestIsCancelled()) {
294 boolean checkColumn = Util.checkColumn(
"creationTime",
"moz_cookies", temps);
299 query = cookieQueryV3;
302 List<HashMap<String, Object>> tempList = this.dbConnect(temps, query);
303 logger.log(Level.INFO,
"{0} - Now getting cookies from {1} with {2} artifacts identified.",
new Object[]{moduleName, temps, tempList.size()});
304 for (HashMap<String, Object> result : tempList) {
306 Collection<BlackboardAttribute> bbattributes =
new ArrayList<>();
307 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
308 NbBundle.getMessage(
this.getClass(),
309 "Firefox.parentModuleName.noSpace"),
310 ((result.get(
"host").toString() != null) ? result.get(
"host").toString() :
"")));
311 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME,
312 NbBundle.getMessage(
this.getClass(),
313 "Firefox.parentModuleName.noSpace"),
314 (Long.valueOf(result.get(
"lastAccessed").toString()))));
315 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_NAME,
316 NbBundle.getMessage(
this.getClass(),
317 "Firefox.parentModuleName.noSpace"),
318 ((result.get(
"name").toString() != null) ? result.get(
"name").toString() :
"")));
319 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_VALUE,
320 NbBundle.getMessage(
this.getClass(),
321 "Firefox.parentModuleName.noSpace"),
322 ((result.get(
"value").toString() != null) ? result.get(
"value").toString() :
"")));
323 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
324 NbBundle.getMessage(
this.getClass(),
325 "Firefox.parentModuleName.noSpace"),
326 NbBundle.getMessage(
this.getClass(),
"Firefox.moduleName")));
328 if (checkColumn ==
true) {
329 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
330 NbBundle.getMessage(
this.getClass(),
331 "Firefox.parentModuleName.noSpace"),
332 (Long.valueOf(result.get(
"creationTime").toString()))));
334 String domain = Util.extractDomain(result.get(
"host").toString());
335 domain = domain.replaceFirst(
"^\\.+(?!$)",
"");
336 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
337 NbBundle.getMessage(
this.getClass(),
338 "Firefox.parentModuleName.noSpace"), domain));
340 BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_COOKIE, cookiesFile, bbattributes);
342 bbartifacts.add(bbart);
349 services.fireModuleDataEvent(
new ModuleDataEvent(
350 NbBundle.getMessage(
this.getClass(),
"Firefox.parentModuleName"),
351 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_COOKIE, bbartifacts));
357 private void getDownload() {
358 getDownloadPreVersion24();
359 getDownloadVersion24();
367 private void getDownloadPreVersion24() {
369 FileManager fileManager = currentCase.getServices().getFileManager();
370 List<AbstractFile> downloadsFiles;
372 downloadsFiles = fileManager.findFiles(dataSource,
"downloads.sqlite",
"Firefox");
373 }
catch (TskCoreException ex) {
374 String msg = NbBundle.getMessage(this.getClass(),
"Firefox.getDlPre24.errMsg.errFetchFiles");
375 logger.log(Level.WARNING, msg);
376 this.addErrorMessage(this.getName() +
": " + msg);
380 if (downloadsFiles.isEmpty()) {
381 logger.log(Level.INFO,
"Didn't find any pre-version-24.0 Firefox download files.");
386 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
388 for (AbstractFile downloadsFile : downloadsFiles) {
389 if (downloadsFile.getSize() == 0) {
392 String fileName = downloadsFile.getName();
393 String temps = RAImageIngestModule.getRATempPath(currentCase,
"firefox") + File.separator + fileName + j +
".db";
396 ContentUtils.writeToFile(downloadsFile,
new File(temps), context::dataSourceIngestIsCancelled);
397 }
catch (IOException ex) {
398 logger.log(Level.SEVERE,
"Error writing the sqlite db for firefox download artifacts.{0}", ex);
399 this.addErrorMessage(NbBundle.getMessage(
this.getClass(),
"Firefox.getDlPre24.errMsg.errAnalyzeFiles",
400 this.getName(), fileName));
403 File dbFile =
new File(temps);
404 if (context.dataSourceIngestIsCancelled()) {
409 List<HashMap<String, Object>> tempList = this.dbConnect(temps, downloadQuery);
410 logger.log(Level.INFO,
"{0}- Now getting downloads from {1} with {2} artifacts identified.",
new Object[]{moduleName, temps, tempList.size()});
411 for (HashMap<String, Object> result : tempList) {
413 Collection<BlackboardAttribute> bbattributes =
new ArrayList<>();
415 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
416 NbBundle.getMessage(
this.getClass(),
417 "Firefox.parentModuleName.noSpace"),
418 ((result.get(
"source").toString() != null) ? result.get(
"source").toString() :
"")));
420 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
421 NbBundle.getMessage(
this.getClass(),
422 "Firefox.parentModuleName.noSpace"),
423 (Long.valueOf(result.get(
"startTime").toString()))));
425 String target = result.get(
"target").toString();
427 if (target != null) {
429 String decodedTarget = URLDecoder.decode(target.toString().replaceAll(
"file:///",
""),
"UTF-8");
430 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
431 NbBundle.getMessage(
this.getClass(),
432 "Firefox.parentModuleName.noSpace"),
434 long pathID = Util.findID(dataSource, decodedTarget);
436 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
437 NbBundle.getMessage(
this.getClass(),
438 "Firefox.parentModuleName.noSpace"),
441 }
catch (UnsupportedEncodingException ex) {
442 logger.log(Level.SEVERE,
"Error decoding Firefox download URL in " + temps, ex);
447 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
448 NbBundle.getMessage(
this.getClass(),
449 "Firefox.parentModuleName.noSpace"),
450 NbBundle.getMessage(
this.getClass(),
"Firefox.moduleName")));
451 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
452 NbBundle.getMessage(
this.getClass(),
453 "Firefox.parentModuleName.noSpace"),
454 (Util.extractDomain((result.get(
"source").toString() != null) ? result.get(
"source").toString() :
""))));
456 BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
458 bbartifacts.add(bbart);
462 this.addErrorMessage(
463 NbBundle.getMessage(
this.getClass(),
"Firefox.getDlPre24.errMsg.errParsingArtifacts",
464 this.getName(), errors));
471 services.fireModuleDataEvent(
new ModuleDataEvent(
472 NbBundle.getMessage(
this.getClass(),
"Firefox.parentModuleName"),
473 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));
481 private void getDownloadVersion24() {
482 FileManager fileManager = currentCase.getServices().getFileManager();
483 List<AbstractFile> downloadsFiles;
485 downloadsFiles = fileManager.findFiles(dataSource,
"places.sqlite",
"Firefox");
486 }
catch (TskCoreException ex) {
487 String msg = NbBundle.getMessage(this.getClass(),
"Firefox.getDlV24.errMsg.errFetchFiles");
488 logger.log(Level.WARNING, msg);
489 this.addErrorMessage(this.getName() +
": " + msg);
493 if (downloadsFiles.isEmpty()) {
494 logger.log(Level.INFO,
"Didn't find any version-24.0 Firefox download files.");
499 Collection<BlackboardArtifact> bbartifacts =
new ArrayList<>();
501 for (AbstractFile downloadsFile : downloadsFiles) {
502 if (downloadsFile.getSize() == 0) {
505 String fileName = downloadsFile.getName();
506 String temps = RAImageIngestModule.getRATempPath(currentCase,
"firefox") + File.separator + fileName +
"-downloads" + j +
".db";
509 ContentUtils.writeToFile(downloadsFile,
new File(temps), context::dataSourceIngestIsCancelled);
510 }
catch (IOException ex) {
511 logger.log(Level.SEVERE,
"Error writing the sqlite db for firefox download artifacts.{0}", ex);
512 this.addErrorMessage(
513 NbBundle.getMessage(
this.getClass(),
"Firefox.getDlV24.errMsg.errAnalyzeFile", this.getName(),
517 File dbFile =
new File(temps);
518 if (context.dataSourceIngestIsCancelled()) {
523 List<HashMap<String, Object>> tempList = this.dbConnect(temps, downloadQueryVersion24);
525 logger.log(Level.INFO,
"{0} - Now getting downloads from {1} with {2} artifacts identified.",
new Object[]{moduleName, temps, tempList.size()});
526 for (HashMap<String, Object> result : tempList) {
528 Collection<BlackboardAttribute> bbattributes =
new ArrayList<>();
530 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_URL,
531 NbBundle.getMessage(
this.getClass(),
532 "Firefox.parentModuleName.noSpace"),
533 ((result.get(
"url").toString() != null) ? result.get(
"url").toString() :
"")));
538 String target = result.get(
"target").toString();
539 if (target != null) {
541 String decodedTarget = URLDecoder.decode(target.toString().replaceAll(
"file:///",
""),
"UTF-8");
542 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH,
543 NbBundle.getMessage(
this.getClass(),
544 "Firefox.parentModuleName.noSpace"),
546 long pathID = Util.findID(dataSource, decodedTarget);
548 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PATH_ID,
549 NbBundle.getMessage(
this.getClass(),
550 "Firefox.parentModuleName.noSpace"),
553 }
catch (UnsupportedEncodingException ex) {
554 logger.log(Level.SEVERE,
"Error decoding Firefox download URL in " + temps, ex);
558 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED,
559 NbBundle.getMessage(
this.getClass(),
560 "Firefox.parentModuleName.noSpace"),
561 Long.valueOf(result.get(
"lastModified").toString())));
562 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
563 NbBundle.getMessage(
this.getClass(),
564 "Firefox.parentModuleName.noSpace"),
565 NbBundle.getMessage(
this.getClass(),
"Firefox.moduleName")));
566 bbattributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
567 NbBundle.getMessage(
this.getClass(),
568 "Firefox.parentModuleName.noSpace"),
569 (Util.extractDomain((result.get(
"url").toString() != null) ? result.get(
"url").toString() :
""))));
571 BlackboardArtifact bbart = this.addArtifact(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadsFile, bbattributes);
573 bbartifacts.add(bbart);
577 this.addErrorMessage(NbBundle.getMessage(
this.getClass(),
"Firefox.getDlV24.errMsg.errParsingArtifacts",
578 this.getName(), errors));
585 services.fireModuleDataEvent(
new ModuleDataEvent(
586 NbBundle.getMessage(
this.getClass(),
"Firefox.parentModuleName"),
587 BlackboardArtifact.ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, bbartifacts));