Skip to content

Commit 9cd1557

Browse files
docs(Request): add/update/cleanup internal implementation docs
Signed-off-by: Josh <josh.t.richards@gmail.com>
1 parent 26b28ea commit 9cd1557

1 file changed

Lines changed: 88 additions & 66 deletions

File tree

lib/private/AppFramework/Http/Request.php

Lines changed: 88 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,24 @@ class Request implements \ArrayAccess, \Countable, IRequest {
4848
// Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
4949
public const USER_AGENT_ANDROID_MOBILE_CHROME = '#Android.*Chrome/[.0-9]*#';
5050
public const USER_AGENT_FREEBOX = '#^Mozilla/5\.0$#';
51+
5152
public const REGEX_LOCALHOST = '/^(127\.0\.0\.1|localhost|\[::1\])$/';
53+
54+
/**
55+
* Whether the raw PUT body stream has already been returned.
56+
*/
5257
private bool $isPutStreamContentAlreadySent = false;
58+
59+
/**
60+
* Internal request data store.
61+
*/
5362
protected array $items = [];
63+
64+
/**
65+
* Magic properties that are exposed directly from $items.
66+
*
67+
* @var list<string>
68+
*/
5469
protected array $allowedKeys = [
5570
'get',
5671
'post',
@@ -64,24 +79,27 @@ class Request implements \ArrayAccess, \Countable, IRequest {
6479
'requesttoken',
6580
];
6681

82+
/**
83+
* Whether request-body decoding has already been attempted.
84+
*/
6785
protected bool $contentDecoded = false;
86+
87+
/**
88+
* Deferred decoding error from the request body, if any.
89+
*/
6890
private ?\JsonException $decodingException = null;
6991

7092
/**
71-
* @param array $vars An associative array with the following optional values:
72-
* - array 'urlParams' the parameters which were matched from the URL
93+
* @param array $vars Associative request data with the following optional keys:
94+
* - array 'urlParams' route parameters extracted from the URL
7395
* - array 'get' the $_GET array
74-
* - array|string 'post' the $_POST array or JSON string
96+
* - array 'post' the $_POST array
7597
* - array 'files' the $_FILES array
7698
* - array 'server' the $_SERVER array
7799
* - array 'env' the $_ENV array
78100
* - array 'cookies' the $_COOKIE array
79-
* - string 'method' the request method (GET, POST etc)
80-
* - string|false 'requesttoken' the requesttoken or false when not available
81-
* @param IRequestId $requestId
82-
* @param IConfig $config
83-
* @param CsrfTokenManager|null $csrfTokenManager
84-
* @param string $inputStream
101+
* - string 'method' the HTTP request method, for example GET or POST
102+
* - string|false 'requesttoken' the request token, or false if unavailable
85103
* @see https://www.php.net/manual/en/reserved.variables.php
86104
*/
87105
public function __construct(
@@ -108,8 +126,16 @@ public function __construct(
108126
$this->items['params']
109127
);
110128
}
129+
111130
/**
131+
* Replaces the current URL parameters and merges them into the parameter set.
132+
*
133+
* URL parameters take precedence over previously merged values with the same
134+
* key.
135+
*
112136
* @param array $parameters
137+
*
138+
* @internal public only so it can be consumed by OC\AppFramework\App
113139
*/
114140
public function setUrlParameters(array $parameters) {
115141
$this->items['urlParams'] = $parameters;
@@ -120,42 +146,30 @@ public function setUrlParameters(array $parameters) {
120146
}
121147

122148
/**
123-
* Countable method
124-
* @return int
149+
* Returns the number of merged request parameters.
125150
*/
126151
#[\Override]
127152
public function count(): int {
128153
return \count($this->items['parameters']);
129154
}
130155

131156
/**
132-
* ArrayAccess methods
157+
* Whether a merged request parameter exists.
133158
*
134-
* Gives access to the combined GET, POST and urlParams arrays
159+
* ArrayAccess operates on the merged parameter set.
135160
*
136-
* Examples:
137-
*
138-
* $var = $request['myvar'];
139-
*
140-
* or
141-
*
142-
* if(!isset($request['myvar']) {
143-
* // Do something
144-
* }
145-
*
146-
* $request['myvar'] = 'something'; // This throws an exception.
147-
*
148-
* @param string $offset The key to lookup
149-
* @return boolean
161+
* @param string $offset Parameter name
162+
* @return bool
150163
*/
151164
#[\Override]
152165
public function offsetExists($offset): bool {
153166
return isset($this->items['parameters'][$offset]);
154167
}
155168

156169
/**
157-
* @see offsetExists
158-
* @param string $offset
170+
* Returns a merged request parameter value, or null if it is missing.
171+
*
172+
* @param string $offset Parameter name
159173
* @return mixed
160174
*/
161175
#[\Override]
@@ -165,7 +179,8 @@ public function offsetGet($offset) {
165179
}
166180

167181
/**
168-
* @see offsetExists
182+
* Request objects are immutable.
183+
*
169184
* @param string $offset
170185
* @param mixed $value
171186
*/
@@ -175,7 +190,8 @@ public function offsetSet($offset, $value): void {
175190
}
176191

177192
/**
178-
* @see offsetExists
193+
* Request objects are immutable.
194+
*
179195
* @param string $offset
180196
*/
181197
#[\Override]
@@ -184,7 +200,8 @@ public function offsetUnset($offset): void {
184200
}
185201

186202
/**
187-
* Magic property accessors
203+
* Request objects are immutable.
204+
*
188205
* @param string $name
189206
* @param mixed $value
190207
*/
@@ -193,17 +210,16 @@ public function __set($name, $value) {
193210
}
194211

195212
/**
196-
* Access request variables by method and name.
197-
* Examples:
213+
* Returns request data through magic property access.
198214
*
199-
* $request->post['myvar']; // Only look for POST variables
200-
* $request->myvar; or $request->{'myvar'}; or $request->{$myvar}
201-
* Looks in the combined GET, POST and urlParams array.
215+
* Named properties read from the merged parameter set. Method-specific
216+
* properties (`get`, `post`, `put`, `patch`) are only available for the
217+
* matching HTTP method and throw a \LogicException otherwise.
202218
*
203-
* If you access e.g. ->post but the current HTTP request method
204-
* is GET a \LogicException will be thrown.
219+
* Depending on the method and content type, `put` may return either parsed
220+
* parameters or a readable stream for the raw request body.
205221
*
206-
* @param string $name The key to look for.
222+
* @param string $name Property name
207223
* @throws \LogicException
208224
* @return mixed|null
209225
*/
@@ -238,6 +254,8 @@ public function __get($name) {
238254
}
239255

240256
/**
257+
* Whether a magic property is available.
258+
*
241259
* @param string $name
242260
* @return bool
243261
*/
@@ -249,6 +267,8 @@ public function __isset($name) {
249267
}
250268

251269
/**
270+
* Request objects are immutable.
271+
*
252272
* @param string $id
253273
*/
254274
public function __unset($id) {
@@ -312,18 +332,17 @@ public function getCookie(string $key) {
312332
}
313333

314334
/**
315-
* Returns the request body content.
335+
* Returns request body content for method-specific magic accessors.
316336
*
317-
* If the HTTP request method is PUT and the body
318-
* not application/x-www-form-urlencoded or application/json a stream
319-
* resource is returned, otherwise an array.
320-
*
321-
* @return array|string|resource The request body content or a resource to read the body stream.
337+
* For PUT requests with a non-empty body that is neither JSON nor
338+
* form-encoded, a readable stream resource for the raw body is returned.
339+
* Otherwise, parsed parameters are returned as an array.
322340
*
341+
* @return array|string|resource The request body content or a resource for the raw body stream
323342
* @throws \LogicException
324343
*/
325344
protected function getContent() {
326-
// If the content can't be parsed into an array then return a stream resource.
345+
// If the content cannot be parsed into parameters, return a raw body stream.
327346
if ($this->isPutStreamContent()) {
328347
if ($this->isPutStreamContentAlreadySent) {
329348
throw new \LogicException(
@@ -348,15 +367,21 @@ private function isPutStreamContent(): bool {
348367
}
349368

350369
/**
351-
* Attempt to decode the content and populate parameters
370+
* Decodes the request body, if applicable, and merges decoded parameters
371+
* into the parameter set.
372+
*
373+
* JSON-compatible content types are decoded from the input stream. For
374+
* non-GET and non-POST form-encoded requests, the input stream is parsed
375+
* into parameters. Decoding errors are stored and can later be rethrown via
376+
* throwDecodingExceptionIfAny().
352377
*/
353378
protected function decodeContent() {
354379
if ($this->contentDecoded) {
355380
return;
356381
}
357382
$params = [];
358383

359-
// 'application/json' and other JSON-related content types must be decoded manually.
384+
// JSON-compatible content types must be decoded manually.
360385
if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) {
361386
$content = file_get_contents($this->inputStream);
362387
if ($content !== '') {
@@ -372,8 +397,7 @@ protected function decodeContent() {
372397
$this->items['post'] = $params;
373398
}
374399
}
375-
// Handle application/x-www-form-urlencoded for methods other than GET
376-
// or post correctly
400+
// Handle form-encoded request bodies for methods other than GET and POST.
377401
} elseif ($this->method !== 'GET'
378402
&& $this->method !== 'POST'
379403
&& str_contains($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded')) {
@@ -426,7 +450,7 @@ public function passesCSRFCheck(): bool {
426450
}
427451

428452
/**
429-
* Whether the cookie checks are required
453+
* Whether cookie-based same-site checks are required for this request.
430454
*/
431455
private function cookieCheckRequired(): bool {
432456
if ($this->getHeader('OCS-APIREQUEST')) {
@@ -440,14 +464,14 @@ private function cookieCheckRequired(): bool {
440464
}
441465

442466
/**
443-
* Wrapper around session_get_cookie_params
467+
* Wrapper around session_get_cookie_params().
444468
*/
445469
public function getCookieParams(): array {
446470
return session_get_cookie_params();
447471
}
448472

449473
/**
450-
* Appends the __Host- prefix to the cookie if applicable
474+
* Returns the cookie name with the __Host- prefix applied when appropriate.
451475
*/
452476
protected function getProtectedCookieName(string $name): string {
453477
$cookieParams = $this->getCookieParams();
@@ -492,16 +516,19 @@ public function getId(): string {
492516
}
493517

494518
/**
495-
* Checks if given $remoteAddress matches any entry in the given array $trustedProxies.
496-
* For details regarding what "match" means, refer to `matchesTrustedProxy`.
497-
* @return boolean true if $remoteAddress matches any entry in $trustedProxies, false otherwise
519+
* Checks whether the given remote address matches one of the configured
520+
* trusted proxies.
521+
*
522+
* Invalid trusted proxy configuration is treated as non-matching.
523+
*
524+
* @return bool true if $remoteAddress matches a trusted proxy, false otherwise
498525
*/
499526
protected function isTrustedProxy($trustedProxies, $remoteAddress) {
500527
try {
501528
return IpUtils::checkIp($remoteAddress, $trustedProxies);
502529
} catch (\Throwable) {
503-
// We can not log to our log here as the logger is using `getRemoteAddress` which uses the function, so we would have a cyclic dependency
504-
// Reaching this line means `trustedProxies` is in invalid format.
530+
// Cannot log through the regular logger here because it may depend on
531+
// getRemoteAddress(), which would create a cyclic dependency.
505532
error_log('Nextcloud trustedProxies has malformed entries');
506533
return false;
507534
}
@@ -550,9 +577,6 @@ public function getRemoteAddress(): string {
550577
return $remoteAddress;
551578
}
552579

553-
/**
554-
* Check overwrite condition
555-
*/
556580
private function isOverwriteCondition(): bool {
557581
$regex = '/' . $this->config->getSystemValueString('overwritecondaddr', '') . '/';
558582
$remoteAddr = isset($this->server['REMOTE_ADDR']) ? $this->server['REMOTE_ADDR'] : '';
@@ -750,11 +774,9 @@ public function getServerHost(): string {
750774
}
751775

752776
/**
753-
* Returns the overwritehost setting from the config if set and
754-
* if the overwrite condition is met
777+
* Returns the overwritehost config value if configured and applicable.
755778
*
756-
* @return string|null overwritehost value or null if not defined or the defined condition
757-
* isn't met
779+
* @return string|null
758780
*/
759781
private function getOverwriteHost() {
760782
if ($this->config->getSystemValueString('overwritehost') !== '' && $this->isOverwriteCondition()) {

0 commit comments

Comments
 (0)