Innovenergy_trunk/frontend/node_modules/testcafe-hammerhead/lib/request-pipeline/context/index.js

407 lines
19 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const cookie_1 = require("../../utils/cookie");
const incoming_message_like_1 = __importDefault(require("../incoming-message-like"));
const charset_1 = __importDefault(require("../../processing/encoding/charset"));
const urlUtils = __importStar(require("../../utils/url"));
const contentTypeUtils = __importStar(require("../../utils/content-type"));
const generate_unique_id_1 = __importDefault(require("../../utils/generate-unique-id"));
const same_origin_policy_1 = require("../same-origin-policy");
const headerTransforms = __importStar(require("../header-transforms"));
const service_routes_1 = __importDefault(require("../../proxy/service-routes"));
const builtin_header_names_1 = __importDefault(require("../builtin-header-names"));
const logger_1 = __importDefault(require("../../utils/logger"));
const create_special_page_response_1 = __importDefault(require("../create-special-page-response"));
const http_1 = require("../../utils/http");
const requestCache = __importStar(require("../cache"));
const base_1 = __importDefault(require("./base"));
const factory_1 = __importDefault(require("../request-hooks/events/factory"));
const stream_1 = require("stream");
const promisify_stream_1 = __importDefault(require("../../utils/promisify-stream"));
const buffer_1 = require("../../utils/buffer");
const is_redirect_status_code_1 = __importDefault(require("../../utils/is-redirect-status-code"));
const CANNOT_BE_USED_WITH_WEB_SOCKET_ERR_MSG = 'The function cannot be used with a WebSocket request.';
class RequestPipelineContext extends base_1.default {
constructor(req, res, serverInfo, proxyless) {
super((0, generate_unique_id_1.default)());
this.req = req;
this.res = res;
this.serverInfo = serverInfo;
this.proxyless = proxyless;
this.isDestResReadableEnded = false;
this.isAjax = false;
this.isPage = false;
this.isHTMLPage = false;
this.isHtmlImport = false;
this.isWebSocket = false;
this.isIframe = false;
this.isSpecialPage = false;
this.isWebSocketConnectionReset = false;
this.goToNextStage = true;
this.isSameOriginPolicyFailed = false;
this._initParsedClientSyncCookie();
this.eventFactory = new factory_1.default(this);
}
_initParsedClientSyncCookie() {
if (!this.req.headers.cookie)
return;
const parsedClientSyncCookieStr = (0, cookie_1.parseClientSyncCookieStr)(this.req.headers.cookie);
if (parsedClientSyncCookieStr)
this.parsedClientSyncCookie = parsedClientSyncCookieStr;
}
// TODO: Rewrite parseProxyUrl instead.
static _flattenParsedProxyUrl(parsed) {
if (!parsed)
return null;
const parsedResourceType = urlUtils.parseResourceType(parsed.resourceType);
const dest = {
url: parsed.destUrl,
protocol: parsed.destResourceInfo.protocol || '',
host: parsed.destResourceInfo.host || '',
hostname: parsed.destResourceInfo.hostname || '',
port: parsed.destResourceInfo.port || '',
partAfterHost: parsed.destResourceInfo.partAfterHost || '',
auth: parsed.destResourceInfo.auth,
isIframe: !!parsedResourceType.isIframe,
isForm: !!parsedResourceType.isForm,
isScript: !!(parsedResourceType.isScript || parsedResourceType.isServiceWorker),
isEventSource: !!parsedResourceType.isEventSource,
isHtmlImport: !!parsedResourceType.isHtmlImport,
isWebSocket: !!parsedResourceType.isWebSocket,
isServiceWorker: !!parsedResourceType.isServiceWorker,
isAjax: !!parsedResourceType.isAjax,
isObject: !!parsedResourceType.isObject,
charset: parsed.charset || '',
reqOrigin: parsed.reqOrigin || '',
credentials: parsed.credentials,
};
return { dest, sessionId: parsed.sessionId, windowId: parsed.windowId };
}
_isFileDownload() {
const contentDisposition = this.destRes.headers[builtin_header_names_1.default.contentDisposition];
return !!contentDisposition &&
contentDisposition.includes('attachment') &&
contentDisposition.includes('filename');
}
_resolveInjectableUrls(injectableUrls) {
return injectableUrls.map(url => this.resolveInjectableUrl(url));
}
_initRequestNatureInfo() {
const acceptHeader = this.req.headers[builtin_header_names_1.default.accept];
this.isWebSocket = this.dest.isWebSocket;
this.isHtmlImport = this.dest.isHtmlImport;
this.isAjax = this.dest.isAjax;
this.isPage = !this.isAjax && !this.isWebSocket && acceptHeader &&
contentTypeUtils.isPage(acceptHeader) || this.isHtmlImport;
this.isIframe = this.dest.isIframe;
this.isSpecialPage = urlUtils.isSpecialPage(this.dest.url);
this.isFileProtocol = this.dest.protocol === 'file:';
this.isHTMLPage = this.isPage && !this.isIframe && !this.isHtmlImport;
}
_getDestFromReferer(parsedReferer) {
const dest = parsedReferer.dest;
dest.partAfterHost = this.req.url || '';
dest.url = urlUtils.formatUrl(dest);
return { dest, sessionId: parsedReferer.sessionId, windowId: parsedReferer.windowId };
}
_addTemporaryEntryToCache() {
if (!this.temporaryCacheEntry)
return;
this.temporaryCacheEntry.value.res.setBody(this.destResBody);
requestCache.add(this.temporaryCacheEntry);
this.temporaryCacheEntry = void 0;
}
// API
dispatch(openSessions) {
const parsedReqUrl = urlUtils.parseProxyUrl(this.req.url || '');
const referer = this.req.headers[builtin_header_names_1.default.referer];
let parsedReferer = referer && urlUtils.parseProxyUrl(referer) || null;
// TODO: Remove it after parseProxyURL is rewritten.
let flattenParsedReqUrl = RequestPipelineContext._flattenParsedProxyUrl(parsedReqUrl);
let flattenParsedReferer = RequestPipelineContext._flattenParsedProxyUrl(parsedReferer);
// NOTE: Remove that after implementing the https://github.com/DevExpress/testcafe-hammerhead/issues/2155
if (!flattenParsedReqUrl && flattenParsedReferer)
flattenParsedReqUrl = this._getDestFromReferer(flattenParsedReferer);
if (!flattenParsedReqUrl)
return false;
const session = openSessions.get(flattenParsedReqUrl.sessionId);
if (session)
this.session = session;
if (!this.session)
return false;
if (!flattenParsedReferer && this.session.options.referer) {
parsedReferer = urlUtils.parseProxyUrl(this.session.options.referer) || null;
flattenParsedReferer = RequestPipelineContext._flattenParsedProxyUrl(parsedReferer);
}
this.dest = flattenParsedReqUrl.dest;
this.windowId = flattenParsedReqUrl.windowId;
this.dest.partAfterHost = RequestPipelineContext._preparePartAfterHost(this.dest.partAfterHost);
this.dest.domain = urlUtils.getDomain(this.dest);
if (flattenParsedReferer) {
this.dest.referer = flattenParsedReferer.dest.url;
this.dest.reqOrigin = this.dest.reqOrigin || urlUtils.getDomain(flattenParsedReferer.dest);
}
else
this.dest.reqOrigin = this.dest.reqOrigin || this.dest.domain;
this._initRequestNatureInfo();
this._applyClientSyncCookie();
return true;
}
_applyClientSyncCookie() {
if (!this.parsedClientSyncCookie)
return;
const clientCookie = this.parsedClientSyncCookie.actual.filter(syncCookie => syncCookie.isClientSync && syncCookie.sid === this.session.id);
this.session.cookies.setByClient(clientCookie);
}
static _preparePartAfterHost(str) {
// Browsers add a leading slash to the pathname part of url (GH-608)
// For example: url http://www.example.com?gd=GID12082014 will be converted
// to http://www.example.com/?gd=GID12082014
return (str[0] === '/' ? '' : '/') + str;
}
buildContentInfo() {
const contentType = this.destRes.headers[builtin_header_names_1.default.contentType] || '';
const accept = this.req.headers[builtin_header_names_1.default.accept] || '';
const encoding = (this.destRes.headers[builtin_header_names_1.default.contentEncoding] || '').toLowerCase();
const isTextPage = this.isPage && contentTypeUtils.isTextPage(contentType);
if (this.isPage && contentType && !isTextPage)
this.isPage = !this.isAjax && contentTypeUtils.isPage(contentType);
const isCSS = contentTypeUtils.isCSSResource(contentType, accept);
const isManifest = contentTypeUtils.isManifest(contentType);
const isScript = this.dest.isScript || contentTypeUtils.isScriptResource(contentType, accept);
const isForm = this.dest.isForm;
const isObject = this.dest.isObject;
const isFormWithEmptyResponse = isForm && this.destRes.statusCode === 204;
const isRedirect = this.destRes.headers[builtin_header_names_1.default.location] &&
this.destRes.statusCode &&
(0, is_redirect_status_code_1.default)(this.destRes.statusCode) ||
false;
const requireAssetsProcessing = (isCSS || isScript || isManifest) && this.destRes.statusCode !== 204;
const isNotModified = this.req.method === 'GET' && this.destRes.statusCode === 304 &&
!!(this.req.headers[builtin_header_names_1.default.ifModifiedSince] ||
this.req.headers[builtin_header_names_1.default.ifNoneMatch]);
const requireProcessing = !this.isAjax && !isFormWithEmptyResponse && !isRedirect &&
!isNotModified && (this.isPage || this.isIframe || requireAssetsProcessing);
const isFileDownload = this._isFileDownload() && !this.dest.isScript;
const isIframeWithImageSrc = this.isIframe && !this.isPage && /^\s*image\//.test(contentType);
const isAttachment = !this.isPage && !this.isAjax && !this.isWebSocket && !this.isIframe &&
!isTextPage && !isManifest && !isScript && !isForm && !isObject;
const charset = new charset_1.default();
const contentTypeUrlToken = urlUtils.getResourceTypeString({
isIframe: this.isIframe,
isAjax: this.isAjax,
isForm, isScript,
}) || '';
// NOTE: We need charset information if we are going to process the resource.
if (requireProcessing && !charset.fromContentType(contentType))
charset.fromUrl(this.dest.charset);
if (isFileDownload)
this.session.handleFileDownload();
if (isAttachment)
this._handleAttachment();
this.contentInfo = {
charset,
requireProcessing,
isIframeWithImageSrc,
isCSS,
isScript,
isManifest,
isObject,
encoding,
contentTypeUrlToken,
isFileDownload,
isNotModified,
isRedirect,
isAttachment,
isTextPage,
};
logger_1.default.proxy.onContentInfoBuilt(this);
}
_handleAttachment() {
let isOpenedInNewWindow = false;
if (this.req.url) {
const url1 = urlUtils.parseProxyUrl(this.req.url);
const url2 = urlUtils.parseProxyUrl(this.req.headers[builtin_header_names_1.default.referer]);
isOpenedInNewWindow = (url1 === null || url1 === void 0 ? void 0 : url1.windowId) !== (url2 === null || url2 === void 0 ? void 0 : url2.windowId);
}
this.session.handleAttachment({ isOpenedInNewWindow });
}
async _getDestResBody(res) {
if (incoming_message_like_1.default.isIncomingMessageLike(res)) {
const body = res.getBody();
if (body)
return body;
}
return (0, http_1.fetchBody)(this.destRes, this.destRes.headers[builtin_header_names_1.default.contentLength]);
}
calculateIsDestResReadableEnded() {
if (!this.contentInfo.isNotModified &&
!this.contentInfo.isRedirect &&
!incoming_message_like_1.default.isIncomingMessageLike(this.destRes)) {
this.destRes.once('end', () => {
this.isDestResReadableEnded = true;
});
}
else
this.isDestResReadableEnded = true;
}
getInjectableScripts() {
const taskScript = this.isIframe ? service_routes_1.default.iframeTask : service_routes_1.default.task;
const scripts = this.session.injectable.scripts.concat(taskScript, this.injectableUserScripts);
return this._resolveInjectableUrls(scripts);
}
getInjectableStyles() {
return this._resolveInjectableUrls(this.session.injectable.styles);
}
redirect(url) {
if (this.isWebSocket)
throw new Error(CANNOT_BE_USED_WITH_WEB_SOCKET_ERR_MSG);
const res = this.res;
res.statusCode = 302;
res.setHeader(builtin_header_names_1.default.location, url);
res.end();
}
saveNonProcessedDestResBody(value) {
this.nonProcessedDestResBody = value;
}
closeWithError(statusCode, resBody = '') {
if ('setHeader' in this.res && !this.res.headersSent) {
this.res.statusCode = statusCode;
this.res.setHeader(builtin_header_names_1.default.contentType, 'text/html');
this.res.write(resBody);
}
this.res.end();
this.goToNextStage = false;
}
toProxyUrl(url, isCrossDomain, resourceType, charset, reqOrigin, credentials) {
const proxyHostname = this.serverInfo.hostname;
const proxyProtocol = this.serverInfo.protocol;
const proxyPort = isCrossDomain ? this.serverInfo.crossDomainPort.toString() : this.serverInfo.port.toString();
const sessionId = this.session.id;
const windowId = this.windowId;
if (isCrossDomain)
reqOrigin = this.dest.domain;
return urlUtils.getProxyUrl(url, {
proxyHostname,
proxyProtocol,
proxyPort,
sessionId,
resourceType,
charset,
windowId,
reqOrigin,
credentials,
});
}
getProxyOrigin(isCrossDomain = false) {
return urlUtils.getDomain({
protocol: this.serverInfo.protocol,
hostname: this.serverInfo.hostname,
port: isCrossDomain ? this.serverInfo.crossDomainPort : this.serverInfo.port,
});
}
isPassSameOriginPolicy() {
const shouldPerformCORSCheck = this.isAjax && !this.contentInfo.isNotModified;
return !shouldPerformCORSCheck || (0, same_origin_policy_1.check)(this);
}
sendResponseHeaders() {
if (this.isWebSocket)
throw new Error(CANNOT_BE_USED_WITH_WEB_SOCKET_ERR_MSG);
const headers = headerTransforms.forResponse(this);
const res = this.res;
if (this.isHTMLPage && this.session.options.disablePageCaching)
headerTransforms.setupPreventCachingHeaders(headers);
logger_1.default.proxy.onResponse(this, headers);
res.writeHead(this.destRes.statusCode, headers);
res.addTrailers(this.destRes.trailers);
}
async mockResponse(eventProvider) {
logger_1.default.destination.onMockedRequest(this);
this.destRes = await this.getMockResponse();
this.buildContentInfo();
if (!this.mock.hasError)
return;
await this.handleMockError(eventProvider);
logger_1.default.proxy.onMockResponseError(this.requestFilterRules[0], this.mock.error);
}
resolveInjectableUrl(url) {
return this.serverInfo.domain + url;
}
respondForSpecialPage() {
this.destRes = (0, create_special_page_response_1.default)();
this.buildContentInfo();
}
async fetchDestResBody() {
this.destResBody = await this._getDestResBody(this.destRes);
if (!this.temporaryCacheEntry)
return;
this._addTemporaryEntryToCache();
}
async pipeNonProcessedResponse() {
if (!this.serverInfo.cacheRequests) {
this.destRes.pipe(this.res);
return;
}
this.destResBody = await this._getDestResBody(this.destRes);
if (this.temporaryCacheEntry && this.destResBody.length < requestCache.MAX_SIZE_FOR_NON_PROCESSED_RESOURCE)
this._addTemporaryEntryToCache();
this.res.write(this.destResBody);
this.res.end();
}
createCacheEntry(res) {
if (requestCache.shouldCache(this) && !incoming_message_like_1.default.isIncomingMessageLike(res))
this.temporaryCacheEntry = requestCache.create(this.reqOpts, res);
}
async callOnResponseEventCallbackWithoutBodyForNonProcessedResource(ctx, onResponseEventDataWithoutBody) {
await Promise.all(onResponseEventDataWithoutBody.map(async (eventData) => {
await ctx.onRequestHookResponse(ctx.session.requestHookEventProvider, ctx.eventFactory, eventData.rule, eventData.opts);
}));
ctx.destRes.pipe(ctx.res);
}
async callOnResponseEventCallbackForMotModifiedResource(ctx) {
await Promise.all(ctx.onResponseEventData.map(async (eventData) => {
await ctx.onRequestHookResponse(ctx.session.requestHookEventProvider, ctx.eventFactory, eventData.rule, eventData.opts);
}));
ctx.res.end();
}
async callOnResponseEventCallbackWithBodyForNonProcessedRequest(ctx, onResponseEventDataWithBody) {
const destResBodyCollectorStream = new stream_1.PassThrough();
ctx.destRes.pipe(destResBodyCollectorStream);
(0, promisify_stream_1.default)(destResBodyCollectorStream).then(async (data) => {
ctx.saveNonProcessedDestResBody(data);
await Promise.all(onResponseEventDataWithBody.map(async (eventData) => {
await ctx.onRequestHookResponse(ctx.session.requestHookEventProvider, ctx.eventFactory, eventData.rule, eventData.opts);
}));
(0, buffer_1.toReadableStream)(data).pipe(ctx.res);
});
}
}
exports.default = RequestPipelineContext;module.exports = exports.default;