UNPKG

chrome-devtools-frontend

Version:
283 lines (258 loc) • 8.92 kB
// Copyright (c) 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* eslint-disable @typescript-eslint/naming-convention */ import * as Common from '../common/common.js'; import * as i18n from '../i18n/i18n.js'; import {InspectorFrontendHostInstance} from './InspectorFrontendHost.js'; import type {LoadNetworkResourceResult} from './InspectorFrontendHostAPI.js'; const UIStrings = { /** *@description Name of an error category used in error messages */ systemError: 'System error', /** *@description Name of an error category used in error messages */ connectionError: 'Connection error', /** *@description Name of an error category used in error messages */ certificateError: 'Certificate error', /** *@description Name of an error category used in error messages */ httpError: 'HTTP error', /** *@description Name of an error category used in error messages */ cacheError: 'Cache error', /** *@description Name of an error category used in error messages */ signedExchangeError: 'Signed Exchange error', /** *@description Name of an error category used in error messages */ ftpError: 'FTP error', /** *@description Name of an error category used in error messages */ certificateManagerError: 'Certificate manager error', /** *@description Name of an error category used in error messages */ dnsResolverError: 'DNS resolver error', /** *@description Name of an error category used in error messages */ unknownError: 'Unknown error', /** *@description Phrase used in error messages that carry a network error name *@example {404} PH1 *@example {net::ERR_INSUFFICIENT_RESOURCES} PH2 */ httpErrorStatusCodeSS: 'HTTP error: status code {PH1}, {PH2}', /** *@description Name of an error category used in error messages */ invalidUrl: 'Invalid URL', /** *@description Name of an error category used in error messages */ decodingDataUrlFailed: 'Decoding Data URL failed', } as const; const str_ = i18n.i18n.registerUIStrings('core/host/ResourceLoader.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export const ResourceLoader = {}; let _lastStreamId = 0; const _boundStreams: Record<number, Common.StringOutputStream.OutputStream> = {}; export const bindOutputStream = function(stream: Common.StringOutputStream.OutputStream): number { _boundStreams[++_lastStreamId] = stream; return _lastStreamId; }; export const discardOutputStream = function(id: number): void { void _boundStreams[id].close(); delete _boundStreams[id]; }; export const streamWrite = function(id: number, chunk: string): void { void _boundStreams[id].write(chunk); }; export interface LoadErrorDescription { statusCode: number; netError?: number; netErrorName?: string; urlValid?: boolean; message?: string; } export const load = function( url: string, headers: Record<string, string>|null, callback: ( arg0: boolean, arg1: Record<string, string>, arg2: string, arg3: LoadErrorDescription, ) => void, allowRemoteFilePaths: boolean): void { const stream = new Common.StringOutputStream.StringOutputStream(); loadAsStream(url, headers, stream, mycallback, allowRemoteFilePaths); function mycallback( success: boolean, headers: Record<string, string>, errorDescription: LoadErrorDescription, ): void { callback(success, headers, stream.data(), errorDescription); } }; function getNetErrorCategory(netError: number): string { if (netError > -100) { return i18nString(UIStrings.systemError); } if (netError > -200) { return i18nString(UIStrings.connectionError); } if (netError > -300) { return i18nString(UIStrings.certificateError); } if (netError > -400) { return i18nString(UIStrings.httpError); } if (netError > -500) { return i18nString(UIStrings.cacheError); } if (netError > -600) { return i18nString(UIStrings.signedExchangeError); } if (netError > -700) { return i18nString(UIStrings.ftpError); } if (netError > -800) { return i18nString(UIStrings.certificateManagerError); } if (netError > -900) { return i18nString(UIStrings.dnsResolverError); } return i18nString(UIStrings.unknownError); } function isHTTPError(netError: number): boolean { return netError <= -300 && netError > -400; } export function netErrorToMessage( netError: number|undefined, httpStatusCode: number|undefined, netErrorName: string|undefined): string|null { if (netError === undefined || netErrorName === undefined) { return null; } if (netError !== 0) { if (isHTTPError(netError)) { return i18nString(UIStrings.httpErrorStatusCodeSS, {PH1: String(httpStatusCode), PH2: netErrorName}); } const errorCategory = getNetErrorCategory(netError); // We don't localize here, as `errorCategory` is already localized, // and `netErrorName` is an error code like 'net::ERR_CERT_AUTHORITY_INVALID'. return `${errorCategory}: ${netErrorName}`; } return null; } function createErrorMessageFromResponse(response: LoadNetworkResourceResult): { success: boolean, description: LoadErrorDescription, } { const {statusCode, netError, netErrorName, urlValid, messageOverride} = response; let message = ''; const success = statusCode >= 200 && statusCode < 300; if (typeof messageOverride === 'string') { message = messageOverride; } else if (!success) { if (typeof netError === 'undefined') { if (urlValid === false) { message = i18nString(UIStrings.invalidUrl); } else { message = i18nString(UIStrings.unknownError); } } else { const maybeMessage = netErrorToMessage(netError, statusCode, netErrorName); if (maybeMessage) { message = maybeMessage; } } } console.assert(success === (message.length === 0)); return {success, description: {statusCode, netError, netErrorName, urlValid, message}}; } const loadXHR = (url: string): Promise<string> => { return new Promise((successCallback, failureCallback) => { function onReadyStateChanged(): void { if (xhr.readyState !== XMLHttpRequest.DONE) { return; } if (xhr.status !== 200) { xhr.onreadystatechange = null; failureCallback(new Error(String(xhr.status))); return; } xhr.onreadystatechange = null; successCallback(xhr.responseText); } const xhr = new XMLHttpRequest(); xhr.withCredentials = false; xhr.open('GET', url, true); xhr.onreadystatechange = onReadyStateChanged; xhr.send(null); }); }; function canBeRemoteFilePath(url: string): boolean { try { const urlObject = new URL(url); return urlObject.protocol === 'file:' && urlObject.host !== ''; } catch { return false; } } export const loadAsStream = function( url: string, headers: Record<string, string>|null, stream: Common.StringOutputStream.OutputStream, callback?: ((arg0: boolean, arg1: Record<string, string>, arg2: LoadErrorDescription) => void), allowRemoteFilePaths?: boolean, ): void { const streamId = bindOutputStream(stream); const parsedURL = new Common.ParsedURL.ParsedURL(url); if (parsedURL.isDataURL()) { loadXHR(url).then(dataURLDecodeSuccessful).catch(dataURLDecodeFailed); return; } if (!allowRemoteFilePaths && canBeRemoteFilePath(url)) { // Remote file paths can cause security problems, see crbug.com/1342722. if (callback) { callback(/* success */ false, /* headers */ {}, { statusCode: 400, // BAD_REQUEST netError: -20, // BLOCKED_BY_CLIENT netErrorName: 'net::BLOCKED_BY_CLIENT', message: 'Loading from a remote file path is prohibited for security reasons.', }); } return; } const rawHeaders = []; if (headers) { for (const key in headers) { rawHeaders.push(key + ': ' + headers[key]); } } InspectorFrontendHostInstance.loadNetworkResource(url, rawHeaders.join('\r\n'), streamId, finishedCallback); function finishedCallback(response: LoadNetworkResourceResult): void { if (callback) { const {success, description} = createErrorMessageFromResponse(response); callback(success, response.headers || {}, description); } discardOutputStream(streamId); } function dataURLDecodeSuccessful(text: string): void { streamWrite(streamId, text); finishedCallback(({statusCode: 200} as LoadNetworkResourceResult)); } function dataURLDecodeFailed(_xhrStatus: Error): void { const messageOverride: string = i18nString(UIStrings.decodingDataUrlFailed); finishedCallback(({statusCode: 404, messageOverride} as LoadNetworkResourceResult)); } };