blob: 895a0886f83d8571ce6ab39241727e22b958689f [file] [log] [blame]
/* Copyright 2017 The Chromium Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
import 'chrome://resources/cr_elements/cr_tab_box/cr_tab_box.js';
import {assert} from 'chrome://resources/js/assert.js';
import {addWebUiListener, sendWithPromise} from 'chrome://resources/js/cr.js';
import {$} from 'chrome://resources/js/util.js';
// Type definitions for custom types used through the file.
type TailoredVerdictOverrideFormElements =
HTMLFormControlsCollection & {tailored_verdict_type: HTMLInputElement};
type TailoredVerdictOverrideForm =
HTMLFormElement & {elements: TailoredVerdictOverrideFormElements};
interface ReportingResult {
message: string;
time: string;
}
interface RealtimeReportingResult {
message: string;
timeMillis: number;
event_type: string;
profile: boolean;
device: boolean;
success: boolean;
}
interface DeepScanResult {
request: string;
request_time: string;
token: string;
response: string;
response_time: string;
response_status: string;
}
/**
* Asks the C++ SafeBrowsingUIHandler to get the lists of Safe Browsing
* ongoing experiments and preferences.
* The SafeBrowsingUIHandler should reply to addExperiment() and
* addPreferences() (below).
*/
function initialize() {
sendWithPromise('getExperiments', [])
.then((experiments: string[]) => addExperiments(experiments));
sendWithPromise('getPrefs', []).then((prefs: string[]) => addPrefs(prefs));
sendWithPromise('getPolicies', [])
.then((policies: Array<string|boolean>) => addPolicies(policies));
sendWithPromise('getCookie', []).then((cookie: [
string, number
]) => addCookie(cookie));
sendWithPromise('getSavedPasswords', [])
.then((passwords: Array<string|boolean>) => addSavedPasswords(passwords));
sendWithPromise('getDatabaseManagerInfo', [])
.then(function(databaseState: Array<string|number|string[]>) {
const fullHashCacheState = databaseState.splice(-1, 1) as string[];
addDatabaseManagerInfo(databaseState);
addFullHashCacheInfo(fullHashCacheState);
});
sendWithPromise('getDownloadUrlsChecked', [])
.then((urlsChecked: string[]) => {
urlsChecked.forEach(function(urlAndResult) {
addDownloadUrlChecked(urlAndResult);
});
});
addWebUiListener(
'download-url-checked-update', function(urlAndResult: string) {
addDownloadUrlChecked(urlAndResult);
});
sendWithPromise('getSentClientDownloadRequests', [])
.then((sentClientDownloadRequests: string[]) => {
sentClientDownloadRequests.forEach(function(cdr) {
addSentClientDownloadRequestsInfo(cdr);
});
});
addWebUiListener(
'sent-client-download-requests-update', function(result: string) {
addSentClientDownloadRequestsInfo(result);
});
sendWithPromise('getReceivedClientDownloadResponses', [])
.then((receivedClientDownloadResponses: string[]) => {
receivedClientDownloadResponses.forEach(function(cdr) {
addReceivedClientDownloadResponseInfo(cdr);
});
});
addWebUiListener(
'received-client-download-responses-update', function(result: string) {
addReceivedClientDownloadResponseInfo(result);
});
sendWithPromise('getSentClientPhishingRequests', [])
.then((sentClientPhishingRequests: string[]) => {
sentClientPhishingRequests.forEach(function(cpr: string) {
addSentClientPhishingRequestsInfo(cpr);
});
});
addWebUiListener(
'sent-client-phishing-requests-update', function(result: string) {
addSentClientPhishingRequestsInfo(result);
});
sendWithPromise('getReceivedClientPhishingResponses', [])
.then((receivedClientPhishingResponses: string[]) => {
receivedClientPhishingResponses.forEach(function(cpr) {
addReceivedClientPhishingResponseInfo(cpr);
});
});
addWebUiListener(
'received-client-phishing-responses-update', function(result: string) {
addReceivedClientPhishingResponseInfo(result);
});
sendWithPromise('getSentCSBRRs', []).then((sentCSBRRs: string[]) => {
sentCSBRRs.forEach(function(csbrr) {
addSentCSBRRsInfo(csbrr);
});
});
addWebUiListener('sent-csbrr-update', function(result: string) {
addSentCSBRRsInfo(result);
});
sendWithPromise('getPGEvents', []).then((pgEvents: ReportingResult[]) => {
pgEvents.forEach(function(pgEvent) {
addPGEvent(pgEvent);
});
});
addWebUiListener('sent-pg-event', function(result: ReportingResult) {
addPGEvent(result);
});
sendWithPromise('getSecurityEvents', [])
.then((securityEvents: ReportingResult[]) => {
securityEvents.forEach(function(securityEvent) {
addSecurityEvent(securityEvent);
});
});
addWebUiListener('sent-security-event', function(result: ReportingResult) {
addSecurityEvent(result);
});
sendWithPromise('getPGPings', []).then((pgPings: string[][]) => {
pgPings.forEach(function(pgPing) {
addPGPing(pgPing);
});
});
addWebUiListener('pg-pings-update', function(result: string[]) {
addPGPing(result);
});
sendWithPromise('getPGResponses', []).then((pgResponses: string[][]) => {
pgResponses.forEach(function(pgResponse) {
addPGResponse(pgResponse);
});
});
addWebUiListener('pg-responses-update', function(result: string[]) {
addPGResponse(result);
});
sendWithPromise('getURTLookupPings', [])
.then((urtLookupPings: string[][]) => {
urtLookupPings.forEach(function(urtLookupPing) {
addURTLookupPing(urtLookupPing);
});
});
addWebUiListener('urt-lookup-pings-update', function(result: string[]) {
addURTLookupPing(result);
});
sendWithPromise('getURTLookupResponses', [])
.then((urtLookupResponses: string[][]) => {
urtLookupResponses.forEach(function(urtLookupResponse) {
addURTLookupResponse(urtLookupResponse);
});
});
addWebUiListener('urt-lookup-responses-update', function(result: string[]) {
addURTLookupResponse(result);
});
sendWithPromise('getHPRTLookupPings', [])
.then((hprtLookupPings: string[][]) => {
hprtLookupPings.forEach(function(hprtLookupPing: string[]) {
addHPRTLookupPing(hprtLookupPing);
});
});
addWebUiListener('hprt-lookup-pings-update', function(result: string[]) {
addHPRTLookupPing(result);
});
sendWithPromise('getHPRTLookupResponses', [])
.then((hprtLookupResponses: string[][]) => {
hprtLookupResponses.forEach(function(hprtLookupResponse) {
addHPRTLookupResponse(hprtLookupResponse);
});
});
addWebUiListener('hprt-lookup-responses-update', function(result: string[]) {
addHPRTLookupResponse(result);
});
sendWithPromise('getLogMessages', [])
.then((logMessages: ReportingResult[]) => {
logMessages.forEach(function(message) {
addLogMessage(message);
});
});
addWebUiListener('log-messages-update', function(message: ReportingResult) {
addLogMessage(message);
});
sendWithPromise('getReportingEvents', [])
.then((reportingEvents: RealtimeReportingResult[]) => {
reportingEvents.forEach(function(reportingEvent) {
addReportingEvent(reportingEvent);
});
});
addWebUiListener(
'reporting-events-update',
function(reportingEvent: RealtimeReportingResult) {
addReportingEvent(reportingEvent);
});
sendWithPromise('getDeepScans', []).then((requests: DeepScanResult[]) => {
requests.forEach(function(request) {
addDeepScan(request);
});
});
addWebUiListener(
'deep-scan-request-update', function(result: DeepScanResult) {
addDeepScan(result);
});
// <if expr="is_android">
sendWithPromise('getReferringAppInfo', []).then((info: string) => {
addReferringAppInfo(info);
});
// </if>
const referrerChangeForm = $('get-referrer-chain-form');
assert(referrerChangeForm);
referrerChangeForm.addEventListener('submit', addReferrerChain);
sendWithPromise('getTailoredVerdictOverride', [])
.then(displayTailoredVerdictOverride);
addWebUiListener(
'tailored-verdict-override-update', displayTailoredVerdictOverride);
const overrideForm =
$<TailoredVerdictOverrideForm>('tailored-verdict-override-form');
assert(overrideForm);
overrideForm.addEventListener('submit', setTailoredVerdictOverride);
const overrideClear = $('tailored-verdict-override-clear');
assert(overrideClear);
overrideClear.addEventListener('click', clearTailoredVerdictOverride);
// Allow tabs to be navigated to by fragment. The fragment with be of the
// format "#tab-<tab id>"
showTab(window.location.hash.substr(5));
window.onhashchange = function() {
showTab(window.location.hash.substr(5));
};
// When the tab updates, update the anchor
const tabbox = $('tabbox');
assert(tabbox);
tabbox.addEventListener('selected-index-change', e => {
const tabs = document.querySelectorAll('div[slot=\'tab\']');
const selectedTab = tabs[e.detail];
assert(selectedTab);
window.location.hash = 'tab-' + selectedTab.id;
}, true);
}
// Adds the currently running experimental Safe Browsing features, and
// their statuses, to the DOM. `experiments` is an array of strings
// where the even-indexed elements contain the feature's name and the
// odd-indexed elements are the feature's status.
function addExperiments(experiments: string[]) {
addContentHelper(experiments, 'result-template', 'experiments-list', 'span');
}
// Adds a list of preferences and their statuses to the DOM. `prefs` is
// an array of strings where the even-indexed elements contain the
// pref's name and the odd-indexed elements are the pref's status.
function addPrefs(prefs: string[]) {
addContentHelper(prefs, 'result-template', 'preferences-list', 'span');
}
// Adds a list of policies and their statuses to the DOM. `policies` is
// an array where the even-indexed elements contain the policy's name
// and the odd-indexed elements are the policy's status. The policy's
// status can be represented either as a string or a boolean.
function addPolicies(policies: Array<string|boolean>) {
addContentHelper(policies, 'result-template', 'policies-list', 'span');
}
// Adds the value of the Safe Browsing Cookie and the time it was
// created. `cookie` is a expected to be an array containing two
// elements where the first element is Safe Browsing Cookie's
// value and the second element is its creation time.
function addCookie(cookie: [string, number]) {
const cookiePanel = $('cookie-panel');
assert(cookiePanel);
const cookieTemplate = $<HTMLTemplateElement>('cookie-template');
assert(cookieTemplate);
const cookieFormatted = cookieTemplate.content.cloneNode(true) as HTMLElement;
const selectedElements = cookieFormatted.querySelectorAll('.result');
assert(selectedElements);
const cookieValueDOM = selectedElements[0];
assert(cookieValueDOM);
const creationDateDOM = selectedElements[1];
assert(creationDateDOM);
const cookieValue = cookie[0];
assert(cookieValue);
const creationDate = cookie[1];
assert(creationDate !== null && creationDate !== undefined);
cookieValueDOM.textContent = cookieValue;
creationDateDOM.textContent = (new Date(creationDate)).toLocaleString();
cookiePanel.appendChild(cookieFormatted);
}
// Adds saved passwords data to the DOM. `passwords` is an array where
// the even-indexed elements contain the username and the odd-indexed
// elements are boolean values indicating how the password is stored.
function addSavedPasswords(passwords: Array<string|boolean>) {
for (let i = 0; i < passwords.length; i += 2) {
const savedPasswordFormatted = document.createElement('div');
const suffix = passwords[i + 1] ? 'GAIA password' : 'Enterprise password';
savedPasswordFormatted.textContent = `${passwords[i]} (${suffix})`;
const savedPasswordsList = $('saved-passwords');
assert(savedPasswordsList);
savedPasswordsList.appendChild(savedPasswordFormatted);
}
}
// Adds the DatabaseManagerInfo proto to the DOM. Each even-indexed
// element in `databaseManagerInfo` contains the proto's property name.
// Each odd-indexed element contains the value stored the property.
function addDatabaseManagerInfo(
databaseManagerInfo: Array<string|number|string[]>) {
for (let i = 0; i < databaseManagerInfo.length; i += 2) {
const preferenceListTemplate = $<HTMLTemplateElement>('result-template');
assert(preferenceListTemplate);
const preferencesListFormatted =
preferenceListTemplate.content.cloneNode(true) as HTMLElement;
const selectedElements = preferencesListFormatted.querySelectorAll('span');
assert(selectedElements);
const labelDOM = selectedElements[0];
assert(labelDOM);
const valueDOM = selectedElements[1];
assert(valueDOM);
labelDOM.textContent = databaseManagerInfo[i] + ': ';
const value = databaseManagerInfo[i + 1];
assert(value);
if (Array.isArray(value)) {
const blockQuote = document.createElement('blockquote');
value.forEach(item => {
const div = document.createElement('div');
div.textContent = item;
blockQuote.appendChild(div);
});
valueDOM.appendChild(blockQuote);
} else {
valueDOM.textContent = value.toString();
}
const databaseInfoList = $('database-info-list');
assert(databaseInfoList);
databaseInfoList.appendChild(preferencesListFormatted);
}
}
// A helper function that formats and adds the content's name and its
// status into the template and then appends the template to the parent
// list.
function addContentHelper(
content: Array<string|boolean>, templateName: string,
parentListName: string, elementSelector: string) {
const resLength = content.length;
for (let i = 0; i < resLength; i += 2) {
const listTemplate = $<HTMLTemplateElement>(templateName);
assert(listTemplate);
const formattedTemplate =
listTemplate.content.cloneNode(true) as HTMLElement;
const contentName = content[i];
const status = content[i + 1];
assert(contentName !== null && contentName !== undefined);
assert(status !== null && status !== undefined);
const selectedElements =
formattedTemplate.querySelectorAll(elementSelector);
const labelDOM = selectedElements[0];
const valueDOM = selectedElements[1];
assert(labelDOM);
assert(valueDOM);
const parentList = $(parentListName);
assert(parentList);
labelDOM.textContent = status + ': ';
valueDOM.textContent = contentName.toString();
parentList.appendChild(formattedTemplate);
}
}
function addFullHashCacheInfo(result: string[]) {
const cacheInfo = $('full-hash-cache-info');
assert(cacheInfo);
cacheInfo.textContent = result.toString();
}
function addDownloadUrlChecked(urlAndResult: string) {
const logDiv = $('download-urls-checked-list');
appendChildWithInnerText(logDiv, urlAndResult);
}
function addSentClientDownloadRequestsInfo(requestInfo: string) {
const logDiv = $('sent-client-download-requests-list');
appendChildWithInnerText(logDiv, requestInfo);
}
function addReceivedClientDownloadResponseInfo(responseInfo: string) {
const logDiv = $('received-client-download-response-list');
appendChildWithInnerText(logDiv, responseInfo);
}
function addSentClientPhishingRequestsInfo(phishingRequestInfo: string) {
const logDiv = $('sent-client-phishing-requests-list');
appendChildWithInnerText(logDiv, phishingRequestInfo);
}
function addReceivedClientPhishingResponseInfo(phishingResponseInfo: string) {
const logDiv = $('received-client-phishing-response-list');
appendChildWithInnerText(logDiv, phishingResponseInfo);
}
function addSentCSBRRsInfo(csbrrsInfo: string) {
const logDiv = $('sent-csbrrs-list');
appendChildWithInnerText(logDiv, csbrrsInfo);
}
function addPGEvent(pgEvent: ReportingResult) {
const logDiv = $('pg-event-log');
const eventFormatted =
'[' + (new Date(pgEvent.time)).toLocaleString() + '] ' + pgEvent.message;
appendChildWithInnerText(logDiv, eventFormatted);
}
function addSecurityEvent(securityEvent: ReportingResult) {
const logDiv = $('security-event-log');
const eventFormatted = '[' + (new Date(securityEvent.time)).toLocaleString() +
'] ' + securityEvent.message;
appendChildWithInnerText(logDiv, eventFormatted);
}
function insertTokenToTable(tableId: string, token?: string) {
const table = $<HTMLTableElement>(tableId);
assert(table);
const row = table.insertRow();
row.className = 'content';
row.id = tableId + '-' + token;
row.insertCell().className = 'content';
row.insertCell().className = 'content';
}
function addResultToTable(
tableId: string, token: string, result: string, position: number) {
let table = $<HTMLTableRowElement>(`${tableId}-${token}`);
if (table === null) {
insertTokenToTable(tableId, token);
table = $<HTMLTableRowElement>(`${tableId}-${token}`);
}
assert(table);
const cell = table.cells[position];
assert(cell);
cell.innerText = result;
}
function addPGPing(pgPing: string[]) {
addResultToTableHelper('pg-ping-list', pgPing, 0);
}
function addPGResponse(pgResponse: string[]) {
addResultToTableHelper('pg-ping-list', pgResponse, 1);
}
function addURTLookupPing(urtPing: string[]) {
addResultToTableHelper('urt-lookup-ping-list', urtPing, 0);
}
function addURTLookupResponse(urtLookupResponse: string[]) {
addResultToTableHelper('urt-lookup-ping-list', urtLookupResponse, 1);
}
function addHPRTLookupPing(hprtPing: string[]) {
addResultToTableHelper('hprt-lookup-ping-list', hprtPing, 0);
}
function addHPRTLookupResponse(hprtLookupResponse: string[]) {
addResultToTableHelper('hprt-lookup-ping-list', hprtLookupResponse, 1);
}
// A helper function that ensures there are elements within `results` before
// adding them to a list.
function addResultToTableHelper(
listName: string, result: Array<string|number>, position: number) {
const tableId =
typeof result[0] === 'number' ? (result[0]).toString() : result[0];
const token = result[1] as string;
assert(tableId !== undefined && tableId !== null);
assert(token !== undefined && token !== null);
addResultToTable(listName, tableId, token, position);
}
function addDeepScan(result: DeepScanResult) {
if (result.request_time !== null) {
const requestFormatted = '[' +
(new Date(result.request_time)).toLocaleString() + ']\n' +
result.request;
addResultToTable('deep-scan-list', result.token, requestFormatted, 0);
}
if (result.response_time != null) {
if (result.response_status === 'SUCCESS') {
// Display the response instead
const resultFormatted = '[' +
(new Date(result.response_time)).toLocaleString() + ']\n' +
result.response;
addResultToTable('deep-scan-list', result.token, resultFormatted, 1);
} else {
// Display the error
const resultFormatted = '[' +
(new Date(result.response_time)).toLocaleString() + ']\n' +
result.response_status;
addResultToTable('deep-scan-list', result.token, resultFormatted, 1);
}
}
}
function addLogMessage(logMessage: ReportingResult) {
const logDiv = $('log-messages');
const eventFormatted = '[' + (new Date(logMessage.time)).toLocaleString() +
'] ' + logMessage.message;
appendChildWithInnerText(logDiv, eventFormatted);
}
function addReportingEvent(reportingEvent: RealtimeReportingResult) {
// If the event doesn't have a timestamp, fall back to the old display format.
if (!reportingEvent.timeMillis) {
const logDiv = $('reporting-events');
const eventFormatted = reportingEvent.message;
appendChildWithInnerText(logDiv, eventFormatted);
return;
}
const table = $<HTMLTableElement>('reporting-events-table')!;
// Unhide the table if it's the first event.
if (table.hidden) {
table.hidden = false;
}
const tableBody = table.querySelector('tbody')!;
const template = $<HTMLTemplateElement>('resultRowTemplate')!;
// Clone the new row and insert it into the table
const reportingEventRow =
template.content.cloneNode(true) as DocumentFragment;
const mainRow = reportingEventRow.querySelector('.main-row')!;
const detailsRow = reportingEventRow.querySelector('.details-row')!;
mainRow.querySelector('.time-cell')!.textContent =
new Date(reportingEvent.timeMillis).toLocaleString();
mainRow.querySelector('.event-type-cell')!.textContent =
reportingEvent.event_type;
mainRow.querySelector('.profile-cell')!.textContent =
reportingEvent.profile ? 'Yes' : 'No';
mainRow.querySelector('.device-cell')!.textContent =
reportingEvent.device ? 'Yes' : 'No';
mainRow.querySelector('.success-cell')!.textContent =
reportingEvent.success ? 'Yes' : 'No';
detailsRow.querySelector('.details-cell')!.textContent =
reportingEvent.message;
const expander = mainRow.querySelector('.expander')!;
const copyButton = mainRow.querySelector('.copy-button')!;
// Add click listener to copy the message.
copyButton.addEventListener('click', (e) => {
e.stopPropagation();
navigator.clipboard.writeText(reportingEvent.message).then(() => {
const originalText = copyButton.textContent;
copyButton.textContent = 'Copied!';
setTimeout(() => {
copyButton.textContent = originalText;
}, 2000);
});
});
// Add click listener to toggle the details row.
expander.addEventListener('click', () => {
expander.classList.toggle('expanded');
detailsRow.classList.toggle('visible');
});
tableBody.appendChild(reportingEventRow);
}
function appendChildWithInnerText(logDiv: Element|null, text: string) {
if (!logDiv) {
return;
}
const textDiv = document.createElement('div');
textDiv.innerText = text;
logDiv.appendChild(textDiv);
}
function addReferrerChain(ev: Event) {
// Don't navigate
ev.preventDefault();
const referrerChainURL = $<HTMLInputElement>('referrer-chain-url');
assert(referrerChainURL);
sendWithPromise('getReferrerChain', referrerChainURL.value)
.then((referrerChain: string) => {
const referrerChainContent = $('referrer-chain-content');
assert(referrerChainContent);
// TrustedTypes is not supported on iOS
if (window.trustedTypes) {
referrerChainContent.innerHTML = window.trustedTypes.emptyHTML;
} else {
referrerChainContent.innerHTML = '';
}
referrerChainContent.textContent = referrerChain;
});
}
// <if expr="is_android">
function addReferringAppInfo(info: string|null) {
const referringAppInfo = $('referring-app-info');
assert(referringAppInfo);
// TrustedTypes is not supported on iOS
if (window.trustedTypes) {
referringAppInfo.innerHTML = window.trustedTypes.emptyHTML;
} else {
referringAppInfo.innerHTML = '';
}
referringAppInfo.textContent = info;
}
// </if>
// Format the browser's response nicely.
function displayTailoredVerdictOverride(
response: {status: number, override_value: object}) {
let displayString = `Status: ${response.status}`;
if (response.override_value) {
displayString +=
`\nOverride value: ${JSON.stringify(response.override_value)}`;
}
const overrideContent = $('tailored-verdict-override-content');
assert(overrideContent);
// TrustedTypes is not supported on iOS
if (window.trustedTypes) {
overrideContent.innerHTML = window.trustedTypes.emptyHTML;
} else {
overrideContent.innerHTML = '';
}
overrideContent.textContent = displayString;
}
function setTailoredVerdictOverride(e: Event) {
// Don't navigate
e.preventDefault();
const form = $<TailoredVerdictOverrideForm>('tailored-verdict-override-form');
assert(form);
const inputs = form.elements;
// The structured data to send to the browser.
const inputValue = {
tailored_verdict_type: inputs.tailored_verdict_type.value,
};
sendWithPromise('setTailoredVerdictOverride', inputValue)
.then(displayTailoredVerdictOverride);
}
function clearTailoredVerdictOverride(e: Event) {
// Don't navigate
e.preventDefault();
const form = $<TailoredVerdictOverrideForm>('tailored-verdict-override-form');
assert(form);
form.reset();
sendWithPromise('clearTailoredVerdictOverride')
.then(displayTailoredVerdictOverride);
}
function showTab(tabId: string) {
const tabs = document.querySelectorAll('div[slot=\'tab\']');
const index = Array.from(tabs).findIndex(t => t.id === tabId);
if (index !== -1) {
const tabbox = document.querySelector('cr-tab-box');
assert(tabbox);
tabbox.setAttribute('selected-index', index.toString());
}
}
document.addEventListener('DOMContentLoaded', initialize);