search.js (8419B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 /* eslint-disable no-unused-vars */ 5 6 "use strict"; 7 8 /** 9 * Search within specified resource. Note that this function runs 10 * within a worker thread. 11 */ 12 function searchInResource(resource, query, modifiers) { 13 const results = []; 14 15 if (resource.url) { 16 results.push( 17 findMatches(resource, query, modifiers, { 18 key: "url", 19 label: "Url", 20 type: "url", 21 panel: "headers", 22 }) 23 ); 24 } 25 26 if (resource.earlyHintsResponseHeaders) { 27 results.push( 28 findMatches(resource, query, modifiers, { 29 key: "earlyHintsResponseHeaders.headers", 30 type: "earlyHintsResponseHeaders", 31 panel: "headers", 32 }) 33 ); 34 } 35 36 if (resource.responseHeaders) { 37 results.push( 38 findMatches(resource, query, modifiers, { 39 key: "responseHeaders.headers", 40 type: "responseHeaders", 41 panel: "headers", 42 }) 43 ); 44 } 45 46 if (resource.requestHeaders) { 47 results.push( 48 findMatches(resource, query, modifiers, { 49 key: "requestHeaders.headers", 50 type: "requestHeaders", 51 panel: "headers", 52 }) 53 ); 54 } 55 56 if (resource.requestHeadersFromUploadStream) { 57 results.push( 58 findMatches(resource, query, modifiers, { 59 key: "requestHeadersFromUploadStream.headers", 60 type: "requestHeadersFromUploadStream", 61 panel: "headers", 62 }) 63 ); 64 } 65 66 if (resource.responseCookies) { 67 let key = "responseCookies"; 68 69 if (resource.responseCookies.cookies) { 70 key = "responseCookies.cookies"; 71 } 72 73 results.push( 74 findMatches(resource, query, modifiers, { 75 key, 76 type: "responseCookies", 77 panel: "cookies", 78 }) 79 ); 80 } 81 82 if (resource.requestCookies) { 83 let key = "requestCookies"; 84 85 if (resource.requestCookies.cookies) { 86 key = "requestCookies.cookies"; 87 } 88 89 results.push( 90 findMatches(resource, query, modifiers, { 91 key, 92 type: "requestCookies", 93 panel: "cookies", 94 }) 95 ); 96 } 97 98 if (resource.responseContent) { 99 results.push( 100 findMatches(resource, query, modifiers, { 101 key: "responseContent.content.text", 102 type: "responseContent", 103 panel: "response", 104 }) 105 ); 106 } 107 108 if (resource.requestPostData) { 109 results.push( 110 findMatches(resource, query, modifiers, { 111 key: "requestPostData.postData.text", 112 type: "requestPostData", 113 panel: "request", 114 }) 115 ); 116 } 117 118 return getResults(results, resource); 119 } 120 121 /** 122 * Concatenates all results 123 * 124 * @param results 125 * @returns {*[]} 126 */ 127 function getResults(results, resource) { 128 const tempResults = [].concat.apply([], results); 129 130 // Generate unique result keys 131 tempResults.forEach((result, index) => { 132 result.key = index; 133 result.parentResource = resource; 134 }); 135 136 return tempResults; 137 } 138 139 function find(query, modifiers, source) { 140 const { caseSensitive } = modifiers; 141 const value = caseSensitive ? source : source.toLowerCase(); 142 const q = caseSensitive ? query : query.toLowerCase(); 143 144 return value.includes(q); 145 } 146 147 /** 148 * Find query matches in arrays, objects and strings. 149 * 150 * @param resource 151 * @param query 152 * @param modifiers 153 * @param data 154 * @returns {*[]|[]|Array|*} 155 */ 156 function findMatches(resource, query, modifiers, data) { 157 if (!resource || !query || !modifiers || !data) { 158 return []; 159 } 160 161 const resourceValue = getValue(data.key, resource); 162 const resourceType = getType(resourceValue); 163 164 if (resource.hasOwnProperty("name") && resource.hasOwnProperty("value")) { 165 return searchInProperties(query, modifiers, resource, data); 166 } 167 168 switch (resourceType) { 169 case "string": 170 return searchInText(query, modifiers, resourceValue, data); 171 case "array": 172 return searchInArray(query, modifiers, resourceValue, data); 173 case "object": 174 return searchInObject(query, modifiers, resourceValue, data); 175 default: 176 return []; 177 } 178 } 179 180 function searchInProperties(query, modifiers, obj, data) { 181 const { name, value } = obj; 182 const match = { 183 ...data, 184 }; 185 186 if (find(query, modifiers, name)) { 187 match.label = name; 188 } 189 190 if (find(query, modifiers, name) || find(query, modifiers, value)) { 191 match.value = value; 192 match.startIndex = value.indexOf(query); 193 194 return match; 195 } 196 197 return []; 198 } 199 200 /** 201 * Get type of resource - deals with arrays as well. 202 * 203 * @param resource 204 * @returns {*} 205 */ 206 function getType(resource) { 207 return Array.isArray(resource) ? "array" : typeof resource; 208 } 209 210 /** 211 * Function returns the value of a key, included nested keys. 212 * 213 * @param path 214 * @param obj 215 * @returns {*} 216 */ 217 function getValue(path, obj) { 218 const properties = Array.isArray(path) ? path : path.split("."); 219 return properties.reduce((prev, curr) => prev?.[curr], obj); 220 } 221 222 /** 223 * Search text for specific string and return all matches found 224 * 225 * @param query 226 * @param modifiers 227 * @param text 228 * @param data 229 * @returns {*} 230 */ 231 function searchInText(query, modifiers, text, data) { 232 const { type } = data; 233 const lines = text.split(/\r\n|\r|\n/); 234 const matches = []; 235 236 // iterate through each line 237 lines.forEach((curr, i) => { 238 const { caseSensitive } = modifiers; 239 const flags = caseSensitive ? "g" : "gi"; 240 const regexQuery = RegExp( 241 caseSensitive ? query : query.toLowerCase(), 242 flags 243 ); 244 const lineMatches = []; 245 let singleMatch; 246 247 while ((singleMatch = regexQuery.exec(lines[i])) !== null) { 248 const startIndex = singleMatch.index; 249 lineMatches.push(startIndex); 250 } 251 252 if (lineMatches.length !== 0) { 253 const line = i + 1; 254 const match = { 255 ...data, 256 label: type !== "url" ? line + "" : "Url", 257 line, 258 startIndex: lineMatches, 259 }; 260 261 match.value = 262 lineMatches.length === 1 263 ? getTruncatedValue(lines[i], query, lineMatches[0]) 264 : lines[i]; 265 266 matches.push(match); 267 } 268 }); 269 270 return matches.length === 0 ? [] : matches; 271 } 272 273 /** 274 * Search for query in array. 275 * Iterates through each array item and handles item based on type. 276 * 277 * @param query 278 * @param modifiers 279 * @param arr 280 * @param data 281 * @returns {*[]} 282 */ 283 function searchInArray(query, modifiers, arr, data) { 284 const { key, label } = data; 285 const matches = arr.map((match, i) => 286 findMatches(match, query, modifiers, { 287 ...data, 288 label: match.hasOwnProperty("name") ? match.name : label, 289 key: key + ".[" + i + "]", 290 }) 291 ); 292 293 return getResults(matches); 294 } 295 296 /** 297 * Return query match and up to 50 characters on left and right. 298 * (50) + [matched query] + (50) 299 * 300 * @param value 301 * @param query 302 * @param startIndex 303 * @returns {*} 304 */ 305 function getTruncatedValue(value, query, startIndex) { 306 const valueSize = value.length; 307 const endIndex = startIndex + query.length; 308 309 if (valueSize < 100 + query.length) { 310 return value; 311 } 312 313 return value.substring(startIndex - 50, endIndex + 50); 314 } 315 316 /** 317 * Iterates through object, including nested objects, returns all 318 * 319 * @param query 320 * @param modifiers 321 * @param obj 322 * @param data 323 * @returns {*|[]} 324 */ 325 function searchInObject(query, modifiers, obj, data) { 326 const matches = data.hasOwnProperty("collector") ? data.collector : []; 327 const { caseSensitive } = modifiers; 328 329 for (const objectKey in obj) { 330 const objectKeyType = getType(obj[objectKey]); 331 332 // if the value is an object, send to search in object 333 if (objectKeyType === "object" && Object.keys(obj[objectKey].length > 1)) { 334 searchInObject(query, obj[objectKey], { 335 ...data, 336 collector: matches, 337 }); 338 339 continue; 340 } 341 342 const value = !caseSensitive 343 ? obj[objectKey].toLowerCase() 344 : obj[objectKey]; 345 const key = !caseSensitive ? objectKey.toLowerCase() : objectKey; 346 const q = !caseSensitive ? query.toLowerCase() : query; 347 348 if ((objectKeyType === "string" && value.includes(q)) || key.includes(q)) { 349 const match = { 350 ...data, 351 }; 352 353 const startIndex = value.indexOf(q); 354 355 match.label = objectKey; 356 match.startIndex = startIndex; 357 match.value = getTruncatedValue(obj[objectKey], query, startIndex); 358 359 matches.push(match); 360 } 361 } 362 363 return matches; 364 }