Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
utils.js203 linesDownload Raw Back to lib
1const {
2  URL_MATCHER,
3  TYPE_URL,
4  TYPE_REGEX,
5  TYPE_PATH,
6} = require('./matchers')
7
8/**
9 * creates a string of asterisks,
10 * this forces a minimum asterisk for security purposes
11 */
12const asterisk = (length = 0) => {
13  length = typeof length === 'string' ? length.length : length
14  if (length < 8) {
15    return '*'.repeat(8)
16  }
17  return '*'.repeat(length)
18}
19
20/**
21 * escapes all special regex chars
22 * @see https://stackoverflow.com/a/9310752
23 * @see https://github.com/tc39/proposal-regex-escaping
24 */
25const escapeRegExp = (text) => {
26  return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, `\\$&`)
27}
28
29/**
30 * provieds a regex "or" of the url versions of a string
31 */
32const urlEncodeRegexGroup = (value) => {
33  const decoded = decodeURIComponent(value)
34  const encoded = encodeURIComponent(value)
35  const union = [...new Set([encoded, decoded, value])].map(escapeRegExp).join('|')
36  return union
37}
38
39/**
40 * a tagged template literal that returns a regex ensures all variables are excaped
41 */
42const urlEncodeRegexTag = (strings, ...values) => {
43  let pattern = ''
44  for (let i = 0; i < values.length; i++) {
45    pattern += strings[i] + `(${urlEncodeRegexGroup(values[i])})`
46  }
47  pattern += strings[strings.length - 1]
48  return new RegExp(pattern)
49}
50
51/**
52 * creates a matcher for redacting url hostname
53 */
54const redactUrlHostnameMatcher = ({ hostname, replacement } = {}) => ({
55  type: TYPE_URL,
56  predicate: ({ url }) => url.hostname === hostname,
57  pattern: ({ url }) => {
58    return urlEncodeRegexTag`(^${url.protocol}//${url.username}:.+@)?${url.hostname}`
59  },
60  replacement: `$1${replacement || asterisk()}`,
61})
62
63/**
64 * creates a matcher for redacting url search / query parameter values
65 */
66const redactUrlSearchParamsMatcher = ({ param, replacement } = {}) => ({
67  type: TYPE_URL,
68  predicate: ({ url }) => url.searchParams.has(param),
69  pattern: ({ url }) => urlEncodeRegexTag`(${param}=)${url.searchParams.get(param)}`,
70  replacement: `$1${replacement || asterisk()}`,
71})
72
73/** creates a matcher for redacting the url password */
74const redactUrlPasswordMatcher = ({ replacement } = {}) => ({
75  type: TYPE_URL,
76  predicate: ({ url }) => url.password,
77  pattern: ({ url }) => urlEncodeRegexTag`(^${url.protocol}//${url.username}:)${url.password}`,
78  replacement: `$1${replacement || asterisk()}`,
79})
80
81const redactUrlReplacement = (...matchers) => (subValue) => {
82  try {
83    const url = new URL(subValue)
84    return redactMatchers(...matchers)(subValue, { url })
85  } catch (err) {
86    return subValue
87  }
88}
89
90/**
91 * creates a matcher / submatcher for urls, this function allows you to first
92 * collect all urls within a larger string and then pass those urls to a
93 * submatcher
94 *
95 * @example
96 * console.log("this will first match all urls, then pass those urls to the password patcher")
97 * redactMatchers(redactUrlMatcher(redactUrlPasswordMatcher()))
98 *
99 * @example
100 * console.log(
101 *   "this will assume you are passing in a string that is a url, and will redact the password"
102 * )
103 * redactMatchers(redactUrlPasswordMatcher())
104 *
105 */
106const redactUrlMatcher = (...matchers) => {
107  return {
108    ...URL_MATCHER,
109    replacement: redactUrlReplacement(...matchers),
110  }
111}
112
113const matcherFunctions = {
114  [TYPE_REGEX]: (matcher) => (value) => {
115    if (typeof value === 'string') {
116      value = value.replace(matcher.pattern, matcher.replacement)
117    }
118    return value
119  },
120  [TYPE_URL]: (matcher) => (value, ctx) => {
121    if (typeof value === 'string') {
122      try {
123        const url = ctx?.url || new URL(value)
124        const { predicate, pattern } = matcher
125        const predicateValue = predicate({ url })
126        if (predicateValue) {
127          value = value.replace(pattern({ url }), matcher.replacement)
128        }
129      } catch (_e) {
130        return value
131      }
132    }
133    return value
134  },
135  [TYPE_PATH]: (matcher) => (value, ctx) => {
136    const rawPath = ctx?.path
137    const path = rawPath.join('.').toLowerCase()
138    const { predicate, replacement } = matcher
139    const replace = typeof replacement === 'function' ? replacement : () => replacement
140    const shouldRun = predicate({ rawPath, path })
141    if (shouldRun) {
142      value = replace(value, { rawPath, path })
143    }
144    return value
145  },
146}
147
148/** converts a matcher to a function */
149const redactMatcher = (matcher) => {
150  return matcherFunctions[matcher.type](matcher)
151}
152
153/** converts a series of matchers to a function */
154const redactMatchers = (...matchers) => (value, ctx) => {
155  const flatMatchers = matchers.flat()
156  return flatMatchers.reduce((result, matcher) => {
157    const fn = (typeof matcher === 'function') ? matcher : redactMatcher(matcher)
158    return fn(result, ctx)
159  }, value)
160}
161
162/**
163 * replacement handler, keeping $1 (if it exists) and replacing the
164 * rest of the string with asterisks, maintaining string length
165 */
166const redactDynamicReplacement = () => (value, start) => {
167  if (typeof start === 'number') {
168    return asterisk(value)
169  }
170  return start + asterisk(value.substring(start.length).length)
171}
172
173/**
174 * replacement handler, keeping $1 (if it exists) and replacing the
175 * rest of the string with a fixed number of asterisks
176 */
177const redactFixedReplacement = (length) => (_value, start) => {
178  if (typeof start === 'number') {
179    return asterisk(length)
180  }
181  return start + asterisk(length)
182}
183
184const redactUrlPassword = (value, replacement) => {
185  return redactMatchers(redactUrlPasswordMatcher({ replacement }))(value)
186}
187
188module.exports = {
189  asterisk,
190  escapeRegExp,
191  urlEncodeRegexGroup,
192  urlEncodeRegexTag,
193  redactUrlHostnameMatcher,
194  redactUrlSearchParamsMatcher,
195  redactUrlPasswordMatcher,
196  redactUrlMatcher,
197  redactUrlReplacement,
198  redactDynamicReplacement,
199  redactFixedReplacement,
200  redactMatchers,
201  redactUrlPassword,
202}
203 
codekingpro/portable-devtools · Team Ai