├── .github
└── workflows
│ └── php.yml
├── .gitignore
├── .scrutinizer.yml
├── LICENSE
├── README.md
├── autoload.php
├── composer.json
├── phpcs.xml
├── phpunit.xml
├── src
├── Psr15
│ ├── Middleware.php
│ └── RequestHandler.php
├── Psr17
│ ├── RequestFactory.php
│ ├── ResponseFactory.php
│ ├── ServerRequestFactory.php
│ ├── StreamFactory.php
│ ├── UploadedFileFactory.php
│ ├── UriFactory.php
│ └── Utils
│ │ └── SuperGlobal.php
└── Psr7
│ ├── Message.php
│ ├── Request.php
│ ├── Response.php
│ ├── ServerRequest.php
│ ├── Stream.php
│ ├── UploadedFile.php
│ ├── Uri.php
│ └── Utils
│ └── UploadedFileHelper.php
└── tests
├── Psr15
├── ApiMiddleware.php
├── FinalHandler.php
├── RequestHandlerTest.php
└── StringMiddleware.php
├── Psr17
├── RequestFactoryTest.php
├── ResponseFactoryTest.php
├── ServerRequestFactoryTest.php
├── StreamFactoryTest.php
├── UploadFileFactoryTest.php
├── UriFactoryTest.php
└── Utils
│ └── SuperGlobalTest.php
├── Psr7
├── MessageTest.php
├── RequestTest.php
├── ResponseTest.php
├── ServerRequestTest.php
├── StreamTest.php
├── UploadedFileTest.php
├── UriTest.php
└── Utils
│ └── UploadedFileHelperTest.php
├── bootstrap.php
└── sample
├── shieldon_logo.png
└── shieldon_logo_bak.png
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches: [ master ]
6 | pull_request:
7 | branches: [ master ]
8 |
9 | jobs:
10 | run:
11 | runs-on: ${{ matrix.operating-system }}
12 | strategy:
13 | matrix:
14 | operating-system: [ubuntu-latest]
15 | php-versions: ['7.3', '7.4', '8.1']
16 | name: PHP ${{ matrix.php-versions }}
17 |
18 | steps:
19 | - uses: actions/checkout@master
20 |
21 | - name: Setup PHP with Xdebug
22 | uses: shivammathur/setup-php@v2
23 | with:
24 | php-version: ${{ matrix.php-versions }}
25 | extensions: mbstring, pdo, pdo_mysql, intl, zip, redis, ctype, json
26 | coverage: xdebug
27 |
28 | - name: Create a folder for testing.
29 | run: sudo mkdir /home/runner/work/psr-http/psr-http/tmp
30 |
31 | - name: Make folder writable.
32 | run: sudo chmod 777 /home/runner/work/psr-http/psr-http/tmp
33 |
34 | - name: Install dependencies
35 | run: composer install --prefer-dist --no-interaction --dev
36 |
37 | - name: Update packages
38 | run: composer self-update
39 |
40 | - name: Run tests.
41 | run: composer test
42 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | composer.phar
3 | composer.lock
4 | phpunit.phar
5 | phpunit.log
6 | tests/report
7 | .DS_Store
8 | .php_cs.cache
9 | .hg
10 | .vscode
11 | tmp
12 | clover.xml
13 | 1.txt
14 | .phpunit.result.cache
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | paths: ["src/*"]
3 | excluded_paths: ["vendor/*", "tests/*"]
4 |
5 | checks:
6 | php:
7 | code_rating: true
8 | duplication: true
9 |
10 | tools:
11 | external_code_coverage: false
12 |
13 | build:
14 | environment:
15 | php:
16 | version: 7.4.0
17 |
18 | nodes:
19 | analysis:
20 | tests:
21 | override:
22 | - php-scrutinizer-run
23 | dependencies:
24 | before:
25 | - composer self-update
26 | - composer update --no-interaction --prefer-dist --no-progress --dev
27 | tests:
28 | before:
29 | -
30 | command: composer test
31 | coverage:
32 | file: 'clover.xml'
33 | format: 'clover'
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Terry L.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/autoload.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | /**
14 | * Register to PSR-4 autoloader.
15 | *
16 | * @return void
17 | */
18 | function psr_http_register()
19 | {
20 | spl_autoload_register('psr_http_autoload', true, false);
21 | }
22 |
23 | /**
24 | * PSR-4 autoloader.
25 | *
26 | * @param string $className
27 | *
28 | * @return void
29 | */
30 | function psr_http_autoload($className)
31 | {
32 | $prefix = 'Shieldon\\';
33 | $dir = __DIR__ . '/src';
34 |
35 | if (0 === strpos($className, $prefix . 'Psr')) {
36 | $parts = explode('\\', substr($className, strlen($prefix)));
37 | $filepath = $dir . '/' . implode('/', $parts) . '.php';
38 |
39 | if (is_file($filepath)) {
40 | require $filepath;
41 | }
42 | }
43 | }
44 |
45 | psr_http_register();
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shieldon/psr-http",
3 | "description": "HTTP implementation for PSR standard.",
4 | "keywords": ["php-http", "psr7", "psr-15", "psr-17"],
5 | "homepage": "https://github.com/terrylinooo/psr-http",
6 | "license": "MIT",
7 | "authors": [
8 | {
9 | "name": "Terry Lin",
10 | "email": "contact@terryl.in",
11 | "homepage": "https://terryl.in",
12 | "role": "Developer"
13 | }
14 | ],
15 | "minimum-stability": "stable",
16 | "require": {
17 | "php": ">=7.1.0",
18 | "psr/http-factory": "^1.0",
19 | "psr/http-message": "^1.1 || ^2.0",
20 | "psr/http-server-handler": "^1.0",
21 | "psr/http-server-middleware": "^1.0"
22 | },
23 | "require-dev": {
24 | "phpunit/phpunit": "^9"
25 | },
26 | "autoload": {
27 | "psr-4": {
28 | "Shieldon\\Psr7\\": "src/Psr7",
29 | "Shieldon\\Psr15\\": "src/Psr15",
30 | "Shieldon\\Psr17\\": "src/Psr17"
31 | }
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "Shieldon\\Test\\Psr7\\": "tests/Psr7",
36 | "Shieldon\\Test\\Psr15\\": "tests/Psr15",
37 | "Shieldon\\Test\\Psr17\\": "tests/Psr17"
38 | }
39 | },
40 | "config": {
41 | "process-timeout": 0,
42 | "sort-packages": true
43 | },
44 | "scripts": {
45 | "test": "php vendor/phpunit/phpunit/phpunit"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | PSR-2
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | *.php
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 | src
126 | tests
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | tests/Psr7/
7 | tests/Psr15/
8 | tests/Psr17/
9 |
10 |
11 |
12 |
13 | src/
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/Psr15/Middleware.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr15;
14 |
15 | use Psr\Http\Server\MiddlewareInterface;
16 | use Psr\Http\Server\RequestHandlerInterface;
17 | use Psr\Http\Message\ServerRequestInterface;
18 | use Psr\Http\Message\ResponseInterface;
19 |
20 | /**
21 | * PSR-15 Middleware
22 | */
23 | abstract class Middleware implements MiddlewareInterface
24 | {
25 | /**
26 | * {@inheritdoc}
27 | */
28 | abstract function process(ServerRequestInterface $request,RequestHandlerInterface $handler): ResponseInterface;
29 | }
30 |
--------------------------------------------------------------------------------
/src/Psr15/RequestHandler.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr15;
14 |
15 | use Psr\Http\Server\RequestHandlerInterface;
16 | use Psr\Http\Server\MiddlewareInterface;
17 | use Psr\Http\Message\ServerRequestInterface;
18 | use Psr\Http\Message\ResponseInterface;
19 | use Shieldon\Psr7\Response;
20 |
21 | /**
22 | * PSR-15 Middleware
23 | */
24 | class RequestHandler implements RequestHandlerInterface
25 | {
26 | /**
27 | * Middlewares in the queue are ready to run.
28 | *
29 | * @var array
30 | */
31 | protected $queue = [];
32 |
33 | /**
34 | * After the last middleware has been called, a fallback handler should
35 | * parse the request and give an appropriate response.
36 | *
37 | * @var RequestHandlerInterface|null
38 | */
39 | protected $fallbackHandler = null;
40 |
41 | /**
42 | * RequestHandler constructor.
43 | *
44 | * @param RequestHandler|null $finalRequestHandler A valid resource.
45 | */
46 | public function __construct(?RequestHandlerInterface $fallbackHandler = null)
47 | {
48 | if ($fallbackHandler instanceof RequestHandlerInterface) {
49 | $this->fallbackHandler = $fallbackHandler;
50 | }
51 | }
52 |
53 | /**
54 | * {@inheritdoc}
55 | */
56 | public function add(MiddlewareInterface $middleware)
57 | {
58 | $this->queue[] = $middleware;
59 | }
60 |
61 | /**
62 | * {@inheritdoc}
63 | */
64 | public function handle(ServerRequestInterface $request): ResponseInterface
65 | {
66 | if (0 === count($this->queue)) {
67 | return $this->final($request);
68 | }
69 |
70 | $middleware = array_shift($this->queue);
71 |
72 | return $middleware->process($request, $this);
73 | }
74 |
75 | /**
76 | * This is the final, there is no middleware needed to execute, pasre the
77 | * layered request and give a parsed response.
78 | *
79 | * @param ServerRequestInterface $request
80 | *
81 | * @return ResponseInterface
82 | */
83 | protected function final(ServerRequestInterface $request): ResponseInterface
84 | {
85 | if (!$this->fallbackHandler) {
86 | return new Response();
87 | }
88 |
89 | return $this->fallbackHandler->handle($request);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Psr17/RequestFactory.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr17;
14 |
15 | use Psr\Http\Message\RequestFactoryInterface;
16 | use Psr\Http\Message\RequestInterface;
17 | use Shieldon\Psr17\UriFactory;
18 | use Shieldon\Psr17\StreamFactory;
19 | use Shieldon\Psr17\Utils\SuperGlobal;
20 | use Shieldon\Psr7\Request;
21 |
22 | use function str_replace;
23 | use function extract;
24 |
25 | /**
26 | * PSR-17 Request Factory
27 | */
28 | class RequestFactory implements RequestFactoryInterface
29 | {
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public function createRequest(string $method, $uri): RequestInterface
34 | {
35 | extract(SuperGlobal::extract());
36 |
37 | $protocol = $server['SERVER_PROTOCOL'] ?? '1.1';
38 | $protocol = str_replace('HTTP/', '', $protocol);
39 |
40 | $uriFactory = new UriFactory();
41 | $streamFactory = new StreamFactory();
42 |
43 | $uri = $uriFactory->createUri($uri);
44 | $body = $streamFactory->createStream();
45 |
46 | return new Request(
47 | $method,
48 | $uri,
49 | $body,
50 | $header, // from extract.
51 | $protocol
52 | );
53 | }
54 |
55 | /*
56 | |--------------------------------------------------------------------------
57 | | Non PSR-7 Methods.
58 | |--------------------------------------------------------------------------
59 | */
60 |
61 | /**
62 | * Create a new Request.
63 | *
64 | * @return RequestInterface
65 | */
66 | public static function fromNew(): RequestInterface
67 | {
68 | return new Request();
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Psr17/ResponseFactory.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr17;
14 |
15 | use Psr\Http\Message\ResponseFactoryInterface;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Shieldon\Psr17\StreamFactory;
18 | use Shieldon\Psr17\Utils\SuperGlobal;
19 | use Shieldon\Psr7\Response;
20 |
21 | use function str_replace;
22 | use function extract;
23 |
24 | /**
25 | * PSR-17 Response Factory
26 | */
27 | class ResponseFactory implements ResponseFactoryInterface
28 | {
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function createResponse(int $code = 200, string $reasonPhrase = ''): ResponseInterface
33 | {
34 | extract(SuperGlobal::extract());
35 |
36 | $protocol = $server['SERVER_PROTOCOL'] ?? '1.1';
37 | $protocol = str_replace('HTTP/', '', $protocol);
38 |
39 | $streamFactory = new streamFactory();
40 |
41 | $body = $streamFactory->createStream();
42 |
43 | return new Response(
44 | $code,
45 | $header, // from extract.
46 | $body,
47 | $protocol,
48 | $reasonPhrase
49 | );
50 | }
51 |
52 | /*
53 | |--------------------------------------------------------------------------
54 | | Non PSR-7 Methods.
55 | |--------------------------------------------------------------------------
56 | */
57 |
58 | /**
59 | * Create a new Response.
60 | *
61 | * @return ResponseInterface
62 | */
63 | public static function fromNew(): ResponseInterface
64 | {
65 | return new Response();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Psr17/ServerRequestFactory.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr17;
14 |
15 | use Psr\Http\Message\ServerRequestFactoryInterface;
16 | use Psr\Http\Message\ServerRequestInterface;
17 | use Psr\Http\Message\UriInterface;
18 | use Shieldon\Psr17\StreamFactory;
19 | use Shieldon\Psr17\UriFactory;
20 | use Shieldon\Psr17\Utils\SuperGlobal;
21 | use Shieldon\Psr7\ServerRequest;
22 |
23 | use function str_replace;
24 | use function extract;
25 |
26 | /**
27 | * PSR-17 Server Request Factory
28 | */
29 | class ServerRequestFactory implements ServerRequestFactoryInterface
30 | {
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function createServerRequest(string $method, $uri, array $serverParams = []): ServerRequestInterface
35 | {
36 | extract(SuperGlobal::extract());
37 |
38 | if ($serverParams !== []) {
39 | $server = $serverParams;
40 | }
41 |
42 | $protocol = $server['SERVER_PROTOCOL'] ?? '1.1';
43 | $protocol = str_replace('HTTP/', '', $protocol);
44 |
45 | if (!($uri instanceof UriInterface)) {
46 | $uriFactory = new UriFactory();
47 | $uri = $uriFactory->createUri($uri);
48 | }
49 |
50 | $streamFactory = new StreamFactory();
51 | $body = $streamFactory->createStream();
52 |
53 | return new ServerRequest(
54 | $method,
55 | $uri,
56 | $body,
57 | $header, // from extract.
58 | $protocol,
59 | $server, // from extract.
60 | $cookie, // from extract.
61 | $post, // from extract.
62 | $get, // from extract.
63 | $files // from extract.
64 | );
65 | }
66 |
67 | /*
68 | |--------------------------------------------------------------------------
69 | | Non PSR-7 Methods.
70 | |--------------------------------------------------------------------------
71 | */
72 |
73 | /**
74 | * Create a ServerRequestInterface instance from global variable.
75 | *
76 | * @return ServerRequestInterface
77 | */
78 | public static function fromGlobal(): ServerRequestInterface
79 | {
80 | extract(SuperGlobal::extract());
81 |
82 | // HTTP method.
83 | $method = $server['REQUEST_METHOD'] ?? 'GET';
84 |
85 | // HTTP protocal version.
86 | $protocol = $server['SERVER_PROTOCOL'] ?? '1.1';
87 | $protocol = str_replace('HTTP/', '', $protocol);
88 |
89 | $uri = UriFactory::fromGlobal();
90 |
91 | $streamFactory = new StreamFactory();
92 | $body = $streamFactory->createStream();
93 |
94 | return new ServerRequest(
95 | $method,
96 | $uri,
97 | $body,
98 | $header, // from extract.
99 | $protocol,
100 | $server, // from extract.
101 | $cookie, // from extract.
102 | $post, // from extract.
103 | $get, // from extract.
104 | $files // from extract.
105 | );
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Psr17/StreamFactory.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr17;
14 |
15 | use Psr\Http\Message\StreamInterface;
16 | use Psr\Http\Message\StreamFactoryInterface;
17 | use Shieldon\Psr7\Stream;
18 | use InvalidArgumentException;
19 | use RuntimeException;
20 |
21 | use function fopen;
22 | use function fwrite;
23 | use function is_resource;
24 | use function preg_match;
25 | use function rewind;
26 |
27 | /**
28 | * PSR-17 Stream Factory
29 | */
30 | class StreamFactory implements StreamFactoryInterface
31 | {
32 | /**
33 | * {@inheritdoc}
34 | */
35 | public function createStream(string $content = ''): StreamInterface
36 | {
37 | $resource = @fopen('php://temp', 'r+');
38 |
39 | self::assertResource($resource);
40 |
41 | fwrite($resource, $content);
42 | rewind($resource);
43 |
44 | return $this->createStreamFromResource($resource);
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
51 | {
52 | if ($mode === '' || !preg_match('/^[rwaxce]{1}[bt]{0,1}[+]{0,1}+$/', $mode)) {
53 | throw new InvalidArgumentException(
54 | sprintf(
55 | 'Invalid file opening mode "%s"',
56 | $mode
57 | )
58 | );
59 | }
60 |
61 | $resource = @fopen($filename, $mode);
62 |
63 | if (!is_resource($resource)) {
64 | throw new RuntimeException(
65 | sprintf(
66 | 'Unable to open file at "%s"',
67 | $filename
68 | )
69 | );
70 | }
71 |
72 | return new Stream($resource);
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public function createStreamFromResource($resource): StreamInterface
79 | {
80 | if (!is_resource($resource)) {
81 | $resource = @fopen('php://temp', 'r+');
82 | }
83 |
84 | self::assertResource($resource);
85 |
86 | return new Stream($resource);
87 | }
88 |
89 | /*
90 | |--------------------------------------------------------------------------
91 | | Non PSR-7 Methods.
92 | |--------------------------------------------------------------------------
93 | */
94 |
95 | /**
96 | * Create a new Stream instance.
97 | *
98 | * @return StreamInterface
99 | */
100 | public static function fromNew(): StreamInterface
101 | {
102 | $resource = @fopen('php://temp', 'r+');
103 | self::assertResource($resource);
104 |
105 | return new Stream($resource);
106 | }
107 |
108 | /**
109 | * Throw an exception if input is not a valid PHP resource.
110 | *
111 | * @param mixed $resource
112 | *
113 | * @return void
114 | */
115 | protected static function assertResource($resource)
116 | {
117 | if (!is_resource($resource)) {
118 | throw new RuntimeException(
119 | 'Unable to open "php://temp" resource.'
120 | );
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Psr17/UploadedFileFactory.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr17;
14 |
15 | use Psr\Http\Message\StreamInterface;
16 | use Psr\Http\Message\UploadedFileFactoryInterface;
17 | use Psr\Http\Message\UploadedFileInterface;
18 | use Shieldon\Psr7\UploadedFile;
19 | use Shieldon\Psr7\Utils\UploadedFileHelper;
20 | use InvalidArgumentException;
21 |
22 | /**
23 | * PSR-17 Uploaded File Factory
24 | */
25 | class UploadedFileFactory implements UploadedFileFactoryInterface
26 | {
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function createUploadedFile(
31 | StreamInterface $stream,
32 | int $size = null,
33 | int $error = \UPLOAD_ERR_OK,
34 | string $clientFilename = null,
35 | string $clientMediaType = null
36 | ): UploadedFileInterface
37 | {
38 | if (!$stream->isReadable()) {
39 | throw new InvalidArgumentException(
40 | 'File is not readable.'
41 | );
42 | }
43 |
44 | return new UploadedFile(
45 | $stream,
46 | $clientFilename,
47 | $clientMediaType,
48 | $size,
49 | $error
50 | );
51 | }
52 |
53 | /*
54 | |--------------------------------------------------------------------------
55 | | Non PSR-7 Methods.
56 | |--------------------------------------------------------------------------
57 | */
58 |
59 | /**
60 | * Create an array with UriInterface structure.
61 | *
62 | * @return array
63 | */
64 | public static function fromGlobal(): array
65 | {
66 | $filesParams = $_FILES ?? [];
67 | $uploadedFiles = [];
68 |
69 | if (!empty($filesParams)) {
70 | $uploadedFiles = UploadedFileHelper::uploadedFileSpecsConvert(
71 | UploadedFileHelper::uploadedFileParse($filesParams)
72 | );
73 | }
74 |
75 | return $uploadedFiles;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Psr17/UriFactory.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr17;
14 |
15 | use Psr\Http\Message\UriFactoryInterface;
16 | use Psr\Http\Message\UriInterface;
17 | use Shieldon\Psr7\Uri;
18 |
19 | /**
20 | * PSR-17 Uri Factory
21 | */
22 | class UriFactory implements UriFactoryInterface
23 | {
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function createUri(string $uri = '') : UriInterface
28 | {
29 | return new Uri($uri);
30 | }
31 |
32 | /*
33 | |--------------------------------------------------------------------------
34 | | Non PSR-7 Methods.
35 | |--------------------------------------------------------------------------
36 | */
37 |
38 | /**
39 | * Create a UriInterface instance from global variable.
40 | *
41 | * @return UriInterface
42 | */
43 | public static function fromGlobal(): UriInterface
44 | {
45 | $server = $_SERVER ?? [];
46 |
47 | $uri = '';
48 | $user = '';
49 | $host = '';
50 | $pass = '';
51 | $path = '';
52 | $port = '';
53 | $query = '';
54 | $scheme = '';
55 |
56 | $uriComponents = [
57 | 'user' => 'PHP_AUTH_USER',
58 | 'host' => 'HTTP_HOST',
59 | 'pass' => 'PHP_AUTH_PW',
60 | 'path' => 'REQUEST_URI',
61 | 'port' => 'SERVER_PORT',
62 | 'query' => 'QUERY_STRING',
63 | 'scheme' => 'REQUEST_SCHEME',
64 | ];
65 |
66 | foreach ($uriComponents as $key => $value) {
67 | ${$key} = $server[$value] ?? '';
68 | }
69 |
70 | $userInfo = $user;
71 |
72 | if ($pass) {
73 | $userInfo .= ':' . $pass;
74 | }
75 |
76 | $authority = '';
77 |
78 | if ($userInfo) {
79 | $authority .= $userInfo . '@';
80 | }
81 |
82 | $authority .= $host;
83 |
84 | if ($port) {
85 | $authority .= ':' . $port;
86 | }
87 |
88 | if ($scheme) {
89 | $uri .= $scheme . ':';
90 | }
91 |
92 | if ($authority) {
93 | $uri .= '//' . $authority;
94 | }
95 |
96 | $uri .= '/' . ltrim($path, '/');
97 |
98 | if ($query) {
99 | $uri .= '?' . $query;
100 | }
101 |
102 | return new Uri($uri);
103 | }
104 |
105 | /**
106 | * Create a new URI.
107 | *
108 | * @return UriInterface
109 | */
110 | public static function fromNew(): UriInterface
111 | {
112 | return new Uri();
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/src/Psr17/Utils/SuperGlobal.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr17\Utils;
14 |
15 | use function microtime;
16 | use function php_sapi_name;
17 | use function str_replace;
18 | use function strtolower;
19 | use function substr;
20 | use function time;
21 |
22 | /**
23 | * Data Helper
24 | */
25 | class SuperGlobal
26 | {
27 | /**
28 | * Extract data from global variables.
29 | *
30 | * @return array
31 | */
32 | public static function extract(): array
33 | {
34 | if (php_sapi_name() === 'cli') {
35 | self::mockCliEnvironment();
36 | }
37 |
38 | // Here we add the HTTP prefix by ourselves...
39 | $headerParamsWithoutHttpPrefix = [
40 | 'CONTENT_TYPE',
41 | 'CONTENT_LENGTH',
42 | ];
43 |
44 | foreach ($headerParamsWithoutHttpPrefix as $value) {
45 | if (isset($_SERVER[$value])) {
46 | $_SERVER['HTTP_' . $value] = $_SERVER[$value];
47 | }
48 | }
49 |
50 | $headerParams = [];
51 | $serverParams = $_SERVER ?? [];
52 | $cookieParams = $_COOKIE ?? [];
53 | $filesParams = $_FILES ?? [];
54 | $postParams = $_POST ?? [];
55 | $getParams = $_GET ?? [];
56 |
57 | foreach ($serverParams as $name => $value) {
58 | if (substr($name, 0, 5) == 'HTTP_') {
59 | $key = strtolower(str_replace('_', '-', substr($name, 5)));
60 | $headerParams[$key] = $value;
61 | }
62 | }
63 |
64 | return [
65 | 'header' => $headerParams,
66 | 'server' => $serverParams,
67 | 'cookie' => $cookieParams,
68 | 'files' => $filesParams,
69 | 'post' => $postParams,
70 | 'get' => $getParams,
71 | ];
72 | }
73 |
74 | // @codeCoverageIgnoreStart
75 |
76 | /**
77 | * Mock data for unit testing purpose ONLY.
78 | *
79 | * @param array $server Overwrite the mock data.
80 | *
81 | * @return void
82 | */
83 | public static function mockCliEnvironment(array $server = []): void
84 | {
85 | $_POST = $_POST ?? [];
86 | $_COOKIE = $_COOKIE ?? [];
87 | $_GET = $_GET ?? [];
88 | $_FILES = $_FILES ?? [];
89 | $_SERVER = $_SERVER ?? [];
90 |
91 | $defaultServerParams = [
92 | 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9',
93 | 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
94 | 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7',
95 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
96 | 'HTTP_HOST' => '127.0.0.1',
97 | 'QUERY_STRING' => '',
98 | 'REMOTE_ADDR' => '127.0.0.1',
99 | 'REQUEST_METHOD' => 'GET',
100 | 'REQUEST_SCHEME' => 'http',
101 | 'REQUEST_TIME' => time(),
102 | 'REQUEST_TIME_FLOAT' => microtime(true),
103 | 'REQUEST_URI' => '',
104 | 'SCRIPT_NAME' => '',
105 | 'SERVER_NAME' => 'localhost',
106 | 'SERVER_PORT' => 80,
107 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
108 | 'CONTENT_TYPE' => 'text/html; charset=UTF-8',
109 | ];
110 |
111 | if (defined('NO_MOCK_ENV')) {
112 | $defaultServerParams = [];
113 | }
114 |
115 | $_SERVER = array_merge($defaultServerParams, $_SERVER, $server);
116 | }
117 |
118 | // @codeCoverageIgnoreEnd
119 | }
120 |
--------------------------------------------------------------------------------
/src/Psr7/Message.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7;
14 |
15 | use Psr\Http\Message\MessageInterface;
16 | use Psr\Http\Message\StreamInterface;
17 | use Shieldon\Psr7\Stream;
18 | use InvalidArgumentException;
19 |
20 | use function array_map;
21 | use function array_merge;
22 | use function count;
23 | use function fopen;
24 | use function fseek;
25 | use function fwrite;
26 | use function gettype;
27 | use function implode;
28 | use function is_array;
29 | use function is_bool;
30 | use function is_float;
31 | use function is_integer;
32 | use function is_scalar;
33 | use function is_string;
34 | use function preg_match;
35 | use function preg_match_all;
36 | use function sprintf;
37 | use function strtolower;
38 | use function trim;
39 |
40 | use const PREG_SET_ORDER;
41 |
42 | /**
43 | * HTTP messages consist of requests from a client to a server and responses
44 | * from a server to a client.
45 | */
46 | class Message implements MessageInterface
47 | {
48 | /**
49 | * A HTTP protocol version number.
50 | *
51 | * @var string
52 | */
53 | protected $protocolVersion = '1.1';
54 |
55 | /**
56 | * An instance with the specified message body.
57 | *
58 | * @var StreamInterface
59 | */
60 | protected $body;
61 |
62 | /**
63 | * An array of mapping header information with `string => array[]` format.
64 | *
65 | * @var array
66 | */
67 | protected $headers = [];
68 |
69 | /**
70 | * A map of header name for lower case and original case.
71 | * In `lower => original` format.
72 | *
73 | * @var array
74 | */
75 | protected $headerNameMapping = [];
76 |
77 | /**
78 | * Valid HTTP version numbers.
79 | *
80 | * @var array
81 | */
82 | protected $validProtocolVersions = [
83 | '1.0',
84 | '1.1',
85 | '2.0',
86 | '3.0',
87 | ];
88 |
89 | /**
90 | * {@inheritdoc}
91 | */
92 | public function getProtocolVersion(): string
93 | {
94 | return $this->protocolVersion;
95 | }
96 |
97 | /**
98 | * {@inheritdoc}
99 | */
100 | public function withProtocolVersion($version): MessageInterface
101 | {
102 | $clone = clone $this;
103 | $clone->protocolVersion = $version;
104 |
105 | return $clone;
106 | }
107 |
108 | /**
109 | * {@inheritdoc}
110 | */
111 | public function getHeaders(): array
112 | {
113 | $headers = $this->headers;
114 |
115 | foreach ($this->headerNameMapping as $origin) {
116 | $name = strtolower($origin);
117 | if (isset($headers[$name])) {
118 | $value = $headers[$name];
119 | unset($headers[$name]);
120 | $headers[$origin] = $value;
121 | }
122 | }
123 |
124 | return $headers;
125 | }
126 |
127 | /**
128 | * {@inheritdoc}
129 | */
130 | public function hasHeader($name): bool
131 | {
132 | $name = strtolower($name);
133 |
134 | return isset($this->headers[$name]);
135 | }
136 |
137 | /**
138 | * {@inheritdoc}
139 | */
140 | public function getHeader($name): array
141 | {
142 | $name = strtolower($name);
143 |
144 | if (isset($this->headers[$name])) {
145 | return $this->headers[$name];
146 | }
147 |
148 | return [];
149 | }
150 |
151 | /**
152 | * {@inheritdoc}
153 | */
154 | public function getHeaderLine($name): string
155 | {
156 | return implode(', ', $this->getHeader($name));
157 | }
158 |
159 | /**
160 | * {@inheritdoc}
161 | */
162 | public function withHeader($name, $value): MessageInterface
163 | {
164 | $origName = $name;
165 |
166 | $name = $this->normalizeHeaderFieldName($name);
167 | $value = $this->normalizeHeaderFieldValue($value);
168 |
169 | $clone = clone $this;
170 | $clone->headers[$name] = $value;
171 | $clone->headerNameMapping[$name] = $origName;
172 |
173 | return $clone;
174 | }
175 |
176 | /**
177 | * {@inheritdoc}
178 | */
179 | public function withAddedHeader($name, $value): MessageInterface
180 | {
181 | $origName = $name;
182 |
183 | $name = $this->normalizeHeaderFieldName($name);
184 | $value = $this->normalizeHeaderFieldValue($value);
185 |
186 | $clone = clone $this;
187 | $clone->headerNameMapping[$name] = $origName;
188 |
189 | if (isset($clone->headers[$name])) {
190 | $clone->headers[$name] = array_merge($this->headers[$name], $value);
191 | } else {
192 | $clone->headers[$name] = $value;
193 | }
194 |
195 | return $clone;
196 | }
197 |
198 | /**
199 | * {@inheritdoc}
200 | */
201 | public function withoutHeader($name): MessageInterface
202 | {
203 | $origName = $name;
204 | $name = strtolower($name);
205 |
206 | $clone = clone $this;
207 | unset($clone->headers[$name]);
208 | unset($clone->headerNameMapping[$name]);
209 |
210 | return $clone;
211 | }
212 |
213 | /**
214 | * {@inheritdoc}
215 | */
216 | public function getBody(): StreamInterface
217 | {
218 | return $this->body;
219 | }
220 |
221 | /**
222 | * {@inheritdoc}
223 | */
224 | public function withBody(StreamInterface $body): MessageInterface
225 | {
226 | $clone = clone $this;
227 | $clone->body = $body;
228 |
229 | return $clone;
230 | }
231 |
232 | /*
233 | |--------------------------------------------------------------------------
234 | | Non PSR-7 Methods.
235 | |--------------------------------------------------------------------------
236 | */
237 |
238 | /**
239 | * Set headers to property $headers.
240 | *
241 | * @param array $headers A collection of header information.
242 | *
243 | * @return void
244 | */
245 | protected function setHeaders(array $headers): void
246 | {
247 | $arr = [];
248 | $origArr = [];
249 |
250 | foreach ($headers as $name => $value) {
251 | $origName = $name;
252 | $name = $this->normalizeHeaderFieldName($name);
253 | $value = $this->normalizeHeaderFieldValue($value);
254 |
255 | $arr[$name] = $value;
256 | $origArr[$name] = $origName;
257 | }
258 |
259 | $this->headers = $arr;
260 | $this->headerNameMapping = $origArr;
261 | }
262 |
263 | /**
264 | * Set the request body.
265 | *
266 | * This method only provides two types of input, string and StreamInterface
267 | *
268 | * String - As a simplest way to initialize a stream resource.
269 | * StreamInterface - If you would like to use stream resource its mode is
270 | * not "r+", you should create a Stream instance by
271 | * yourself.
272 | *
273 | * @param string|StreamInterface $body Request body
274 | *
275 | * @return void
276 | */
277 | protected function setBody($body): void
278 | {
279 | if ($body instanceof StreamInterface) {
280 | $this->body = $body;
281 |
282 | } elseif (is_string($body)) {
283 | $resource = fopen('php://temp', 'r+');
284 |
285 | if ($body !== '') {
286 | fwrite($resource, $body);
287 | fseek($resource, 0);
288 | }
289 |
290 | $this->body = new Stream($resource);
291 | }
292 | }
293 |
294 | /**
295 | * Parse raw header text into an associated array.
296 | *
297 | * @param string $message Raw header text.
298 | *
299 | * @return array
300 | */
301 | public static function parseRawHeader(string $message): array
302 | {
303 | preg_match_all('/^([^:\n]*): ?(.*)$/m', $message, $headers, PREG_SET_ORDER);
304 |
305 | $num = count($headers);
306 |
307 | if ($num > 1) {
308 | $headers = array_merge(...array_map(function($line) {
309 | $name = trim($line[1]);
310 | $field = trim($line[2]);
311 | return [$name => $field];
312 | }, $headers));
313 |
314 | return $headers;
315 |
316 | } elseif ($num === 1) {
317 | $name = trim($headers[0][1]);
318 | $field = trim($headers[0][2]);
319 | return [$name => $field];
320 | }
321 |
322 | return [];
323 | }
324 |
325 | /**
326 | * Normalize the header field name.
327 | *
328 | * @param string $name
329 | *
330 | * @return string
331 | */
332 | protected function normalizeHeaderFieldName($name): string
333 | {
334 | $this->assertHeaderFieldName($name);
335 |
336 | return trim(strtolower($name));
337 | }
338 |
339 | /**
340 | * Normalize the header field value.
341 | *
342 | * @param mixed $value
343 | *
344 | * @return mixed
345 | */
346 | protected function normalizeHeaderFieldValue($value)
347 | {
348 | $this->assertHeaderFieldValue($value);
349 |
350 | $result = false;
351 |
352 | if (is_string($value)) {
353 | $result = [trim($value)];
354 |
355 | } elseif (is_array($value)) {
356 | foreach ($value as $v) {
357 | if (is_string($v)) {
358 | $value[] = trim($v);
359 | }
360 | }
361 | $result = $value;
362 |
363 | } elseif (is_float($value) || is_integer($value)) {
364 | $result = [(string) $value];
365 | }
366 |
367 | return $result;
368 | }
369 |
370 | /**
371 | * Throw exception if the header is not compatible with RFC 7230.
372 | *
373 | * @param string $name The header name.
374 | *
375 | * @return void
376 | *
377 | * @throws InvalidArgumentException
378 | */
379 | protected function assertHeaderFieldName($name): void
380 | {
381 | if (!is_string($name)) {
382 | throw new InvalidArgumentException(
383 | sprintf(
384 | 'Header field name must be a string, but "%s" given.',
385 | gettype($name)
386 | )
387 | );
388 | }
389 | // see https://tools.ietf.org/html/rfc7230#section-3.2.6
390 | // alpha => a-zA-Z
391 | // digit => 0-9
392 | // others => !#$%&\'*+-.^_`|~
393 |
394 | if (!preg_match('/^[a-zA-Z0-9!#$%&\'*+-.^_`|~]+$/', $name)) {
395 | throw new InvalidArgumentException(
396 | sprintf(
397 | '"%s" is not valid header name, it must be an RFC 7230 compatible string.',
398 | $name
399 | )
400 | );
401 | }
402 | }
403 |
404 | /**
405 | * Throw exception if the header is not compatible with RFC 7230.
406 | *
407 | * @param array|null $value The header value.
408 | *
409 | * @return void
410 | *
411 | * @throws InvalidArgumentException
412 | */
413 | protected function assertHeaderFieldValue($value = null): void
414 | {
415 | if (is_scalar($value) && !is_bool($value)) {
416 | $value = [(string) $value];
417 | }
418 |
419 | if (empty($value)) {
420 | throw new InvalidArgumentException(
421 | 'Empty array is not allowed.'
422 | );
423 | }
424 |
425 | if (is_array($value)) {
426 | foreach ($value as $item) {
427 |
428 | if ($item === '') {
429 | return;
430 | }
431 |
432 | if (!is_scalar($item) || is_bool($item)) {
433 | throw new InvalidArgumentException(
434 | sprintf(
435 | 'The header values only accept string and number, but "%s" provided.',
436 | gettype($item)
437 | )
438 | );
439 | }
440 |
441 | // https://www.rfc-editor.org/rfc/rfc7230.txt (page.25)
442 | // field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
443 | // field-vchar = VCHAR / obs-text
444 | // obs-text = %x80-FF
445 | // SP = space
446 | // HTAB = horizontal tab
447 | // VCHAR = any visible [USASCII] character. (x21-x7e)
448 | // %x80-FF = character range outside ASCII.
449 |
450 | // I THINK THAT obs-text SHOULD N0T BE USED.
451 | // OR EVEN I CAN PASS CHINESE CHARACTERS, THAT'S WEIRD.
452 | if (!preg_match('/^[ \t\x21-\x7e]+$/', $item)) {
453 | throw new InvalidArgumentException(
454 | sprintf(
455 | '"%s" is not valid header value, it must contains visible ASCII characters only.',
456 | $item
457 | )
458 | );
459 | }
460 | }
461 | } else {
462 | throw new InvalidArgumentException(
463 | sprintf(
464 | 'The header field value only accepts string and array, but "%s" provided.',
465 | gettype($value)
466 | )
467 | );
468 | }
469 | }
470 |
471 | /**
472 | * Check out whether a protocol version number is supported.
473 | *
474 | * @param string $version HTTP protocol version.
475 | *
476 | * @return void
477 | *
478 | * @throws InvalidArgumentException
479 | */
480 | protected function assertProtocolVersion(string $version): void
481 | {
482 | if (!in_array($version, $this->validProtocolVersions)) {
483 | throw new InvalidArgumentException(
484 | sprintf(
485 | 'Unsupported HTTP protocol version number. "%s" provided.',
486 | $version
487 | )
488 | );
489 | }
490 | }
491 | }
492 |
--------------------------------------------------------------------------------
/src/Psr7/Request.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7;
14 |
15 | use Psr\Http\Message\RequestInterface;
16 | use Psr\Http\Message\StreamInterface;
17 | use Psr\Http\Message\UriInterface;
18 | use Shieldon\Psr7\Message;
19 | use Shieldon\Psr7\Uri;
20 | use InvalidArgumentException;
21 |
22 | use function in_array;
23 | use function is_string;
24 | use function preg_match;
25 | use function sprintf;
26 | use function strtoupper;
27 |
28 | /*
29 | * Representation of an outgoing, client-side request.
30 | */
31 | class Request extends Message implements RequestInterface
32 | {
33 | /**
34 | * The HTTP method of the outgoing request.
35 | *
36 | * @var string
37 | */
38 | protected $method;
39 |
40 | /**
41 | * The target URL of the outgoing request.
42 | *
43 | * @var string
44 | */
45 | protected $requestTarget;
46 |
47 | /**
48 | * A UriInterface object.
49 | *
50 | * @var UriInterface
51 | */
52 | protected $uri;
53 |
54 |
55 | /**
56 | * Valid HTTP methods.
57 | *
58 | * @ref http://tools.ietf.org/html/rfc7231
59 | *
60 | * @var array
61 | */
62 | protected $validMethods = [
63 |
64 | // The HEAD method asks for a response identical to that of a GET
65 | // request, but without the response body.
66 | 'HEAD',
67 |
68 | // The GET method requests a representation of the specified
69 | // resource. Requests using GET should only retrieve data.
70 | 'GET',
71 |
72 | // The POST method is used to submit an entity to the specified
73 | // resource, often causing a change in state or side effects on the
74 | // server.
75 | 'POST',
76 |
77 | // The PUT method replaces all current representations of the target
78 | // resource with the request payload.
79 | 'PUT',
80 |
81 | // The DELETE method deletes the specified resource.
82 | 'DELETE',
83 |
84 | // The PATCH method is used to apply partial modifications to a
85 | // resource.
86 | 'PATCH',
87 |
88 | // The CONNECT method establishes a tunnel to the server identified
89 | // by the target resource.
90 | 'CONNECT',
91 |
92 | //The OPTIONS method is used to describe the communication options
93 | // for the target resource.
94 | 'OPTIONS',
95 |
96 | // The TRACE method performs a message loop-back test along the
97 | // path to the target resource.
98 | 'TRACE',
99 | ];
100 |
101 | /**
102 | * Request constructor.
103 | *
104 | * @param string $method Request HTTP method
105 | * @param string|UriInterface $uri Request URI
106 | * @param string|StreamInterface $body Request body - see setBody()
107 | * @param array $headers Request headers
108 | * @param string $version Request protocol version
109 | */
110 | public function __construct(
111 | string $method = 'GET',
112 | $uri = '' ,
113 | $body = '' ,
114 | array $headers = [] ,
115 | string $version = '1.1'
116 | ) {
117 | $this->method = $method;
118 |
119 | $this->assertMethod($method);
120 |
121 | $this->assertProtocolVersion($version);
122 | $this->protocolVersion = $version;
123 |
124 | if ($uri instanceof UriInterface) {
125 | $this->uri = $uri;
126 |
127 | } elseif (is_string($uri)) {
128 | $this->uri = new Uri($uri);
129 |
130 | } else {
131 | throw new InvalidArgumentException(
132 | sprintf(
133 | 'URI should be a string or an instance of UriInterface, but "%s" provided.',
134 | gettype($uri)
135 | )
136 | );
137 | }
138 |
139 | $this->setBody($body);
140 | $this->setHeaders($headers);
141 | }
142 |
143 | /**
144 | * {@inheritdoc}
145 | */
146 | public function getRequestTarget(): string
147 | {
148 | if ($this->requestTarget) {
149 | return $this->requestTarget;
150 | }
151 |
152 | $path = $this->uri->getPath();
153 | $query = $this->uri->getQuery();
154 |
155 | if (empty($path)) {
156 | $path = '/';
157 | }
158 |
159 | if (!empty($query)) {
160 | $path .= '?' . $query;
161 | }
162 |
163 | return $path;
164 | }
165 |
166 | /**
167 | * {@inheritdoc}
168 | */
169 | public function withRequestTarget($requestTarget): RequestInterface
170 | {
171 | if (!is_string($requestTarget)) {
172 | throw new InvalidArgumentException(
173 | 'A request target must be a string.'
174 | );
175 | }
176 |
177 | if (preg_match('/\s/', $requestTarget)) {
178 | throw new InvalidArgumentException(
179 | 'A request target cannot contain any whitespace.'
180 | );
181 | }
182 |
183 | $clone = clone $this;
184 | $clone->requestTarget = $requestTarget;
185 |
186 | return $clone;
187 | }
188 |
189 | /**
190 | * {@inheritdoc}
191 | */
192 | public function getMethod(): string
193 | {
194 | return $this->method;
195 | }
196 |
197 | /**
198 | * {@inheritdoc}
199 | */
200 | public function withMethod($method): RequestInterface
201 | {
202 | $this->assertMethod($method);
203 |
204 | $clone = clone $this;
205 | $clone->method = $method;
206 |
207 | return $clone;
208 | }
209 |
210 | /**
211 | * {@inheritdoc}
212 | */
213 | public function getUri(): UriInterface
214 | {
215 | return $this->uri;
216 | }
217 |
218 | /**
219 | * {@inheritdoc}
220 | */
221 | public function withUri(UriInterface $uri, $preserveHost = false): RequestInterface
222 | {
223 | $host = $uri->getHost();
224 |
225 | $clone = clone $this;
226 | $clone->uri = $uri;
227 |
228 | if (
229 | // This method MUST update the Host header of the returned request by
230 | // default if the URI contains a host component.
231 | (!$preserveHost && $host !== '') ||
232 |
233 | // When `$preserveHost` is set to `true`.
234 | // If the Host header is missing or empty, and the new URI contains
235 | // a host component, this method MUST update the Host header in the returned
236 | // request.
237 | ($preserveHost && !$this->hasHeader('Host') && $host !== '')
238 | ) {
239 | $headers = $this->getHeaders();
240 | $headers['host'] = $host;
241 | $clone->setHeaders($headers);
242 | }
243 |
244 | return $clone;
245 | }
246 |
247 | /*
248 | |--------------------------------------------------------------------------
249 | | Non PSR-7 Methods.
250 | |--------------------------------------------------------------------------
251 | */
252 |
253 | /**
254 | * Check out whether a method defined in RFC 7231 request methods.
255 | *
256 | * @param string $method Http methods
257 | *
258 | * @return void
259 | *
260 | * @throws InvalidArgumentException
261 | */
262 | protected function assertMethod($method): void
263 | {
264 | if (!is_string($method)) {
265 | throw new InvalidArgumentException(
266 | sprintf(
267 | 'HTTP method must be a string.',
268 | $method
269 | )
270 | );
271 | }
272 |
273 | if (!in_array(strtoupper($this->method), $this->validMethods)) {
274 | throw new InvalidArgumentException(
275 | sprintf(
276 | 'Unsupported HTTP method. It must be compatible with RFC-7231 request method, but "%s" provided.',
277 | $method
278 | )
279 | );
280 | }
281 | }
282 | }
283 |
--------------------------------------------------------------------------------
/src/Psr7/Response.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7;
14 |
15 | use Psr\Http\Message\ResponseInterface;
16 | use Shieldon\Psr7\Message;
17 | use InvalidArgumentException;
18 |
19 | use function gettype;
20 | use function is_integer;
21 | use function is_string;
22 | use function sprintf;
23 | use function str_replace;
24 | use function strpos;
25 |
26 | /*
27 | * Representation of an outgoing, server-side response.
28 | */
29 | class Response extends Message implements ResponseInterface
30 | {
31 | /**
32 | * HTTP status number.
33 | *
34 | * @var int
35 | */
36 | protected $status;
37 |
38 | /**
39 | * HTTP status reason phrase.
40 | *
41 | * @var string
42 | */
43 | protected $reasonPhrase;
44 |
45 | /**
46 | * HTTP status codes.
47 | *
48 | * @see https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
49 | *
50 | * @var array
51 | */
52 | protected static $statusCode = [
53 |
54 | // 1xx: Informational
55 | // Request received, continuing process.
56 | 100 => 'Continue',
57 | 101 => 'Switching Protocols',
58 | 102 => 'Processing',
59 |
60 | // 2xx: Success
61 | // The action was successfully received, understood, and accepted.
62 | 200 => 'OK',
63 | 201 => 'Created',
64 | 202 => 'Accepted',
65 | 203 => 'Non-Authoritative Information',
66 | 204 => 'No Content',
67 | 205 => 'Reset Content',
68 | 206 => 'Partial Content',
69 | 207 => 'Multi-status',
70 | 208 => 'Already Reported',
71 |
72 | // 3xx: Redirection
73 | // Further action must be taken in order to complete the request.
74 | 300 => 'Multiple Choices',
75 | 301 => 'Moved Permanently',
76 | 302 => 'Found',
77 | 303 => 'See Other',
78 | 304 => 'Not Modified',
79 | 305 => 'Use Proxy',
80 | 306 => 'Switch Proxy',
81 | 307 => 'Temporary Redirect',
82 | 308 => 'Permanent Redirect',
83 | // => '309-399 Unassigned.'
84 |
85 | // 4xx: Client Error
86 | // The request contains bad syntax or cannot be fulfilled.
87 | 400 => 'Bad Request',
88 | 401 => 'Unauthorized',
89 | 402 => 'Payment Required',
90 | 403 => 'Forbidden',
91 | 404 => 'Not Found',
92 | 405 => 'Method Not Allowed',
93 | 406 => 'Not Acceptable',
94 | 407 => 'Proxy Authentication Required',
95 | 408 => 'Request Time-out',
96 | 409 => 'Conflict',
97 | 410 => 'Gone',
98 | 411 => 'Length Required',
99 | 412 => 'Precondition Failed',
100 | 413 => 'Request Entity Too Large',
101 | 414 => 'Request-URI Too Large',
102 | 415 => 'Unsupported Media Type',
103 | 416 => 'Requested range not satisfiable',
104 | 417 => 'Expectation Failed',
105 | // => '418-412: Unassigned'
106 | 422 => 'Unprocessable Entity',
107 | 423 => 'Locked',
108 | 424 => 'Failed Dependency',
109 | 425 => 'Unordered Collection',
110 | 426 => 'Upgrade Required',
111 | 428 => 'Precondition Required',
112 | 429 => 'Too Many Requests',
113 | 431 => 'Request Header Fields Too Large',
114 | // => '432-450: Unassigned.'
115 | 451 => 'Unavailable For Legal Reasons',
116 | // => '452-499: Unassigned.'
117 |
118 | // 5xx: Server Error
119 | // The server failed to fulfill an apparently valid request.
120 | 500 => 'Internal Server Error',
121 | 501 => 'Not Implemented',
122 | 502 => 'Bad Gateway',
123 | 503 => 'Service Unavailable',
124 | 504 => 'Gateway Time-out',
125 | 505 => 'HTTP Version not supported',
126 | 506 => 'Variant Also Negotiates',
127 | 507 => 'Insufficient Storage',
128 | 508 => 'Loop Detected',
129 | 510 => 'Not Extended',
130 | 511 => 'Network Authentication Required',
131 | // => '512-599 Unassigned.'
132 | ];
133 |
134 | /**
135 | * Response Constructor.
136 | *
137 | * @param int $status Response HTTP status code.
138 | * @param array $headers Response headers.
139 | * @param StreamInterface|string $body Response body.
140 | * @param string $version Response protocol version.
141 | * @param string $reason Reaspnse HTTP reason phrase.
142 | */
143 | public function __construct(
144 | int $status = 200 ,
145 | array $headers = [] ,
146 | $body = '' ,
147 | string $version = '1.1',
148 | string $reason = 'OK'
149 | ) {
150 | $this->assertStatus($status);
151 | $this->assertReasonPhrase($reason);
152 | $this->assertProtocolVersion($version);
153 |
154 | $this->setHeaders($headers);
155 | $this->setBody($body);
156 |
157 | $this->status = $status;
158 | $this->protocolVersion = $version;
159 | $this->reasonPhrase = $reason;
160 | }
161 |
162 | /**
163 | * {@inheritdoc}
164 | */
165 | public function getStatusCode(): int
166 | {
167 | return $this->status;
168 | }
169 |
170 | /**
171 | * {@inheritdoc}
172 | */
173 | public function withStatus($code, $reasonPhrase = ''): ResponseInterface
174 | {
175 | $this->assertStatus($code);
176 | $this->assertReasonPhrase($reasonPhrase);
177 |
178 | if ($reasonPhrase === '' && isset(self::$statusCode[$code])) {
179 | $reasonPhrase = self::$statusCode[$code];
180 | }
181 |
182 | $clone = clone $this;
183 | $clone->status = $code;
184 | $clone->reasonPhrase = $reasonPhrase;
185 |
186 | return $clone;
187 | }
188 |
189 | /**
190 | * {@inheritdoc}
191 | */
192 | public function getReasonPhrase(): string
193 | {
194 | return $this->reasonPhrase;
195 | }
196 |
197 | /*
198 | |--------------------------------------------------------------------------
199 | | Non PSR-7 Methods.
200 | |--------------------------------------------------------------------------
201 | */
202 |
203 | /**
204 | * Throw exception when the HTTP status code is not valid.
205 | *
206 | * @param int $code HTTP status code.
207 | *
208 | * @return void
209 | *
210 | * @throws InvalidArgumentException
211 | */
212 | protected function assertStatus($code)
213 | {
214 | if (!is_integer($code)) {
215 | throw new InvalidArgumentException(
216 | sprintf(
217 | 'Status code should be an integer value, but "%s" provided.',
218 | gettype($code)
219 | )
220 | );
221 | }
222 |
223 | if (!($code > 100 && $code < 599)) {
224 | throw new InvalidArgumentException(
225 | sprintf(
226 | 'Status code should be in a range of 100-599, but "%s" provided.',
227 | $code
228 | )
229 | );
230 | }
231 | }
232 |
233 | /**
234 | * Throw exception when the HTTP reason phrase is not valid.
235 | *
236 | * @param string $reasonPhrase
237 | *
238 | * @return void
239 | *
240 | * @throws InvalidArgumentException
241 | */
242 | protected function assertReasonPhrase($reasonPhrase)
243 | {
244 | if ($reasonPhrase === '') {
245 | return;
246 | }
247 |
248 | if (!is_string($reasonPhrase)) {
249 | throw new InvalidArgumentException(
250 | sprintf(
251 | 'Reason phrase must be a string, but "%s" provided.',
252 | gettype($reasonPhrase)
253 | )
254 | );
255 | }
256 |
257 | // Special characters, such as "line breaks", "tab" and others...
258 | $escapeCharacters = [
259 | '\f', '\r', '\n', '\t', '\v', '\0', '[\b]', '\s', '\S', '\w', '\W', '\d', '\D', '\b', '\B', '\cX', '\xhh', '\uhhhh'
260 | ];
261 |
262 | $filteredPhrase = str_replace($escapeCharacters, '', $reasonPhrase);
263 |
264 | if ($reasonPhrase !== $filteredPhrase) {
265 | foreach ($escapeCharacters as $escape) {
266 | if (strpos($reasonPhrase, $escape) !== false) {
267 | throw new InvalidArgumentException(
268 | sprintf(
269 | 'Reason phrase contains "%s" that is considered as a prohibited character.',
270 | $escape
271 | )
272 | );
273 | }
274 | }
275 | }
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/Psr7/ServerRequest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7;
14 |
15 | use Psr\Http\Message\ServerRequestInterface;
16 | use Psr\Http\Message\UploadedFileInterface;
17 | use Psr\Http\Message\UriInterface;
18 | use Psr\Http\Message\StreamInterface;
19 | use Shieldon\Psr7\Request;
20 | use Shieldon\Psr7\Utils\UploadedFileHelper;
21 | use InvalidArgumentException;
22 | use function file_get_contents;
23 | use function gettype;
24 | use function is_array;
25 | use function is_null;
26 | use function is_object;
27 | use function json_decode;
28 | use function json_last_error;
29 | use function parse_str;
30 | use function preg_split;
31 | use function sprintf;
32 | use function strtolower;
33 | use function strtoupper;
34 | use const JSON_ERROR_NONE;
35 |
36 | /*
37 | * Representation of an incoming, server-side HTTP request.
38 | */
39 | class ServerRequest extends Request implements ServerRequestInterface
40 | {
41 | /**
42 | * Typically derived from PHP's $_SERVER superglobal.
43 | *
44 | * @var array
45 | */
46 | protected $serverParams;
47 |
48 | /**
49 | * Typically derived from PHP's $_COOKIE superglobal.
50 | *
51 | * @var array
52 | */
53 | protected $cookieParams;
54 |
55 | /**
56 | * Typically derived from PHP's $_POST superglobal.
57 | *
58 | * @var array|object|null
59 | */
60 | protected $parsedBody;
61 |
62 | /**
63 | * Typically derived from PHP's $_GET superglobal.
64 | *
65 | * @var array
66 | */
67 | protected $queryParams;
68 |
69 | /**
70 | * Typically derived from PHP's $_FILES superglobal.
71 | * A collection of uploadFileInterface instances.
72 | *
73 | * @var array
74 | */
75 | protected $uploadedFiles;
76 |
77 | /**
78 | * The request "attributes" may be used to allow injection of any
79 | * parameters derived from the request: e.g., the results of path
80 | * match operations; the results of decrypting cookies; the results of
81 | * deserializing non-form-encoded message bodies; etc. Attributes
82 | * will be application and request specific, and CAN be mutable.
83 | *
84 | * @var array
85 | */
86 | protected $attributes;
87 |
88 | /**
89 | * ServerRequest constructor.
90 | *
91 | * @param string $method Request HTTP method
92 | * @param string|UriInterface $uri Request URI object URI or URL
93 | * @param string|StreamInterface $body Request body
94 | * @param array $headers Request headers
95 | * @param string $version Request protocol version
96 | * @param array $serverParams Typically $_SERVER superglobal
97 | * @param array $cookieParams Typically $_COOKIE superglobal
98 | * @param array $postParams Typically $_POST superglobal
99 | * @param array $getParams Typically $_GET superglobal
100 | * @param array $filesParams Typically $_FILES superglobal
101 | */
102 | public function __construct(
103 | string $method = 'GET',
104 | $uri = '' ,
105 | $body = '' ,
106 | array $headers = [] ,
107 | string $version = '1.1',
108 | array $serverParams = [] ,
109 | array $cookieParams = [] ,
110 | array $postParams = [] ,
111 | array $getParams = [] ,
112 | array $filesParams = []
113 | ) {
114 | parent::__construct($method, $uri, $body, $headers, $version);
115 |
116 | $this->serverParams = $serverParams;
117 | $this->cookieParams = $cookieParams;
118 | $this->queryParams = $getParams;
119 | $this->attributes = [];
120 |
121 | $this->determineParsedBody($postParams);
122 |
123 | // This property will be assigned to a parsed array that contains
124 | // the UploadedFile instance(s) as the $filesParams is given.
125 | $this->uploadedFiles = [];
126 |
127 | if (!empty($filesParams)) {
128 | $this->uploadedFiles = UploadedFileHelper::uploadedFileSpecsConvert(
129 | UploadedFileHelper::uploadedFileParse($filesParams)
130 | );
131 | }
132 | }
133 |
134 | /**
135 | * {@inheritdoc}
136 | */
137 | public function getServerParams(): array
138 | {
139 | return $this->serverParams;
140 | }
141 |
142 | /**
143 | * {@inheritdoc}
144 | */
145 | public function getCookieParams(): array
146 | {
147 | return $this->cookieParams;
148 | }
149 |
150 | /**
151 | * {@inheritdoc}
152 | */
153 | public function withCookieParams(array $cookies): ServerRequestInterface
154 | {
155 | $clone = clone $this;
156 | $clone->cookieParams = $cookies;
157 |
158 | return $clone;
159 | }
160 |
161 | /**
162 | * {@inheritdoc}
163 | */
164 | public function getQueryParams(): array
165 | {
166 | return $this->queryParams;
167 | }
168 |
169 | /**
170 | * {@inheritdoc}
171 | */
172 | public function withQueryParams(array $query): ServerRequestInterface
173 | {
174 | $clone = clone $this;
175 | $clone->queryParams = $query;
176 |
177 | return $clone;
178 | }
179 |
180 | /**
181 | * {@inheritdoc}
182 | */
183 | public function getUploadedFiles(): array
184 | {
185 | return $this->uploadedFiles;
186 | }
187 |
188 | /**
189 | * {@inheritdoc}
190 | */
191 | public function withUploadedFiles(array $uploadedFiles): ServerRequestInterface
192 | {
193 | $this->assertUploadedFiles($uploadedFiles);
194 |
195 | $clone = clone $this;
196 | $clone->uploadedFiles = $uploadedFiles;
197 |
198 | return $clone;
199 | }
200 |
201 | /**
202 | * {@inheritdoc}
203 | */
204 | public function getParsedBody()
205 | {
206 | return $this->parsedBody;
207 | }
208 |
209 | /**
210 | * {@inheritdoc}
211 | */
212 | public function withParsedBody($data): ServerRequestInterface
213 | {
214 | $this->assertParsedBody($data);
215 |
216 | $clone = clone $this;
217 | $clone->parsedBody = $data;
218 |
219 | return $clone;
220 | }
221 |
222 | /**
223 | * {@inheritdoc}
224 | */
225 | public function getAttributes(): array
226 | {
227 | return $this->attributes;
228 | }
229 |
230 | /**
231 | * {@inheritdoc}
232 | */
233 | public function getAttribute($name, $default = null)
234 | {
235 | return isset($this->attributes[$name]) ? $this->attributes[$name] : $default;
236 | }
237 |
238 | /**
239 | * {@inheritdoc}
240 | */
241 | public function withAttribute($name, $value): ServerRequestInterface
242 | {
243 | $clone = clone $this;
244 | $clone->attributes[$name] = $value;
245 |
246 | return $clone;
247 | }
248 |
249 | /**
250 | * {@inheritdoc}
251 | */
252 | public function withoutAttribute($name): ServerRequestInterface
253 | {
254 | $clone = clone $this;
255 |
256 | if (isset($this->attributes[$name])) {
257 | unset($clone->attributes[$name]);
258 | }
259 |
260 | return $clone;
261 | }
262 |
263 | /*
264 | |--------------------------------------------------------------------------
265 | | Non-PSR-7 Methods.
266 | |--------------------------------------------------------------------------
267 | */
268 |
269 | /**
270 | * Check out whether an array is compatible to PSR-7 file structure.
271 | *
272 | * @param array $values The array to check.
273 | *
274 | * @return void
275 | *
276 | * @throws InvalidArgumentException
277 | */
278 | protected function assertUploadedFiles(array $values): void
279 | {
280 | foreach ($values as $value) {
281 | if (is_array($value)) {
282 | $this->assertUploadedFiles($value);
283 | } elseif (!($value instanceof UploadedFileInterface)) {
284 | throw new InvalidArgumentException(
285 | 'Invalid PSR-7 array structure for handling UploadedFile.'
286 | );
287 | }
288 | }
289 | }
290 |
291 | /**
292 | * Throw an exception if an unsupported argument type is provided.
293 | *
294 | * @param string|array|null $data The deserialized body data. This will
295 | * typically be in an array or object.
296 | *
297 | * @return void
298 | *
299 | * @throws InvalidArgumentException
300 | */
301 | protected function assertParsedBody($data): void
302 | {
303 | if (
304 | ! is_null($data) &&
305 | ! is_array($data) &&
306 | ! is_object($data)
307 | ) {
308 | throw new InvalidArgumentException(
309 | sprintf(
310 | 'Only accepts array, object and null, but "%s" provided.',
311 | gettype($data)
312 | )
313 | );
314 | }
315 | }
316 |
317 | /**
318 | * Confirm the content type and post values whether fit the requirement.
319 | *
320 | * @param array $postParams
321 | * @return void
322 | */
323 | protected function determineParsedBody(array $postParams)
324 | {
325 | $headerContentType = $this->getHeaderLine('Content-Type');
326 | $contentTypeArr = preg_split('/\s*[;,]\s*/', $headerContentType);
327 | $contentType = strtolower($contentTypeArr[0]);
328 | $httpMethod = strtoupper($this->getMethod());
329 |
330 | // Is it a form submit or not.
331 | $isForm = false;
332 |
333 | if ($httpMethod === 'POST') {
334 |
335 | // If the request Content-Type is either application/x-www-form-urlencoded
336 | // or multipart/form-data, and the request method is POST, this method MUST
337 | // return the contents of $_POST.
338 | $postRequiredContentTypes = [
339 | '', // For unit testing purpose.
340 | 'application/x-www-form-urlencoded',
341 | 'multipart/form-data',
342 | ];
343 |
344 | if (in_array($contentType, $postRequiredContentTypes)) {
345 | $this->parsedBody = $postParams ?? null;
346 | $isForm = true;
347 | }
348 | }
349 |
350 | // @codeCoverageIgnoreStart
351 | // Maybe other http methods such as PUT, DELETE, etc...
352 | if ($httpMethod !== 'GET' && !$isForm) {
353 |
354 | // If it a JSON formatted string?
355 | $isJson = false;
356 |
357 | // Receive content from PHP stdin input, if exists.
358 | $rawText = file_get_contents('php://input');
359 |
360 | if (!empty($rawText)) {
361 |
362 | if ($contentType === 'application/json') {
363 | $jsonParsedBody = json_decode($rawText);
364 | $isJson = (json_last_error() === JSON_ERROR_NONE);
365 | }
366 |
367 | // Condition 1 - It's a JSON, now the body is a JSON object.
368 | if ($isJson) {
369 | $this->parsedBody = $jsonParsedBody ?: null;
370 | }
371 |
372 | // Condition 2 - It's not a JSON, might be a http build query.
373 | if (!$isJson) {
374 | parse_str($rawText, $parsedStr);
375 | $this->parsedBody = $parsedStr ?: null;
376 | }
377 | }
378 | }
379 |
380 | // This part is manually tested by using PostMan.
381 | // @codeCoverageIgnoreEnd
382 | }
383 | }
384 |
--------------------------------------------------------------------------------
/src/Psr7/Stream.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7;
14 |
15 | use Psr\Http\Message\StreamInterface;
16 | use InvalidArgumentException;
17 | use RuntimeException;
18 |
19 | use function fclose;
20 | use function fread;
21 | use function fseek;
22 | use function fstat;
23 | use function ftell;
24 | use function fwrite;
25 | use function gettype;
26 | use function is_resource;
27 | use function preg_match;
28 | use function sprintf;
29 | use function stream_get_contents;
30 | use function stream_get_meta_data;
31 |
32 | use const SEEK_CUR;
33 | use const SEEK_END;
34 | use const SEEK_SET;
35 | use const PREG_OFFSET_CAPTURE;
36 |
37 | /*
38 | * Describes a data stream.
39 | */
40 | class Stream implements StreamInterface
41 | {
42 | /**
43 | * @var bool
44 | */
45 | protected $readable;
46 |
47 | /**
48 | * @var bool
49 | */
50 | protected $writable;
51 |
52 | /**
53 | * @var bool
54 | */
55 | protected $seekable;
56 |
57 | /**
58 | * The size of the stream.
59 | *
60 | * @var int|null
61 | */
62 | protected $size;
63 |
64 | /**
65 | * The keys returned are identical to the keys returned from PHP's
66 | * stream_get_meta_data() function.
67 | *
68 | * @var array
69 | */
70 | protected $meta;
71 |
72 | /**
73 | * Typically a PHP resource.
74 | *
75 | * @var resource
76 | */
77 | protected $stream;
78 |
79 | /**
80 | * Stream constructor
81 | *
82 | * @param resource $stream A valid resource.
83 | */
84 | public function __construct($stream)
85 | {
86 | $this->assertStream($stream);
87 | $this->stream = $stream;
88 |
89 | $meta = $this->getMetadata();
90 |
91 | $this->readable = false;
92 | $this->writable = false;
93 |
94 | // The mode parameter specifies the type of access you require to
95 | // the stream. @see https://www.php.net/manual/en/function.fopen.php
96 | if (strpos($meta['mode'], '+') !== false) {
97 | $this->readable = true;
98 | $this->writable = true;
99 | }
100 |
101 | if (preg_match('/^[waxc][t|b]{0,1}$/', $meta['mode'], $matches, PREG_OFFSET_CAPTURE)) {
102 | $this->writable = true;
103 | }
104 |
105 | if (strpos($meta['mode'], 'r') !== false) {
106 | $this->readable = true;
107 | }
108 |
109 | $this->seekable = $meta['seekable'];
110 | }
111 |
112 | /**
113 | * {@inheritdoc}
114 | */
115 | public function isWritable(): bool
116 | {
117 | return $this->writable;
118 | }
119 |
120 | /**
121 | * {@inheritdoc}
122 | */
123 | public function isReadable(): bool
124 | {
125 | return $this->readable;
126 | }
127 |
128 | /**
129 | * {@inheritdoc}
130 | */
131 | public function isSeekable(): bool
132 | {
133 | return $this->seekable;
134 | }
135 |
136 | /**
137 | * {@inheritdoc}
138 | */
139 | public function close(): void
140 | {
141 | if ($this->isStream()) {
142 | fclose($this->stream);
143 | }
144 |
145 | $this->detach();
146 | }
147 |
148 | /**
149 | * {@inheritdoc}
150 | */
151 | public function detach()
152 | {
153 | if (!$this->isStream()) {
154 | return null;
155 | }
156 |
157 | $legacy = $this->stream;
158 |
159 | $this->readable = false;
160 | $this->writable = false;
161 | $this->seekable = false;
162 | $this->size = null;
163 | $this->meta = [];
164 |
165 | unset($this->stream);
166 |
167 | return $legacy;
168 | }
169 |
170 | /**
171 | * {@inheritdoc}
172 | */
173 | public function getSize(): ?int
174 | {
175 | if (!$this->isStream()) {
176 | return null;
177 | }
178 |
179 | if ($this->size === null) {
180 | $stats = fstat($this->stream);
181 | $this->size = $stats['size'] ?? null;
182 | }
183 |
184 | return $this->size;
185 | }
186 |
187 | /**
188 | * {@inheritdoc}
189 | */
190 | public function tell(): int
191 | {
192 | $this->assertPropertyStream();
193 |
194 | $pointer = false;
195 |
196 | if ($this->stream) {
197 | $pointer = ftell($this->stream);
198 | }
199 |
200 | if ($pointer === false) {
201 |
202 | // @codeCoverageIgnoreStart
203 |
204 | throw new RuntimeException(
205 | 'Unable to get the position of the file pointer in stream.'
206 | );
207 |
208 | // @codeCoverageIgnoreEnd
209 | }
210 |
211 | return $pointer;
212 | }
213 |
214 | /**
215 | * {@inheritdoc}
216 | */
217 | public function eof(): bool
218 | {
219 | return $this->stream ? feof($this->stream) : true;
220 | }
221 |
222 | /**
223 | * {@inheritdoc}
224 | */
225 | public function seek($offset, $whence = SEEK_SET): void
226 | {
227 | $this->assertPropertyStream();
228 |
229 | if (!$this->seekable) {
230 | throw new RuntimeException(
231 | 'Stream is not seekable.'
232 | );
233 | }
234 |
235 | $offset = (int) $offset;
236 | $whence = (int) $whence;
237 |
238 | $message = [
239 | SEEK_CUR => 'Set position to current location plus offset.',
240 | SEEK_END => 'Set position to end-of-stream plus offset.',
241 | SEEK_SET => 'Set position equal to offset bytes.',
242 | ];
243 |
244 | $errorMsg = $message[$whence] ?? 'Unknown error.';
245 |
246 | if (fseek($this->stream, $offset, $whence) === -1) {
247 | throw new RuntimeException(
248 | sprintf(
249 | '%s. Unable to seek to stream at position %s',
250 | $errorMsg,
251 | $offset
252 | )
253 | );
254 | }
255 | }
256 |
257 | /**
258 | * {@inheritdoc}
259 | */
260 | public function rewind(): void
261 | {
262 | $this->seek(0);
263 | }
264 |
265 | /**
266 | * {@inheritdoc}
267 | */
268 | public function write($string): int
269 | {
270 | $this->assertPropertyStream();
271 |
272 | $size = 0;
273 |
274 | if ($this->isWritable()) {
275 | $size = fwrite($this->stream, $string);
276 | }
277 |
278 | if ($size === false) {
279 |
280 | // @codeCoverageIgnoreStart
281 |
282 | throw new RuntimeException(
283 | 'Unable to write to stream.'
284 | );
285 |
286 | // @codeCoverageIgnoreEnd
287 | }
288 |
289 | // Make sure that `getSize()`will count the correct size again after writing anything.
290 | $this->size = null;
291 |
292 | return $size;
293 | }
294 |
295 | /**
296 | * {@inheritdoc}
297 | */
298 | public function read($length): string
299 | {
300 | $this->assertPropertyStream();
301 |
302 | $string = false;
303 |
304 | if ($this->isReadable()) {
305 | $string = fread($this->stream, $length);
306 | }
307 |
308 | if ($string === false) {
309 |
310 | // @codeCoverageIgnoreStart
311 |
312 | throw new RuntimeException(
313 | 'Unable to read from stream.'
314 | );
315 |
316 | // @codeCoverageIgnoreEnd
317 | }
318 |
319 | return $string;
320 | }
321 |
322 | /**
323 | * {@inheritdoc}
324 | */
325 | public function getContents(): string
326 | {
327 | $this->assertPropertyStream();
328 |
329 | $string = false;
330 |
331 | if ($this->isReadable()) {
332 | $string = stream_get_contents($this->stream);
333 | }
334 |
335 | if ($string === false) {
336 | throw new RuntimeException(
337 | 'Unable to read stream contents.'
338 | );
339 | }
340 |
341 | return $string;
342 | }
343 |
344 | /**
345 | * {@inheritdoc}
346 | */
347 | public function getMetadata($key = null)
348 | {
349 | if ($this->isStream()) {
350 | $this->meta = stream_get_meta_data($this->stream);
351 |
352 | if (!$key) {
353 | return $this->meta;
354 | }
355 |
356 | if (isset($this->meta[$key])) {
357 | return $this->meta[$key];
358 | }
359 | }
360 |
361 | return null;
362 | }
363 |
364 | /**
365 | * {@inheritdoc}
366 | */
367 | public function __toString(): string
368 | {
369 | if ($this->isSeekable()) {
370 | $this->rewind();
371 | }
372 |
373 | return $this->getContents();
374 | }
375 |
376 | /*
377 | |--------------------------------------------------------------------------
378 | | Non PSR-7 Methods.
379 | |--------------------------------------------------------------------------
380 | */
381 |
382 | /**
383 | * Throw exception if stream is not a valid PHP resource.
384 | *
385 | * @param resource $stream A valid resource.
386 | *
387 | * @return void
388 | *
389 | * InvalidArgumentException
390 | */
391 | protected function assertStream($stream): void
392 | {
393 | if (!is_resource($stream)) {
394 | throw new InvalidArgumentException(
395 | sprintf(
396 | 'Stream should be a resource, but "%s" provided.',
397 | gettype($stream)
398 | )
399 | );
400 | }
401 | }
402 |
403 | /**
404 | * Throw an exception if the property does not exist.
405 | *
406 | * @return RuntimeException
407 | */
408 | protected function assertPropertyStream(): void
409 | {
410 | if (!$this->isStream()) {
411 | throw new RuntimeException(
412 | 'Stream does not exist.'
413 | );
414 | }
415 | }
416 |
417 | /**
418 | * Check if stream exists or not.
419 | *
420 | * @return bool
421 | */
422 | protected function isStream(): bool
423 | {
424 | return (isset($this->stream) && is_resource($this->stream));
425 | }
426 | }
427 |
--------------------------------------------------------------------------------
/src/Psr7/UploadedFile.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7;
14 |
15 | use Psr\Http\Message\StreamInterface;
16 | use Psr\Http\Message\UploadedFileInterface;
17 | use Shieldon\Psr7\Stream;
18 | use InvalidArgumentException;
19 | use RuntimeException;
20 |
21 | use function file_exists;
22 | use function file_put_contents;
23 | use function is_string;
24 | use function is_uploaded_file;
25 | use function is_writable;
26 | use function move_uploaded_file;
27 | use function php_sapi_name;
28 | use function rename;
29 |
30 | use const UPLOAD_ERR_CANT_WRITE;
31 | use const UPLOAD_ERR_EXTENSION;
32 | use const UPLOAD_ERR_FORM_SIZE;
33 | use const UPLOAD_ERR_INI_SIZE;
34 | use const UPLOAD_ERR_NO_FILE;
35 | use const UPLOAD_ERR_NO_TMP_DIR;
36 | use const UPLOAD_ERR_OK;
37 | use const UPLOAD_ERR_PARTIAL;
38 | use const LOCK_EX;
39 |
40 | /*
41 | * Describes a data stream.
42 | */
43 | class UploadedFile implements UploadedFileInterface
44 | {
45 | /**
46 | * The full path of the file provided by client.
47 | *
48 | * @var string|null
49 | */
50 | protected $file;
51 |
52 | /**
53 | * A stream representing the uploaded file.
54 | *
55 | * @var StreamInterface|null
56 | */
57 | protected $stream;
58 |
59 | /**
60 | * Is file copy to stream when first time calling getStream().
61 | *
62 | * @var bool
63 | */
64 | protected $isFileToStream = false;
65 |
66 | /**
67 | * The file size based on the "size" key in the $_FILES array.
68 | *
69 | * @var int|null
70 | */
71 | protected $size;
72 |
73 | /**
74 | * The filename based on the "name" key in the $_FILES array.
75 | *
76 | * @var string|null
77 | */
78 | protected $name;
79 |
80 | /**
81 | * The type of a file. This value is based on the "type" key in the $_FILES array.
82 | *
83 | * @var string|null
84 | */
85 | protected $type;
86 |
87 | /**
88 | * The error code associated with the uploaded file.
89 | *
90 | * @var int
91 | */
92 | protected $error;
93 |
94 | /**
95 | * Check if the uploaded file has been moved or not.
96 | *
97 | * @var bool
98 | */
99 | protected $isMoved = false;
100 |
101 | /**
102 | * The type of interface between web server and PHP.
103 | * This value is typically from `php_sapi_name`, might be changed ony for
104 | * unit testing purpose.
105 | *
106 | * @var string
107 | */
108 | private $sapi;
109 |
110 | /**
111 | * UploadedFile constructor.
112 | *
113 | * @param string|StreamInterface $source The full path of a file or stream.
114 | * @param string|null $name The file name.
115 | * @param string|null $type The file media type.
116 | * @param int|null $size The file size in bytes.
117 | * @param int $error The status code of the upload.
118 | * @param string|null $sapi Only assign for unit testing purpose.
119 | */
120 | public function __construct(
121 | $source ,
122 | ?string $name = null,
123 | ?string $type = null,
124 | ?int $size = null,
125 | int $error = 0 ,
126 | ?string $sapi = null
127 | ) {
128 |
129 | if (is_string($source)) {
130 | $this->file = $source;
131 |
132 | } elseif ($source instanceof StreamInterface) {
133 | $this->stream = $source;
134 |
135 | } else {
136 | throw new InvalidArgumentException(
137 | 'First argument accepts only a string or StreamInterface instance.'
138 | );
139 | }
140 |
141 | $this->name = $name;
142 | $this->type = $type;
143 | $this->size = $size;
144 | $this->error = $error;
145 | $this->sapi = php_sapi_name();
146 |
147 | if ($sapi) {
148 | $this->sapi = $sapi;
149 | }
150 | }
151 |
152 | /**
153 | * {@inheritdoc}
154 | */
155 | public function getStream(): StreamInterface
156 | {
157 | if ($this->isMoved) {
158 | throw new RuntimeException(
159 | 'The stream has been moved.'
160 | );
161 | }
162 |
163 | if (!$this->isFileToStream && !$this->stream) {
164 | $resource = @fopen($this->file, 'r');
165 | if (is_resource($resource)) {
166 | $this->stream = new Stream($resource);
167 | }
168 | $this->isFileToStream = true;
169 | }
170 |
171 | if (!$this->stream) {
172 | throw new RuntimeException(
173 | 'No stream is available or can be created.'
174 | );
175 | }
176 |
177 | return $this->stream;
178 | }
179 |
180 | /**
181 | * {@inheritdoc}
182 | */
183 | public function moveTo($targetPath): void
184 | {
185 | if ($this->isMoved) {
186 | // Throw exception on the second or subsequent call to the method.
187 | throw new RuntimeException(
188 | 'Uploaded file already moved'
189 | );
190 | }
191 |
192 | if (!is_writable(dirname($targetPath))) {
193 | // Throw exception if the $targetPath specified is invalid.
194 | throw new RuntimeException(
195 | sprintf(
196 | 'The target path "%s" is not writable.',
197 | $targetPath
198 | )
199 | );
200 | }
201 |
202 | // Is a file..
203 | if (is_string($this->file) && ! empty($this->file)) {
204 |
205 | if ($this->sapi === 'cli') {
206 |
207 | if (!rename($this->file, $targetPath)) {
208 |
209 | // @codeCoverageIgnoreStart
210 |
211 | // Throw exception on any error during the move operation.
212 | throw new RuntimeException(
213 | sprintf(
214 | 'Could not rename the file to the target path "%s".',
215 | $targetPath
216 | )
217 | );
218 |
219 | // @codeCoverageIgnoreEnd
220 | }
221 | } else {
222 |
223 | if (
224 | ! is_uploaded_file($this->file) ||
225 | ! move_uploaded_file($this->file, $targetPath)
226 | ) {
227 | // Throw exception on any error during the move operation.
228 | throw new RuntimeException(
229 | sprintf(
230 | 'Could not move the file to the target path "%s".',
231 | $targetPath
232 | )
233 | );
234 | }
235 | }
236 |
237 | } elseif ($this->stream instanceof StreamInterface) {
238 | $content = $this->stream->getContents();
239 |
240 | file_put_contents($targetPath, $content, LOCK_EX);
241 |
242 | // @codeCoverageIgnoreStart
243 |
244 | if (!file_exists($targetPath)) {
245 | // Throw exception on any error during the move operation.
246 | throw new RuntimeException(
247 | sprintf(
248 | 'Could not move the stream to the target path "%s".',
249 | $targetPath
250 | )
251 | );
252 | }
253 |
254 | // @codeCoverageIgnoreEnd
255 |
256 | unset($content, $this->stream);
257 | }
258 |
259 | $this->isMoved = true;
260 | }
261 |
262 | /**
263 | * {@inheritdoc}
264 | */
265 | public function getSize(): ?int
266 | {
267 | return $this->size;
268 | }
269 |
270 | /**
271 | * {@inheritdoc}
272 | */
273 | public function getError(): int
274 | {
275 | return $this->error;
276 | }
277 |
278 | /**
279 | * {@inheritdoc}
280 | */
281 | public function getClientFilename(): ?string
282 | {
283 | return $this->name;
284 | }
285 |
286 | /**
287 | * {@inheritdoc}
288 | */
289 | public function getClientMediaType(): ?string
290 | {
291 | return $this->type;
292 | }
293 |
294 | /*
295 | |--------------------------------------------------------------------------
296 | | Non-PSR-7 Methods.
297 | |--------------------------------------------------------------------------
298 | */
299 |
300 | /**
301 | * Get error message when uploading files.
302 | *
303 | * @return string
304 | */
305 | public function getErrorMessage(): string
306 | {
307 | $message = [
308 | UPLOAD_ERR_INI_SIZE => 'The uploaded file exceeds the upload_max_filesize directive in php.ini',
309 | UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.',
310 | UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded.',
311 | UPLOAD_ERR_NO_FILE => 'No file was uploaded.',
312 | UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder.',
313 | UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk.',
314 | UPLOAD_ERR_EXTENSION => 'File upload stopped by extension.',
315 | UPLOAD_ERR_OK => 'There is no error, the file uploaded with success.',
316 | ];
317 |
318 | return $message[$this->error] ?? 'Unknown upload error.';
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/src/Psr7/Uri.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7;
14 |
15 | use Psr\Http\Message\UriInterface;
16 | use InvalidArgumentException;
17 |
18 | use function filter_var;
19 | use function gettype;
20 | use function is_integer;
21 | use function is_null;
22 | use function is_string;
23 | use function ltrim;
24 | use function parse_url;
25 | use function rawurlencode;
26 | use function sprintf;
27 |
28 | /*
29 | * Value object representing a URI.
30 | */
31 | class Uri implements UriInterface
32 | {
33 | /**
34 | * foo://example.com:8042/over/there?name=ferret#nose
35 | * \_/ \______________/\_________/ \_________/ \__/
36 | * | | | | |
37 | * scheme authority path query fragment
38 | */
39 |
40 | /**
41 | * The scheme component of the URI.
42 | * For example, https://terryl.in/
43 | * In this case, "https" is the scheme.
44 | *
45 | * @var string
46 | */
47 | protected $scheme;
48 |
49 | /**
50 | * The user component of the URI.
51 | * For example, https://jack:1234@terryl.in
52 | * In this case, "jack" is the user.
53 | *
54 | * @var string
55 | */
56 | protected $user;
57 |
58 | /**
59 | * The password component of the URI.
60 | * For example, http://jack:1234@terryl.in
61 | * In this case, "1234" is the password.
62 | *
63 | * @var string
64 | */
65 | protected $pass;
66 |
67 | /**
68 | * The host component of the URI.
69 | * For example, https://terryl.in:443/zh/
70 | * In this case, "terryl.in" is the host.
71 | *
72 | * @var string
73 | */
74 | protected $host;
75 |
76 | /**
77 | * The port component of the URI.
78 | * For example, https://terryl.in:443
79 | * In this case, "443" is the port.
80 | *
81 | * @var int|null
82 | */
83 | protected $port;
84 |
85 | /**
86 | * The path component of the URI.
87 | * For example, https://terryl.in/zh/?paged=2
88 | * In this case, "/zh/" is the path.
89 | *
90 | * @var string
91 | */
92 | protected $path;
93 |
94 | /**
95 | * The query component of the URI.
96 | * For example, https://terryl.in/zh/?paged=2
97 | * In this case, "paged=2" is the query.
98 | *
99 | * @var string
100 | */
101 | protected $query;
102 |
103 | /**
104 | * The fragment component of the URI.
105 | * For example, https://terryl.in/#main-container
106 | * In this case, "main-container" is the fragment.
107 | *
108 | * @var string
109 | */
110 | protected $fragment;
111 |
112 | /**
113 | * Uri constructor.
114 | *
115 | * @param string $uri The URI.
116 | */
117 | public function __construct($uri = '')
118 | {
119 | $this->assertString($uri, 'uri');
120 | $this->init((array) parse_url($uri));
121 | }
122 |
123 | /**
124 | * {@inheritdoc}
125 | */
126 | public function getScheme(): string
127 | {
128 | return $this->scheme;
129 | }
130 |
131 | /**
132 | * {@inheritdoc}
133 | */
134 | public function getAuthority(): string
135 | {
136 | $authority = '';
137 |
138 | if ($this->getUserInfo()) {
139 | $authority .= $this->getUserInfo() . '@';
140 | }
141 |
142 | $authority .= $this->getHost();
143 |
144 | if (!is_null($this->getPort())) {
145 | $authority .= ':' . $this->getPort();
146 | }
147 |
148 | return $authority;
149 | }
150 |
151 | /**
152 | * {@inheritdoc}
153 | */
154 | public function getUserInfo(): string
155 | {
156 | $userInfo = $this->user;
157 |
158 | if ($this->pass !== '') {
159 | $userInfo .= ':' . $this->pass;
160 | }
161 |
162 | return $userInfo;
163 | }
164 |
165 | /**
166 | * {@inheritdoc}
167 | */
168 | public function getHost(): string
169 | {
170 | return $this->host;
171 | }
172 |
173 | /**
174 | * {@inheritdoc}
175 | */
176 | public function getPort(): ?int
177 | {
178 | return $this->port;
179 | }
180 |
181 | /**
182 | * {@inheritdoc}
183 | */
184 | public function getPath(): string
185 | {
186 | return $this->path;
187 | }
188 |
189 | /**
190 | * {@inheritdoc}
191 | */
192 | public function getQuery(): string
193 | {
194 | return $this->query;
195 | }
196 |
197 | /**
198 | * {@inheritdoc}
199 | */
200 | public function getFragment(): string
201 | {
202 | return $this->fragment;
203 | }
204 |
205 | /**
206 | * {@inheritdoc}
207 | */
208 | public function withScheme($scheme): UriInterface
209 | {
210 | $this->assertScheme($scheme);
211 |
212 | $scheme = $this->filter('scheme', ['scheme' => $scheme]);
213 |
214 | $clone = clone $this;
215 | $clone->scheme = $scheme;
216 | return $clone;
217 | }
218 |
219 | /**
220 | * {@inheritdoc}
221 | */
222 | public function withUserInfo($user, $pass = null): UriInterface
223 | {
224 | $this->assertString($user, 'user');
225 | $user = $this->filter('user', ['user' => $user]);
226 |
227 | if ($pass) {
228 | $this->assertString($pass, 'pass');
229 | $pass = $this->filter('pass', ['pass' => $pass]);
230 | }
231 |
232 | $clone = clone $this;
233 | $clone->user = $user;
234 | $clone->pass = $pass;
235 |
236 | return $clone;
237 | }
238 |
239 | /**
240 | * {@inheritdoc}
241 | */
242 | public function withHost($host): UriInterface
243 | {
244 | $this->assertHost($host);
245 |
246 | $host = $this->filter('host', ['host' => $host]);
247 |
248 | $clone = clone $this;
249 | $clone->host = $host;
250 |
251 | return $clone;
252 | }
253 |
254 | /**
255 | * {@inheritdoc}
256 | */
257 | public function withPort($port): UriInterface
258 | {
259 | $this->assertPort($port);
260 |
261 | $port = $this->filter('port', ['port' => $port]);
262 |
263 | $clone = clone $this;
264 | $clone->port = $port;
265 |
266 | return $clone;
267 | }
268 |
269 | /**
270 | * {@inheritdoc}
271 | */
272 | public function withPath($path): UriInterface
273 | {
274 | $this->assertString($path, 'path');
275 |
276 | $path = $this->filter('path', ['path' => $path]);
277 |
278 | $clone = clone $this;
279 | $clone->path = '/' . rawurlencode(ltrim($path, '/'));
280 |
281 | return $clone;
282 | }
283 |
284 | /**
285 | * {@inheritdoc}
286 | */
287 | public function withQuery($query): UriInterface
288 | {
289 | $this->assertString($query, 'query');
290 |
291 | $query = $this->filter('query', ['query' => $query]);
292 |
293 | // & => %26
294 | // ? => %3F
295 |
296 | $clone = clone $this;
297 | $clone->query = $query;
298 |
299 | return $clone;
300 | }
301 |
302 | /**
303 | * {@inheritdoc}
304 | */
305 | public function withFragment($fragment): UriInterface
306 | {
307 | $this->assertString($fragment, 'fragment');
308 |
309 | $fragment = $this->filter('fragment', ['fragment' => $fragment]);
310 |
311 | $clone = clone $this;
312 | $clone->fragment = $fragment;
313 |
314 | return $clone;
315 | }
316 |
317 | /**
318 | * {@inheritdoc}
319 | */
320 | public function __toString(): string
321 | {
322 | $uri = '';
323 |
324 | // If a scheme is present, it MUST be suffixed by ":".
325 | if ($this->getScheme() !== '') {
326 | $uri .= $this->getScheme() . ':';
327 | }
328 |
329 | // If an authority is present, it MUST be prefixed by "//".
330 | if ($this->getAuthority() !== '') {
331 | $uri .= '//' . $this->getAuthority();
332 | }
333 |
334 | // If the path is rootless and an authority is present, the path MUST
335 | // be prefixed by "/".
336 | $uri .= '/' . ltrim($this->getPath(), '/');
337 |
338 | // If a query is present, it MUST be prefixed by "?".
339 | if ($this->getQuery() !== '') {
340 | $uri .= '?' . $this->getQuery();
341 | }
342 |
343 | // If a fragment is present, it MUST be prefixed by "#".
344 | if ($this->getFragment() !== '') {
345 | $uri .= '#' . $this->getFragment();
346 | }
347 |
348 | return $uri;
349 | }
350 |
351 | /*
352 | |--------------------------------------------------------------------------
353 | | Non PSR-7 Methods.
354 | |--------------------------------------------------------------------------
355 | */
356 |
357 | /**
358 | * Initialize.
359 | *
360 | * @param array $data Parsed URL data.
361 | *
362 | * @return void
363 | */
364 | protected function init(array $data = []): void
365 | {
366 | $components = [
367 | 'scheme',
368 | 'user',
369 | 'pass',
370 | 'host',
371 | 'port',
372 | 'path',
373 | 'query',
374 | 'fragment'
375 | ];
376 |
377 | foreach ($components as $v) {
378 | $this->{$v} = $this->filter($v, $data);
379 | }
380 | }
381 |
382 | /**
383 | * Filter URI components.
384 | *
385 | * Users can provide both encoded and decoded characters.
386 | * Implementations ensure the correct encoding as outlined.
387 | * @see https://tools.ietf.org/html/rfc3986#section-2.2
388 | *
389 | * @param string $key The part of URI.
390 | * @param array $data Data parsed from a given URL.
391 | *
392 | * @return string|int|null
393 | */
394 | protected function filter(string $key, $data)
395 | {
396 | $notExists = [
397 | 'scheme' => '',
398 | 'user' => '',
399 | 'pass' => '',
400 | 'host' => '',
401 | 'port' => null,
402 | 'path' => '',
403 | 'query' => '',
404 | 'fragment' => '',
405 | ];
406 |
407 | if (!isset($data[$key])) {
408 | return $notExists[$key];
409 | }
410 |
411 | $value = $data[$key];
412 |
413 | // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
414 | // $genDelims = ':/\?#\[\]@';
415 |
416 | // sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
417 | // / "*" / "+" / "," / ";" / "="
418 | $subDelims = '!\$&\'\(\)\*\+,;=';
419 |
420 | // $unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
421 | $unReserved = 'a-zA-Z0-9\-\._~';
422 |
423 | // Encoded characters, such as "?" encoded to "%3F".
424 | $encodePattern = '%(?![A-Fa-f0-9]{2})';
425 |
426 | $regex = '';
427 |
428 | switch ($key) {
429 | case 'host':
430 | case 'scheme':
431 | return strtolower($value);
432 | break;
433 |
434 | case 'query':
435 | case 'fragment':
436 | $specPattern = '%:@\/\?';
437 | $regex = '/(?:[^' . $unReserved . $subDelims . $specPattern . ']+|' . $encodePattern . ')/';
438 | break;
439 |
440 | case 'path':
441 | $specPattern = '%:@\/';
442 | $regex = '/(?:[^' . $unReserved . $subDelims . $specPattern . ']+|' . $encodePattern . ')/';
443 | break;
444 |
445 | case 'user':
446 | case 'pass':
447 | $regex = '/(?:[^%' . $unReserved . $subDelims . ']+|' . $encodePattern . ')/';
448 | break;
449 |
450 | case 'port':
451 | if ($this->scheme === 'http' && (int) $value !== 80) {
452 | return (int) $value;
453 | }
454 | if ($this->scheme === 'https' && (int) $value !== 443) {
455 | return (int) $value;
456 | }
457 | if ($this->scheme === '') {
458 | return (int) $value;
459 | }
460 | return null;
461 |
462 | // endswitch
463 | }
464 |
465 | if ($regex) {
466 | return preg_replace_callback(
467 | $regex,
468 | function ($match) {
469 | return rawurlencode($match[0]);
470 | },
471 | $value
472 | );
473 | }
474 |
475 | // @codeCoverageIgnoreStart
476 |
477 | return $value;
478 |
479 | // @codeCoverageIgnoreEnd
480 | }
481 |
482 | /**
483 | * Throw exception for the invalid scheme.
484 | *
485 | * @param string $scheme The scheme string of a URI.
486 | *
487 | * @return void
488 | *
489 | * @throws InvalidArgumentException
490 | */
491 | protected function assertScheme($scheme): void
492 | {
493 | $this->assertString($scheme, 'scheme');
494 |
495 | $validSchemes = [
496 | 0 => '',
497 | 1 => 'http',
498 | 2 => 'https',
499 | ];
500 |
501 | if (!in_array($scheme, $validSchemes)) {
502 | throw new InvalidArgumentException(
503 | sprintf(
504 | 'The string "%s" is not a valid scheme.',
505 | $scheme
506 | )
507 | );
508 | }
509 | }
510 |
511 | /**
512 | * Throw exception for the invalid value.
513 | *
514 | * @param string $value The value to check.
515 | * @param string $name The name of the value.
516 | *
517 | * @return void
518 | *
519 | * @throws InvalidArgumentException
520 | */
521 | protected function assertString($value, string $name = 'it'): void
522 | {
523 | if (!is_string($value)) {
524 | throw new InvalidArgumentException(
525 | sprintf(
526 | ucfirst($name) . ' must be a string, but %s provided.',
527 | gettype($value)
528 | )
529 | );
530 | }
531 | }
532 |
533 | /**
534 | * Throw exception for the invalid host string.
535 | *
536 | * @param string $host The host string to of a URI.
537 | *
538 | * @return void
539 | *
540 | * @throws InvalidArgumentException
541 | */
542 | protected function assertHost($host): void
543 | {
544 | $this->assertString($host);
545 |
546 | if (empty($host)) {
547 | // Note: An empty host value is equivalent to removing the host.
548 | // So that if the host is empty, ignore the following check.
549 | return;
550 | }
551 |
552 | if (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
553 | throw new InvalidArgumentException(
554 | sprintf(
555 | '"%s" is not a valid host',
556 | $host
557 | )
558 | );
559 | }
560 | }
561 |
562 | /**
563 | * Throw exception for the invalid port.
564 | *
565 | * @param null|int $port The port number to of a URI.
566 | *
567 | * @return void
568 | *
569 | * @throws InvalidArgumentException
570 | */
571 | protected function assertPort($port): void
572 | {
573 | if (
574 | !is_null($port) &&
575 | !is_integer($port)
576 | ) {
577 | throw new InvalidArgumentException(
578 | sprintf(
579 | 'Port must be an integer or a null value, but %s provided.',
580 | gettype($port)
581 | )
582 | );
583 | }
584 |
585 | if (!($port > 0 && $port < 65535)) {
586 | throw new InvalidArgumentException(
587 | sprintf(
588 | 'Port number should be in a range of 0-65535, but %s provided.',
589 | $port
590 | )
591 | );
592 | }
593 | }
594 | }
595 |
--------------------------------------------------------------------------------
/src/Psr7/Utils/UploadedFileHelper.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Psr7\Utils;
14 |
15 | use Shieldon\Psr7\UploadedFile;
16 |
17 | use function array_merge_recursive;
18 | use function rtrim;
19 | use function is_array;
20 | use function is_string;
21 | use function is_numeric;
22 |
23 | /*
24 | * The helper functions for converting $_FILES to an array of UploadedFile
25 | * instance, only used on ServerRequest class.
26 | * This class is not a part of PSR 7.
27 | */
28 | class UploadedFileHelper
29 | {
30 | /**
31 | * Create an array for PSR-7 Uploaded File needed.
32 | *
33 | * @param array $files An array generally from $_FILES
34 | * @param bool $isConvert To covert and return $files as an UploadedFile instance.
35 | *
36 | * @return array|UploadedFile
37 | */
38 | public static function uploadedFileParse(array $files)
39 | {
40 | $specTree = [];
41 |
42 | $specFields = [
43 | 0 => 'tmp_name',
44 | 1 => 'name',
45 | 2 => 'type',
46 | 3 => 'size',
47 | 4 => 'error',
48 | ];
49 |
50 | foreach ($files as $fileKey => $fileValue) {
51 | if (!isset($fileValue['tmp_name'])) {
52 | // @codeCoverageIgnoreStart
53 | return [];
54 | // @codeCoverageIgnoreEnd
55 | }
56 |
57 | if (is_string($fileValue['tmp_name']) || is_numeric($fileValue['tmp_name'])) {
58 | $specTree[$fileKey] = $fileValue;
59 |
60 | } elseif (is_array($fileValue['tmp_name'])) {
61 |
62 | $tmp = [];
63 |
64 | // We want to find out how many levels of array it has.
65 | foreach ($specFields as $i => $attr) {
66 | $tmp[$i] = self::uploadedFileNestedFields($fileValue, $attr);
67 | }
68 |
69 | $parsedTree = array_merge_recursive(
70 | $tmp[0], // tmp_name
71 | $tmp[1], // name
72 | $tmp[2], // type
73 | $tmp[3], // size
74 | $tmp[4] // error
75 | );
76 |
77 | $specTree[$fileKey] = $parsedTree;
78 | unset($tmp, $parsedTree);
79 | }
80 | }
81 |
82 | return self::uploadedFileArrayTrim($specTree);
83 | }
84 |
85 | /**
86 | * Find out how many levels of an array it has.
87 | *
88 | * @param array $files Data structure from $_FILES.
89 | * @param string $attr The attributes of a file.
90 | *
91 | * @return array
92 | */
93 | public static function uploadedFileNestedFields(array $files, string $attr): array
94 | {
95 | $result = [];
96 | $values = $files;
97 |
98 | if (isset($files[$attr])) {
99 | $values = $files[$attr];
100 | }
101 |
102 | foreach ($values as $key => $value) {
103 |
104 | /**
105 | * Hereby to add `_` to be a part of the key for letting `array_merge_recursive`
106 | * method can deal with numeric keys as string keys.
107 | * It will be restored in the next step.
108 | *
109 | * @see uploadedFileArrayTrim
110 | */
111 | if (is_numeric($key)) {
112 | $key .= '_';
113 | }
114 |
115 | if (is_array($value)) {
116 | $result[$key] = self::uploadedFileNestedFields($value, $attr);
117 | } else {
118 | $result[$key][$attr] = $value;
119 | }
120 | }
121 |
122 | return $result;
123 | }
124 |
125 | /**
126 | * That's because that PHP function `array_merge_recursive` has the different
127 | * results as dealing with string keys and numeric keys.
128 | * In the previous step, we made numeric keys to stringify, so that we want to
129 | * restore them back to numeric ones.
130 | *
131 | * @param array|string $values
132 | *
133 | * @return array|string
134 | */
135 | public static function uploadedFileArrayTrim($values)
136 | {
137 | $result = [];
138 |
139 | if (is_array($values)) {
140 |
141 | foreach ($values as $key => $value) {
142 |
143 | // Restore the keys back to the original ones.
144 | $key = rtrim($key, '_');
145 |
146 | if (is_array($value)) {
147 | $result[$key] = self::uploadedFileArrayTrim($value);
148 | } else {
149 | $result[$key] = $value;
150 | }
151 | }
152 | }
153 |
154 | return $result;
155 | }
156 |
157 | /**
158 | * Convert the parse-ready array into PSR-7 specs.
159 | *
160 | * @param string|array $values
161 | *
162 | * @return array
163 | */
164 | public static function uploadedFileSpecsConvert($values)
165 | {
166 | $result = [];
167 |
168 | if (is_array($values)) {
169 |
170 | foreach ($values as $key => $value) {
171 |
172 | if (is_array($value)) {
173 |
174 | // Continue querying self, until a string is found.
175 | $result[$key] = self::uploadedFileSpecsConvert($value);
176 |
177 | } elseif ($key === 'tmp_name') {
178 |
179 | /**
180 | * Once one of the keys on the same level has been found,
181 | * then we can fetch the others at a time.
182 | * In this case, the `tmp_name` found.
183 | */
184 | $result = new uploadedFile(
185 | $values['tmp_name'],
186 | $values['name'],
187 | $values['type'],
188 | $values['size'],
189 | $values['error']
190 | );
191 | }
192 | }
193 | }
194 |
195 | return $result;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/tests/Psr15/ApiMiddleware.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr15;
12 |
13 | use Psr\Http\Server\RequestHandlerInterface;
14 | use Psr\Http\Message\ServerRequestInterface;
15 | use Psr\Http\Message\ResponseInterface;
16 | use Shieldon\Psr15\Middleware;
17 | use Shieldon\Psr7\Response;
18 |
19 | class ApiMiddleware extends Middleware
20 | {
21 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
22 | {
23 | $contentType = $request->getHeaderLine('Content-Type');
24 | $key = $request->getHeaderLine('key');
25 | $secret = $request->getHeaderLine('secret');
26 |
27 | if ($contentType !== 'application/json') {
28 | return (new Response)->withStatus(406, 'Content type is not accepted.');
29 | }
30 |
31 | if ($key !== '23492834234') {
32 | return (new Response)->withStatus(401, 'API key is invalid.');
33 | }
34 |
35 | if ($secret !== '1a163782ee166156294d173fcf8b8e87') {
36 | return (new Response)->withStatus(401, 'API secret is invalid.');
37 | }
38 |
39 | return $handler->handle($request);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/tests/Psr15/FinalHandler.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | declare(strict_types=1);
12 |
13 | namespace Shieldon\Test\Psr15;
14 |
15 | use Psr\Http\Message\ServerRequestInterface;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Shieldon\Psr15\RequestHandler;
18 | use Shieldon\Psr7\Response;
19 |
20 | /**
21 | * PSR-15 Middleware
22 | */
23 | class FinalHandler extends RequestHandler
24 | {
25 | public function handle(ServerRequestInterface $request): ResponseInterface
26 | {
27 | $response = new Response();
28 |
29 | if (!empty($request->getAttribute('string'))) {
30 | $response = $response->withStatus(200, 'OK');
31 | $response->getBody()->write('e04su3su;6');
32 | $response->getBody()->rewind();
33 | }
34 |
35 | return $response;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/Psr15/RequestHandlerTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr15;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Shieldon\Psr15\RequestHandler;
15 | use Shieldon\Psr17\ServerRequestFactory;
16 | use Shieldon\Psr17\Utils\SuperGlobal;
17 | use Shieldon\Test\Psr15\ApiMiddleware;
18 | use Shieldon\Test\Psr15\StringMiddleware;
19 |
20 | class RequestHandlerTest extends TestCase
21 | {
22 | public function test_requestHandler()
23 | {
24 | $app = new RequestHandler();
25 |
26 | $app->add(new ApiMiddleware());
27 | $app->add(new StringMiddleware());
28 |
29 | $response = $app->handle(ServerRequestFactory::fromGlobal());
30 |
31 | $this->assertEquals(406, $response->getStatusCode());
32 | $this->assertEquals('', $response->getBody()->getContents());
33 | }
34 |
35 | public function test_requestHandler_Condition_2()
36 | {
37 | $finalHandler = new FinalHandler();
38 |
39 | $app = new RequestHandler($finalHandler);
40 |
41 | $app->add(new ApiMiddleware());
42 | $app->add(new StringMiddleware());
43 |
44 | $request = ServerRequestFactory::fromGlobal();
45 |
46 | $request = $request->withHeader('Content-Type', 'application/json')->
47 | withHeader('key', '23492834234')->
48 | withHeader('secret', '1a163782ee166156294d173fcf8b8e87');
49 |
50 |
51 | $response = $app->handle($request);
52 |
53 | $this->assertEquals(200, $response->getStatusCode());
54 | $this->assertEquals('e04su3su;6', $response->getBody()->getContents());
55 | }
56 |
57 | public function test_requestHandler_Condition_3()
58 | {
59 | // Without a fallback handler...
60 | $app = new RequestHandler();
61 |
62 | $app->add(new ApiMiddleware());
63 | $app->add(new StringMiddleware());
64 |
65 | $request = ServerRequestFactory::fromGlobal();
66 |
67 | $request = $request->withHeader('Content-Type', 'application/json')->
68 | withHeader('key', '23492834234')->
69 | withHeader('secret', '1a163782ee166156294d173fcf8b8e87');
70 |
71 |
72 | $response = $app->handle($request);
73 |
74 | $this->assertEquals(200, $response->getStatusCode());
75 | $this->assertEquals('', $response->getBody()->getContents());
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/tests/Psr15/StringMiddleware.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr15;
12 |
13 | use Psr\Http\Server\RequestHandlerInterface;
14 | use Psr\Http\Message\ServerRequestInterface;
15 | use Psr\Http\Message\ResponseInterface;
16 | use Shieldon\Psr15\Middleware;
17 |
18 | class StringMiddleware extends Middleware
19 | {
20 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
21 | {
22 | $request = $request->withAttribute('string', 'e04');
23 |
24 | return $handler->handle($request);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/Psr17/RequestFactoryTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr17;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\RequestInterface;
15 | use Shieldon\Psr17\RequestFactory;
16 |
17 | class RequestFactoryTest extends TestCase
18 | {
19 | public function test_createRequest()
20 | {
21 | $requestFactory = new RequestFactory();
22 | $request = $requestFactory->createRequest('POST', 'https://www.google.com');
23 |
24 | $this->assertTrue(($request instanceof RequestInterface));
25 | }
26 |
27 | public function test_createNew()
28 | {
29 | $request = RequestFactory::fromNew();
30 |
31 | $this->assertTrue(($request instanceof RequestInterface));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Psr17/ResponseFactoryTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr17;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\ResponseInterface;
15 | use Shieldon\Psr17\ResponseFactory;
16 |
17 | class ResponseFactoryTest extends TestCase
18 | {
19 | public function test_createResponse()
20 | {
21 | $responseFactory = new ResponseFactory();
22 | $response = $responseFactory->createResponse(200, 'OK');
23 |
24 | $this->assertTrue(($response instanceof ResponseInterface));
25 | }
26 |
27 | public function test_createNew()
28 | {
29 | $response = ResponseFactory::fromNew();
30 |
31 | $this->assertTrue(($response instanceof ResponseInterface));
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/Psr17/ServerRequestFactoryTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr17;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\ServerRequestInterface;
15 | use Shieldon\Psr17\ServerRequestFactory;
16 | use Shieldon\Psr17\Utils\SuperGlobal;
17 |
18 | class ServerRequestFactoryTest extends TestCase
19 | {
20 | public function test_createServerRequest()
21 | {
22 | SuperGlobal::mockCliEnvironment();
23 |
24 | $serverRequestFactory = new ServerRequestFactory();
25 | $serverRequest = $serverRequestFactory->createServerRequest('GET', '', $_SERVER);
26 |
27 | $this->assertTrue(($serverRequest instanceof ServerRequestInterface));
28 | }
29 |
30 | public function test_createServerRequestFromGlobal()
31 | {
32 | SuperGlobal::mockCliEnvironment([
33 | 'PHP_AUTH_USER' => 'terry',
34 | 'PHP_AUTH_PW' => '1234',
35 | 'QUERY_STRING' => 'foo=bar'
36 | ]);
37 |
38 | $serverRequest = ServerRequestFactory::fromGlobal();
39 |
40 | $this->assertTrue(($serverRequest instanceof ServerRequestInterface));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/tests/Psr17/StreamFactoryTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr17;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\StreamInterface;
15 | use Shieldon\Psr17\StreamFactory;
16 | use ReflectionObject;
17 | use InvalidArgumentException;
18 | use RuntimeException;
19 |
20 | class StreamFactoryTest extends TestCase
21 | {
22 | public function test_createStream()
23 | {
24 | $streamFactory = new StreamFactory();
25 | $stream = $streamFactory->createStream('Foo Bar');
26 |
27 | ob_start();
28 | echo $stream;
29 | $output = ob_get_contents();
30 | ob_end_clean();
31 |
32 | $this->assertSame('Foo Bar', $output);
33 | }
34 |
35 | public function test_createStreamFromFile()
36 | {
37 | $sourceFile = BOOTSTRAP_DIR . '/sample/shieldon_logo.png';
38 |
39 | $streamFactory = new StreamFactory();
40 | $stream = $streamFactory->createStreamFromFile($sourceFile);
41 | $this->assertTrue(($stream instanceof StreamInterface));
42 | $this->assertSame($stream->getSize(), 15166);
43 | }
44 |
45 | public function test_createStreamFromResource()
46 | {
47 | $streamFactory = new StreamFactory();
48 | $stream = $streamFactory->createStreamFromResource('this is string, not resource');
49 |
50 | $this->assertTrue(($stream instanceof StreamInterface));
51 | }
52 |
53 | public function test_fromNew()
54 | {
55 | $stream = StreamFactory::fromNew();
56 |
57 | $this->assertTrue(($stream instanceof StreamInterface));
58 | }
59 |
60 | /*
61 | |--------------------------------------------------------------------------
62 | | Exceptions
63 | |--------------------------------------------------------------------------
64 | */
65 |
66 | public function test_Exception_CreateStreamFromFile_InvalidOpeningMethod()
67 | {
68 | $this->expectException(InvalidArgumentException::class);
69 |
70 | $sourceFile = BOOTSTRAP_DIR . '/sample/shieldon_logo.png';
71 |
72 | $streamFactory = new StreamFactory();
73 |
74 | // Exception:
75 | // => Invalid file opening mode "b"
76 | $stream = $streamFactory->createStreamFromFile($sourceFile, 'b');
77 | }
78 |
79 | public function test_Exception_CreateStreamFromFile_UnableToOpen()
80 | {
81 | $this->expectException(RuntimeException::class);
82 |
83 | $sourceFile = BOOTSTRAP_DIR . '/sample/shieldon_logo_not_exists.png';
84 |
85 | $streamFactory = new StreamFactory();
86 |
87 | // Exception:
88 | // => Invalid file opening mode "b"
89 | $stream = $streamFactory->createStreamFromFile($sourceFile);
90 | }
91 |
92 | public function test_Exception_assertResource()
93 | {
94 | $this->expectException(RuntimeException::class);
95 |
96 | $streamFactory = new StreamFactory();
97 | $reflection = new ReflectionObject($streamFactory);
98 | $assertParsedBody = $reflection->getMethod('assertResource');
99 | $assertParsedBody->setAccessible(true);
100 |
101 | // Exception:
102 | // => Unable to open "php://temp" resource.
103 | $assertParsedBody->invokeArgs($streamFactory, ['test string']);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/tests/Psr17/UploadFileFactoryTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr17;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\UploadedFileInterface;
15 | use Shieldon\Psr7\UploadedFile;
16 | use Shieldon\Psr17\UploadedFileFactory;
17 | use Shieldon\Psr17\StreamFactory;
18 | use ReflectionObject;
19 | use InvalidArgumentException;
20 |
21 | class UploadFileFactoryTest extends TestCase
22 | {
23 | public function test_createUploadedFile()
24 | {
25 | $uploadedFileFactory = new UploadedFileFactory();
26 |
27 | $sourceFile = BOOTSTRAP_DIR . '/sample/shieldon_logo.png';
28 | $cloneFile = save_testing_file('shieldon_logo_clone_2.png');
29 | $targetPath = save_testing_file('shieldon_logo_moved_from_file_2.png');
30 |
31 | // Clone a sample file for testing MoveTo method.
32 | if (!copy($sourceFile, $cloneFile)) {
33 | $this->assertTrue(false);
34 | }
35 |
36 | $streamFactory = new StreamFactory();
37 | $stream = $streamFactory->createStreamFromFile($cloneFile);
38 |
39 | $uploadedFileFactory = new UploadedFileFactory();
40 |
41 | $uploadedFile = $uploadedFileFactory->createUploadedFile($stream);
42 | $this->assertTrue(($uploadedFile instanceof UploadedFileInterface));
43 |
44 | $uploadedFile->moveTo($targetPath);
45 |
46 | if (file_exists($targetPath)) {
47 | $this->assertTrue(true);
48 | }
49 |
50 | unlink($targetPath);
51 | }
52 |
53 | public function test_createFromGlobal()
54 | {
55 | $_FILES = [
56 |
57 | //
58 |
59 | 'files1' => [
60 | 'name' => 'example1.jpg',
61 | 'type' => 'image/jpeg',
62 | 'tmp_name' => '/tmp/php200A.tmp',
63 | 'error' => 0,
64 | 'size' => 100000,
65 | ],
66 |
67 | //
68 | //
69 |
70 | 'files2' => [
71 | 'name' => [
72 | 'a' => 'example21.jpg',
73 | 'b' => 'example22.jpg',
74 | ],
75 | 'type' => [
76 | 'a' => 'image/jpeg',
77 | 'b' => 'image/jpeg',
78 | ],
79 | 'tmp_name' => [
80 | 'a' => '/tmp/php343C.tmp',
81 | 'b' => '/tmp/php343D.tmp',
82 | ],
83 | 'error' => [
84 | 'a' => 0,
85 | 'b' => 0,
86 | ],
87 | 'size' => [
88 | 'a' => 125100,
89 | 'b' => 145000,
90 | ],
91 | ],
92 |
93 | //
94 | //
95 |
96 | 'files3' => [
97 | 'name' => [
98 | 0 => 'example31.jpg',
99 | 1 => 'example32.jpg',
100 | ],
101 | 'type' => [
102 | 0 => 'image/jpeg',
103 | 1 => 'image/jpeg',
104 | ],
105 | 'tmp_name' => [
106 | 0 => '/tmp/php310C.tmp',
107 | 1 => '/tmp/php313D.tmp',
108 | ],
109 | 'error' => [
110 | 0 => 0,
111 | 1 => 0,
112 | ],
113 | 'size' => [
114 | 0 => 200000,
115 | 1 => 300000,
116 | ],
117 | ],
118 |
119 | //
120 |
121 | 'files4' => [
122 | 'name' => [
123 | 'details' => [
124 | 'avatar' => 'my-avatar.png',
125 | ],
126 | ],
127 | 'type' => [
128 | 'details' => [
129 | 'avatar' => 'image/png',
130 | ],
131 | ],
132 | 'tmp_name' => [
133 | 'details' => [
134 | 'avatar' => '/tmp/phpmFLrzD',
135 | ],
136 | ],
137 | 'error' => [
138 | 'details' => [
139 | 'avatar' => 0,
140 | ],
141 | ],
142 | 'size' => [
143 | 'details' => [
144 | 'avatar' => 90996,
145 | ],
146 | ],
147 | ],
148 | ];
149 |
150 | $results = UploadedFileFactory::fromGlobal();
151 |
152 | $expectedFiles = [
153 | 'files1' => new UploadedFile(
154 | '/tmp/php200A.tmp',
155 | 'example1.jpg',
156 | 'image/jpeg',
157 | 100000,
158 | 0
159 | ),
160 | 'files2' => [
161 | 'a' => new UploadedFile(
162 | '/tmp/php343C.tmp',
163 | 'example21.jpg',
164 | 'image/jpeg',
165 | 125100,
166 | 0
167 | ),
168 | 'b' => new UploadedFile(
169 | '/tmp/php343D.tmp',
170 | 'example22.jpg',
171 | 'image/jpeg',
172 | 145000,
173 | 0
174 | ),
175 | ],
176 | 'files3' => [
177 | 0 => new UploadedFile(
178 | '/tmp/php310C.tmp',
179 | 'example31.jpg',
180 | 'image/jpeg',
181 | 200000,
182 | 0
183 | ),
184 | 1 => new UploadedFile(
185 | '/tmp/php313D.tmp',
186 | 'example32.jpg',
187 | 'image/jpeg',
188 | 300000,
189 | 0
190 | ),
191 | ],
192 | 'files4' => [
193 | 'details' => [
194 | 'avatar' => new UploadedFile(
195 | '/tmp/phpmFLrzD',
196 | 'my-avatar.png',
197 | 'image/png',
198 | 90996,
199 | 0
200 | ),
201 | ],
202 | ],
203 | ];
204 |
205 | $this->assertEquals($results, $expectedFiles);
206 | }
207 |
208 | public function testExample()
209 | {
210 | $_FILES = [
211 | 'foo' => [
212 | 'name' => 'example1.jpg',
213 | 'type' => 'image/jpeg',
214 | 'tmp_name' => '/tmp/php200A.tmp',
215 | 'error' => 0,
216 | 'size' => 100000,
217 | ],
218 | ];
219 |
220 | $uploadFileArr = UploadedFileFactory::fromGlobal();
221 |
222 | $filename = $uploadFileArr['foo']->getClientFilename();
223 |
224 | $this->assertEquals('example1.jpg', $filename);
225 | }
226 |
227 | /*
228 | |--------------------------------------------------------------------------
229 | | Exceptions
230 | |--------------------------------------------------------------------------
231 | */
232 |
233 | public function test_Exception_FileIsNotReadable()
234 | {
235 | $this->expectException(InvalidArgumentException::class);
236 |
237 | $uploadedFileFactory = new UploadedFileFactory();
238 |
239 | $sourceFile = BOOTSTRAP_DIR . '/sample/shieldon_logo.png';
240 |
241 | $streamFactory = new StreamFactory();
242 | $stream = $streamFactory->createStreamFromFile($sourceFile);
243 |
244 | $reflection = new ReflectionObject($stream);
245 | $readable = $reflection->getProperty('readable');
246 | $readable->setAccessible(true);
247 | $readable->setValue($stream, false);
248 |
249 | $uploadedFileFactory = new UploadedFileFactory();
250 |
251 | // Exception:
252 | // => File is not readable.
253 | $uploadedFile = $uploadedFileFactory->createUploadedFile($stream);
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/tests/Psr17/UriFactoryTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr17;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\UriInterface;
15 | use Shieldon\Psr17\UriFactory;
16 | use Shieldon\Psr17\Utils\SuperGlobal;
17 |
18 | class UriFactoryTest extends TestCase
19 | {
20 | public function test_createUri()
21 | {
22 | $uriFactory = new UriFactory;
23 |
24 | $uri = $uriFactory->createUri();
25 | $this->assertTrue(($uri instanceof UriInterface));
26 | }
27 |
28 | public function test_fromGlobal()
29 | {
30 | SuperGlobal::mockCliEnvironment([
31 | 'PHP_AUTH_USER' => 'terry', // user
32 | 'HTTP_HOST' => 'example.org', // host
33 | 'PHP_AUTH_PW' => '1234', // pass
34 | 'REQUEST_URI' => '/test', // path
35 | 'SERVER_PORT' => '8080', // port
36 | 'QUERY_STRING' => 'foo=bar', // query
37 | 'REQUEST_SCHEME' => 'https', // scheme
38 | ]);
39 |
40 | $uri = uriFactory::fromGlobal();
41 |
42 | $this->assertSame($uri->getScheme(), 'https');
43 | $this->assertSame($uri->getHost(), 'example.org');
44 | $this->assertSame($uri->getUserInfo(), 'terry:1234'); // string
45 | $this->assertSame($uri->getPath(), '/test'); // string
46 | $this->assertSame($uri->getPort(), 8080); // int|null
47 | $this->assertSame($uri->getQuery(), 'foo=bar'); // string
48 | $this->assertSame($uri->getFragment(), ''); // string
49 | }
50 |
51 | public function test_fromNew()
52 | {
53 | $uri = uriFactory::fromNew();
54 | $this->assertTrue(($uri instanceof UriInterface));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/Psr17/Utils/SuperGlobalTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr17\Utils;
12 |
13 | class SuperGlobalTest extends \PHPUnit\Framework\TestCase
14 | {
15 | public function test_Static_extract()
16 | {
17 | $_SERVER = [];
18 |
19 | $data = \Shieldon\Psr17\Utils\SuperGlobal::extract();
20 |
21 | $array = [
22 | 'HTTP_ACCEPT' => 'text/html,application/xhtml+xml,application/xml;q=0.9',
23 | 'HTTP_ACCEPT_CHARSET' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.3',
24 | 'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.9,zh-TW;q=0.8,zh;q=0.7',
25 | 'HTTP_HOST' => '127.0.0.1',
26 | 'HTTP_USER_AGENT' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
27 | 'QUERY_STRING' => '',
28 | 'REMOTE_ADDR' => '127.0.0.1',
29 | 'REQUEST_METHOD' => 'GET',
30 | 'REQUEST_SCHEME' => 'http',
31 | 'REQUEST_URI' => '',
32 | 'SCRIPT_NAME' => '',
33 | 'SERVER_NAME' => 'localhost',
34 | 'SERVER_PORT' => 80,
35 | 'SERVER_PROTOCOL' => 'HTTP/1.1',
36 | 'CONTENT_TYPE' => 'text/html; charset=UTF-8',
37 | 'HTTP_CONTENT_TYPE' => 'text/html; charset=UTF-8', // This is added by line: 46
38 | ];
39 |
40 | unset($data['server']['REQUEST_TIME']);
41 | unset($data['server']['REQUEST_TIME_FLOAT']);
42 |
43 | $this->assertEquals($data['server'], $array);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/tests/Psr7/MessageTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 |
15 | use InvalidArgumentException;
16 | use Psr\Http\Message\MessageInterface;
17 | use ReflectionObject;
18 | use Shieldon\Psr7\Message;
19 | use Shieldon\Psr7\Stream;
20 | use stdClass;
21 |
22 | class MessageTest extends TestCase
23 | {
24 | public function test__construct()
25 | {
26 | $message = new Message();
27 |
28 | $this->assertTrue(($message instanceof MessageInterface));
29 | }
30 |
31 | public function test_GetPrefixMethods()
32 | {
33 | $message = $this->test_setHeaders();
34 |
35 | $this->assertSame($message->getProtocolVersion(), '1.1');
36 | $this->assertEquals($message->getHeader('user-agent'), ['Mozilla/5.0 (Windows NT 10.0; Win64; x64)']);
37 | $this->assertEquals($message->getHeader('header-not-exists'), []);
38 | $this->assertEquals($message->getHeaderLine('user-agent'), 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)');
39 |
40 | // Test - has
41 | $this->assertTrue($message->hasHeader('user-agent'));
42 | }
43 |
44 | public function test_WithPrefixMethods()
45 | {
46 | $message = $this->test_setHeaders();
47 | $newMessage = $message->withProtocolVersion('2.0')->withHeader('hello-world', 'ok');
48 | $this->assertSame($newMessage->getProtocolVersion(), '2.0');
49 | $this->assertEquals($newMessage->getHeader('hello-world'), ['ok']);
50 |
51 | $new2Message = $newMessage
52 | ->withAddedHeader('hello-world', 'not-ok')
53 | ->withAddedHeader('foo-bar', 'okok')
54 | ->withAddedHeader('others', 2)
55 | ->withAddedHeader('others', 6.4);
56 |
57 | $this->assertEquals($new2Message->getHeader('hello-world'), ['ok', 'not-ok']);
58 | $this->assertEquals($new2Message->getHeader('foo-bar'), ['okok']);
59 | $this->assertEquals($new2Message->getHeader('others'), ['2', '6.4']);
60 |
61 | // Test - without
62 | $new3Message = $new2Message->withoutHeader('hello-world');
63 | $this->assertFalse($new3Message->hasHeader('hello-world'));
64 | }
65 |
66 | public function test_bodyMethods()
67 | {
68 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
69 | $stream = new Stream($resource);
70 |
71 | $message = new Message();
72 | $newMessage = $message->withBody($stream);
73 | $this->assertEquals($newMessage->getBody(), $stream);
74 | }
75 |
76 | public function test_setHeaders(): MessageInterface
77 | {
78 | $message = new Message();
79 |
80 | $testArray = [
81 | 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)',
82 | 'Custom-Value' => '1234',
83 | ];
84 |
85 | $expectedArray = [
86 | 'User-Agent' => ['Mozilla/5.0 (Windows NT 10.0; Win64; x64)'],
87 | 'Custom-Value' => ['1234'],
88 | ];
89 |
90 | $reflection = new ReflectionObject($message);
91 | $setHeaders = $reflection->getMethod('setHeaders');
92 | $setHeaders->setAccessible(true);
93 | $setHeaders->invokeArgs($message, [$testArray]);
94 |
95 | $this->assertEquals($message->getHeaders(), $expectedArray);
96 |
97 | return $message;
98 | }
99 |
100 | public function test_Static_ParseRawHeader()
101 | {
102 | // Test 1 - General request header.
103 | $rawHeader =<<assertSame($headers['Accept'], '*/*');
115 | $this->assertSame($headers['Content-Type'], 'application/x-www-form-urlencoded;charset=UTF-8');
116 | $this->assertSame($headers['Sec-Fetch-Dest'], 'empty');
117 | $this->assertSame($headers['Sec-Fetch-Mode'], 'cors');
118 | $this->assertSame($headers['Sec-Fetch-Site'], 'same-site');
119 | $this->assertSame($headers['User-Agent'], 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36');
120 | $this->assertSame($headers['X-Client-Data'], 'CJC2yQEIorbJAQjBtskBCKmdygEIqLTKARibvsoB');
121 |
122 | // Test - General response header.
123 | $rawHeader =<<assertSame($headers['access-control-allow-credentials'], 'true');
149 | $this->assertSame($headers['access-control-allow-methods'], 'OPTIONS');
150 | $this->assertSame($headers['access-control-allow-origin'], 'https://www.facebook.com');
151 | $this->assertSame($headers['access-control-expose-headers'], 'X-FB-Debug, X-Loader-Length');
152 | $this->assertSame($headers['alt-svc'], 'h3-27=":443"; ma=3600');
153 | $this->assertSame($headers['cache-control'], 'private, no-cache, no-store, must-revalidate');
154 | $this->assertSame($headers['content-length'], '0');
155 | $this->assertSame($headers['content-security-policy'], "default-src * data: blob: 'self';script-src *.facebook.com *.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* 'unsafe-inline' 'unsafe-eval' blob: data: 'self';style-src data: blob: 'unsafe-inline' *;connect-src *.facebook.com facebook.com *.fbcdn.net *.facebook.net *.spotilocal.com:* wss://*.facebook.com:* https://fb.scanandcleanlocal.com:* attachment.fbsbx.com ws://localhost:* blob: *.cdninstagram.com 'self' chrome-extension://boadgeojelhgndaghljhdicfkmllpafd chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm;block-all-mixed-content;upgrade-insecure-requests;");
156 | $this->assertSame($headers['content-type'], 'text/html; charset="utf-8"');
157 | $this->assertSame($headers['date'], 'Thu, 04 Jun 2020 02:46:12 GMT');
158 | $this->assertSame($headers['date'], 'Thu, 04 Jun 2020 02:46:12 GMT');
159 | $this->assertSame($headers['expires'], 'Sat, 01 Jan 2000 00:00:00 GMT');
160 | $this->assertSame($headers['pragma'], 'no-cache');
161 | $this->assertSame($headers['status'], '200');
162 | $this->assertSame($headers['strict-transport-security'], 'max-age=15552000; preload');
163 | $this->assertSame($headers['vary'], 'Origin');
164 | $this->assertSame($headers['x-content-type-options'], 'nosniff');
165 | $this->assertSame($headers['x-fb-debug'], 'XNgyVH3VRKeOuGyxAit2WqKJ+334baHQuKQP0CM2lr/8ToZmwhNFU9N5ctr3LeTgXYWXemfGlJaAl/PASeEL5Q==');
166 | $this->assertSame($headers['x-frame-options'], 'DENY');
167 | $this->assertSame($headers['x-xss-protection'], '0');
168 |
169 | // Test 3 - Just one line.
170 | $rawHeader =<<assertEquals(count($headers), 1);
176 | $this->assertSame($headers['access-control-allow-credentials'], 'true');
177 |
178 | // Test 4 - Empty.
179 | $rawHeader = '';
180 | $headers = Message::parseRawHeader($rawHeader);
181 | $this->assertSame($headers, []);
182 | }
183 |
184 | public function test_WithAddedHeaderArrayValueAndKeys()
185 | {
186 | $message = new Message();
187 | $message = $message->withAddedHeader('content-type', ['foo' => 'text/html']);
188 | $message = $message->withAddedHeader('content-type', ['foo' => 'text/plain', 'bar' => 'application/json']);
189 |
190 | $headerLine = $message->getHeaderLine('content-type');
191 | $this->assertMatchesRegularExpression('|text/html|', $headerLine);
192 | $this->assertMatchesRegularExpression('|text/plain|', $headerLine);
193 | $this->assertMatchesRegularExpression('|application/json|', $headerLine);
194 |
195 | $message = $message->withAddedHeader('foo', '');
196 | $headerLine = $message->getHeaderLine('foo');
197 | $this->assertSame('', $headerLine);
198 | }
199 |
200 | /*
201 | |--------------------------------------------------------------------------
202 | | Exceptions
203 | |--------------------------------------------------------------------------
204 | */
205 |
206 | public function test_Exception_AssertHeaderFieldName()
207 | {
208 | $this->expectException(InvalidArgumentException::class);
209 |
210 | $message = new Message();
211 |
212 | // Exception:
213 | // => "hello-wo)rld" is not valid header name, it must be an RFC 7230 compatible string.
214 | $newMessage = $message->withHeader('hello-wo)rld', 'ok');
215 | }
216 |
217 | public function test_Exception_AssertHeaderFieldName_2()
218 | {
219 | $this->expectException(InvalidArgumentException::class);
220 |
221 | $message = new Message();
222 |
223 | // Exception:
224 | // => "hello-wo)rld" is not valid header name, it must be an RFC 7230 compatible string.
225 | $newMessage = $message->withHeader(['test'], 'ok');
226 | }
227 |
228 | public function test_Exception_AssertHeaderFieldValue_Booolean()
229 | {
230 | $this->expectException(InvalidArgumentException::class);
231 |
232 | $message = new Message();
233 |
234 | // Exception:
235 | // => The header field value only accepts string and array, but "boolean" provided.
236 | $newMessage = $message->withHeader('hello-world', false);
237 | }
238 |
239 | public function test_Exception_AssertHeaderFieldValue_Null()
240 | {
241 | $this->expectException(InvalidArgumentException::class);
242 |
243 | $message = new Message();
244 |
245 | // Exception:
246 | // => The header field value only accepts string and array, but "NULL" provided.
247 | $newMessage = $message->withHeader('hello-world', null);
248 | }
249 |
250 | public function test_Exception_AssertHeaderFieldValue_Object()
251 | {
252 | $this->expectException(InvalidArgumentException::class);
253 |
254 | $message = new Message();
255 | $mockObject = new stdClass();
256 | $mockObject->test = 1;
257 |
258 | // Exception:
259 | // => The header field value only accepts string and array, but "object" provided.
260 | $newMessage = $message->withHeader('hello-world', $mockObject);
261 | }
262 |
263 | public function test_Exception_AssertHeaderFieldValue_Array()
264 | {
265 | $this->expectException(InvalidArgumentException::class);
266 |
267 | // An invalid type is inside the array.
268 | $testArr = [
269 | 'test',
270 | true,
271 | ];
272 |
273 | $message = new Message();
274 |
275 | // Exception:
276 | // => The header values only accept string and number, but "boolean" provided.
277 | $newMessage = $message->withHeader('hello-world', $testArr);
278 | }
279 |
280 | public function test_Exception_AssertHeaderFieldValue_InvalidString()
281 | {
282 | $this->expectException(InvalidArgumentException::class);
283 |
284 | $message = new Message();
285 |
286 | // Exception:
287 | // => "This string contains many invisible spaces." is not valid header
288 | // value, it must contains visible ASCII characters only.
289 | $newMessage = $message->withHeader('hello-world', 'This string contains many invisible spaces.');
290 |
291 | // $newMessage = $message->withHeader('hello-world', 'This string contains visible space.');
292 | }
293 | }
294 |
--------------------------------------------------------------------------------
/tests/Psr7/RequestTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 |
15 | use Psr\Http\Message\RequestInterface;
16 | use Psr\Http\Message\MessageInterface;
17 | use Psr\Http\Message\UriInterface;
18 | use Shieldon\Psr7\Request;
19 | use Shieldon\Psr7\Stream;
20 | use Shieldon\Psr7\Uri;
21 | use InvalidArgumentException;
22 |
23 | class RequestTest extends TestCase
24 | {
25 | public function test__construct()
26 | {
27 | $request = new Request('GET', '', '', [], '1.1');
28 |
29 | $this->assertTrue(($request instanceof RequestInterface));
30 | $this->assertTrue(($request instanceof MessageInterface));
31 |
32 | $uri = new Uri('https://www.example.com');
33 | $request = new Request('GET', $uri, '', [], '1.1');
34 |
35 | $this->assertSame($request->getUri()->getHost(), 'www.example.com');
36 | }
37 |
38 | public function test_GetPrefixMethods()
39 | {
40 | // Test 1
41 |
42 | $request = new Request('POST', 'https://terryl.in/zh/?test=test', '', ['test' => 1234], '1.1');
43 |
44 | $this->assertSame($request->getRequestTarget(), '/zh/?test=test');
45 | $this->assertSame($request->getMethod(), 'POST');
46 |
47 | // Let's double check the Uri instance again.
48 | $this->assertTrue(($request->getUri() instanceof UriInterface));
49 | $this->assertSame($request->getUri()->getScheme(), 'https');
50 | $this->assertSame($request->getUri()->getHost(), 'terryl.in');
51 | $this->assertSame($request->getUri()->getUserInfo(), '');
52 | $this->assertSame($request->getUri()->getPath(), '/zh/');
53 | $this->assertSame($request->getUri()->getPort(), null);
54 | $this->assertSame($request->getUri()->getQuery(), 'test=test');
55 | $this->assertSame($request->getUri()->getFragment(), '');
56 |
57 | // Test 2
58 |
59 | $request = new Request('GET', 'https://terryl.in', '', [], '1.1');
60 |
61 | $this->assertSame($request->getRequestTarget(), '/');
62 | }
63 |
64 | public function test_WithPrefixMethods()
65 | {
66 | $request = new Request('GET', 'https://terryl.in/zh/', '', [], '1.1');
67 |
68 | $newRequest = $request->withMethod('POST')->withUri(new Uri('https://play.google.com'));
69 |
70 | $this->assertSame($newRequest->getMethod(), 'POST');
71 | $this->assertSame($newRequest->getRequestTarget(), '/');
72 | $this->assertSame($newRequest->getUri()->getHost(), 'play.google.com');
73 |
74 | $new2Request = $newRequest->withRequestTarget('/newTarget/test/?q=1234');
75 | $this->assertSame($new2Request->getRequestTarget(), '/newTarget/test/?q=1234');
76 |
77 | $new3Request = $new2Request->withUri(new Uri('https://www.facebook.com'), true);
78 |
79 | // Preserve Host
80 | $this->assertSame($new3Request->getHeaderLine('host'), 'play.google.com');
81 | $this->assertSame($new3Request->getUri()->getHost(), 'www.facebook.com');
82 | }
83 |
84 | public function test_setBody()
85 | {
86 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
87 | $stream = new Stream($resource);
88 |
89 | $request = new Request('POST', 'https://terryl.in/zh/', $stream, [], '1.1');
90 | $this->assertEquals($request->getBody(), $stream);
91 |
92 | $request = new Request('POST', 'https://terryl.in/zh/', 'test stream', [], '1.1');
93 | $this->assertEquals(sprintf('%s', $request->getBody()->getContents()), 'test stream');
94 | }
95 |
96 | /*
97 | |--------------------------------------------------------------------------
98 | | Exceptions
99 | |--------------------------------------------------------------------------
100 | */
101 |
102 | public function test_Exception_assertMethod_1()
103 | {
104 | $this->expectException(InvalidArgumentException::class);
105 | $request = new Request('GET', 'https://terryl.in/', '', [], '1.1');
106 |
107 |
108 | // Exception:
109 | // => HTTP method must be a string.
110 | $newRequest = $request->withMethod(['POST']);
111 | }
112 |
113 | public function test_Exception_assertMethod_2()
114 | {
115 | $this->expectException(InvalidArgumentException::class);
116 |
117 | // Exception:
118 | // => Unsupported HTTP method. It must be compatible with RFC-7231
119 | // request method, but "GETX" provided.
120 | $request = new Request('GETX', 'https://terryl.in/', '', [], '1.1');
121 | }
122 |
123 | public function test_Exception_assertProtocolVersion()
124 | {
125 | $this->expectException(InvalidArgumentException::class);
126 |
127 | // Exception:
128 | // => Unsupported HTTP protocol version number. "1.5" provided.
129 | $request = new Request('GET', 'https://terryl.in/', '', [], '1.5');
130 | }
131 |
132 | public function test_Exception_withRequestTarget_ContainSpaceCharacter()
133 | {
134 | $this->expectException(InvalidArgumentException::class);
135 |
136 | $request = new Request('GET', 'https://terryl.in/', '', [], '1.1');
137 |
138 | // Exception:
139 | // => A request target cannot contain any whitespace.
140 | $newRequest = $request->withRequestTarget('/newTarget/te st/?q=1234');
141 | }
142 |
143 | public function test_Exception_withRequestTarget_InvalidType()
144 | {
145 | $this->expectException(InvalidArgumentException::class);
146 |
147 | $request = new Request('GET', 'https://terryl.in/', '', [], '1.1');
148 |
149 | // Exception:
150 | // => A request target must be a string.
151 | $newRequest = $request->withRequestTarget(['foo' => 'bar']);
152 | }
153 |
154 | public function test_Exception_Constructor()
155 | {
156 | $this->expectException(InvalidArgumentException::class);
157 |
158 | // Exception:
159 | // => URI should be a string or an instance of UriInterface, but array provided.
160 | $request = new Request('GET', [], '', [], '1.1');
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/tests/Psr7/ResponseTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 |
15 | use Psr\Http\Message\MessageInterface;
16 | use Psr\Http\Message\ResponseInterface;
17 | use Shieldon\Psr7\Response;
18 | use InvalidArgumentException;
19 |
20 | class ResponseTest extends TestCase
21 | {
22 | public function test__construct()
23 | {
24 | $response = new Response();
25 |
26 | $this->assertTrue(($response instanceof ResponseInterface));
27 | $this->assertTrue(($response instanceof MessageInterface));
28 |
29 | $newResponse = $response->withStatus(555, 'Custom reason phrase');
30 |
31 | $this->assertSame($newResponse->getStatusCode(), 555);
32 | $this->assertSame($newResponse->getReasonPhrase(), 'Custom reason phrase');
33 |
34 | $new2Response = $newResponse->withStatus(500);
35 |
36 | $this->assertSame($new2Response->getStatusCode(), 500);
37 | $this->assertSame($new2Response->getReasonPhrase(), 'Internal Server Error');
38 | }
39 |
40 | /*
41 | |--------------------------------------------------------------------------
42 | | Exceptions
43 | |--------------------------------------------------------------------------
44 | */
45 |
46 | public function test_Exception_AssertStatus_InvalidRange()
47 | {
48 | $this->expectException(InvalidArgumentException::class);
49 |
50 | // Exception:
51 | // => Status code should be in a range of 100-599, but 600 provided.
52 | $response = new Response(600);
53 | }
54 |
55 | public function test_Exception_AssertStatus_InvalidType()
56 | {
57 | $this->expectException(InvalidArgumentException::class);
58 |
59 | $response = new Response();
60 |
61 | // Exception:
62 | // => Status code should be an integer value, but string provided.
63 | $newResponse = $response->withStatus("500", 'Custom reason phrase');
64 | }
65 |
66 | public function test_Exception_assertReasonPhrase_InvalidType()
67 | {
68 | $this->expectException(InvalidArgumentException::class);
69 |
70 | $response = new Response();
71 |
72 | // Exception:
73 | // => Reason phrase must be a string, but integer provided.
74 | $newResponse = $response->withStatus(200, 12345678);
75 | }
76 |
77 | public function test_Exception_assertReasonPhrase_ProhibitedCharacter()
78 | {
79 | $this->expectException(InvalidArgumentException::class);
80 |
81 | $response = new Response();
82 |
83 | // Exception:
84 | // => Reason phrase contains "\r" that is considered as a prohibited character.
85 | $newResponse = $response->withStatus(200, 'Custom reason phrase\n\rThe next line');
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/tests/Psr7/ServerRequestTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Shieldon\Psr7\ServerRequest;
15 | use Shieldon\Psr7\UploadedFile;
16 | use Shieldon\Psr7\Utils\UploadedFileHelper;
17 | use InvalidArgumentException;
18 | use Psr\Http\Message\ServerRequestInterface;
19 | use Psr\Http\Message\RequestInterface;
20 | use Psr\Http\Message\MessageInterface;
21 | use ReflectionObject;
22 |
23 | class ServerRequestTest extends TestCase
24 | {
25 | public function test__construct()
26 | {
27 | $serverRequest = new ServerRequest('GET', '', '', [], '1.1', [], [], [], [], self::mockFile(1));
28 |
29 | $this->assertTrue(($serverRequest instanceof RequestInterface));
30 | $this->assertTrue(($serverRequest instanceof MessageInterface));
31 | $this->assertTrue(($serverRequest instanceof ServerRequestInterface));
32 | }
33 |
34 | public function test_Properties()
35 | {
36 | $serverRequest = self::getServerRequest();
37 |
38 | $properties = [
39 | 'serverParams',
40 | 'cookieParams',
41 | 'parsedBody',
42 | 'queryParams',
43 | 'uploadedFiles',
44 | 'attributes',
45 | ];
46 |
47 | $reflection = new ReflectionObject($serverRequest);
48 |
49 | foreach ($properties as $v) {
50 | $tmp = $reflection->getProperty($v);
51 | $tmp->setAccessible(true);
52 | ${$v} = $tmp->getValue($serverRequest);
53 | unset($tmp);
54 | }
55 |
56 | $this->assertSame($serverParams, []);
57 | $this->assertSame($cookieParams, []);
58 | $this->assertSame($parsedBody, null);
59 | $this->assertSame($queryParams, []);
60 | $this->assertSame($uploadedFiles, []);
61 | $this->assertSame($attributes, []);
62 | }
63 |
64 | public function test_GetPrefixMethods()
65 | {
66 | // Test 1
67 |
68 | $serverRequest = self::getServerRequest();
69 |
70 | $this->assertSame($serverRequest->getServerParams(), []);
71 | $this->assertSame($serverRequest->getCookieParams(), []);
72 | $this->assertSame($serverRequest->getParsedBody(), null);
73 | $this->assertSame($serverRequest->getQueryParams(), []);
74 | $this->assertSame($serverRequest->getUploadedFiles(), []);
75 | $this->assertSame($serverRequest->getAttributes(), []);
76 |
77 | // Test 2
78 | $serverRequest = self::getServerRequest(
79 | 'POST',
80 | ['foo' => 'bar'],
81 | ['foo' => 'bar'],
82 | ['foo' => 'bar'],
83 | ['foo' => 'bar'],
84 | self::mockFile(1)
85 | );
86 |
87 | $this->assertEquals($serverRequest->getServerParams(), ['foo' => 'bar']);
88 | $this->assertEquals($serverRequest->getCookieParams(), ['foo' => 'bar']);
89 | $this->assertEquals($serverRequest->getParsedBody(), ['foo' => 'bar']);
90 | $this->assertEquals($serverRequest->getQueryParams(), ['foo' => 'bar']);
91 | $this->assertEquals($serverRequest->getAttributes(), []);
92 |
93 | $this->assertEquals($serverRequest->getUploadedFiles(), [
94 | 'files1' => new UploadedFile(
95 | '/tmp/php200A.tmp',
96 | 'example1.jpg',
97 | 'image/jpeg',
98 | 100000,
99 | 0
100 | ),
101 | ]);
102 | }
103 |
104 | public function test_WithPrefixMethods()
105 | {
106 | $serverRequest = self::getServerRequest();
107 |
108 | $newUpload = UploadedFileHelper::uploadedFileSpecsConvert(
109 | UploadedFileHelper::uploadedFileParse(self::mockFile(2))
110 | );
111 |
112 | $new = $serverRequest->withCookieParams(['foo3' => 'bar3'])
113 | ->withParsedBody(['foo4' => 'bar4', 'foo5' => 'bar5'])
114 | ->withQueryParams(['foo6' => 'bar6', 'foo7' => 'bar7'])
115 | ->withAttribute('foo8', 'bar9')
116 | ->withUploadedFiles($newUpload);
117 |
118 | $this->assertEquals($new->getServerParams(), []);
119 | $this->assertEquals($new->getCookieParams(), ['foo3' => 'bar3']);
120 | $this->assertEquals($new->getParsedBody(), ['foo4' => 'bar4', 'foo5' => 'bar5']);
121 | $this->assertEquals($new->getQueryParams(), ['foo6' => 'bar6', 'foo7' => 'bar7']);
122 | $this->assertEquals($new->getAttribute('foo8'), 'bar9');
123 |
124 | $this->assertEquals($new->getUploadedFiles(), [
125 | 'avatar' => new UploadedFile(
126 | '/tmp/phpmFLrzD',
127 | 'my-avatar.png',
128 | 'image/png',
129 | 90996,
130 | 0
131 | ),
132 | ]);
133 |
134 | $new2 = $new->withoutAttribute('foo8');
135 |
136 | $this->assertEquals($new2->getAttribute('foo8'), null);
137 | }
138 |
139 | /*
140 | |--------------------------------------------------------------------------
141 | | Exceptions
142 | |--------------------------------------------------------------------------
143 | */
144 |
145 | public function test_Exception_AssertUploadedFiles()
146 | {
147 | $this->expectException(InvalidArgumentException::class);
148 |
149 | $serverRequest = new ServerRequest('GET', 'https://example.com');
150 |
151 | $reflection = new ReflectionObject($serverRequest);
152 | $assertUploadedFiles = $reflection->getMethod('assertUploadedFiles');
153 | $assertUploadedFiles->setAccessible(true);
154 |
155 | // Exception:
156 | // => Invalid PSR-7 array structure for handling UploadedFile.
157 | $assertUploadedFiles->invokeArgs($serverRequest, [
158 | [
159 | ['files' => ''],
160 | ],
161 | ]);
162 | }
163 |
164 | public function test_Exception_AsertParsedBody()
165 | {
166 | $this->expectException(InvalidArgumentException::class);
167 |
168 | $serverRequest = new ServerRequest('GET', 'https://example.com');
169 |
170 | $reflection = new ReflectionObject($serverRequest);
171 | $assertParsedBody = $reflection->getMethod('assertParsedBody');
172 | $assertParsedBody->setAccessible(true);
173 |
174 | // Exception:
175 | // => Only accepts array, object and null, but string provided.
176 | $assertParsedBody->invokeArgs($serverRequest, ['invalid string body']);
177 |
178 | // Just for code coverage.
179 | $assertParsedBody->invokeArgs($serverRequest, [[]]);
180 | }
181 |
182 | /*
183 | |--------------------------------------------------------------------------
184 | | Methods that help for testing.
185 | |--------------------------------------------------------------------------
186 | */
187 |
188 | /**
189 | * Get a ServerRequest instance for testing simply.
190 | *
191 | * @param string $method
192 | * @param array $server
193 | * @param array $cookie
194 | * @param array $post
195 | * @param array $get
196 | * @param array $files
197 | *
198 | * @return ServerRequest
199 | */
200 | private static function getServerRequest(
201 | $method = 'GET',
202 | $server = [],
203 | $cookie = [],
204 | $post = [],
205 | $get = [],
206 | $files = []
207 | )
208 | {
209 | return new ServerRequest(
210 | $method,
211 | '',
212 | '',
213 | [],
214 | '1.1',
215 | $server,
216 | $cookie,
217 | $post,
218 | $get,
219 | $files
220 | );
221 | }
222 |
223 | /**
224 | * Moke a $_FILES variable for testing simply.
225 | *
226 | * @return array
227 | */
228 | private static function mockFile($item = 1)
229 | {
230 | if ($item === 1) {
231 | $_FILES['files1'] = [
232 | 'name' => 'example1.jpg',
233 | 'type' => 'image/jpeg',
234 | 'tmp_name' => '/tmp/php200A.tmp',
235 | 'error' => 0,
236 | 'size' => 100000,
237 | ];
238 | }
239 |
240 | if ($item === 2) {
241 | $_FILES['avatar'] = [
242 | 'tmp_name' => '/tmp/phpmFLrzD',
243 | 'name' => 'my-avatar.png',
244 | 'type' => 'image/png',
245 | 'error' => 0,
246 | 'size' => 90996,
247 | ];
248 | }
249 |
250 | return $_FILES;
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/tests/Psr7/StreamTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\StreamInterface;
15 | use Shieldon\Psr7\Stream;
16 |
17 | use InvalidArgumentException;
18 | use RuntimeException;
19 | use ReflectionObject;
20 |
21 | class StreamTest extends TestCase
22 | {
23 | public function test__construct()
24 | {
25 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
26 | $stream = new Stream($resource);
27 |
28 | $this->assertTrue(($stream instanceof StreamInterface));
29 |
30 | $this->assertTrue($stream->isWritable());
31 | $this->assertTrue($stream->isReadable());
32 | $this->assertTrue($stream->isSeekable());
33 |
34 | $this->assertTrue(
35 | is_integer($stream->getSize())
36 | );
37 |
38 | $this->assertTrue(
39 | is_bool($stream->eof())
40 | );
41 |
42 | $this->assertTrue(
43 | is_integer($stream->tell())
44 | );
45 |
46 | // close.
47 | $this->assertEquals($resource, $stream->detach());
48 |
49 | $this->assertTrue(
50 | is_null($stream->getSize())
51 | );
52 |
53 | $this->assertTrue(
54 | is_null($stream->detach())
55 | );
56 |
57 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'c');
58 | $stream = new Stream($resource);
59 | $meta = $stream->getMetadata();
60 |
61 | $this->assertTrue($stream->isWritable());
62 | $this->assertFalse($stream->isReadable());
63 |
64 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r');
65 | $stream = new Stream($resource);
66 | $meta = $stream->getMetadata();
67 |
68 | $this->assertFalse($stream->isWritable());
69 | $this->assertTrue($stream->isReadable());
70 | }
71 |
72 | public function test__toString()
73 | {
74 | $stream = new Stream(fopen('php://temp', 'r+'));
75 | $stream->write('Foo Bar');
76 |
77 | ob_start();
78 | echo $stream;
79 | $output = ob_get_contents();
80 | ob_end_clean();
81 |
82 | $this->assertSame('Foo Bar', $output);
83 | }
84 |
85 | public function test_getSize()
86 | {
87 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
88 | $stream = new Stream($resource);
89 | $this->assertSame($stream->getSize(), 15166);
90 |
91 | $stream->close();
92 | }
93 |
94 | public function test_getMetadata()
95 | {
96 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
97 | $stream = new Stream($resource);
98 |
99 | $expectedMeta = [
100 | 'timed_out' => false,
101 | 'blocked' => true,
102 | 'eof' => false,
103 | 'wrapper_type' => 'plainfile',
104 | 'stream_type' => 'STDIO',
105 | 'mode' => 'r+',
106 | 'unread_bytes' => 0,
107 | 'seekable' => true,
108 | 'uri' => '/home/terrylin/data/psr7/tests/sample/shieldon_logo.png',
109 | ];
110 |
111 | $meta = $stream->getMetadata();
112 |
113 | $this->assertEquals($expectedMeta['mode'], $meta['mode']);
114 | $this->assertEquals($stream->getMetadata('mode'), 'r+');
115 |
116 | $stream->close();
117 |
118 | $this->assertEquals($stream->getMetadata(), null);
119 | }
120 |
121 | public function test_SeekAndRewind()
122 | {
123 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
124 | $stream = new Stream($resource);
125 |
126 | $stream->seek(10);
127 | $this->assertSame($stream->tell(), 10);
128 |
129 | $stream->rewind();
130 | $this->assertSame($stream->tell(), 0);
131 |
132 | $stream->close();
133 | }
134 |
135 |
136 | public function test_ReadAndWrite()
137 | {
138 | $stream = new Stream(fopen('php://temp', 'r+'));
139 | $stream->write('Foo Bar');
140 | $stream->rewind();
141 | $this->assertSame($stream->read(2), 'Fo');
142 |
143 | $stream->close();
144 | }
145 |
146 | /*
147 | |--------------------------------------------------------------------------
148 | | Exceptions
149 | |--------------------------------------------------------------------------
150 | */
151 |
152 | public function test_Exception_assertStream()
153 | {
154 | $this->expectException(InvalidArgumentException::class);
155 |
156 | // Exception:
157 | // => Stream should be a resource, but string provided.
158 | $stream = new Stream('string');
159 | }
160 |
161 | public function test_Exception_Tell_StreamDoesNotExist()
162 | {
163 | $this->expectException(RuntimeException::class);
164 |
165 | $stream = new Stream(fopen('php://temp', 'r+'));
166 |
167 | $stream->close();
168 |
169 | // Exception:
170 | // => Stream does not exist.
171 | $position = $stream->tell();
172 | }
173 |
174 | public function test_Exception_Seek_StreamDoesNotExist()
175 | {
176 | $this->expectException(RuntimeException::class);
177 |
178 | $stream = new Stream(fopen('php://temp', 'r+'));
179 |
180 | $stream->close();
181 |
182 | // Exception:
183 | // => Stream does not exist.
184 | $stream->seek(10);
185 | }
186 |
187 | public function test_Exception_Seek_NotSeekable()
188 | {
189 | $this->expectException(RuntimeException::class);
190 |
191 | $stream = new Stream(fopen('php://temp', 'r'));
192 |
193 | $reflection = new ReflectionObject($stream);
194 | $seekable = $reflection->getProperty('seekable');
195 | $seekable->setAccessible(true);
196 | $seekable->setValue($stream, false);
197 |
198 | // Exception:
199 | // => Stream is not seekable.
200 | $stream->seek(10);
201 | }
202 |
203 | public function test_Exception_Seek_StreamDoesNotSeekable()
204 | {
205 | $this->expectException(RuntimeException::class);
206 |
207 | $stream = new Stream(fopen('php://temp', 'r'));
208 |
209 | // Exception:
210 | // => Set position equal to offset bytes.. Unable to seek to stream at position 10
211 | $stream->seek(10);
212 | }
213 |
214 | public function test_Exception_Write_StreamDoesNotExist()
215 | {
216 | $this->expectException(RuntimeException::class);
217 |
218 | $stream = new Stream(fopen('php://temp', 'r+'));
219 |
220 | $stream->close();
221 |
222 | // Exception:
223 | // => Stream does not exist.
224 | $stream->write('Foo Bar');
225 | }
226 |
227 | public function test_Exception_Read_StreamDoesNotExist()
228 | {
229 | $this->expectException(RuntimeException::class);
230 |
231 | $stream = new Stream(fopen('php://temp', 'r+'));
232 | $stream->write('Foo Bar');
233 | $stream->rewind();
234 | $stream->close();
235 |
236 | // Exception:
237 | // => Stream does not exist.
238 | $stream->read(2);
239 | }
240 |
241 | public function test_Exception_getContents_StreamDoesNotExist()
242 | {
243 | $this->expectException(RuntimeException::class);
244 |
245 | $stream = new Stream(fopen('php://temp', 'r+'));
246 | $stream->write('Foo Bar');
247 | $stream->rewind();
248 | $stream->close();
249 |
250 | // Exception:
251 | // => Stream does not exist.
252 | $result = $stream->getContents();
253 | }
254 |
255 | public function test_Exception_getContents_StreamIsNotReadable()
256 | {
257 | $this->expectException(RuntimeException::class);
258 |
259 | $stream = new Stream(fopen('php://temp', 'r+'));
260 | $stream->write('Foo Bar');
261 | $stream->rewind();
262 |
263 | $reflection = new ReflectionObject($stream);
264 | $seekable = $reflection->getProperty('readable');
265 | $seekable->setAccessible(true);
266 | $seekable->setValue($stream, false);
267 |
268 | // Exception:
269 | // => Unable to read stream contents.
270 | $result = $stream->getContents();
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/tests/Psr7/UploadedFileTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\UploadedFileInterface;
15 | use Shieldon\Psr7\UploadedFile;
16 | use Shieldon\Psr7\Stream;
17 |
18 | use InvalidArgumentException;
19 | use Psr\Http\Message\StreamInterface;
20 | use RuntimeException;
21 | use ReflectionObject;
22 |
23 | class UploadedFileTest extends TestCase
24 | {
25 | public function test__construct()
26 | {
27 | // Test 1
28 |
29 | $uploadedFile = new UploadedFile(
30 | '/tmp/php200A.tmp', // source
31 | 'example1.jpg', // name
32 | 'image/jpeg', // type
33 | 100000, // size
34 | 0 // error
35 | );
36 |
37 | $this->assertTrue(($uploadedFile instanceof UploadedFileInterface));
38 |
39 | // Test 2
40 |
41 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
42 | $stream = new Stream($resource);
43 | $uploadedFile = new UploadedFile($stream);
44 | $stream2 = $uploadedFile->getStream(); // Test `getStream()`
45 |
46 | $this->assertEquals($stream, $stream2);
47 | $this->assertEquals($stream, $stream2);
48 | }
49 |
50 | public function test_MoveTo_Sapi_Cli()
51 | {
52 | $sourceFile = BOOTSTRAP_DIR . '/sample/shieldon_logo.png';
53 | $cloneFile = save_testing_file('shieldon_logo_clone.png');
54 | $targetPath = save_testing_file('shieldon_logo_moved_from_file.png');
55 |
56 | // Clone a sample file for testing MoveTo method.
57 | if (!copy($sourceFile, $cloneFile)) {
58 | $this->assertTrue(false);
59 | }
60 |
61 | $uploadedFile = new UploadedFile(
62 | $cloneFile,
63 | 'shieldon_logo.png',
64 | 'image/png',
65 | 100000,
66 | 0
67 | );
68 |
69 | $uploadedFile->moveTo($targetPath);
70 |
71 | if (file_exists($targetPath)) {
72 | $this->assertTrue(true);
73 | }
74 |
75 | unlink($targetPath);
76 | }
77 |
78 | public function test_GetPrefixMethods()
79 | {
80 | $sourceFile = BOOTSTRAP_DIR . '/sample/shieldon_logo.png';
81 | $cloneFile = save_testing_file('shieldon_logo_clone.png');
82 |
83 | // Clone a sample file for testing MoveTo method.
84 | if (!copy($sourceFile, $cloneFile)) {
85 | $this->assertTrue(false);
86 | }
87 |
88 | $uploadedFile = new UploadedFile(
89 | $cloneFile,
90 | 'shieldon_logo.png',
91 | 'image/png',
92 | 100000,
93 | 0
94 | );
95 |
96 | $this->assertSame($uploadedFile->getSize(), 100000);
97 | $this->assertSame($uploadedFile->getError(), 0);
98 | $this->assertSame($uploadedFile->getClientFilename(), 'shieldon_logo.png');
99 | $this->assertSame($uploadedFile->getClientMediaType(), 'image/png');
100 | $this->assertSame($uploadedFile->getErrorMessage(), 'There is no error, the file uploaded with success.');
101 |
102 | $stream = $uploadedFile->getStream();
103 |
104 | $this->assertTrue(($stream instanceof StreamInterface));
105 | }
106 |
107 | public function testGetErrorMessage()
108 | {
109 | $uploadedFile = new UploadedFile(
110 | BOOTSTRAP_DIR . '/sample/shieldon_logo.png',
111 | 'shieldon_logo.png',
112 | 'image/png',
113 | 100000,
114 | UPLOAD_ERR_OK
115 | );
116 |
117 | $this->assertSame($uploadedFile->getErrorMessage(), 'There is no error, the file uploaded with success.');
118 |
119 | $reflection = new ReflectionObject($uploadedFile);
120 | $error = $reflection->getProperty('error');
121 | $error->setAccessible(true);
122 |
123 | $error->setValue($uploadedFile, UPLOAD_ERR_INI_SIZE);
124 | $this->assertSame(
125 | $uploadedFile->getErrorMessage(),
126 | 'The uploaded file exceeds the upload_max_filesize directive in php.ini'
127 | );
128 |
129 | $error->setValue($uploadedFile, UPLOAD_ERR_FORM_SIZE);
130 | $this->assertSame(
131 | $uploadedFile->getErrorMessage(),
132 | 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.'
133 | );
134 |
135 | $error->setValue($uploadedFile, UPLOAD_ERR_PARTIAL);
136 | $this->assertSame($uploadedFile->getErrorMessage(), 'The uploaded file was only partially uploaded.');
137 |
138 | $error->setValue($uploadedFile, UPLOAD_ERR_NO_FILE);
139 | $this->assertSame($uploadedFile->getErrorMessage(), 'No file was uploaded.');
140 |
141 | $error->setValue($uploadedFile, UPLOAD_ERR_NO_TMP_DIR);
142 | $this->assertSame($uploadedFile->getErrorMessage(), 'Missing a temporary folder.');
143 |
144 | $error->setValue($uploadedFile, UPLOAD_ERR_CANT_WRITE);
145 | $this->assertSame($uploadedFile->getErrorMessage(), 'Failed to write file to disk.');
146 |
147 | $error->setValue($uploadedFile, UPLOAD_ERR_EXTENSION);
148 | $this->assertSame($uploadedFile->getErrorMessage(), 'File upload stopped by extension.');
149 |
150 | $error->setValue($uploadedFile, 19890604);
151 | $this->assertSame($uploadedFile->getErrorMessage(), 'Unknown upload error.');
152 | }
153 |
154 | /*
155 | |--------------------------------------------------------------------------
156 | | Exceptions
157 | |--------------------------------------------------------------------------
158 | */
159 |
160 | public function test_Exception_ArgumentIsInvalidSource()
161 | {
162 | $this->expectException(InvalidArgumentException::class);
163 |
164 | // Exception:
165 | // => First argument accepts only a string or StreamInterface instance.
166 | $uploadedFile = new UploadedFile([]);
167 | }
168 |
169 | public function test_Exception_GetStream_StreamIsNotAvailable()
170 | {
171 | $this->expectException(RuntimeException::class);
172 |
173 | // Test 1: Source is not a stream.
174 |
175 | $uploadedFile = new UploadedFile(
176 | '/tmp/php200A.tmp', // source
177 | 'example1.jpg', // name
178 | 'image/jpeg', // type
179 | 100000, // size
180 | 0 // error
181 | );
182 |
183 | // Exception:
184 | // => No stream is available or can be created.
185 | $stream = $uploadedFile->getStream();
186 | }
187 |
188 | public function test_Exception_GetStream_StreamIsMoved()
189 | {
190 | $this->expectException(RuntimeException::class);
191 |
192 | // Test 2: Stream has been moved, so can't find it using getStream().
193 |
194 | $resource = fopen(BOOTSTRAP_DIR . '/sample/shieldon_logo.png', 'r+');
195 | $stream = new Stream($resource);
196 | $uploadedFile = new UploadedFile($stream);
197 |
198 | $targetPath = save_testing_file('shieldon_logo_moved_from_stream.png');
199 | $uploadedFile->moveTo($targetPath);
200 |
201 | if (!file_exists($targetPath)) {
202 | // Remind us there is something wrong on this test.
203 | $this->assertTrue(false);
204 | }
205 |
206 | unlink($targetPath);
207 |
208 | // Exception:
209 | // => The stream has been moved
210 | $stream = $uploadedFile->getStream();
211 | }
212 |
213 | public function test_Exception_MoveTo_FileIsMoved()
214 | {
215 | $this->expectException(RuntimeException::class);
216 |
217 | $uploadedFile = new UploadedFile(
218 | '/tmp/php200A.tmp',
219 | 'shieldon_logo.png',
220 | 'image/png',
221 | 100000,
222 | 0
223 | );
224 |
225 | $reflection = new ReflectionObject($uploadedFile);
226 | $isMoved = $reflection->getProperty('isMoved');
227 | $isMoved->setAccessible(true);
228 | $isMoved->setValue($uploadedFile, true);
229 |
230 | $targetPath = save_testing_file('shieldon_logo_moved_from_stream.png');
231 |
232 | // Exception:
233 | // => The uploaded file has been moved.
234 | $uploadedFile->moveTo($targetPath);
235 | }
236 |
237 | public function test_Exception_MoveTo_TargetIsNotWritable()
238 | {
239 | $this->expectException(RuntimeException::class);
240 |
241 | $uploadedFile = new UploadedFile(
242 | BOOTSTRAP_DIR . '/sample/shieldon_logo.png',
243 | 'shieldon_logo.png',
244 | 'image/png',
245 | 100000,
246 | 0
247 | );
248 |
249 | // Exception:
250 | // => The target path "/tmp/folder-not-exists/test.png" is not writable.
251 | $uploadedFile->moveTo(BOOTSTRAP_DIR . '/tmp/folder-not-exists/test.png');
252 | }
253 |
254 | public function test_Exception_MoveTo_FileNotUploaded()
255 | {
256 | $this->expectException(RuntimeException::class);
257 |
258 | $uploadedFile = new UploadedFile(
259 | BOOTSTRAP_DIR . '/sample/shieldon_logo.png',
260 | 'shieldon_logo.png',
261 | 'image/png',
262 | 100000,
263 | 0,
264 | 'mock-is-uploaded-file-false'
265 | );
266 |
267 | $targetPath = save_testing_file('shieldon_logo_moved_from_file.png');
268 |
269 | $uploadedFile->moveTo($targetPath);
270 | }
271 |
272 | public function test_Exception_MoveTo_FileCannotBeMoved()
273 | {
274 | $this->expectException(RuntimeException::class);
275 |
276 | $uploadedFile = new UploadedFile(
277 | BOOTSTRAP_DIR . '/sample/shieldon_logo.png',
278 | 'shieldon_logo.png',
279 | 'image/png',
280 | 100000,
281 | 0,
282 | 'unit-test-2'
283 | );
284 |
285 | $targetPath = save_testing_file('shieldon_logo_moved_from_file.png');
286 |
287 | $uploadedFile->moveTo($targetPath);
288 | }
289 | }
290 |
--------------------------------------------------------------------------------
/tests/Psr7/UriTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Psr\Http\Message\UriInterface;
15 | use Shieldon\Psr7\Uri;
16 |
17 | use InvalidArgumentException;
18 | use ReflectionObject;
19 |
20 | class UriTest extends TestCase
21 | {
22 | public function test__construct()
23 | {
24 | $uri = new Uri('http://jack:1234@example.com/demo/?test=5678&test2=90#section-1');
25 |
26 | $this->assertTrue(($uri instanceof UriInterface));
27 | }
28 |
29 | public function test__toString()
30 | {
31 | // Test 1
32 |
33 | $uri = new Uri('http://jack:1234@example.com:8888/demo/?test=5678&test2=90#section-1');
34 |
35 | ob_start();
36 | echo $uri;
37 | $output = ob_get_contents();
38 | ob_end_clean();
39 |
40 | $this->assertSame('http://jack:1234@example.com:8888/demo/?test=5678&test2=90#section-1', $output);
41 |
42 | // Test 2
43 |
44 | $uri = new Uri('http://example.com:8888/demo/#section-1');
45 |
46 | ob_start();
47 | echo $uri;
48 | $output = ob_get_contents();
49 | ob_end_clean();
50 |
51 | $this->assertSame('http://example.com:8888/demo/#section-1', $output);
52 | }
53 |
54 | public function test_Properties()
55 | {
56 | $uri = new Uri('http://jack:1234@example.com:8080/demo/?test=5678&test2=90#section-1');
57 |
58 | $components = [
59 | 'scheme',
60 | 'user',
61 | 'pass',
62 | 'host',
63 | 'port',
64 | 'path',
65 | 'query',
66 | 'fragment',
67 | ];
68 |
69 | $reflection = new ReflectionObject($uri);
70 |
71 | foreach ($components as $v) {
72 | $tmp = $reflection->getProperty($v);
73 | $tmp->setAccessible(true);
74 | ${$v} = $tmp->getValue($uri);
75 | unset($tmp);
76 | }
77 |
78 | $this->assertSame($scheme, 'http');
79 | $this->assertSame($host, 'example.com');
80 | $this->assertSame($user, 'jack');
81 | $this->assertSame($pass, '1234');
82 | $this->assertSame($path, '/demo/');
83 | $this->assertSame($port, 8080);
84 | $this->assertSame($query, 'test=5678&test2=90');
85 | $this->assertSame($fragment, 'section-1');
86 | }
87 |
88 | public function test_GetPrefixMethods()
89 | {
90 | // Test 1
91 |
92 | $uri = new Uri('http://jack:1234@example.com:8080/demo/?test=5678&test2=90#section-1');
93 |
94 | $this->assertSame($uri->getScheme(), 'http');
95 | $this->assertSame($uri->getHost(), 'example.com');
96 | $this->assertSame($uri->getUserInfo(), 'jack:1234');
97 | $this->assertSame($uri->getPath(), '/demo/');
98 | $this->assertSame($uri->getPort(), 8080);
99 | $this->assertSame($uri->getQuery(), 'test=5678&test2=90');
100 | $this->assertSame($uri->getFragment(), 'section-1');
101 |
102 | // Test 2
103 |
104 | $uri = new Uri('https://www.example.com');
105 |
106 | $this->assertSame($uri->getScheme(), 'https');
107 | $this->assertSame($uri->getHost(), 'www.example.com');
108 | $this->assertSame($uri->getUserInfo(), ''); // string
109 | $this->assertSame($uri->getPath(), ''); // string
110 | $this->assertSame($uri->getPort(), null); // int|null
111 | $this->assertSame($uri->getQuery(), ''); // string
112 | $this->assertSame($uri->getFragment(), ''); // string
113 | }
114 |
115 | public function test_WithPrefixMethods()
116 | {
117 | $uri = new Uri('https://www.example.com');
118 |
119 | // Test 1
120 |
121 | $newUri = $uri->withScheme('http')
122 | ->withHost('example.com')
123 | ->withPort(8080)
124 | ->withUserInfo('jack', '4321')
125 | ->withPath('/en')
126 | ->withQuery('test=123')
127 | ->withFragment('1234');
128 |
129 | $this->assertSame($newUri->getScheme(), 'http');
130 | $this->assertSame($newUri->getHost(), 'example.com');
131 | $this->assertSame($newUri->getUserInfo(), 'jack:4321');
132 | $this->assertSame($newUri->getPath(), '/en');
133 | $this->assertSame($newUri->getPort(), 8080);
134 | $this->assertSame($newUri->getQuery(), 'test=123');
135 | $this->assertSame($newUri->getFragment(), '1234');
136 |
137 | unset($newUri);
138 |
139 | // Test 2
140 |
141 | $newUri = $uri->withScheme('http')
142 | ->withHost('freedom.com')
143 | ->withPort(80)
144 | ->withUserInfo('people')
145 | ->withPath('/天安門')
146 | ->withQuery('chineseChars=六四')
147 | ->withFragment('19890604');
148 |
149 | $this->assertSame($newUri->getScheme(), 'http');
150 | $this->assertSame($newUri->getHost(), 'freedom.com');
151 | $this->assertSame($newUri->getUserInfo(), 'people:');
152 | $this->assertSame($newUri->getPath(), '/%25E5%25A4%25A9%25E5%25AE%2589%25E9%2596%2580');
153 | $this->assertSame($newUri->getPort(), null);
154 | $this->assertSame($newUri->getQuery(), 'chineseChars=%E5%85%AD%E5%9B%9B');
155 | $this->assertSame($newUri->getFragment(), '19890604');
156 | }
157 |
158 | public function test_filterPort()
159 | {
160 | $uri = new Uri('http://example.com:80');
161 | $this->assertSame($uri->getPort(), null);
162 |
163 | $uri = new Uri('//example.com:80');
164 | $this->assertSame($uri->getPort(), 80);
165 | }
166 |
167 | /*
168 | |--------------------------------------------------------------------------
169 | | Exceptions
170 | |--------------------------------------------------------------------------
171 | */
172 |
173 | public function test_Exception_AssertScheme()
174 | {
175 | $this->expectException(InvalidArgumentException::class);
176 |
177 | $uri = new Uri();
178 |
179 | $reflection = new ReflectionObject($uri);
180 | $assertScheme = $reflection->getMethod('assertScheme');
181 | $assertScheme->setAccessible(true);
182 |
183 | // Exception:
184 | // => The string "telnet" is not a valid scheme.
185 | $assertScheme->invokeArgs($uri, ['telnet']);
186 | }
187 |
188 | public function test_Exception_AssertString()
189 | {
190 | $this->expectException(InvalidArgumentException::class);
191 |
192 | $uri = new Uri();
193 |
194 | $reflection = new ReflectionObject($uri);
195 | $assertString = $reflection->getMethod('assertString');
196 | $assertString->setAccessible(true);
197 |
198 | // Exception:
199 | // => It must be a string, but integer provided.
200 | $assertString->invokeArgs($uri, [1234]);
201 | }
202 |
203 | public function test_Exception_AssertHost()
204 | {
205 | $this->expectException(InvalidArgumentException::class);
206 |
207 | $uri = new Uri();
208 |
209 | $reflection = new ReflectionObject($uri);
210 | $assertHost = $reflection->getMethod('assertHost');
211 | $assertHost->setAccessible(true);
212 |
213 | // Exception:
214 | // => "example_test.com" is not a valid host
215 | $assertHost->invokeArgs($uri, ['example_test.com']);
216 | }
217 |
218 | public function test_AssertHost_ReturnVoid()
219 | {
220 | $uri = new Uri();
221 |
222 | $reflection = new ReflectionObject($uri);
223 | $assertHost = $reflection->getMethod('assertHost');
224 | $assertHost->setAccessible(true);
225 | $result = $assertHost->invokeArgs($uri, ['']);
226 |
227 | $this->assertSame($result, null);
228 | }
229 |
230 | public function test_Exception_AssertPort_InvalidVariableType()
231 | {
232 | $this->expectException(InvalidArgumentException::class);
233 |
234 | $uri = new Uri();
235 |
236 | $reflection = new ReflectionObject($uri);
237 | $assertPort = $reflection->getMethod('assertPort');
238 | $assertPort->setAccessible(true);
239 |
240 | // Exception:
241 | // => Port must be an integer or a null value, but string provided.
242 | $assertPort->invokeArgs($uri, ['8080']);
243 | }
244 |
245 | public function test_Exception_AssertPort_InvalidRangeNumer()
246 | {
247 | $this->expectException(InvalidArgumentException::class);
248 |
249 | $uri = new Uri();
250 |
251 | $reflection = new ReflectionObject($uri);
252 | $assertPort = $reflection->getMethod('assertPort');
253 | $assertPort->setAccessible(true);
254 |
255 | // Exception:
256 | // => Port number should be in a range of 0-65535, but 70000 provided.
257 | $assertPort->invokeArgs($uri, [70000]);
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/tests/Psr7/Utils/UploadedFileHelperTest.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 |
11 | namespace Shieldon\Test\Psr7;
12 |
13 | use PHPUnit\Framework\TestCase;
14 | use Shieldon\Psr7\UploadedFile;
15 | use Shieldon\Psr7\Utils\UploadedFileHelper;
16 |
17 | class UploadedFileHelperTest extends TestCase
18 | {
19 | public function test_ParseUploadedFiles()
20 | {
21 | $files = [
22 |
23 | //
24 |
25 | 'files1' => [
26 | 'name' => 'example1.jpg',
27 | 'type' => 'image/jpeg',
28 | 'tmp_name' => '/tmp/php200A.tmp',
29 | 'error' => 0,
30 | 'size' => 100000,
31 | ],
32 |
33 | //
34 | //
35 |
36 | 'files2' => [
37 | 'name' => [
38 | 'a' => 'example21.jpg',
39 | 'b' => 'example22.jpg',
40 | ],
41 | 'type' => [
42 | 'a' => 'image/jpeg',
43 | 'b' => 'image/jpeg',
44 | ],
45 | 'tmp_name' => [
46 | 'a' => '/tmp/php343C.tmp',
47 | 'b' => '/tmp/php343D.tmp',
48 | ],
49 | 'error' => [
50 | 'a' => 0,
51 | 'b' => 0,
52 | ],
53 | 'size' => [
54 | 'a' => 125100,
55 | 'b' => 145000,
56 | ],
57 | ],
58 |
59 | //
60 | //
61 |
62 | 'files3' => [
63 | 'name' => [
64 | 0 => 'example31.jpg',
65 | 1 => 'example32.jpg',
66 | ],
67 | 'type' => [
68 | 0 => 'image/jpeg',
69 | 1 => 'image/jpeg',
70 | ],
71 | 'tmp_name' => [
72 | 0 => '/tmp/php310C.tmp',
73 | 1 => '/tmp/php313D.tmp',
74 | ],
75 | 'error' => [
76 | 0 => 0,
77 | 1 => 0,
78 | ],
79 | 'size' => [
80 | 0 => 200000,
81 | 1 => 300000,
82 | ],
83 | ],
84 |
85 | //
86 |
87 | 'files4' => [
88 | 'name' => [
89 | 'details' => [
90 | 'avatar' => 'my-avatar.png',
91 | ],
92 | ],
93 | 'type' => [
94 | 'details' => [
95 | 'avatar' => 'image/png',
96 | ],
97 | ],
98 | 'tmp_name' => [
99 | 'details' => [
100 | 'avatar' => '/tmp/phpmFLrzD',
101 | ],
102 | ],
103 | 'error' => [
104 | 'details' => [
105 | 'avatar' => 0,
106 | ],
107 | ],
108 | 'size' => [
109 | 'details' => [
110 | 'avatar' => 90996,
111 | ],
112 | ],
113 | ],
114 | ];
115 |
116 | $expectedFiles = [
117 | 'files1' => [
118 | 'name' => 'example1.jpg',
119 | 'type' => 'image/jpeg',
120 | 'tmp_name' => '/tmp/php200A.tmp',
121 | 'error' => 0,
122 | 'size' => 100000,
123 | ],
124 | 'files2' => [
125 | 'a' => [
126 | 'tmp_name' => '/tmp/php343C.tmp',
127 | 'name' => 'example21.jpg',
128 | 'type' => 'image/jpeg',
129 | 'error' => 0,
130 | 'size' => 125100,
131 | ],
132 | 'b' => [
133 | 'tmp_name' => '/tmp/php343D.tmp',
134 | 'name' => 'example22.jpg',
135 | 'type' => 'image/jpeg',
136 | 'error' => 0,
137 | 'size' => 145000,
138 | ],
139 | ],
140 | 'files3' => [
141 | 0 => [
142 | 'tmp_name' => '/tmp/php310C.tmp',
143 | 'name' => 'example31.jpg',
144 | 'type' => 'image/jpeg',
145 | 'error' => 0,
146 | 'size' => 200000,
147 | ],
148 | 1 => [
149 | 'tmp_name' => '/tmp/php313D.tmp',
150 | 'name' => 'example32.jpg',
151 | 'type' => 'image/jpeg',
152 | 'error' => 0,
153 | 'size' => 300000,
154 | ],
155 | ],
156 | 'files4' => [
157 | 'details' => [
158 | 'avatar' => [
159 | 'tmp_name' => '/tmp/phpmFLrzD',
160 | 'name' => 'my-avatar.png',
161 | 'type' => 'image/png',
162 | 'error' => 0,
163 | 'size' => 90996,
164 | ],
165 | ],
166 | ],
167 | ];
168 |
169 | $results = UploadedFileHelper::uploadedFileParse($files);
170 |
171 | $this->assertEquals($results, $expectedFiles);
172 | }
173 | public function test_UploadedFileSpecsConvert()
174 | {
175 | $formattedFiles = [
176 | 'files1' => [
177 | 'name' => 'example1.jpg',
178 | 'type' => 'image/jpeg',
179 | 'tmp_name' => '/tmp/php200A.tmp',
180 | 'error' => 0,
181 | 'size' => 100000,
182 | ],
183 | 'files2' => [
184 | 'a' => [
185 | 'tmp_name' => '/tmp/php343C.tmp',
186 | 'name' => 'example21.jpg',
187 | 'type' => 'image/jpeg',
188 | 'error' => 0,
189 | 'size' => 125100,
190 | ],
191 | 'b' => [
192 | 'tmp_name' => '/tmp/php343D.tmp',
193 | 'name' => 'example22.jpg',
194 | 'type' => 'image/jpeg',
195 | 'error' => 0,
196 | 'size' => 145000,
197 | ],
198 | ],
199 | 'files3' => [
200 | 0 => [
201 | 'tmp_name' => '/tmp/php310C.tmp',
202 | 'name' => 'example31.jpg',
203 | 'type' => 'image/jpeg',
204 | 'error' => 0,
205 | 'size' => 200000,
206 | ],
207 | 1 => [
208 | 'tmp_name' => '/tmp/php313D.tmp',
209 | 'name' => 'example32.jpg',
210 | 'type' => 'image/jpeg',
211 | 'error' => 0,
212 | 'size' => 300000,
213 | ],
214 | ],
215 | 'files4' => [
216 | 'details' => [
217 | 'avatar' => [
218 | 'tmp_name' => '/tmp/phpmFLrzD',
219 | 'name' => 'my-avatar.png',
220 | 'type' => 'image/png',
221 | 'error' => 0,
222 | 'size' => 90996,
223 | ],
224 | ],
225 | ],
226 | ];
227 |
228 | $expectedFiles = [
229 | 'files1' => new UploadedFile(
230 | '/tmp/php200A.tmp',
231 | 'example1.jpg',
232 | 'image/jpeg',
233 | 100000,
234 | 0
235 | ),
236 | 'files2' => [
237 | 'a' => new UploadedFile(
238 | '/tmp/php343C.tmp',
239 | 'example21.jpg',
240 | 'image/jpeg',
241 | 125100,
242 | 0
243 | ),
244 | 'b' => new UploadedFile(
245 | '/tmp/php343D.tmp',
246 | 'example22.jpg',
247 | 'image/jpeg',
248 | 145000,
249 | 0
250 | ),
251 | ],
252 | 'files3' => [
253 | 0 => new UploadedFile(
254 | '/tmp/php310C.tmp',
255 | 'example31.jpg',
256 | 'image/jpeg',
257 | 200000,
258 | 0
259 | ),
260 | 1 => new UploadedFile(
261 | '/tmp/php313D.tmp',
262 | 'example32.jpg',
263 | 'image/jpeg',
264 | 300000,
265 | 0
266 | ),
267 | ],
268 | 'files4' => [
269 | 'details' => [
270 | 'avatar' => new UploadedFile(
271 | '/tmp/phpmFLrzD',
272 | 'my-avatar.png',
273 | 'image/png',
274 | 90996,
275 | 0
276 | ),
277 | ],
278 | ],
279 | ];
280 |
281 | $results = UploadedFileHelper::uploadedFileSpecsConvert($formattedFiles);
282 |
283 | $this->assertEquals($results, $expectedFiles);
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |