/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* eslint-disable no-unused-vars */ "use strict"; /** * Search within specified resource. Note that this function runs * within a worker thread. */ function searchInResource(resource, query, modifiers) { const results = []; if (resource.url) { results.push( findMatches(resource, query, modifiers, { key: "url", label: "Url", type: "url", panel: "headers", }) ); } if (resource.responseHeaders) { results.push( findMatches(resource, query, modifiers, { key: "responseHeaders.headers", type: "responseHeaders", panel: "headers", }) ); } if (resource.requestHeaders) { results.push( findMatches(resource, query, modifiers, { key: "requestHeaders.headers", type: "requestHeaders", panel: "headers", }) ); } if (resource.requestHeadersFromUploadStream) { results.push( findMatches(resource, query, modifiers, { key: "requestHeadersFromUploadStream.headers", type: "requestHeadersFromUploadStream", panel: "headers", }) ); } if (resource.responseCookies) { let key = "responseCookies"; if (resource.responseCookies.cookies) { key = "responseCookies.cookies"; } results.push( findMatches(resource, query, modifiers, { key, type: "responseCookies", panel: "cookies", }) ); } if (resource.requestCookies) { let key = "requestCookies"; if (resource.requestCookies.cookies) { key = "requestCookies.cookies"; } results.push( findMatches(resource, query, modifiers, { key, type: "requestCookies", panel: "cookies", }) ); } if (resource.responseContent) { results.push( findMatches(resource, query, modifiers, { key: "responseContent.content.text", type: "responseContent", panel: "response", }) ); } if (resource.requestPostData) { results.push( findMatches(resource, query, modifiers, { key: "requestPostData.postData.text", type: "requestPostData", panel: "request", }) ); } return getResults(results, resource); } /** * Concatenates all results * @param results * @returns {*[]} */ function getResults(results, resource) { const tempResults = [].concat.apply([], results); // Generate unique result keys tempResults.forEach((result, index) => { result.key = index; result.parentResource = resource; }); return tempResults; } function find(query, modifiers, source) { const { caseSensitive } = modifiers; const value = caseSensitive ? source : source.toLowerCase(); const q = caseSensitive ? query : query.toLowerCase(); return value.includes(q); } /** * Find query matches in arrays, objects and strings. * @param resource * @param query * @param modifiers * @param data * @returns {*[]|[]|Array|*} */ function findMatches(resource, query, modifiers, data) { if (!resource || !query || !modifiers || !data) { return []; } const resourceValue = getValue(data.key, resource); const resourceType = getType(resourceValue); if (resource.hasOwnProperty("name") && resource.hasOwnProperty("value")) { return searchInProperties(query, modifiers, resource, data); } switch (resourceType) { case "string": return searchInText(query, modifiers, resourceValue, data); case "array": return searchInArray(query, modifiers, resourceValue, data); case "object": return searchInObject(query, modifiers, resourceValue, data); default: return []; } } function searchInProperties(query, modifiers, obj, data) { const { name, value } = obj; const match = { ...data, }; if (find(query, modifiers, name)) { match.label = name; } if (find(query, modifiers, name) || find(query, modifiers, value)) { match.value = value; match.startIndex = value.indexOf(query); return match; } return []; } /** * Get type of resource - deals with arrays as well. * @param resource * @returns {*} */ function getType(resource) { return Array.isArray(resource) ? "array" : typeof resource; } /** * Function returns the value of a key, included nested keys. * @param path * @param obj * @returns {*} */ function getValue(path, obj) { const properties = Array.isArray(path) ? path : path.split("."); return properties.reduce((prev, curr) => prev?.[curr], obj); } /** * Search text for specific string and return all matches found * @param query * @param modifiers * @param text * @param data * @returns {*} */ function searchInText(query, modifiers, text, data) { const { type } = data; const lines = text.split(/\r\n|\r|\n/); const matches = []; // iterate through each line lines.forEach((curr, i) => { const { caseSensitive } = modifiers; const flags = caseSensitive ? "g" : "gi"; const regexQuery = RegExp( caseSensitive ? query : query.toLowerCase(), flags ); const lineMatches = []; let singleMatch; while ((singleMatch = regexQuery.exec(lines[i])) !== null) { const startIndex = regexQuery.lastIndex; lineMatches.push(startIndex); } if (lineMatches.length !== 0) { const line = i + 1; const match = { ...data, label: type !== "url" ? line + "" : "Url", line, startIndex: lineMatches, }; match.value = lineMatches.length === 1 ? getTruncatedValue(lines[i], query, lineMatches[0]) : lines[i]; matches.push(match); } }); return matches.length === 0 ? [] : matches; } /** * Search for query in array. * Iterates through each array item and handles item based on type. * @param query * @param modifiers * @param arr * @param data * @returns {*[]} */ function searchInArray(query, modifiers, arr, data) { const { key, label } = data; const matches = arr.map((match, i) => findMatches(match, query, modifiers, { ...data, label: match.hasOwnProperty("name") ? match.name : label, key: key + ".[" + i + "]", }) ); return getResults(matches); } /** * Return query match and up to 50 characters on left and right. * (50) + [matched query] + (50) * @param value * @param query * @param startIndex * @returns {*} */ function getTruncatedValue(value, query, startIndex) { const valueSize = value.length; const indexEnd = startIndex + query.length; if (valueSize < 100 + query.length) { return value; } const start = value.substring(startIndex, startIndex - 50); const end = value.substring(indexEnd, indexEnd + 50); return start + end; } /** * Iterates through object, including nested objects, returns all * @param query * @param modifiers * @param obj * @param data * @returns {*|[]} */ function searchInObject(query, modifiers, obj, data) { const matches = data.hasOwnProperty("collector") ? data.collector : []; const { caseSensitive } = modifiers; for (const objectKey in obj) { const objectKeyType = getType(obj[objectKey]); // if the value is an object, send to search in object if (objectKeyType === "object" && Object.keys(obj[objectKey].length > 1)) { searchInObject(query, obj[objectKey], { ...data, collector: matches, }); continue; } const value = !caseSensitive ? obj[objectKey].toLowerCase() : obj[objectKey]; const key = !caseSensitive ? objectKey.toLowerCase() : objectKey; const q = !caseSensitive ? query.toLowerCase() : query; if ((objectKeyType === "string" && value.includes(q)) || key.includes(q)) { const match = { ...data, }; const startIndex = value.indexOf(q); match.label = objectKey; match.startIndex = startIndex; match.value = getTruncatedValue(obj[objectKey], query, startIndex); matches.push(match); } } return matches; }