tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }