diff options
Diffstat (limited to 'devtools/client/netmonitor/src/utils/filter-text-utils.js')
-rw-r--r-- | devtools/client/netmonitor/src/utils/filter-text-utils.js | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/devtools/client/netmonitor/src/utils/filter-text-utils.js b/devtools/client/netmonitor/src/utils/filter-text-utils.js new file mode 100644 index 0000000000..911e70e4bd --- /dev/null +++ b/devtools/client/netmonitor/src/utils/filter-text-utils.js @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +"use strict"; + +const { + FILTER_FLAGS, + SUPPORTED_HTTP_CODES, +} = require("resource://devtools/client/netmonitor/src/constants.js"); +const { + getFormattedIPAndPort, + getRequestPriorityAsText, +} = require("resource://devtools/client/netmonitor/src/utils/format-utils.js"); +const { + getUnicodeUrl, +} = require("resource://devtools/client/shared/unicode-url.js"); +const { + getUrlBaseName, +} = require("resource://devtools/client/netmonitor/src/utils/request-utils.js"); + +/* + The function `parseFilters` is from: + https://github.com/ChromeDevTools/devtools-frontend/ + + front_end/network/FilterSuggestionBuilder.js#L138-L163 + Commit f340aefd7ec9b702de9366a812288cfb12111fce +*/ + +function parseFilters(query) { + const flags = []; + const text = []; + const parts = query.split(/\s+/); + + for (const part of parts) { + if (!part) { + continue; + } + const colonIndex = part.indexOf(":"); + if (colonIndex === -1) { + const isNegative = part.startsWith("-"); + // Stores list of HTTP codes that starts with value of lastToken + const filteredStatusCodes = SUPPORTED_HTTP_CODES.filter(item => { + item = isNegative ? item.substr(1) : item; + return item.toLowerCase().startsWith(part.toLowerCase()); + }); + + if (filteredStatusCodes.length) { + flags.push({ + type: "status-code", // a standard key before a colon + value: isNegative ? part.substring(1) : part, + isNegative, + }); + continue; + } + + // Value of lastToken is just text that does not correspond to status codes + text.push(part); + continue; + } + let key = part.substring(0, colonIndex); + const negative = key.startsWith("-"); + if (negative) { + key = key.substring(1); + } + if (!FILTER_FLAGS.includes(key)) { + text.push(part); + continue; + } + let value = part.substring(colonIndex + 1); + value = processFlagFilter(key, value); + flags.push({ + type: key, + value, + negative, + }); + } + + return { text, flags }; +} + +function processFlagFilter(type, value) { + switch (type) { + case "regexp": + return value; + case "size": + case "transferred": + case "larger-than": + case "transferred-larger-than": + let multiplier = 1; + if (value.endsWith("k")) { + multiplier = 1000; + value = value.substring(0, value.length - 1); + } else if (value.endsWith("m")) { + multiplier = 1000 * 1000; + value = value.substring(0, value.length - 1); + } + const quantity = Number(value); + if (isNaN(quantity)) { + return null; + } + return quantity * multiplier; + default: + return value.toLowerCase(); + } +} + +function isFlagFilterMatch(item, { type, value, negative }) { + if (value == null) { + return false; + } + + // Ensures when filter token is exactly a flag ie. "remote-ip:", all values are shown + if (value.length < 1) { + return true; + } + + let match = true; + let { responseCookies = { cookies: [] } } = item; + responseCookies = responseCookies.cookies || responseCookies; + + const matchers = { + "status-code": () => + item.status && item.status.toString().startsWith(value), + method: () => item.method.toLowerCase() === value, + protocol: () => { + const protocol = item.httpVersion; + return typeof protocol === "string" + ? protocol.toLowerCase().includes(value) + : false; + }, + domain: () => item.urlDetails.host.toLowerCase().includes(value), + "remote-ip": () => { + const data = getFormattedIPAndPort(item.remoteAddress, item.remotePort); + return data ? data.toLowerCase().includes(value) : false; + }, + "has-response-header": () => { + if (typeof item.responseHeaders === "object") { + const { headers } = item.responseHeaders; + return headers.findIndex(h => h.name.toLowerCase() === value) > -1; + } + return false; + }, + cause: () => { + const causeType = item.cause.type; + return typeof causeType === "string" + ? causeType.toLowerCase().includes(value) + : false; + }, + initiator: () => { + const initiator = item.cause.lastFrame + ? getUrlBaseName(item.cause.lastFrame.filename) + + ":" + + item.cause.lastFrame.lineNumber + : ""; + return typeof initiator === "string" + ? initiator.toLowerCase().includes(value) + : !value; + }, + transferred: () => { + if (item.fromCache) { + return false; + } + return isSizeMatch(value, item.transferredSize); + }, + size: () => isSizeMatch(value, item.contentSize), + "larger-than": () => item.contentSize > value, + "transferred-larger-than": () => { + if (item.fromCache) { + return false; + } + return item.transferredSize > value; + }, + "mime-type": () => { + if (!item.mimeType) { + return false; + } + return item.mimeType.includes(value); + }, + is: () => { + if (value === "from-cache" || value === "cached") { + return item.fromCache || item.status === "304"; + } + if (value === "running") { + return !item.status; + } + return match; + }, + scheme: () => item.urlDetails.scheme === value, + regexp: () => { + try { + const pattern = new RegExp(value); + return pattern.test(item.url); + } catch (e) { + return false; + } + }, + priority: () => + getRequestPriorityAsText(item.priority).toLowerCase() == value, + "set-cookie-domain": () => { + if (responseCookies.length) { + const { host } = item.urlDetails; + const i = responseCookies.findIndex(c => { + const domain = c.hasOwnProperty("domain") ? c.domain : host; + return domain.includes(value); + }); + return i > -1; + } + return false; + }, + "set-cookie-name": () => + responseCookies.findIndex(c => c.name.toLowerCase().includes(value)) > -1, + "set-cookie-value": () => + responseCookies.findIndex(c => c.value.toLowerCase().includes(value)) > + -1, + }; + + const matcher = matchers[type]; + if (matcher) { + match = matcher(); + } + + return negative ? !match : match; +} + +function isSizeMatch(value, size) { + return value >= size - size / 10 && value <= size + size / 10; +} + +function isTextFilterMatch({ url }, text) { + const lowerCaseUrl = getUnicodeUrl(url).toLowerCase(); + let lowerCaseText = text.toLowerCase(); + const textLength = text.length; + // Support negative filtering + if (text.startsWith("-") && textLength > 1) { + lowerCaseText = lowerCaseText.substring(1, textLength); + return !lowerCaseUrl.includes(lowerCaseText); + } + + // no text is a positive match + return !text || lowerCaseUrl.includes(lowerCaseText); +} + +function isFreetextMatch(item, text) { + if (!text) { + return true; + } + + const filters = parseFilters(text); + let match = true; + + for (const textFilter of filters.text) { + match = match && isTextFilterMatch(item, textFilter); + } + + for (const flagFilter of filters.flags) { + match = match && isFlagFilterMatch(item, flagFilter); + } + + return match; +} + +module.exports = { + isFreetextMatch, +}; |