UNPKG

@uyu423/pinpoint-node-agent

Version:

Pinpoint node agent provided by NAVER (Personalized version)

346 lines (303 loc) 11.5 kB
/** * Pinpoint Node.js Agent * Copyright 2020-present NAVER Corp. * Apache License v2.0 */ 'use strict' const log = require('../utils/logger') // https://github.com/spring-projects/spring-framework/blob/master/spring-core/src/main/java/org/springframework/util/AntPathMatcher.java const DEFAULT_PATH_SEPARATOR = "/" const EMPTY_STRING_ARRAY = [] class AntPathMatcher { constructor(config) { this.pathSeparator = DEFAULT_PATH_SEPARATOR this.pathSeparatorPatternCache = new PathSeparatorPatternCache(DEFAULT_PATH_SEPARATOR) this.setCachePatterns(true) this.tokenizedPatternCache = new Map() this.stringMatcherCache = new Map() this.setTrimTokens(false) if (config) { const patterns = config.traceExclusionUrlPatterns if (Array.isArray(patterns)) { this.patterns = Array.from(patterns) } if (config.traceExclusionUrlCacheSize) { this.cachePathSize = config.traceExclusionUrlCacheSize this.pathMatchedCache = new Map() } } } setCachePatterns(cachePatterns) { this.cachePatterns = cachePatterns; } deactivatePatternCache() { this.cachePatterns = false this.tokenizedPatternCache.clear() this.stringMatcherCache.clear() } setTrimTokens(trimTokens) { this.trimTokens = trimTokens } match(pattern, path) { try { return this.doMatch(pattern, path, true) } catch (error) { if (error && error.message) { log.error('match error: ' + error.message) } return false } } doMatch(pattern, path, fullMatch) { if (path == null || path.startsWith(this.pathSeparator) != pattern.startsWith(this.pathSeparator)) { return false } const pattDirs = this.tokenizePattern(pattern) const pathDirs = this.tokenizePath(path) let pattIdxStart = 0 let pattIdxEnd = pattDirs.length - 1 let pathIdxStart = 0 let pathIdxEnd = pathDirs.length - 1 // Match all elements up to the first ** while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { let pattDir = pattDirs[pattIdxStart]; if ("**" === pattDir) { break; } if (!this.matchStrings(pattDir, pathDirs[pathIdxStart])) { return false; } pattIdxStart++; pathIdxStart++; } if (pathIdxStart > pathIdxEnd) { // Path is exhausted, only match if rest of pattern is * or **'s if (pattIdxStart > pattIdxEnd) { return (pattern.endsWith(this.pathSeparator) == path.endsWith(this.pathSeparator)); } if (!fullMatch) { return true; } if (pattIdxStart == pattIdxEnd && pattDirs[pattIdxStart] === "*" && path.endsWith(this.pathSeparator)) { return true; } for (let i = pattIdxStart; i <= pattIdxEnd; i++) { if (pattDirs[i] !== "**") { return false; } } return true; } else if (pattIdxStart > pattIdxEnd) { // String not exhausted, but pattern is. Failure. return false; } else if (!fullMatch && "**" === pattDirs[pattIdxStart]) { // Path start definitely matches due to "**" part in pattern. return true; } // up to last '**' while (pattIdxStart <= pattIdxEnd && pathIdxStart <= pathIdxEnd) { const pattDir = pattDirs[pattIdxEnd]; if (pattDir === "**") { break; } if (!this.matchStrings(pattDir, pathDirs[pathIdxEnd])) { return false; } pattIdxEnd--; pathIdxEnd--; } if (pathIdxStart > pathIdxEnd) { // String is exhausted for (let i = pattIdxStart; i <= pattIdxEnd; i++) { if (pattDirs[i] !== "**") { return false; } } return true; } while (pattIdxStart != pattIdxEnd && pathIdxStart <= pathIdxEnd) { let patIdxTmp = -1; for (let i = pattIdxStart + 1; i <= pattIdxEnd; i++) { if (pattDirs[i] === "**") { patIdxTmp = i; break; } } if (patIdxTmp == pattIdxStart + 1) { // '**/**' situation, so skip one pattIdxStart++; continue; } // Find the pattern between padIdxStart & padIdxTmp in str between // strIdxStart & strIdxEnd const patLength = (patIdxTmp - pattIdxStart - 1); const strLength = (pathIdxEnd - pathIdxStart + 1); let foundIdx = -1; strLoop: for (let i = 0; i <= strLength - patLength; i++) { for (let j = 0; j < patLength; j++) { const subPat = pattDirs[pattIdxStart + j + 1]; const subStr = pathDirs[pathIdxStart + i + j]; if (!this.matchStrings(subPat, subStr)) { continue strLoop; } } foundIdx = pathIdxStart + i; break; } if (foundIdx == -1) { return false; } pattIdxStart = patIdxTmp; pathIdxStart = foundIdx + patLength; } for (let i = pattIdxStart; i <= pattIdxEnd; i++) { if (pattDirs[i] !== "**") { return false; } } return true } matchPath(path) { // guard if (!this.patterns || !path) { return false } if (this.pathMatchedCache) { const cacheMatched = this.pathMatchedCache.get(path) if (typeof cacheMatched !== 'undefined') { this.pathMatchedCache.delete(path) this.pathMatchedCache.set(path, cacheMatched) this.deleteLowestHitPath() return cacheMatched } } // Double Not: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_NOT const matched = Boolean(this.patterns.find(pattern => this.match(pattern, path))) if (this.pathMatchedCache) { this.pathMatchedCache.set(path, matched) this.deleteLowestHitPath() } return matched } deleteLowestHitPath() { if (this.pathMatchedCache.size > this.cachePathSize) { const iterator = this.pathMatchedCache[Symbol.iterator]() const [key, value] = iterator.next().value this.pathMatchedCache.delete(key) } } tokenizePattern(pattern) { let tokenized if (this.cachePatterns) { tokenized = this.tokenizedPatternCache.get(pattern) } if (!tokenized) { tokenized = this.tokenizePath(pattern) if (this.cachePatterns) { this.tokenizedPatternCache.set(pattern, tokenized) } } return tokenized } tokenizePath(path) { return this.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true); } tokenizeToStringArray(str, delimiter, trimToken, ignoreEmptyTokens) { if (str == null) { return EMPTY_STRING_ARRAY; } return str.split(delimiter) .map(token => trimToken ? token.trim() : token) .filter(token => !ignoreEmptyTokens || token.length > 0) } matchStrings(pattern, str) { return this.getStringMatcher(pattern).matchStrings(str) } getStringMatcher(pattern) { let matcher = null if (this.cachePatterns) { matcher = this.stringMatcherCache.get(pattern); } if (matcher == null) { matcher = new AntPathStringMatcher(pattern); if (this.cachePatterns) { this.stringMatcherCache.set(pattern, matcher); } } return matcher; } } class PathSeparatorPatternCache { constructor(pathSeparator) { this.endsOnWildCard = pathSeparator + "*" this.endsOnDoubleWildCard = pathSeparator + "**" } getEndsOnWildCard() { return this.endsOnWildCard; } getEndsOnDoubleWildCard() { return this.endsOnDoubleWildCard; } } // reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions const GLOB_PATTERN = new RegExp("\\?|\\*|\\{((?:\\{[^/]+?}|[^/{}]|\\\\[{}])+?)}", "g") // https://www.regular-expressions.info/modifiers.html Spring source const DEFAULT_VARIABLE_PATTERN = "((?s).*)" is old // https://stackoverflow.com/questions/10927930/meaning-of-s-in-regex Turn on "dot matches newline" for the remainder of the regular expression. (Older regex flavors may turn it on for the entire regex.) // so dot matches newline no needs const DEFAULT_VARIABLE_PATTERN = "(.*)" class AntPathStringMatcher { constructor(pattern, caseSensitive = true) { this.rawPattern = pattern this.caseSensitive = caseSensitive let end = 0 const patternBuilder = [] let matcher while ((matcher = GLOB_PATTERN.exec(pattern)) !== null) { patternBuilder.push(quote(pattern, end, matcher.index)) const match = matcher[0] if ("?" === match) { patternBuilder.push('.') } else if ("*" === match) { patternBuilder.push('.*') } else if (match.startsWith("{") && match.endsWith("}")) { const colonIdx = match.indexOf(':'); if (colonIdx == -1) { patternBuilder.push(DEFAULT_VARIABLE_PATTERN) } else { const variablePattern = match.substring(colonIdx + 1, match.length() - 1); patternBuilder.push('(') patternBuilder.push(variablePattern) patternBuilder.push(')') const variableName = match.substring(1, colonIdx); } } end = GLOB_PATTERN.lastIndex } // No glob pattern was found, this is an exact String match if (end == 0) { this.exactMatch = true; this.pattern = null; } else { this.exactMatch = false; patternBuilder.push(quote(pattern, end, pattern.length)); this.pattern = new RegExp(`^${patternBuilder.join('')}$`, this.caseSensitive ? "g" : "gi") } } matchStrings(str) { if (this.exactMatch) { return this.caseSensitive ? this.rawPattern === str : this.rawPattern.toUpperCase() === str.toUpperCase(); } else if (this.pattern != null) { this.pattern.lastIndex = 0 return this.pattern.test(str) } return false; } } function quote(s, start, end) { if (start == end) { return ""; } return s.substring(start, end).replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string } module.exports = AntPathMatcher