Skip to content

Commit 97bbbec

Browse files
[6.x] Block methods in Antlers by default (#14042)
1 parent d9fa1ca commit 97bbbec

File tree

10 files changed

+229
-29
lines changed

10 files changed

+229
-29
lines changed

src/Providers/ViewServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ private function registerAntlers()
103103
$runtimeConfig->guardedContentTagPatterns = config('statamic.antlers.guardedContentTags', []);
104104
$runtimeConfig->guardedContentModifiers = config('statamic.antlers.guardedContentModifiers', []);
105105
$runtimeConfig->allowPhpInUserContent = config('statamic.antlers.allowPhpInContent', false);
106+
$runtimeConfig->allowMethodsInUserContent = config('statamic.antlers.allowMethodsInContent', false);
106107

107108
$runtimeConfig->guardedContentVariablePatterns = array_merge(
108109
$runtimeConfig->guardedVariablePatterns,

src/View/Antlers/Language/Errors/AntlersErrorCodes.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,5 @@ class AntlersErrorCodes
132132
const TYPE_UNEXPECTED_CHARACTER_WHILE_PARSING_SHORTHAND_PARAMETER = 'ANTLR_132';
133133
const TYPE_UNEXPECTED_EOI_PARSING_SHORTHAND_PARAMETER = 'ANTLR_133';
134134
const TYPE_DIRECTIVE_MISSING_ARGUMENTS = 'ANTLR_134';
135+
const RUNTIME_METHOD_CALL_USER_CONTENT = 'ANTLR_135';
135136
}

src/View/Antlers/Language/Runtime/GlobalRuntimeState.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,13 @@ public static function mergeTagRuntimeAssignments($assignments)
184184
*/
185185
public static $allowPhpInContent = false;
186186

187+
/**
188+
* Controls if method invocations are evaluated in user content.
189+
*
190+
* @var bool
191+
*/
192+
public static $allowMethodsInContent = false;
193+
187194
/**
188195
* Maintains a list of all field prefixes that have been encountered.
189196
*
@@ -264,6 +271,9 @@ public static function resetGlobalState()
264271
self::$yieldCount = 0;
265272
self::$yieldStacks = [];
266273
self::$abandonedNodes = [];
274+
self::$isEvaluatingUserData = false;
275+
self::$isEvaluatingData = false;
276+
self::$userContentEvalState = null;
267277

268278
StackReplacementManager::clearStackState();
269279
LiteralReplacementManager::resetLiteralState();

src/View/Antlers/Language/Runtime/NodeProcessor.php

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2196,21 +2196,27 @@ public function reduce($processNodes)
21962196

21972197
if ($val instanceof Value) {
21982198
if ($val->shouldParseAntlers()) {
2199+
$prevIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
2200+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
21992201
GlobalRuntimeState::$isEvaluatingUserData = true;
22002202
GlobalRuntimeState::$isEvaluatingData = true;
22012203
GlobalRuntimeState::$userContentEvalState = [
22022204
$val,
22032205
$node,
22042206
];
22052207

2206-
$val = $val->antlersValue($this->antlersParser, $this->getActiveData());
2207-
GlobalRuntimeState::$userContentEvalState = null;
2208-
GlobalRuntimeState::$isEvaluatingUserData = false;
2209-
GlobalRuntimeState::$isEvaluatingData = false;
2208+
try {
2209+
$val = $val->antlersValue($this->antlersParser, $this->getActiveData());
2210+
} finally {
2211+
GlobalRuntimeState::$userContentEvalState = null;
2212+
GlobalRuntimeState::$isEvaluatingUserData = $prevIsEvaluatingUserData;
2213+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
2214+
}
22102215
} else {
2216+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
22112217
GlobalRuntimeState::$isEvaluatingData = true;
22122218
$val = $val->value();
2213-
GlobalRuntimeState::$isEvaluatingData = false;
2219+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
22142220
}
22152221
}
22162222

@@ -2708,9 +2714,13 @@ protected function runModifier($modifier, $parameters, $data, $context = [])
27082714
return $data->value();
27092715
}
27102716

2717+
$prevIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
27112718
GlobalRuntimeState::$isEvaluatingUserData = true;
2712-
$value = $data->antlersValue($this->antlersParser, $context);
2713-
GlobalRuntimeState::$isEvaluatingUserData = false;
2719+
try {
2720+
$value = $data->antlersValue($this->antlersParser, $context);
2721+
} finally {
2722+
GlobalRuntimeState::$isEvaluatingUserData = $prevIsEvaluatingUserData;
2723+
}
27142724

27152725
try {
27162726
return Modify::value($value)->context($context)->$modifier($parameters)->fetch();

src/View/Antlers/Language/Runtime/PathDataManager.php

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -985,12 +985,14 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta
985985
$reductionValue = array_pop($reductionStack);
986986

987987
if ($reductionValue instanceof Value) {
988+
$prevIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
989+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
988990
GlobalRuntimeState::$isEvaluatingUserData = true;
989991
GlobalRuntimeState::$isEvaluatingData = true;
990992
$augmented = RuntimeValues::getValue($reductionValue);
991993
$augmented = self::guardRuntimeReturnValue($augmented);
992-
GlobalRuntimeState::$isEvaluatingUserData = false;
993-
GlobalRuntimeState::$isEvaluatingData = false;
994+
GlobalRuntimeState::$isEvaluatingUserData = $prevIsEvaluatingUserData;
995+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
994996

995997
if (! $isPair) {
996998
return $augmented;
@@ -1000,45 +1002,52 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta
10001002

10011003
continue;
10021004
} elseif ($reductionValue instanceof Values) {
1005+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10031006
GlobalRuntimeState::$isEvaluatingData = true;
10041007
$reductionStack[] = $reductionValue->toArray();
1005-
GlobalRuntimeState::$isEvaluatingData = false;
1008+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10061009

10071010
continue;
10081011
} elseif ($reductionValue instanceof \Statamic\Entries\Collection) {
1012+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10091013
GlobalRuntimeState::$isEvaluatingData = true;
10101014
$reductionStack[] = RuntimeValues::resolveWithRuntimeIsolation($reductionValue);
1011-
GlobalRuntimeState::$isEvaluatingData = false;
1015+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10121016

10131017
continue;
10141018
} elseif ($reductionValue instanceof ArrayableString) {
1019+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10151020
GlobalRuntimeState::$isEvaluatingData = true;
10161021
$reductionStack[] = $reductionValue->toArray();
1017-
GlobalRuntimeState::$isEvaluatingData = false;
1022+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10181023

10191024
continue;
10201025
} elseif ($reductionValue instanceof Augmentable) {
10211026
// Avoids resolving augmented data "too early".
10221027
if ($reduceBuildersAndAugmentables) {
1028+
$prevIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
1029+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10231030
GlobalRuntimeState::$isEvaluatingUserData = true;
10241031
GlobalRuntimeState::$isEvaluatingData = true;
10251032
$augmented = RuntimeValues::resolveWithRuntimeIsolation($reductionValue);
10261033
$augmented = self::guardRuntimeReturnValue($augmented);
1027-
GlobalRuntimeState::$isEvaluatingUserData = false;
1028-
GlobalRuntimeState::$isEvaluatingData = false;
1034+
GlobalRuntimeState::$isEvaluatingUserData = $prevIsEvaluatingUserData;
1035+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10291036
$reductionStack[] = $augmented;
10301037
} else {
10311038
return $reductionValue;
10321039
}
10331040

10341041
continue;
10351042
} elseif ($reductionValue instanceof Collection) {
1043+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10361044
GlobalRuntimeState::$isEvaluatingData = true;
10371045
$reductionStack[] = $reductionValue->all();
1038-
GlobalRuntimeState::$isEvaluatingData = false;
1046+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10391047

10401048
continue;
10411049
} elseif ($reductionValue instanceof Model) {
1050+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10421051
GlobalRuntimeState::$isEvaluatingData = true;
10431052
$data = $reductionValue->toArray();
10441053

@@ -1055,19 +1064,21 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta
10551064
}
10561065

10571066
$reductionStack[] = $data;
1058-
GlobalRuntimeState::$isEvaluatingData = false;
1067+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10591068

10601069
continue;
10611070
} elseif ($reductionValue instanceof Arrayable) {
1071+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10621072
GlobalRuntimeState::$isEvaluatingData = true;
10631073
$reductionStack[] = $reductionValue->toArray();
1064-
GlobalRuntimeState::$isEvaluatingData = false;
1074+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10651075

10661076
continue;
10671077
} elseif ($reductionValue instanceof Builder && $reduceBuildersAndAugmentables) {
1078+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10681079
GlobalRuntimeState::$isEvaluatingData = true;
10691080
$reductionStack[] = $reductionValue->get();
1070-
GlobalRuntimeState::$isEvaluatingData = false;
1081+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
10711082

10721083
continue;
10731084
}
@@ -1089,6 +1100,8 @@ public static function reduce($value, $isPair = true, $reduceBuildersAndAugmenta
10891100
*/
10901101
public static function reduceForAntlers($value, Parser $parser, $data, $isPair = true)
10911102
{
1103+
$prevIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
1104+
$prevIsEvaluatingData = GlobalRuntimeState::$isEvaluatingData;
10921105
GlobalRuntimeState::$isEvaluatingUserData = true;
10931106
GlobalRuntimeState::$isEvaluatingData = true;
10941107

@@ -1101,20 +1114,14 @@ public static function reduceForAntlers($value, Parser $parser, $data, $isPair =
11011114
}
11021115

11031116
if ($value instanceof Value) {
1104-
GlobalRuntimeState::$isEvaluatingUserData = true;
1105-
11061117
if (! $isPair) {
11071118
$returnValue = $value->antlersValue($parser, $data);
11081119
} else {
11091120
$returnValue = self::reduce($value->antlersValue($parser, $data));
11101121
}
11111122
$returnValue = self::guardRuntimeReturnValue($returnValue);
1112-
1113-
GlobalRuntimeState::$isEvaluatingUserData = false;
11141123
} elseif ($value instanceof Values) {
1115-
GlobalRuntimeState::$isEvaluatingUserData = true;
11161124
$returnValue = $value->toArray();
1117-
GlobalRuntimeState::$isEvaluatingUserData = false;
11181125
} else {
11191126
if (! $isPair) {
11201127
if (is_array($value)) {
@@ -1129,8 +1136,8 @@ public static function reduceForAntlers($value, Parser $parser, $data, $isPair =
11291136
}
11301137
}
11311138

1132-
GlobalRuntimeState::$isEvaluatingUserData = false;
1133-
GlobalRuntimeState::$isEvaluatingData = false;
1139+
GlobalRuntimeState::$isEvaluatingUserData = $prevIsEvaluatingUserData;
1140+
GlobalRuntimeState::$isEvaluatingData = $prevIsEvaluatingData;
11341141

11351142
return $returnValue;
11361143
}

src/View/Antlers/Language/Runtime/RuntimeConfiguration.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,16 @@ class RuntimeConfiguration
109109
*/
110110
public $allowPhpInUserContent = false;
111111

112+
/**
113+
* Indicates if method invocations should be evaluated in user content.
114+
*
115+
* When disabled, method calls like object:method() within
116+
* fields with antlers:true will be blocked.
117+
*
118+
* @var bool
119+
*/
120+
public $allowMethodsInUserContent = false;
121+
112122
/**
113123
* Registers a new Antlers preparser callback.
114124
*

src/View/Antlers/Language/Runtime/RuntimeParser.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ public function __construct(DocumentParser $documentParser, NodeProcessor $nodeP
139139
public function setRuntimeConfiguration(RuntimeConfiguration $configuration)
140140
{
141141
GlobalRuntimeState::$allowPhpInContent = $configuration->allowPhpInUserContent;
142+
GlobalRuntimeState::$allowMethodsInContent = $configuration->allowMethodsInUserContent;
142143
GlobalRuntimeState::$throwErrorOnAccessViolation = $configuration->throwErrorOnAccessViolation;
143144
GlobalRuntimeState::$bannedVarPaths = $configuration->guardedVariablePatterns;
144145
GlobalRuntimeState::$bannedContentVarPaths = $configuration->guardedContentVariablePatterns;

src/View/Antlers/Language/Runtime/Sandbox/Environment.php

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Exception;
77
use Illuminate\Support\Arr;
88
use Illuminate\Support\Collection;
9+
use Illuminate\Support\Facades\Log;
910
use Illuminate\Support\MessageBag;
1011
use Illuminate\Support\ViewErrorBag;
1112
use Statamic\Contracts\Query\Builder;
@@ -890,6 +891,27 @@ public function process($nodes)
890891

891892
continue;
892893
} elseif ($currentNode instanceof MethodInvocationNode) {
894+
if (GlobalRuntimeState::$isEvaluatingUserData && ! GlobalRuntimeState::$allowMethodsInContent) {
895+
array_pop($stack);
896+
897+
if (GlobalRuntimeState::$throwErrorOnAccessViolation) {
898+
throw ErrorFactory::makeRuntimeError(
899+
AntlersErrorCodes::RUNTIME_METHOD_CALL_USER_CONTENT,
900+
$currentNode,
901+
'Method invocation in user content.'
902+
);
903+
} else {
904+
Log::warning('Method call evaluated in user content.', [
905+
'file' => GlobalRuntimeState::$currentExecutionFile,
906+
'trace' => GlobalRuntimeState::$templateFileStack,
907+
]);
908+
}
909+
910+
$stack[] = null;
911+
912+
continue;
913+
}
914+
893915
$leftNode = array_pop($stack);
894916

895917
if ($leftNode == null) {
@@ -1369,22 +1391,26 @@ private function adjustValue($value, $originalNode)
13691391
private function checkForFieldValue($value, $hasModifiers = false, $modifierChain = null)
13701392
{
13711393
if ($value instanceof Value) {
1394+
$prevIsEvaluatingUserData = GlobalRuntimeState::$isEvaluatingUserData;
13721395
GlobalRuntimeState::$isEvaluatingUserData = true;
13731396
if ($value->shouldParseAntlers()) {
13741397
if (! $hasModifiers || ($modifierChain != null && $modifierChain[0]->nameNode->name != 'raw')) {
13751398
GlobalRuntimeState::$userContentEvalState = [
13761399
$value,
13771400
$this->nodeProcessor->getActiveNode(),
13781401
];
1379-
$value = $value->antlersValue($this->nodeProcessor->getAntlersParser(), $this->data);
1380-
GlobalRuntimeState::$userContentEvalState = null;
1402+
try {
1403+
$value = $value->antlersValue($this->nodeProcessor->getAntlersParser(), $this->data);
1404+
} finally {
1405+
GlobalRuntimeState::$userContentEvalState = null;
1406+
}
13811407
}
13821408
} else {
13831409
if (! $hasModifiers) {
13841410
$value = $value->value();
13851411
}
13861412
}
1387-
GlobalRuntimeState::$isEvaluatingUserData = false;
1413+
GlobalRuntimeState::$isEvaluatingUserData = $prevIsEvaluatingUserData;
13881414
}
13891415

13901416
return $value;

tests/Antlers/ParserTestCase.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ protected function setUp(): void
4646
parent::setUp();
4747

4848
GlobalRuntimeState::resetGlobalState();
49+
GlobalRuntimeState::$throwErrorOnAccessViolation = false;
50+
GlobalRuntimeState::$allowPhpInContent = false;
51+
GlobalRuntimeState::$allowMethodsInContent = false;
4952

5053
$this->setupTestBlueprintAndFields();
5154

0 commit comments

Comments
 (0)