diff --git a/server/src/diagnostics/handlers/coreHandlers.ts b/server/src/diagnostics/handlers/coreHandlers.ts index 502febb..ffcada0 100644 --- a/server/src/diagnostics/handlers/coreHandlers.ts +++ b/server/src/diagnostics/handlers/coreHandlers.ts @@ -9,9 +9,11 @@ import RelateTagHandler from './relateHandler'; import ShorthandModifierHandler from './shorthandModifierHandler'; import StatamicVersionHandler from './statamicVersionHandler'; import TagsThatErrorHandler from './tagsThatErrorHandler'; +import UrlInputHandler from './urlInputHandler'; const CoreHandlers: IDiagnosticsHandler[] = [ DataDumpHandler, + UrlInputHandler, MixedModifierHandler, ModifierRuntimeTypeHandler, DoubleColonHandler, diff --git a/server/src/diagnostics/handlers/urlInputHandler.ts b/server/src/diagnostics/handlers/urlInputHandler.ts new file mode 100644 index 0000000..5110899 --- /dev/null +++ b/server/src/diagnostics/handlers/urlInputHandler.ts @@ -0,0 +1,58 @@ +import { AntlersError, ErrrorLevel } from '../../runtime/errors/antlersError'; +import { AntlersErrorCodes } from '../../runtime/errors/antlersErrorCodes'; +import { AntlersNode } from '../../runtime/nodes/abstractNode'; +import { intersect } from '../../runtime/utilities/arrayHelpers'; +import { IDiagnosticsHandler } from '../diagnosticsHandler'; +import ModifierManager from '../../antlers/modifierManager'; + +const UrlInputHandler: IDiagnosticsHandler = { + checkNode(node: AntlersNode) { + const tagName = node.getTagName().toLowerCase(), + errors: AntlersError[] = []; + if (tagName == 'get' || tagName == 'post') { + const overlap = intersect(ModifierManager.instance?.getModifierNames() ?? [], node.modifiers.modifierNames); + + // Return early if the developer is using custom modifiers. These may sanitize. + if (overlap.length == 0 && node.modifiers.modifierNames.length > 0 && + (node.modifiers.modifierNames.length == 1 && ModifierManager?.instance?.hasMacro(node.modifiers.modifierNames[0])) == false) { + return errors; + } + + let foundSanitizeInMacro = false; + + for (let i = 0; i < node.modifiers.modifierNames.length; i++) { + const name = node.modifiers.modifierNames[i]; + + if (ModifierManager.instance?.hasMacro(name)) { + const macro = ModifierManager.instance?.getMacro(name); + + if (macro != null) { + for (let j = 0; j < macro.modifiers.length; j++) { + const macroModifier = macro.modifiers[j]; + + if (macroModifier.name == 'sanitize') { + foundSanitizeInMacro = true; + break; + } + } + } + } + } + + if (foundSanitizeInMacro == true) { return errors; } + + if (node.modifiers.modifierNames.includes('sanitize') == false) { + errors.push(AntlersError.makeSyntaxError( + AntlersErrorCodes.LINT_URL_VARIABLE_WITHOUT_SANITIZE, + node, + 'Request variables can be controlled by the end user, and should be sanitized', + ErrrorLevel.Warning + )); + } + } + + return errors; + } +} + +export default UrlInputHandler; diff --git a/server/src/runtime/errors/antlersErrorCodes.ts b/server/src/runtime/errors/antlersErrorCodes.ts index eb50980..8879d98 100644 --- a/server/src/runtime/errors/antlersErrorCodes.ts +++ b/server/src/runtime/errors/antlersErrorCodes.ts @@ -157,4 +157,5 @@ export class AntlersErrorCodes { static readonly LINT_INTERLEAVED_TAG_PAIRS = 'ANTLR_516'; static readonly LINT_MISSING_REQUIRED_PARAMETER = 'ANTLR_517'; static readonly LINT_POSSIBLE_PARTIAL_RECURSION = 'ANTLR_518'; + static readonly LINT_URL_VARIABLE_WITHOUT_SANITIZE = 'ANTLR_519'; } \ No newline at end of file diff --git a/server/src/runtime/parser/languageParser.ts b/server/src/runtime/parser/languageParser.ts index aeb6e36..c4ffe13 100644 --- a/server/src/runtime/parser/languageParser.ts +++ b/server/src/runtime/parser/languageParser.ts @@ -15,6 +15,7 @@ export class LanguageParser { private pathParser: PathParser = new PathParser(); protected tokens: AbstractNode[] = []; + private createdVariables: VariableNode[] = []; private createdModifierChains: ModifierChainNode[] = []; private createdLanguageOperators: LanguageOperatorConstruct[] = []; private createdArrays: ArrayNode[] = []; @@ -62,6 +63,10 @@ export class LanguageParser { return this.createdArrays; } + public getCreatedVariables(): VariableNode[] { + return this.createdVariables; + } + public getRuntimeAssignments(): StaticTracedAssignment[] { return this.tracedAssignments; } @@ -347,6 +352,7 @@ export class LanguageParser { variableNode.refId = node.refId; variableNode.modifierChain = node.modifierChain; variableNode.index = node.index; + this.createdVariables.push(variableNode); return variableNode; } @@ -418,6 +424,7 @@ export class LanguageParser { NodeHelpers.mergeVarRight(left, right); newNodes.push(right); + this.createdVariables.push(right); this.mergedVariablePaths.push(right); this.mergedComponents.set(node, right); @@ -444,7 +451,7 @@ export class LanguageParser { NodeHelpers.mergeVarContentLeft(node.sourceTerminator + node.value + node.sourceTerminator, node, left); NodeHelpers.mergeVarContentLeft(right.name, right, left); - + this.createdVariables.push(left); newNodes.push(left); this.mergedVariablePaths.push(left); this.mergedComponents.set(node, left); @@ -511,6 +518,7 @@ export class LanguageParser { varNodeWrap.content = operator.content; varNodeWrap.name = operator.content; varNodeWrap.originalAbstractNode = operator; + this.createdVariables.push(varNodeWrap); return varNodeWrap; } @@ -2218,6 +2226,8 @@ export class LanguageParser { varNode.endPosition = node.endPosition; varNode.modifierChain = node.modifierChain; varNode.originalAbstractNode = node; + this.createdVariables.push(varNode); + newNodes.push(varNode); } else { newNodes.push(node);