Page MenuHomePhorge

Deprecation notice at PhabricatorEditorURIEngine.php:283
Open, NormalPublic

Description

Steps to reproduce
  1. Unclear, wait for deprecation warning for appear…?

See also Report Warnings.

/config/ Version table
BinaryVersionPath
php8.1.2-1ubuntu2.15apache2handler
...skipped, the log line 2 has arcanist/phorge version...

1preg_replace(): Passing null to parameter #3 ($subject) of type array|string is deprecated
2arcanist(head=stable, ref.master=5bc53cfe53d0, ref.stable=ca72430916af), phorge(head=stable, ref.master=1b49165ddd16, ref.stable=d519f75dfdee, custom=1)
3#0 preg_replace(string, string, NULL) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:283]
4#1 PhabricatorEditorURIEngine::escapeToken(NULL) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:144]
5#2 PhabricatorEditorURIEngine::newURITokensForRepository() called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:128]
6#3 PhabricatorEditorURIEngine::getURITokensForRepository(string) called at [<phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php:84]
7#4 PhabricatorEditorURIEngine::getURITokensForPath(string) called at [<phorge>/src/applications/differential/view/DifferentialChangesetDetailView.php:346]
8#5 DifferentialChangesetDetailView::getEditorURITemplate() called at [<phorge>/src/applications/differential/view/DifferentialChangesetDetailView.php:256]
9#6 DifferentialChangesetDetailView::render() called at [<phorge>/src/applications/differential/view/DifferentialChangesetListView.php:240]
10#7 DifferentialChangesetListView::render() called at [<phorge>/src/view/AphrontView.php:222]
11#8 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
12#9 phutil_escape_html(DifferentialChangesetListView) called at [<phorge>/src/infrastructure/markup/render.php:139]
13#10 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
14#11 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
15#12 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
16#13 phutil_tag(string, array, array) called at [<phorge>/src/view/phui/PHUITwoColumnView.php:242]
17#14 PHUITwoColumnView::buildFooter() called at [<phorge>/src/view/phui/PHUITwoColumnView.php:123]
18#15 PHUITwoColumnView::getTagContent() called at [<phorge>/src/view/AphrontTagView.php:161]
19#16 AphrontTagView::render() called at [<phorge>/src/view/AphrontView.php:222]
20#17 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
21#18 phutil_escape_html(PHUITwoColumnView) called at [<phorge>/src/infrastructure/markup/render.php:139]
22#19 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:139]
23#20 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
24#21 phutil_tag(string, array, array) called at [<phorge>/src/view/formation/PHUIFormationContentView.php:18]
25#22 PHUIFormationContentView::render() called at [<phorge>/src/view/AphrontView.php:222]
26#23 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
27#24 phutil_escape_html(PHUIFormationContentView) called at [<phorge>/src/infrastructure/markup/render.php:139]
28#25 phutil_escape_html(array) called at [<phorge>/src/infrastructure/markup/render.php:97]
29#26 phutil_tag(string, array, array) called at [<phorge>/src/view/formation/PHUIFormationView.php:67]
30#27 PHUIFormationView::render() called at [<phorge>/src/view/AphrontView.php:222]
31#28 AphrontView::producePhutilSafeHTML() called at [<phorge>/src/infrastructure/markup/render.php:115]
32#29 phutil_escape_html(PHUIFormationView) called at [<phorge>/src/infrastructure/markup/render.php:171]
33#30 phutil_implode_html(string, array) called at [<phorge>/src/view/page/PhabricatorBarePageView.php:58]
34#31 PhabricatorBarePageView::willRenderPage() called at [<phorge>/src/view/page/PhabricatorStandardPageView.php:217]
35#32 PhabricatorStandardPageView::willRenderPage() called at [<phorge>/src/view/page/AphrontPageView.php:46]
36#33 AphrontPageView::render() called at [<phorge>/src/view/page/PhabricatorStandardPageView.php:919]
37#34 PhabricatorStandardPageView::produceAphrontResponse() called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:722]
38#35 AphrontApplicationConfiguration::produceResponse(AphrontRequest, PhabricatorStandardPageView) called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:299]
39#36 AphrontApplicationConfiguration::processRequest(AphrontRequest, PhutilDeferredLog, AphrontPHPHTTPSink, MultimeterControl) called at [<phorge>/src/aphront/configuration/AphrontApplicationConfiguration.php:204]
40#37 AphrontApplicationConfiguration::runHTTPRequest(AphrontPHPHTTPSink) called at [<phorge>/webroot/index.php:35]

Related Objects

Event Timeline

revi triaged this task as Normal priority.
revi created this object in space S1 Public.

@revi: Could you please double-check and confirm that the line 144 in <phorge>/src/infrastructure/editor/PhabricatorEditorURIEngine.php is
'n' => $this->escapeToken($repository->getRepositorySlug()),
in your installation? Thanks in advance!

1<?php
2
3final class PhabricatorEditorURIEngine
4 extends Phobject {
5
6 private $viewer;
7 private $repository;
8 private $pattern;
9 private $rawTokens;
10 private $repositoryTokens;
11
12 public static function newForViewer(PhabricatorUser $viewer) {
13 if (!$viewer->isLoggedIn()) {
14 return null;
15 }
16
17 $pattern = $viewer->getUserSetting(PhabricatorEditorSetting::SETTINGKEY);
18
19 if ($pattern === null || trim($pattern) === '') {
20 return null;
21 }
22
23 $engine = id(new self())
24 ->setViewer($viewer)
25 ->setPattern($pattern);
26
27 // If there's a problem with the pattern,
28
29 try {
30 $engine->validatePattern();
31 } catch (PhabricatorEditorURIParserException $ex) {
32 return null;
33 }
34
35 return $engine;
36 }
37
38 public function setViewer(PhabricatorUser $viewer) {
39 $this->viewer = $viewer;
40 return $this;
41 }
42
43 public function getViewer() {
44 return $this->viewer;
45 }
46
47 public function setRepository(PhabricatorRepository $repository) {
48 $this->repository = $repository;
49 return $this;
50 }
51
52 public function getRepository() {
53 return $this->repository;
54 }
55
56 public function setPattern($pattern) {
57 $this->pattern = $pattern;
58 return $this;
59 }
60
61 public function getPattern() {
62 return $this->pattern;
63 }
64
65 public function validatePattern() {
66 $this->getRawURITokens();
67 return true;
68 }
69
70 public function getURIForPath($path, $line) {
71 $tokens = $this->getURITokensForRepository($path);
72
73 $variables = array(
74 'f' => $this->escapeToken($path),
75 'l' => $this->escapeToken($line),
76 );
77
78 $tokens = $this->newTokensWithVariables($tokens, $variables);
79
80 return $this->newStringFromTokens($tokens);
81 }
82
83 public function getURITokensForPath($path) {
84 $tokens = $this->getURITokensForRepository($path);
85
86 $variables = array(
87 'f' => $this->escapeToken($path),
88 );
89
90 return $this->newTokensWithVariables($tokens, $variables);
91 }
92
93 public static function getVariableDefinitions() {
94 return array(
95 'f' => array(
96 'name' => pht('File Name'),
97 'example' => pht('path/to/source.c'),
98 ),
99 'l' => array(
100 'name' => pht('Line Number'),
101 'example' => '777',
102 ),
103 'n' => array(
104 'name' => pht('Repository Short Name'),
105 'example' => 'arcanist',
106 ),
107 'd' => array(
108 'name' => pht('Repository ID'),
109 'example' => '42',
110 ),
111 'p' => array(
112 'name' => pht('Repository PHID'),
113 'example' => 'PHID-REPO-abcdefghijklmnopqrst',
114 ),
115 'r' => array(
116 'name' => pht('Repository Callsign'),
117 'example' => 'XYZ',
118 ),
119 '%' => array(
120 'name' => pht('Literal Percent Symbol'),
121 'example' => '%',
122 ),
123 );
124 }
125
126 private function getURITokensForRepository() {
127 if (!$this->repositoryTokens) {
128 $this->repositoryTokens = $this->newURITokensForRepository();
129 }
130
131 return $this->repositoryTokens;
132 }
133
134 private function newURITokensForRepository() {
135 $tokens = $this->getRawURITokens();
136
137 $repository = $this->getRepository();
138 if (!$repository) {
139 throw new PhutilInvalidStateException('setRepository');
140 }
141
142 $variables = array(
143 'r' => $this->escapeToken($repository->getCallsign()),
144 'n' => $this->escapeToken($repository->getRepositorySlug()),
145 'd' => $this->escapeToken($repository->getID()),
146 'p' => $this->escapeToken($repository->getPHID()),
147 );
148
149 return $this->newTokensWithVariables($tokens, $variables);
150 }
151
152 private function getRawURITokens() {
153 if (!$this->rawTokens) {
154 $this->rawTokens = $this->newRawURITokens();
155 }
156 return $this->rawTokens;
157 }
158
159 private function newRawURITokens() {
160 $raw_pattern = $this->getPattern();
161 $raw_tokens = self::newPatternTokens($raw_pattern);
162
163 $variable_definitions = self::getVariableDefinitions();
164
165 foreach ($raw_tokens as $token) {
166 if ($token['type'] !== 'variable') {
167 continue;
168 }
169
170 $value = $token['value'];
171
172 if (isset($variable_definitions[$value])) {
173 continue;
174 }
175
176 throw new PhabricatorEditorURIParserException(
177 pht(
178 'Editor pattern "%s" is invalid: the pattern contains an '.
179 'unrecognized variable ("%s"). Use "%%%%" to encode a literal '.
180 'percent symbol.',
181 $raw_pattern,
182 '%'.$value));
183 }
184
185 $variables = array(
186 '%' => '%',
187 );
188
189 $tokens = $this->newTokensWithVariables($raw_tokens, $variables);
190
191 $first_literal = null;
192 if ($tokens) {
193 foreach ($tokens as $token) {
194 if ($token['type'] === 'literal') {
195 $first_literal = $token['value'];
196 }
197 break;
198 }
199
200 if ($first_literal === null) {
201 throw new PhabricatorEditorURIParserException(
202 pht(
203 'Editor pattern "%s" is invalid: the pattern must begin with '.
204 'a valid editor protocol, but begins with a variable. This is '.
205 'very sneaky and also very forbidden.',
206 $raw_pattern));
207 }
208 }
209
210 $uri = new PhutilURI($first_literal);
211 $editor_protocol = $uri->getProtocol();
212
213 if (!$editor_protocol) {
214 throw new PhabricatorEditorURIParserException(
215 pht(
216 'Editor pattern "%s" is invalid: the pattern must begin with '.
217 'a valid editor protocol, but does not begin with a recognized '.
218 'protocol string.',
219 $raw_pattern));
220 }
221
222 $allowed_key = 'uri.allowed-editor-protocols';
223 $allowed_protocols = PhabricatorEnv::getEnvConfig($allowed_key);
224 if (empty($allowed_protocols[$editor_protocol])) {
225 throw new PhabricatorEditorURIParserException(
226 pht(
227 'Editor pattern "%s" is invalid: the pattern must begin with '.
228 'a valid editor protocol, but the protocol "%s://" is not allowed.',
229 $raw_pattern,
230 $editor_protocol));
231 }
232
233 return $tokens;
234 }
235
236 private function newTokensWithVariables(array $tokens, array $variables) {
237 // Replace all "variable" tokens that we have replacements for with
238 // the literal value.
239 foreach ($tokens as $key => $token) {
240 $type = $token['type'];
241
242 if ($type == 'variable') {
243 $variable = $token['value'];
244 if (isset($variables[$variable])) {
245 $tokens[$key] = array(
246 'type' => 'literal',
247 'value' => $variables[$variable],
248 );
249 }
250 }
251 }
252
253 // Now, merge sequences of adjacent "literal" tokens into a single token.
254 $last_literal = null;
255 foreach ($tokens as $key => $token) {
256 $is_literal = ($token['type'] === 'literal');
257
258 if (!$is_literal) {
259 $last_literal = null;
260 continue;
261 }
262
263 if ($last_literal !== null) {
264 $tokens[$key]['value'] =
265 $tokens[$last_literal]['value'].$token['value'];
266 unset($tokens[$last_literal]);
267 }
268
269 $last_literal = $key;
270 }
271
272 $tokens = array_values($tokens);
273
274 return $tokens;
275 }
276
277 private function escapeToken($token) {
278 // Paths are user controlled, so a clever user could potentially make
279 // editor links do surprising things with paths containing "/../".
280
281 // Find anything that looks like "/../" and mangle it.
282
283 $token = preg_replace('((^|/)\.\.(/|\z))', '\1dot-dot\2', $token);
284
285 return phutil_escape_uri($token);
286 }
287
288 private function newStringFromTokens(array $tokens) {
289 $result = array();
290
291 foreach ($tokens as $token) {
292 $token_type = $token['type'];
293 $token_value = $token['value'];
294
295 $is_literal = ($token_type === 'literal');
296 if (!$is_literal) {
297 throw new Exception(
298 pht(
299 'Editor pattern token list can not be converted into a string: '.
300 'it still contains a non-literal token ("%s", of type "%s").',
301 $token_value,
302 $token_type));
303 }
304
305 $result[] = $token_value;
306 }
307
308 $result = implode('', $result);
309
310 return $result;
311 }
312
313 public static function newPatternTokens($raw_pattern) {
314 $token_positions = array();
315
316 $len = strlen($raw_pattern);
317
318 for ($ii = 0; $ii < $len; $ii++) {
319 $c = $raw_pattern[$ii];
320 if ($c === '%') {
321 if (!isset($raw_pattern[$ii + 1])) {
322 throw new PhabricatorEditorURIParserException(
323 pht(
324 'Editor pattern "%s" is invalid: the final character in a '.
325 'pattern may not be an unencoded percent symbol ("%%"). '.
326 'Use "%%%%" to encode a literal percent symbol.',
327 $raw_pattern));
328 }
329
330 $token_positions[] = $ii;
331 $ii++;
332 }
333 }
334
335 // Add a final marker past the end of the string, so we'll collect any
336 // trailing literal bytes.
337 $token_positions[] = $len;
338
339 $tokens = array();
340 $cursor = 0;
341 foreach ($token_positions as $pos) {
342 $token_len = ($pos - $cursor);
343
344 if ($token_len > 0) {
345 $tokens[] = array(
346 'type' => 'literal',
347 'value' => substr($raw_pattern, $cursor, $token_len),
348 );
349 }
350
351 $cursor = $pos;
352
353 if ($cursor < $len) {
354 $tokens[] = array(
355 'type' => 'variable',
356 'value' => substr($raw_pattern, $cursor + 1, 1),
357 );
358 }
359
360 $cursor = $pos + 2;
361 }
362
363 return $tokens;
364 }
365
366}

$ git show
commit d519f75dfdee61bb109468aa708c47f53f3e5128 (HEAD -> stable, origin/stable)
Author: Mark Jervelund <EMAIL-REDACTED>
Date:   Mon Feb 12 11:51:24 2024 +0100

(Commit message skipped)