mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
Merge pull request #5439 from markmckinnon/5679-Allow-Plaso-to-more-silently-fail-for-non-images
5679-Allow-Plaso-to-more-silently-fail-for-non-images
This commit is contained in:
commit
5f58ac294b
@ -85,7 +85,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
private static final int LOG2TIMELINE_WORKERS = 2;
|
private static final int LOG2TIMELINE_WORKERS = 2;
|
||||||
private static final long TERMINATION_CHECK_INTERVAL = 5;
|
private static final long TERMINATION_CHECK_INTERVAL = 5;
|
||||||
private static final TimeUnit TERMINATION_CHECK_INTERVAL_UNITS = TimeUnit.SECONDS;
|
private static final TimeUnit TERMINATION_CHECK_INTERVAL_UNITS = TimeUnit.SECONDS;
|
||||||
|
|
||||||
private File log2TimeLineExecutable;
|
private File log2TimeLineExecutable;
|
||||||
private File psortExecutable;
|
private File psortExecutable;
|
||||||
|
|
||||||
@ -103,8 +103,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
|
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
"PlasoIngestModule.executable.not.found=Plaso Executable Not Found.",
|
"PlasoIngestModule.executable.not.found=Plaso Executable Not Found.",
|
||||||
"PlasoIngestModule.requires.windows=Plaso module requires windows.",
|
"PlasoIngestModule.requires.windows=Plaso module requires windows."})
|
||||||
"PlasoIngestModule.dataSource.not.an.image=Datasource is not an Image."})
|
|
||||||
@Override
|
@Override
|
||||||
public void startUp(IngestJobContext context) throws IngestModuleException {
|
public void startUp(IngestJobContext context) throws IngestModuleException {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@ -121,11 +120,6 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
throw new IngestModuleException(Bundle.PlasoIngestModule_executable_not_found(), exception);
|
throw new IngestModuleException(Bundle.PlasoIngestModule_executable_not_found(), exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
Content dataSource = context.getDataSource();
|
|
||||||
if (!(dataSource instanceof Image)) {
|
|
||||||
throw new IngestModuleException(Bundle.PlasoIngestModule_dataSource_not_an_image());
|
|
||||||
}
|
|
||||||
image = (Image) dataSource;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NbBundle.Messages({
|
@NbBundle.Messages({
|
||||||
@ -138,80 +132,90 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
"PlasoIngestModule.psort.cancelled=psort run was canceled",
|
"PlasoIngestModule.psort.cancelled=psort run was canceled",
|
||||||
"PlasoIngestModule.bad.imageFile=Cannot find image file name and path",
|
"PlasoIngestModule.bad.imageFile=Cannot find image file name and path",
|
||||||
"PlasoIngestModule.completed=Plaso Processing Completed",
|
"PlasoIngestModule.completed=Plaso Processing Completed",
|
||||||
"PlasoIngestModule.has.run=Plaso Plugin has been run.",
|
"PlasoIngestModule.has.run=Plaso",
|
||||||
"PlasoIngestModule.psort.fail=Plaso returned an error when sorting events. Results are not complete."})
|
"PlasoIngestModule.psort.fail=Plaso returned an error when sorting events. Results are not complete.",
|
||||||
|
"PlasoIngestModule.dataSource.not.an.image=Skipping non-disk image datasource"})
|
||||||
@Override
|
@Override
|
||||||
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
|
public ProcessResult process(Content dataSource, DataSourceIngestModuleProgress statusHelper) {
|
||||||
assert dataSource.equals(image);
|
|
||||||
|
|
||||||
statusHelper.switchToDeterminate(100);
|
if (!(dataSource instanceof Image)) {
|
||||||
currentCase = Case.getCurrentCase();
|
IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
|
||||||
fileManager = currentCase.getServices().getFileManager();
|
Bundle.PlasoIngestModule_has_run(),
|
||||||
|
Bundle.PlasoIngestModule_dataSource_not_an_image());
|
||||||
|
IngestServices.getInstance().postMessage(message);
|
||||||
|
return ProcessResult.OK;
|
||||||
|
} else {
|
||||||
|
image = (Image) dataSource;
|
||||||
|
|
||||||
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
|
statusHelper.switchToDeterminate(100);
|
||||||
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime);
|
currentCase = Case.getCurrentCase();
|
||||||
try {
|
fileManager = currentCase.getServices().getFileManager();
|
||||||
Files.createDirectories(moduleOutputPath);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS
|
|
||||||
return ProcessResult.ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run log2timeline
|
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH-mm-ss z", Locale.US).format(System.currentTimeMillis());//NON-NLS
|
||||||
logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS
|
Path moduleOutputPath = Paths.get(currentCase.getModuleDirectory(), PLASO, currentTime);
|
||||||
statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0);
|
try {
|
||||||
ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image);
|
Files.createDirectories(moduleOutputPath);
|
||||||
try {
|
} catch (IOException ex) {
|
||||||
Process log2TimeLineProcess = log2TimeLineCommand.start();
|
logger.log(Level.SEVERE, "Error creating Plaso module output directory.", ex); //NON-NLS
|
||||||
try (BufferedReader log2TimeLineOutpout = new BufferedReader(new InputStreamReader(log2TimeLineProcess.getInputStream()))) {
|
|
||||||
L2TStatusProcessor statusReader = new L2TStatusProcessor(log2TimeLineOutpout, statusHelper, moduleOutputPath);
|
|
||||||
new Thread(statusReader, "log2timeline status reader").start(); //NON-NLS
|
|
||||||
ExecUtil.waitForTermination(LOG2TIMELINE_EXECUTABLE, log2TimeLineProcess, TERMINATION_CHECK_INTERVAL, TERMINATION_CHECK_INTERVAL_UNITS, new DataSourceIngestModuleProcessTerminator(context));
|
|
||||||
statusReader.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.dataSourceIngestIsCancelled()) {
|
|
||||||
logger.log(Level.INFO, "Log2timeline run was canceled"); //NON-NLS
|
|
||||||
return ProcessResult.OK;
|
|
||||||
}
|
|
||||||
if (Files.notExists(moduleOutputPath.resolve(PLASO))) {
|
|
||||||
logger.log(Level.WARNING, "Error running log2timeline: there was no storage file."); //NON-NLS
|
|
||||||
return ProcessResult.ERROR;
|
return ProcessResult.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort the output
|
// Run log2timeline
|
||||||
statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33);
|
logger.log(Level.INFO, "Starting Plaso Run.");//NON-NLS
|
||||||
ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath);
|
statusHelper.progress(Bundle.PlasoIngestModule_starting_log2timeline(), 0);
|
||||||
int result = ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context));
|
ProcessBuilder log2TimeLineCommand = buildLog2TimeLineCommand(moduleOutputPath, image);
|
||||||
if (result != 0) {
|
try {
|
||||||
logger.log(Level.SEVERE, String.format("Error running Psort, error code returned %d", result)); //NON-NLS
|
Process log2TimeLineProcess = log2TimeLineCommand.start();
|
||||||
MessageNotifyUtil.Notify.error(MODULE_NAME, Bundle.PlasoIngestModule_psort_fail());
|
try (BufferedReader log2TimeLineOutpout = new BufferedReader(new InputStreamReader(log2TimeLineProcess.getInputStream()))) {
|
||||||
return ProcessResult.ERROR;
|
L2TStatusProcessor statusReader = new L2TStatusProcessor(log2TimeLineOutpout, statusHelper, moduleOutputPath);
|
||||||
}
|
new Thread(statusReader, "log2timeline status reader").start(); //NON-NLS
|
||||||
|
ExecUtil.waitForTermination(LOG2TIMELINE_EXECUTABLE, log2TimeLineProcess, TERMINATION_CHECK_INTERVAL, TERMINATION_CHECK_INTERVAL_UNITS, new DataSourceIngestModuleProcessTerminator(context));
|
||||||
|
statusReader.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
if (context.dataSourceIngestIsCancelled()) {
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
logger.log(Level.INFO, "psort run was canceled"); //NON-NLS
|
logger.log(Level.INFO, "Log2timeline run was canceled"); //NON-NLS
|
||||||
return ProcessResult.OK;
|
return ProcessResult.OK;
|
||||||
}
|
}
|
||||||
Path plasoFile = moduleOutputPath.resolve("plasodb.db3"); //NON-NLS
|
if (Files.notExists(moduleOutputPath.resolve(PLASO))) {
|
||||||
if (Files.notExists(plasoFile)) {
|
logger.log(Level.WARNING, "Error running log2timeline: there was no storage file."); //NON-NLS
|
||||||
logger.log(Level.SEVERE, "Error running Psort: there was no sqlite db file."); //NON-NLS
|
return ProcessResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sort the output
|
||||||
|
statusHelper.progress(Bundle.PlasoIngestModule_running_psort(), 33);
|
||||||
|
ProcessBuilder psortCommand = buildPsortCommand(moduleOutputPath);
|
||||||
|
int result = ExecUtil.execute(psortCommand, new DataSourceIngestModuleProcessTerminator(context));
|
||||||
|
if (result != 0) {
|
||||||
|
logger.log(Level.SEVERE, String.format("Error running Psort, error code returned %d", result)); //NON-NLS
|
||||||
|
MessageNotifyUtil.Notify.error(MODULE_NAME, Bundle.PlasoIngestModule_psort_fail());
|
||||||
|
return ProcessResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
|
logger.log(Level.INFO, "psort run was canceled"); //NON-NLS
|
||||||
|
return ProcessResult.OK;
|
||||||
|
}
|
||||||
|
Path plasoFile = moduleOutputPath.resolve("plasodb.db3"); //NON-NLS
|
||||||
|
if (Files.notExists(plasoFile)) {
|
||||||
|
logger.log(Level.SEVERE, "Error running Psort: there was no sqlite db file."); //NON-NLS
|
||||||
|
return ProcessResult.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse the output and make artifacts
|
||||||
|
createPlasoArtifacts(plasoFile.toString(), statusHelper);
|
||||||
|
|
||||||
|
} catch (IOException ex) {
|
||||||
|
logger.log(Level.SEVERE, "Error running Plaso.", ex);//NON-NLS
|
||||||
return ProcessResult.ERROR;
|
return ProcessResult.ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse the output and make artifacts
|
IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
|
||||||
createPlasoArtifacts(plasoFile.toString(), statusHelper);
|
Bundle.PlasoIngestModule_has_run(),
|
||||||
|
Bundle.PlasoIngestModule_completed());
|
||||||
} catch (IOException ex) {
|
IngestServices.getInstance().postMessage(message);
|
||||||
logger.log(Level.SEVERE, "Error running Plaso.", ex);//NON-NLS
|
return ProcessResult.OK;
|
||||||
return ProcessResult.ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IngestMessage message = IngestMessage.createMessage(IngestMessage.MessageType.DATA,
|
|
||||||
Bundle.PlasoIngestModule_has_run(),
|
|
||||||
Bundle.PlasoIngestModule_completed());
|
|
||||||
IngestServices.getInstance().postMessage(message);
|
|
||||||
return ProcessResult.OK;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProcessBuilder buildLog2TimeLineCommand(Path moduleOutputPath, Image image) {
|
private ProcessBuilder buildLog2TimeLineCommand(Path moduleOutputPath, Image image) {
|
||||||
@ -240,8 +244,10 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
|
|
||||||
static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
|
static private ProcessBuilder buildProcessWithRunAsInvoker(String... commandLine) {
|
||||||
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
|
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
|
||||||
/* Add an environment variable to force log2timeline/psort to run with
|
/*
|
||||||
* the same permissions Autopsy uses. */
|
* Add an environment variable to force log2timeline/psort to run with
|
||||||
|
* the same permissions Autopsy uses.
|
||||||
|
*/
|
||||||
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
|
processBuilder.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
|
||||||
return processBuilder;
|
return processBuilder;
|
||||||
}
|
}
|
||||||
@ -277,31 +283,30 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
"PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ",
|
"PlasoIngestModule.create.artifacts.cancelled=Cancelled Plaso Artifact Creation ",
|
||||||
"# {0} - file that events are from",
|
"# {0} - file that events are from",
|
||||||
"PlasoIngestModule.artifact.progress=Adding events to case: {0}",
|
"PlasoIngestModule.artifact.progress=Adding events to case: {0}",
|
||||||
"PlasoIngestModule.info.empty.database=Plaso database was empty.",
|
"PlasoIngestModule.info.empty.database=Plaso database was empty.",})
|
||||||
})
|
|
||||||
private void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper) {
|
private void createPlasoArtifacts(String plasoDb, DataSourceIngestModuleProgress statusHelper) {
|
||||||
Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
|
Blackboard blackboard = currentCase.getSleuthkitCase().getBlackboard();
|
||||||
|
|
||||||
String sqlStatement = "SELECT substr(filename,1) AS filename, "
|
String sqlStatement = "SELECT substr(filename,1) AS filename, "
|
||||||
+ " strftime('%s', datetime) AS epoch_date, "
|
+ " strftime('%s', datetime) AS epoch_date, "
|
||||||
+ " description, "
|
+ " description, "
|
||||||
+ " source, "
|
+ " source, "
|
||||||
+ " type, "
|
+ " type, "
|
||||||
+ " sourcetype "
|
+ " sourcetype "
|
||||||
+ " FROM log2timeline "
|
+ " FROM log2timeline "
|
||||||
+ " WHERE source NOT IN ('FILE', "
|
+ " WHERE source NOT IN ('FILE', "
|
||||||
+ " 'WEBHIST') " // bad dates and duplicates with what we have.
|
+ " 'WEBHIST') " // bad dates and duplicates with what we have.
|
||||||
+ " AND sourcetype NOT IN ('UNKNOWN', "
|
+ " AND sourcetype NOT IN ('UNKNOWN', "
|
||||||
+ " 'PE Import Time');"; // lots of bad dates //NON-NLS
|
+ " 'PE Import Time');"; // lots of bad dates //NON-NLS
|
||||||
|
|
||||||
try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + plasoDb); //NON-NLS
|
try (SQLiteDBConnect tempdbconnect = new SQLiteDBConnect("org.sqlite.JDBC", "jdbc:sqlite:" + plasoDb); //NON-NLS
|
||||||
ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) {
|
ResultSet resultSet = tempdbconnect.executeQry(sqlStatement)) {
|
||||||
|
|
||||||
boolean dbHasData = false;
|
boolean dbHasData = false;
|
||||||
|
|
||||||
while (resultSet.next()) {
|
while (resultSet.next()) {
|
||||||
dbHasData = true;
|
dbHasData = true;
|
||||||
|
|
||||||
if (context.dataSourceIngestIsCancelled()) {
|
if (context.dataSourceIngestIsCancelled()) {
|
||||||
logger.log(Level.INFO, "Cancelled Plaso Artifact Creation."); //NON-NLS
|
logger.log(Level.INFO, "Cancelled Plaso Artifact Creation."); //NON-NLS
|
||||||
return;
|
return;
|
||||||
@ -314,20 +319,20 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
logger.log(Level.INFO, "File {0} from Plaso output not found in case. Associating it with the data source instead.", currentFileName);//NON-NLS
|
logger.log(Level.INFO, "File {0} from Plaso output not found in case. Associating it with the data source instead.", currentFileName);//NON-NLS
|
||||||
resolvedFile = image;
|
resolvedFile = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
String description = resultSet.getString("description");
|
String description = resultSet.getString("description");
|
||||||
TimelineEventType eventType = findEventSubtype(currentFileName, resultSet);
|
TimelineEventType eventType = findEventSubtype(currentFileName, resultSet);
|
||||||
|
|
||||||
// If the description is empty use the event type display name
|
// If the description is empty use the event type display name
|
||||||
// as the description.
|
// as the description.
|
||||||
if ( description == null || description.isEmpty() ) {
|
if (description == null || description.isEmpty()) {
|
||||||
if (eventType != TimelineEventType.OTHER) {
|
if (eventType != TimelineEventType.OTHER) {
|
||||||
description = eventType.getDisplayName();
|
description = eventType.getDisplayName();
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<BlackboardAttribute> bbattributes = Arrays.asList(
|
Collection<BlackboardAttribute> bbattributes = Arrays.asList(
|
||||||
new BlackboardAttribute(
|
new BlackboardAttribute(
|
||||||
TSK_DATETIME, MODULE_NAME,
|
TSK_DATETIME, MODULE_NAME,
|
||||||
@ -338,14 +343,16 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
new BlackboardAttribute(
|
new BlackboardAttribute(
|
||||||
TSK_TL_EVENT_TYPE, MODULE_NAME,
|
TSK_TL_EVENT_TYPE, MODULE_NAME,
|
||||||
eventType.getTypeID()));
|
eventType.getTypeID()));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
BlackboardArtifact bbart = resolvedFile.newArtifact(TSK_TL_EVENT);
|
BlackboardArtifact bbart = resolvedFile.newArtifact(TSK_TL_EVENT);
|
||||||
bbart.addAttributes(bbattributes);
|
bbart.addAttributes(bbattributes);
|
||||||
try {
|
try {
|
||||||
/* Post the artifact which will index the artifact for
|
/*
|
||||||
|
* Post the artifact which will index the artifact for
|
||||||
* keyword search, and fire an event to notify UI of
|
* keyword search, and fire an event to notify UI of
|
||||||
* this new artifact */
|
* this new artifact
|
||||||
|
*/
|
||||||
blackboard.postArtifact(bbart, MODULE_NAME);
|
blackboard.postArtifact(bbart, MODULE_NAME);
|
||||||
} catch (BlackboardException ex) {
|
} catch (BlackboardException ex) {
|
||||||
logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS
|
logger.log(Level.SEVERE, "Error Posting Artifact.", ex);//NON-NLS
|
||||||
@ -354,12 +361,12 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS
|
logger.log(Level.SEVERE, "Exception Adding Artifact.", ex);//NON-NLS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is data the db
|
// Check if there is data the db
|
||||||
if( !dbHasData ) {
|
if (!dbHasData) {
|
||||||
logger.log(Level.INFO, String.format("PlasoDB was empty: %s", plasoDb));
|
logger.log(Level.INFO, String.format("PlasoDB was empty: %s", plasoDb));
|
||||||
MessageNotifyUtil.Notify.info(MODULE_NAME, Bundle.PlasoIngestModule_info_empty_database());
|
MessageNotifyUtil.Notify.info(MODULE_NAME, Bundle.PlasoIngestModule_info_empty_database());
|
||||||
}
|
}
|
||||||
} catch (SQLException ex) {
|
} catch (SQLException ex) {
|
||||||
logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS
|
logger.log(Level.SEVERE, "Error while trying to read into a sqlite db.", ex);//NON-NLS
|
||||||
}
|
}
|
||||||
@ -377,8 +384,8 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
// check the cached file
|
// check the cached file
|
||||||
//TODO: would we reduce 'cache misses' if we retrieved the events sorted by file? Is that overhead worth it?
|
//TODO: would we reduce 'cache misses' if we retrieved the events sorted by file? Is that overhead worth it?
|
||||||
if (previousFile != null
|
if (previousFile != null
|
||||||
&& previousFile.getName().equalsIgnoreCase(fileName)
|
&& previousFile.getName().equalsIgnoreCase(fileName)
|
||||||
&& previousFile.getParentPath().equalsIgnoreCase(filePath)) {
|
&& previousFile.getParentPath().equalsIgnoreCase(filePath)) {
|
||||||
return previousFile;
|
return previousFile;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -416,7 +423,7 @@ public class PlasoIngestModule implements DataSourceIngestModule {
|
|||||||
switch (row.getString("source")) {
|
switch (row.getString("source")) {
|
||||||
case "WEBHIST": //These shouldn't actually be present, but keeping the logic just in case...
|
case "WEBHIST": //These shouldn't actually be present, but keeping the logic just in case...
|
||||||
if (fileName.toLowerCase().contains(COOKIE)
|
if (fileName.toLowerCase().contains(COOKIE)
|
||||||
|| row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS
|
|| row.getString("type").toLowerCase().contains(COOKIE)) {//NON-NLS
|
||||||
|
|
||||||
return TimelineEventType.WEB_COOKIE;
|
return TimelineEventType.WEB_COOKIE;
|
||||||
} else {
|
} else {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user