├── .phpstorm.meta.php
├── composer.json
├── license.md
├── readme.md
└── src
├── Bridges
├── HttpDI
│ ├── HttpExtension.php
│ └── SessionExtension.php
└── HttpTracy
│ ├── SessionPanel.php
│ ├── dist
│ ├── panel.phtml
│ └── tab.phtml
│ ├── panel.latte
│ └── tab.latte
└── Http
├── Context.php
├── FileUpload.php
├── Helpers.php
├── IRequest.php
├── IResponse.php
├── Request.php
├── RequestFactory.php
├── Response.php
├── Session.php
├── SessionSection.php
├── Url.php
├── UrlImmutable.php
└── UrlScript.php
/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 | getUrl();
59 | echo $url; // https://nette.org/en/documentation?action=edit
60 | echo $url->getHost(); // nette.org
61 | ```
62 |
63 | Browsers do not send a fragment to the server, so `$url->getFragment()` will return an empty string.
64 |
65 | getQuery(string $key = null): string|array|null
66 | -----------------------------------------------
67 | Returns GET request parameters:
68 |
69 | ```php
70 | $all = $httpRequest->getQuery(); // array of all URL parameters
71 | $id = $httpRequest->getQuery('id'); // returns GET parameter 'id' (or null)
72 | ```
73 |
74 | getPost(string $key = null): string|array|null
75 | ----------------------------------------------
76 | Returns POST request parameters:
77 |
78 | ```php
79 | $all = $httpRequest->getPost(); // array of all POST parameters
80 | $id = $httpRequest->getPost('id'); // returns POST parameter 'id' (or null)
81 | ```
82 |
83 | getFile(string $key): Nette\Http\FileUpload|array|null
84 | ------------------------------------------------------
85 | Returns [upload](#Uploaded-Files) as object [Nette\Http\FileUpload](https://api.nette.org/3.0/Nette/Http/FileUpload.html):
86 |
87 | ```php
88 | $file = $httpRequest->getFile('avatar');
89 | if ($file->hasFile()) { // was any file uploaded?
90 | $file->getName(); // name of the file sent by user
91 | $file->getSanitizedName(); // the name without dangerous characters
92 | }
93 | ```
94 |
95 | getFiles(): array
96 | -----------------
97 | Returns tree of [upload files](#Uploaded-Files) in a normalized structure, with each leaf an instance of [Nette\Http\FileUpload](https://api.nette.org/3.0/Nette/Http/FileUpload.html):
98 |
99 | ```php
100 | $files = $httpRequest->getFiles();
101 | ```
102 |
103 | getCookie(string $key): string|array|null
104 | -----------------------------------------
105 | Returns a cookie or `null` if it does not exist.
106 |
107 | ```php
108 | $sessId = $httpRequest->getCookie('sess_id');
109 | ```
110 |
111 | getCookies(): array
112 | -------------------
113 | Returns all cookies:
114 |
115 | ```php
116 | $cookies = $httpRequest->getCookies();
117 | ```
118 |
119 | getMethod(): string
120 | -------------------
121 | Returns the HTTP method with which the request was made.
122 |
123 | ```php
124 | echo $httpRequest->getMethod(); // GET, POST, HEAD, PUT
125 | ```
126 |
127 | isMethod(string $method): bool
128 | ------------------------------
129 | Checks the HTTP method with which the request was made. The parameter is case-insensitive.
130 |
131 | ```php
132 | if ($httpRequest->isMethod('GET')) ...
133 | ```
134 |
135 | getHeader(string $header): ?string
136 | ----------------------------------
137 | Returns an HTTP header or `null` if it does not exist. The parameter is case-insensitive:
138 |
139 | ```php
140 | $userAgent = $httpRequest->getHeader('User-Agent');
141 | ```
142 |
143 | getHeaders(): array
144 | -------------------
145 | Returns all HTTP headers as associative array:
146 |
147 | ```php
148 | $headers = $httpRequest->getHeaders();
149 | echo $headers['Content-Type'];
150 | ```
151 |
152 | getReferer(): ?Nette\Http\UrlImmutable
153 | --------------------------------------
154 | What URL did the user come from? Beware, it is not reliable at all.
155 |
156 | isSecured(): bool
157 | -----------------
158 | Is the connection encrypted (HTTPS)? You may need to [set up a proxy|configuring#HTTP proxy] for proper functionality.
159 |
160 | isSameSite(): bool
161 | ------------------
162 | Is the request coming from the same (sub) domain and is initiated by clicking on a link?
163 |
164 | isAjax(): bool
165 | --------------
166 | Is it an AJAX request?
167 |
168 | getRemoteAddress(): ?string
169 | ---------------------------
170 | Returns the user's IP address. You may need to [set up a proxy|configuring#HTTP proxy] for proper functionality.
171 |
172 | getRemoteHost(): ?string
173 | ------------------------
174 | Returns DNS translation of the user's IP address. You may need to [set up a proxy|configuring#HTTP proxy] for proper functionality.
175 |
176 | getRawBody(): ?string
177 | ---------------------
178 | Returns the body of the HTTP request:
179 |
180 | ```php
181 | $body = $httpRequest->getRawBody();
182 | ```
183 |
184 | detectLanguage(array $langs): ?string
185 | -------------------------------------
186 | Detects language. As a parameter `$lang`, we pass an array of languages that the application supports, and it returns the one preferred by browser. It is not magic, the method just uses the `Accept-Language` header. If no match is reached, it returns `null`.
187 |
188 | ```php
189 | // Header sent by browser: Accept-Language: cs,en-us;q=0.8,en;q=0.5,sl;q=0.3
190 |
191 | $langs = ['hu', 'pl', 'en']; // languages supported in application
192 | echo $httpRequest->detectLanguage($langs); // en
193 | ```
194 |
195 |
196 |
197 | RequestFactory
198 | --------------
199 |
200 | The object of the current HTTP request is created by [Nette\Http\RequestFactory](https://api.nette.org/3.0/Nette/Http/RequestFactory.html). If you are writing an application that does not use a DI container, you create a request as follows:
201 |
202 | ```php
203 | $factory = new Nette\Http\RequestFactory;
204 | $httpRequest = $factory->fromGlobals();
205 | ```
206 |
207 | RequestFactory can be configured before calling `fromGlobals()`. We can disable all sanitization of input parameters from invalid UTF-8 sequences using `$factory->setBinary()`. And also set up a proxy server, which is important for the correct detection of the user's IP address using `$factory->setProxy(...)`.
208 |
209 | It's possible to clean up URLs from characters that can get into them because of poorly implemented comment systems on various other websites by using filters:
210 |
211 | ```php
212 | // remove spaces from path
213 | $requestFactory->urlFilters['path']['%20'] = '';
214 |
215 | // remove dot, comma or right parenthesis form the end of the URL
216 | $requestFactory->urlFilters['url']['[.,)]$'] = '';
217 |
218 | // clean the path from duplicated slashes (default filter)
219 | $requestFactory->urlFilters['path']['/{2,}'] = '/';
220 | ```
221 |
222 |
223 |
224 | HTTP Response
225 | =============
226 |
227 | An HTTP response is an [Nette\Http\Response](https://api.nette.org/3.0/Nette/Http/Response.html) object. Unlike the [Request](#HTTP-Request), the object is mutable, so you can use setters to change the state, ie to send headers. Remember that all setters **must be called before any actual output is sent.** The `isSent()` method tells if output have been sent. If it returns `true`, each attempt to send a header throws an `Nette\InvalidStateException` exception.
228 |
229 |
230 | setCode(int $code, string $reason = null)
231 | -----------------------------------------
232 | Changes a status [response code](https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10). For better source code readability it is recommended to use [predefined constants](https://api.nette.org/3.0/Nette/Http/IResponse.html) instead of actual numbers.
233 |
234 | ```php
235 | $httpResponse->setCode(Nette\Http\Response::S404_NotFound);
236 | ```
237 |
238 | getCode(): int
239 | --------------
240 | Returns the status code of the response.
241 |
242 | isSent(): bool
243 | --------------
244 | Returns whether headers have already been sent from the server to the browser, so it is no longer possible to send headers or change the status code.
245 |
246 | setHeader(string $name, string $value)
247 | --------------------------------------
248 | Sends an HTTP header and **overwrites** previously sent header of the same name.
249 |
250 | ```php
251 | $httpResponse->setHeader('Pragma', 'no-cache');
252 | ```
253 |
254 | addHeader(string $name, string $value)
255 | --------------------------------------
256 | Sends an HTTP header and **doesn't overwrite** previously sent header of the same name.
257 |
258 | ```php
259 | $httpResponse->addHeader('Accept', 'application/json');
260 | $httpResponse->addHeader('Accept', 'application/xml');
261 | ```
262 |
263 | deleteHeader(string $name)
264 | --------------------------
265 | Deletes a previously sent HTTP header.
266 |
267 | getHeader(string $header): ?string
268 | ----------------------------------
269 | Returns the sent HTTP header, or `null` if it does not exist. The parameter is case-insensitive.
270 |
271 | ```php
272 | $pragma = $httpResponse->getHeader('Pragma');
273 | ```
274 |
275 | getHeaders(): array
276 | -------------------
277 | Returns all sent HTTP headers as associative array.
278 |
279 | ```php
280 | $headers = $httpResponse->getHeaders();
281 | echo $headers['Pragma'];
282 | ```
283 |
284 | setContentType(string $type, string $charset = null)
285 | ----------------------------------------------------
286 | Sends the header `Content-Type`.
287 |
288 | ```php
289 | $httpResponse->setContentType('text/plain', 'UTF-8');
290 | ```
291 |
292 | redirect(string $url, int $code = self::S302_FOUND): void
293 | ---------------------------------------------------------
294 | Redirects to another URL. Don't forget to quit the script then.
295 |
296 | ```php
297 | $httpResponse->redirect('http://example.com');
298 | exit;
299 | ```
300 |
301 | setExpiration(?string $time)
302 | ----------------------------
303 | Sets the expiration of the HTTP document using the `Cache-Control` and `Expires` headers. The parameter is either a time interval (as text) or `null`, which disables caching.
304 |
305 | ```php
306 | // browser cache expires in one hour
307 | $httpResponse->setExpiration('1 hour');
308 | ```
309 |
310 | setCookie(string $name, string $value, string|int|\DateTimeInterface|null $expire, string $path = null, string $domain = null, bool $secure = null, bool $httpOnly = null, string $sameSite = null)
311 | --------------------------------------------------------------------------------------------------------------------------------------------------------------
312 | Sends a cookie. The default values of the parameters are:
313 | - `$path` with scope to all directories (`'/'`)
314 | - `$domain` with scope of the current (sub)domain, but not its subdomains
315 | - `$secure` defaults to false
316 | - `$httpOnly` is true, so the cookie is inaccessible to JavaScript
317 | - `$sameSite` is null, so the flag is not specified
318 |
319 | The `$expire` parameter can be specified as a string, an object implementing `DateTimeInterface`, or the number of seconds.
320 |
321 | ```php
322 | $httpResponse->setCookie('lang', 'en', '100 days');
323 | ```
324 |
325 | deleteCookie(string $name, string $path = null, string $domain = null, bool $secure = null): void
326 | -------------------------------------------------------------------------------------------------
327 | Deletes a cookie. The default values of the parameters are:
328 | - `$path` with scope to all directories (`'/'`)
329 | - `$domain` with scope of the current (sub)domain, but not its subdomains
330 | - `$secure` defaults to false
331 |
332 | ```php
333 | $httpResponse->deleteCookie('lang');
334 | ```
335 |
336 |
337 | Uploaded Files
338 | ==============
339 |
340 | Method `Nette\Http\Request::getFiles()` return a tree of upload files in a normalized structure, with each leaf an instance of `Nette\Http\FileUpload`. These objects encapsulate the data submitted by the `` form element.
341 |
342 | The structure reflects the naming of elements in HTML. In the simplest example, this might be a single named form element submitted as:
343 |
344 | ```html
345 |
346 | ```
347 |
348 | In this case, the `$request->getFiles()` returns array:
349 |
350 | ```php
351 | [
352 | 'avatar' => /* FileUpload instance */
353 | ]
354 | ```
355 |
356 | The `FileUpload` object is created even if the user did not upload any file or the upload failed. Method `hasFile()` returns true if a file has been sent:
357 |
358 | ```php
359 | $request->getFile('avatar')->hasFile();
360 | ```
361 |
362 | In the case of an input using array notation for the name:
363 |
364 | ```html
365 |
366 | ```
367 |
368 | returned tree ends up looking like this:
369 |
370 | ```php
371 | [
372 | 'my-form' => [
373 | 'details' => [
374 | 'avatar' => /* FileUpload instance */
375 | ],
376 | ],
377 | ]
378 | ```
379 |
380 | You can also create arrays of files:
381 |
382 | ```html
383 |
384 | ```
385 |
386 | In such a case structure looks like:
387 |
388 | ```php
389 | [
390 | 'my-form' => [
391 | 'details' => [
392 | 'avatars' => [
393 | 0 => /* FileUpload instance */,
394 | 1 => /* FileUpload instance */,
395 | 2 => /* FileUpload instance */,
396 | ],
397 | ],
398 | ],
399 | ]
400 | ```
401 |
402 | The best way to access index 1 of a nested array is as follows:
403 |
404 | ```php
405 | $file = Nette\Utils\Arrays::get(
406 | $request->getFiles(),
407 | ['my-form', 'details', 'avatars', 1],
408 | null
409 | );
410 | if ($file instanceof FileUpload) {
411 | ...
412 | }
413 | ```
414 |
415 | Because you can't trust data from the outside and therefore don't rely on the form of the file structure, it's safer to use the `Arrays::get()` than the `$request->getFiles()['my-form']['details']['avatars'][1]`, which may fail.
416 |
417 |
418 | Overview of `FileUpload` Methods .{toc: FileUpload}
419 | ---------------------------------------------------
420 |
421 | hasFile(): bool
422 | ---------------
423 | Returns `true` if the user has uploaded a file.
424 |
425 | isOk(): bool
426 | ------------
427 | Returns `true` if the file was uploaded successfully.
428 |
429 | getError(): int
430 | ---------------
431 | Returns the error code associated with the uploaded file. It is be one of [UPLOAD_ERR_XXX](http://php.net/manual/en/features.file-upload.errors.php) constants. If the file was uploaded successfully, it returns `UPLOAD_ERR_OK`.
432 |
433 | move(string $dest)
434 | ------------------
435 | Moves an uploaded file to a new location. If the destination file already exists, it will be overwritten.
436 |
437 | ```php
438 | $file->move('/path/to/files/name.ext');
439 | ```
440 |
441 | getContents(): ?string
442 | ----------------------
443 | Returns the contents of the uploaded file. If the upload was not successful, it returns `null`.
444 |
445 | getContentType(): ?string
446 | -------------------------
447 | Detects the MIME content type of the uploaded file based on its signature. If the upload was not successful or the detection failed, it returns `null`.
448 |
449 | Requires PHP extension `fileinfo`.
450 |
451 | getName(): string
452 | -----------------
453 | Returns the original file name as submitted by the browser.
454 |
455 | Do not trust the value returned by this method. A client could send a malicious filename with the intention to corrupt or hack your application.
456 |
457 | getSanitizedName(): string
458 | --------------------------
459 | Returns the sanitized file name. It contains only ASCII characters `[a-zA-Z0-9.-]`. If the name does not contain such characters, it returns 'unknown'. If the file is JPEG, PNG, GIF, or WebP image, it returns the correct file extension.
460 |
461 | getSize(): int
462 | --------------
463 | Returns the size of the uploaded file. If the upload was not successful, it returns `0`.
464 |
465 | getTemporaryFile(): string
466 | --------------------------
467 | Returns the path of the temporary location of the uploaded file. If the upload was not successful, it returns `''`.
468 |
469 | isImage(): bool
470 | ---------------
471 | Returns `true` if the uploaded file is a JPEG, PNG, GIF, or WebP image. Detection is based on its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to [load it](#toImage).
472 |
473 | Requires PHP extension `fileinfo`.
474 |
475 | getImageSize(): ?array
476 | ----------------------
477 | Returns a pair of `[width, height]` with dimensions of the uploaded image. If the upload was not successful or is not a valid image, it returns `null`.
478 |
479 | toImage(): Nette\Utils\Image
480 | ----------------------------
481 | Loads an image as an `Image` object. If the upload was not successful or is not a valid image, it throws an `Nette\Utils\ImageException` exception.
482 |
483 |
484 |
485 | Sessions
486 | ========
487 |
488 | When using sessions, each user receives a unique identifier called session ID, which is passed in a cookie. This serves as the key to the session data. Unlike cookies, which are stored on the browser side, session data is stored on the server side.
489 |
490 | The session is managed by the [Nette\Http\Session](https://api.nette.org/3.0/Nette/Http/Session.html) object.
491 |
492 |
493 | Starting Session
494 | ----------------
495 |
496 | By default, Nette automatically starts a session if the HTTP request contains a cookie with a session ID. It also starts automatically when we start reading from or writing data to it. Manually is session started by `$session->start()`.
497 |
498 | PHP sends HTTP headers affecting caching when starting the session, see `session_cache_limiter`, and possibly a cookie with the session ID. Therefore, it is always necessary to start the session before sending any output to the browser, otherwise an exception will be thrown. So if you know that a session will be used during page rendering, start it manually before, for example in the presenter.
499 |
500 | In developer mode, Tracy starts the session because it uses it to display redirection and AJAX requests bars in the Tracy Bar.
501 |
502 |
503 | Section
504 | -------
505 |
506 | In pure PHP, the session data store is implemented as an array accessible via a global variable `$_SESSION`. The problem is that applications normally consist of a number of independent parts, and if all have only one same array available, sooner or later a name collision will occur.
507 |
508 | Nette Framework solves the problem by dividing the entire space into sections (objects [Nette\Http\SessionSection](https://api.nette.org/3.0/Nette/Http/SessionSection.html)). Each unit then uses its own section with a unique name and no collisions can occur.
509 |
510 | We get the section from the session manager:
511 |
512 | ```php
513 | $section = $session->getSession('unique name');
514 | ```
515 |
516 | In the presenter it is enough to call `getSession()` with the parameter:
517 |
518 | ```php
519 | // $this is Presenter
520 | $section = $this->getSession('unique name');
521 | ```
522 |
523 | The existence of the section can be checked by the method `$session->hasSection('unique name')`.
524 |
525 | And then it's really simple to work with that section:
526 |
527 | ```php
528 | // variable writing
529 | $section->userName = 'john'; // nebo $section['userName'] = 'john';
530 |
531 | // variable reading
532 | echo $section->userName; // nebo echo $section['userName'];
533 |
534 | // variable removing
535 | unset($section->userName); // unset($section['userName']);
536 | ```
537 |
538 | It's possible to use `foreach` cycle to obtain all variables from section:
539 |
540 | ```php
541 | foreach ($section as $key => $val) {
542 | echo "$key = $val";
543 | }
544 | ```
545 |
546 | Accessing a non-existent variable does not generate any error (the returned value is null). It could be undesirable behavior in some cases and that's why there is a possibility to change it:
547 |
548 | ```php
549 | $section->warnOnUndefined = true;
550 | ```
551 |
552 |
553 | How to Set Expiration
554 | ---------------------
555 |
556 | Expiration can be set for individual sections or even individual variables. We can let the user's login expire in 20 minutes, but still remember the contents of a shopping cart.
557 |
558 | ```php
559 | // section will expire after 20 minutes
560 | $section->setExpiration('20 minutes');
561 |
562 | // variable $section->flash will expire after 30 seconds
563 | $section->setExpiration('30 seconds', 'flash');
564 | ```
565 |
566 | The cancellation of the previously set expiration can be achieved by the method `removeExpiration()`. Immediate deletion of the whole section will be ensured by the method `remove()`.
567 |
568 |
569 |
570 | Session Management
571 | ------------------
572 |
573 | Overview of methods of the `Nette\Http\Session` class for session management:
574 |
575 | start(): void
576 | -------------
577 | Starts a session.
578 |
579 | isStarted(): bool
580 | -----------------
581 | Is the session started?
582 |
583 | close(): void
584 | -------------
585 | Ends the session. The session ends automatically at the end of the script.
586 |
587 | destroy(): void
588 | ---------------
589 | Ends and deletes the session.
590 |
591 | exists(): bool
592 | --------------
593 | Does the HTTP request contain a cookie with a session ID?
594 |
595 | regenerateId(): void
596 | --------------------
597 | Generates a new random session ID. Data remain unchanged.
598 |
599 | getId(): string
600 | ---------------
601 | Returns the session ID.
602 |
603 |
604 | Configuration
605 | -------------
606 |
607 | Methods must be called before starting a session.
608 |
609 | setName(string $name): static
610 | -----------------------------
611 | Changes the session name. It is possible to run several different sessions at the same time within one website, each under a different name.
612 |
613 | getName(): string
614 | -----------------
615 | Returns the session name.
616 |
617 | setOptions(array $options): static
618 | ----------------------------------
619 | Configures the session. It is possible to set all PHP [session directives](https://www.php.net/manual/en/session.configuration.php) (in camelCase format, eg write `savePath` instead of `session.save_path`) and also [readAndClose](https://www.php.net/manual/en/function.session-start.php#refsect1-function.session-start-parameters).
620 |
621 | setExpiration(?string $time): static
622 | ------------------------------------
623 | Sets the time of inactivity after which the session expires.
624 |
625 | setCookieParameters(string $path, string $domain = null, bool $secure = null, string $samesite = null): static
626 | --------------------------------------------------------------------------------------------------------------
627 | Sets parameters for cookies.
628 |
629 | setSavePath(string $path): static
630 | ---------------------------------
631 | Sets the directory where session files are stored.
632 |
633 | setHandler(\SessionHandlerInterface $handler): static
634 | -----------------------------------------------------
635 | Sets custom handler, see [PHP documentation](https://www.php.net/manual/en/class.sessionhandlerinterface.php).
636 |
637 |
638 | Safety First
639 | ------------
640 |
641 | The server assumes that it communicates with the same user as long as requests contain the same session ID. The task of security mechanisms is to ensure that this behavior really works and that there is no possibility to substitute or steal an identifier.
642 |
643 | That's why Nette Framework properly configures PHP directives to transfer session ID only in cookies, to avoid access from JavaScript and to ignore the identifiers in the URL. Moreover in critical moments, such as user login, it generates a new Session ID.
644 |
645 | Function ini_set is used for configuring PHP, but unfortunately, its use is prohibited at some web hosting services. If it's your case, try to ask your hosting provider to allow this function for you, or at least to configure his server properly. .[note]
646 |
647 |
648 |
649 | Url
650 | ===
651 |
652 | The [Nette\Http\Url](https://api.nette.org/3.0/Nette/Http/Url.html) class makes it easy to work with the URL and its individual components, which are outlined in this diagram:
653 |
654 | ```
655 | scheme user password host port path query fragment
656 | | | | | | | | |
657 | /--\ /--\ /------\ /-------\ /--\/----------\ /--------\ /----\
658 | http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer
659 | \______\__________________________/
660 | | |
661 | hostUrl authority
662 | ```
663 |
664 | URL generation is intuitive:
665 |
666 | ```php
667 | use Nette\Http\Url;
668 |
669 | $url = new Url;
670 | $url->setScheme('https')
671 | ->setHost('localhost')
672 | ->setPath('/edit')
673 | ->setQueryParameter('foo', 'bar');
674 |
675 | echo $url; // 'https://localhost/edit?foo=bar'
676 | ```
677 |
678 | You can also parse the URL and then manipulate it:
679 |
680 | ```php
681 | $url = new Url(
682 | 'http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer'
683 | );
684 | ```
685 |
686 | The following methods are available to get or change individual URL components:
687 |
688 | Setter | Getter | Returned value
689 | ----------------------------------------|-------------------------------|------------------
690 | `setScheme(string $scheme)` | `getScheme(): string` | `'http'`
691 | `setUser(string $user)` | `getUser(): string` | `'john'`
692 | `setPassword(string $password)` | `getPassword(): string` | `'xyz*12'`
693 | `setHost(string $host)` | `getHost(): string` | `'nette.org'`
694 | `setPort(int $port)` | `getPort(): ?int` | `8080`
695 | `setPath(string $path)` | `getPath(): string` | `'/en/download'`
696 | `setQuery(string\|array $query)` | `getQuery(): string` | `'name=param'`
697 | `setFragment(string $fragment)` | `getFragment(): string` | `'footer'`
698 | -- | `getAuthority(): string` | `'nette.org:8080'`
699 | -- | `getHostUrl(): string` | `'http://nette.org:8080'`
700 | -- | `getAbsoluteUrl(): string` | full URL
701 |
702 | We can also operate with individual query parameters using:
703 |
704 | Setter | Getter
705 | ----------------------------------------|---------
706 | `setQuery(string\|array $query)` | `getQueryParameters(): array`
707 | `setQueryParameter(string $name, $val)` | `getQueryParameter(string $name)`
708 |
709 | Method `getDomain(int $level = 2)` returns the right or left part of the host. This is how it works if the host is `www.nette.org`:
710 |
711 | Usage | Result
712 | ----------------------------------------|---------
713 | `getDomain(1)` | `'org'`
714 | `getDomain(2)` | `'nette.org'`
715 | `getDomain(3)` | `'www.nette.org'`
716 | `getDomain(0)` | `'www.nette.org'`
717 | `getDomain(-1)` | `'www.nette'`
718 | `getDomain(-2)` | `'www'`
719 | `getDomain(-3)` | `''`
720 |
721 |
722 | The `Url` class implements the `JsonSerializable` interface and has a `__toString()` method so that the object can be printed or used in data passed to `json_encode()`.
723 |
724 | ```php
725 | echo $url;
726 | echo json_encode([$url]);
727 | ```
728 |
729 | Method `isEqual(string|Url $anotherUrl): bool` tests whether the two URLs are identical.
730 |
731 | ```php
732 | $url->isEqual('https://nette.org');
733 | ```
734 |
735 |
736 | UrlImmutable
737 | ============
738 |
739 | The class [Nette\Http\UrlImmutable](https://api.nette.org/3.0/Nette/Http/UrlImmutable.html) is an immutable alternative to class `Url` (just as in PHP `DateTimeImmutable` is immutable alternative to `DateTime`). Instead of setters, it has so-called withers, which do not change the object, but return new instances with a modified value:
740 |
741 | ```php
742 | use Nette\Http\UrlImmutable;
743 |
744 | $url = new UrlImmutable(
745 | 'http://john:xyz%2A12@nette.org:8080/en/download?name=param#footer'
746 | );
747 |
748 | $newUrl = $url
749 | ->withUser('')
750 | ->withPassword('')
751 | ->withPath('/cs/');
752 |
753 | echo $newUrl; // 'http://nette.org:8080/cs/?name=param#footer'
754 | ```
755 |
756 | The following methods are available to get or change individual URL components:
757 |
758 | Wither | Getter | Returned value
759 | ----------------------------------------|-------------------------------|------------------
760 | `withScheme(string $scheme)` | `getScheme(): string` | `'http'`
761 | `withUser(string $user)` | `getUser(): string` | `'john'`
762 | `withPassword(string $password)` | `getPassword(): string` | `'xyz*12'`
763 | `withHost(string $host)` | `getHost(): string` | `'nette.org'`
764 | `withPort(int $port)` | `getPort(): ?int` | `8080`
765 | `withPath(string $path)` | `getPath(): string` | `'/en/download'`
766 | `withQuery(string\|array $query)` | `getQuery(): string` | `'name=param'`
767 | `withFragment(string $fragment)` | `getFragment(): string` | `'footer'`
768 | -- | `getAuthority(): string` | `'nette.org:8080'`
769 | -- | `getHostUrl(): string` | `'http://nette.org:8080'`
770 | -- | `getAbsoluteUrl(): string` | full URL
771 |
772 | We can also operate with individual query parameters using:
773 |
774 | Wither | Getter
775 | ------------------------------------|---------
776 | `withQuery(string\|array $query)` | `getQueryParameters(): array`
777 | -- | `getQueryParameter(string $name)`
778 |
779 | The `getDomain(int $level = 2)` method works the same as the method in `Url`. Method `withoutUserInfo()` removes `user` and `password`.
780 |
781 | The `UrlImmutable` class implements the `JsonSerializable` interface and has a `__toString()` method so that the object can be printed or used in data passed to `json_encode()`.
782 |
783 | ```php
784 | echo $url;
785 | echo json_encode([$url]);
786 | ```
787 |
788 | Method `isEqual(string|Url $anotherUrl): bool` tests whether the two URLs are identical.
789 |
790 |
791 | If you like Nette, **[please make a donation now](https://github.com/sponsors/dg)**. Thank you!
792 |
--------------------------------------------------------------------------------
/src/Bridges/HttpDI/HttpExtension.php:
--------------------------------------------------------------------------------
1 | Expect::anyOf(Expect::arrayOf('string'), Expect::string()->castTo('array'))->firstIsDefault()->dynamic(),
31 | 'headers' => Expect::arrayOf('scalar|null')->default([
32 | 'X-Powered-By' => 'Nette Framework 3',
33 | 'Content-Type' => 'text/html; charset=utf-8',
34 | ])->mergeDefaults(),
35 | 'frames' => Expect::anyOf(Expect::string(), Expect::bool(), null)->default('SAMEORIGIN'), // X-Frame-Options
36 | 'csp' => Expect::arrayOf('array|scalar|null'), // Content-Security-Policy
37 | 'cspReportOnly' => Expect::arrayOf('array|scalar|null'), // Content-Security-Policy-Report-Only
38 | 'featurePolicy' => Expect::arrayOf('array|scalar|null'), // Feature-Policy
39 | 'cookiePath' => Expect::string()->dynamic(),
40 | 'cookieDomain' => Expect::string()->dynamic(),
41 | 'cookieSecure' => Expect::anyOf('auto', null, true, false)->firstIsDefault()->dynamic(), // Whether the cookie is available only through HTTPS
42 | 'disableNetteCookie' => Expect::bool(false), // disables cookie use by Nette
43 | ]);
44 | }
45 |
46 |
47 | public function loadConfiguration(): void
48 | {
49 | $builder = $this->getContainerBuilder();
50 | $config = $this->config;
51 |
52 | $builder->addDefinition($this->prefix('requestFactory'))
53 | ->setFactory(Nette\Http\RequestFactory::class)
54 | ->addSetup('setProxy', [$config->proxy]);
55 |
56 | $request = $builder->addDefinition($this->prefix('request'))
57 | ->setFactory('@Nette\Http\RequestFactory::fromGlobals');
58 |
59 | $response = $builder->addDefinition($this->prefix('response'))
60 | ->setFactory(Nette\Http\Response::class);
61 |
62 | if ($config->cookiePath !== null) {
63 | $response->addSetup('$cookiePath', [$config->cookiePath]);
64 | }
65 |
66 | if ($config->cookieDomain !== null) {
67 | $value = $config->cookieDomain === 'domain'
68 | ? $builder::literal('$this->getService(?)->getUrl()->getDomain(2)', [$request->getName()])
69 | : $config->cookieDomain;
70 | $response->addSetup('$cookieDomain', [$value]);
71 | }
72 |
73 | if ($config->cookieSecure !== null) {
74 | $value = $config->cookieSecure === 'auto'
75 | ? $builder::literal('$this->getService(?)->isSecured()', [$request->getName()])
76 | : $config->cookieSecure;
77 | $response->addSetup('$cookieSecure', [$value]);
78 | }
79 |
80 | if ($this->name === 'http') {
81 | $builder->addAlias('nette.httpRequestFactory', $this->prefix('requestFactory'));
82 | $builder->addAlias('httpRequest', $this->prefix('request'));
83 | $builder->addAlias('httpResponse', $this->prefix('response'));
84 | }
85 |
86 | if (!$this->cliMode) {
87 | $this->sendHeaders();
88 | }
89 | }
90 |
91 |
92 | private function sendHeaders(): void
93 | {
94 | $config = $this->config;
95 | $headers = array_map('strval', $config->headers);
96 |
97 | if (isset($config->frames) && $config->frames !== true && !isset($headers['X-Frame-Options'])) {
98 | $frames = $config->frames;
99 | if ($frames === false) {
100 | $frames = 'DENY';
101 | } elseif (preg_match('#^https?:#', $frames)) {
102 | $frames = "ALLOW-FROM $frames";
103 | }
104 |
105 | $headers['X-Frame-Options'] = $frames;
106 | }
107 |
108 | foreach (['csp', 'cspReportOnly'] as $key) {
109 | if (empty($config->$key)) {
110 | continue;
111 | }
112 |
113 | $value = self::buildPolicy($config->$key);
114 | if (str_contains($value, "'nonce'")) {
115 | $this->initialization->addBody('$cspNonce = base64_encode(random_bytes(16));');
116 | $value = Nette\DI\ContainerBuilder::literal(
117 | 'str_replace(?, ? . $cspNonce, ?)',
118 | ["'nonce", "'nonce-", $value],
119 | );
120 | }
121 |
122 | $headers['Content-Security-Policy' . ($key === 'csp' ? '' : '-Report-Only')] = $value;
123 | }
124 |
125 | if (!empty($config->featurePolicy)) {
126 | $headers['Feature-Policy'] = self::buildPolicy($config->featurePolicy);
127 | }
128 |
129 | $this->initialization->addBody('$response = $this->getService(?);', [$this->prefix('response')]);
130 | foreach ($headers as $key => $value) {
131 | if ($value !== '') {
132 | $this->initialization->addBody('$response->setHeader(?, ?);', [$key, $value]);
133 | }
134 | }
135 |
136 | if (!$config->disableNetteCookie) {
137 | $this->initialization->addBody(
138 | 'Nette\Http\Helpers::initCookie($this->getService(?), $response);',
139 | [$this->prefix('request')],
140 | );
141 | }
142 | }
143 |
144 |
145 | private static function buildPolicy(array $config): string
146 | {
147 | $nonQuoted = ['require-sri-for' => 1, 'sandbox' => 1];
148 | $value = '';
149 | foreach ($config as $type => $policy) {
150 | if ($policy === false) {
151 | continue;
152 | }
153 |
154 | $policy = $policy === true ? [] : (array) $policy;
155 | $value .= $type;
156 | foreach ($policy as $item) {
157 | if (is_array($item)) {
158 | $item = key($item) . ':';
159 | }
160 |
161 | $value .= !isset($nonQuoted[$type]) && preg_match('#^[a-z-]+$#D', $item)
162 | ? " '$item'"
163 | : " $item";
164 | }
165 |
166 | $value .= '; ';
167 | }
168 |
169 | return $value;
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/src/Bridges/HttpDI/SessionExtension.php:
--------------------------------------------------------------------------------
1 | Expect::bool(false),
33 | 'autoStart' => Expect::anyOf('smart', 'always', 'never', true, false)->firstIsDefault(),
34 | 'expiration' => Expect::string()->dynamic(),
35 | 'handler' => Expect::string()->dynamic(),
36 | 'readAndClose' => Expect::bool(),
37 | 'cookieSamesite' => Expect::anyOf(IResponse::SameSiteLax, IResponse::SameSiteStrict, IResponse::SameSiteNone)
38 | ->firstIsDefault(),
39 | ])->otherItems('mixed');
40 | }
41 |
42 |
43 | public function loadConfiguration(): void
44 | {
45 | $builder = $this->getContainerBuilder();
46 | $config = $this->config;
47 |
48 | $session = $builder->addDefinition($this->prefix('session'))
49 | ->setFactory(Nette\Http\Session::class);
50 |
51 | if ($config->expiration) {
52 | $session->addSetup('setExpiration', [$config->expiration]);
53 | }
54 |
55 | if ($config->handler) {
56 | $session->addSetup('setHandler', [$config->handler]);
57 | }
58 |
59 | if (($config->cookieDomain ?? null) === 'domain') {
60 | $config->cookieDomain = $builder::literal('$this->getByType(Nette\Http\IRequest::class)->getUrl()->getDomain(2)');
61 | }
62 |
63 | $this->compiler->addExportedType(Nette\Http\IRequest::class);
64 |
65 | if ($this->debugMode && $config->debugger) {
66 | $session->addSetup('@Tracy\Bar::addPanel', [
67 | new Nette\DI\Definitions\Statement(Nette\Bridges\HttpTracy\SessionPanel::class),
68 | ]);
69 | }
70 |
71 | $options = (array) $config;
72 | unset($options['expiration'], $options['handler'], $options['autoStart'], $options['debugger']);
73 | if ($config->autoStart === 'never') {
74 | $options['autoStart'] = false;
75 | }
76 |
77 | if ($config->readAndClose === null) {
78 | unset($options['readAndClose']);
79 | }
80 |
81 | if (!empty($options)) {
82 | $session->addSetup('setOptions', [$options]);
83 | }
84 |
85 | if ($this->name === 'session') {
86 | $builder->addAlias('session', $this->prefix('session'));
87 | }
88 |
89 | if (!$this->cliMode) {
90 | $name = $this->prefix('session');
91 |
92 | if ($config->autoStart === 'smart') {
93 | $this->initialization->addBody('$this->getService(?)->autoStart(false);', [$name]);
94 |
95 | } elseif ($config->autoStart === 'always' || $config->autoStart === true) {
96 | $this->initialization->addBody('$this->getService(?)->start();', [$name]);
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Bridges/HttpTracy/SessionPanel.php:
--------------------------------------------------------------------------------
1 |
4 |
12 |
13 |