Passed
Push — feature/0.7.0 ( 0c7d59...ae5b22 )
by Ryuichi
78:01 queued 33:04
created

Response::send()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
namespace WebStream\Http;
3
4
use WebStream\DI\Injector;
5
use WebStream\Module\Utility\CommonUtils;
6
7
/**
8
 * Response
9
 * @author Ryuichi TANAKA.
10
 * @since 2012/12/19
11
 * @version 0.7
12
 */
13
class Response
14
{
15
    use Injector, CommonUtils;
16
17
    /** HTTPバージョン */
18
    const HTTP_VERSION = '1.1';
19
    /** 文字コード */
20
    private $charset = 'UTF-8';
21
    /** Cache-Control */
22
    private $cacheControl = 'no-cache';
23
    /** Pragma */
24
    private $pragma = 'no-cache';
25
    /** Mime type */
26
    private $mimeType = 'text/html';
27
    /** ロケーション */
28
    private $location;
29
    /** Access-Control-Allow-Origin */
30
    private $accessControlAllowOrigin = [];
31
    /** X-Frame-Options */
32
    private $xframeOptions = 'SAMEORIGIN';
33
    /** X-XSS-Protection */
34
    private $xxssProtection = '1; mode=block';
35
    /** ステータスコード */
36
    private $statusCode = 200;
37
    /** レスポンスボディ */
38
    private $body;
39
    /** レスポンスファイル */
40
    private $file;
41
    /** Content-Length */
42
    private $contentLength;
43
    /** Content-Disposition */
44
    private $contentDisposition;
45
    /** Content-Transfer-Encoding */
46
    private $contentTransferEncoding;
47
    /** Expires */
48
    private $expires;
49
50
    /**
51
     * デストラクタ
52
     */
53
    public function __destruct()
54
    {
55
        $this->logger->debug("Response is clear.");
0 ignored issues
show
Bug Best Practice introduced by
The property logger does not exist on WebStream\Http\Response. Since you implemented __get, consider adding a @property annotation.
Loading history...
56
    }
57
58
    /**
59
     * 文字コードを設定
60
     * @param String 文字コード
61
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 文字コード at position 0 could not be parsed: Unknown type name '文字コード' at position 0 in 文字コード.
Loading history...
62
    public function setCharset($charset)
63
    {
64
        $this->charset = $charset;
65
    }
66
67
    /**
68
     * Cache-Controlを設定
69
     * @param String Cache-Control
70
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment Cache-Control at position 0 could not be parsed: Unknown type name 'Cache-Control' at position 0 in Cache-Control.
Loading history...
71
    public function setCacheControl($cacheControl)
72
    {
73
        $this->cacheControl = $cacheControl;
74
    }
75
76
    /**
77
     * Pragmaを設定
0 ignored issues
show
Bug introduced by
The type WebStream\Http\Pragma was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
78
     * @param String Pragma
79
     */
80
    public function setPragma($pragma)
81
    {
82
        $this->pragma = $pragma;
83
    }
84
85
    /**
86
     * MimeTypeを設定
87
     * ファイルタイプにより指定
88
     * @param String ファイルタイプ
89
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイルタイプ at position 0 could not be parsed: Unknown type name 'ファイルタイプ' at position 0 in ファイルタイプ.
Loading history...
90
    public function setType($fileType)
91
    {
92
        if (array_key_exists($fileType, $this->mime)) {
93
            $this->mimeType = $this->mime[$fileType];
94
        } else {
95
            // 不明なファイルが指定された場合、画面に表示させずダウンロードさせる
96
            $this->mimeType = $this->mime['file'];
97
        }
98
    }
99
100
    /**
101
     * MimeTypeを設定
0 ignored issues
show
Bug introduced by
The type WebStream\Http\MimeType was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
102
     * MimeTypeを直接指定
103
     * @param String MimeType
104
     */
105
    public function setMimeType($mimeType)
106
    {
107
        $this->mimeType = $mimeType;
108
    }
109
110
    /**
111
     * リダイレクトロケーションを設定
112
     * @param String ロケーションパス
113
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ロケーションパス at position 0 could not be parsed: Unknown type name 'ロケーションパス' at position 0 in ロケーションパス.
Loading history...
114
    public function setLocation($location)
115
    {
116
        $this->location = $location;
117
    }
118
119
    /**
120
     * Access-Control-Allow-Originを設定
121
     * 複数指定する場合は、引数に列挙する
122
     * @param String URLまたはワイルドカード
123
     */
124
    public function setAccessControlAllowOrigin()
125
    {
126
        $arguments = func_get_args();
127
        foreach ($arguments as $argument) {
128
            $this->accessControlAllowOrigin[] = $argument;
129
        }
130
    }
131
132
    /**
133
     * X-Frame-Optionsを設定
134
     * @param String SAMEORIGINまたはDENY
135
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment SAMEORIGINまたはDENY at position 0 could not be parsed: Unknown type name 'SAMEORIGINまたはDENY' at position 0 in SAMEORIGINまたはDENY.
Loading history...
136
    public function setXFrameOptions($xframeOptions)
137
    {
138
        $this->xframeOptions = $xframeOptions;
139
    }
140
141
    /**
142
     * X-XSS-Protectionを設定
143
     * @param String XSSフィルタ設定(0:無効、1:有効)
144
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment XSSフィルタ設定(0:無効、1:有効) at position 0 could not be parsed: Unknown type name 'XSSフィルタ設定' at position 0 in XSSフィルタ設定(0:無効、1:有効).
Loading history...
145
    public function setXXssProtection($xxssProtection)
146
    {
147
        $this->xxssProtection = $xxssProtection;
148
    }
149
150
    /**
151
     * ステータスコードを設定
152
     * @param Integer ステータスコード
153
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ステータスコード at position 0 could not be parsed: Unknown type name 'ステータスコード' at position 0 in ステータスコード.
Loading history...
154
    public function setStatusCode($statusCode)
155
    {
156
        if (!is_string($statusCode) && !is_int($statusCode)) {
157
            throw new ConnectionException("Invalid status code format: " . strval($statusCode));
0 ignored issues
show
Bug introduced by
The type WebStream\Http\ConnectionException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
158
        }
159
160
        if (!array_key_exists($statusCode, $this->status)) {
161
            throw new ConnectionException("Unknown status code: " . $statusCode);
162
        }
163
        $this->statusCode = $statusCode;
164
    }
165
166
    /**
167
     * Content-Lengthを設定
168
     * @param Integer Content-Length
169
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment Content-Length at position 0 could not be parsed: Unknown type name 'Content-Length' at position 0 in Content-Length.
Loading history...
170
    public function setContentLength($contentLength)
171
    {
172
        $this->contentLength = $contentLength;
173
    }
174
175
    /**
176
     * Content-Dispositionを設定
177
     * @param String ファイル名
178
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイル名 at position 0 could not be parsed: Unknown type name 'ファイル名' at position 0 in ファイル名.
Loading history...
179
    public function setContentDisposition($filename)
180
    {
181
        if (file_exists($filename)) {
182
            $this->contentDisposition = 'attachement; filename="'. basename($filename) . '"';
183
        }
184
    }
185
186
    /**
187
     * Content-Transfer-Encodingを設定
188
     * @param String エンコーディング方法
189
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment エンコーディング方法 at position 0 could not be parsed: Unknown type name 'エンコーディング方法' at position 0 in エンコーディング方法.
Loading history...
190
    public function setContentTransferEncoding($contentTransferEncoding)
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $contentTransferEncoding exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
191
    {
192
        $this->contentTransferEncoding = $contentTransferEncoding;
193
    }
194
195
    /**
196
     * Expiresを設定
197
     * @param Integer 有効期限
198
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 有効期限 at position 0 could not be parsed: Unknown type name '有効期限' at position 0 in 有効期限.
Loading history...
199
    public function setExpires($expires)
200
    {
201
        $this->expires = $expires;
202
    }
203
204
    /**
205
     * レスポンスボディを設定
206
     * @param String レスポンスボディ
207
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment レスポンスボディ at position 0 could not be parsed: Unknown type name 'レスポンスボディ' at position 0 in レスポンスボディ.
Loading history...
208
    public function setBody($body)
209
    {
210
        $this->body = $body;
211
    }
212
213
    /**
214
     * レスポンスファイルを設定
215
     * @param String レスポンスファイル
216
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment レスポンスファイル at position 0 could not be parsed: Unknown type name 'レスポンスファイル' at position 0 in レスポンスファイル.
Loading history...
217
    public function setFile($file)
218
    {
219
        $this->file = $file;
220
    }
221
222
    /**
223
     * レスポンスを送出する
224
     */
225
    public function send()
226
    {
227
        $this->header();
228
        $this->body();
229
    }
230
231
    /**
232
     * レスポンスヘッダを送出する
233
     */
234
    public function header()
235
    {
236
        if (headers_sent()) {
237
            return;
238
        }
239
240
        // StatusCode
241
        $headerMessage = 'HTTP/' . self::HTTP_VERSION . ' ' .
242
                         $this->statusCode . ' ' . $this->status[$this->statusCode];
243
        header($headerMessage);
244
245
        // Redirect
246
        if (intval($this->statusCode) === 301) {
247
            header('Location: ' . $this->location);
248
        }
249
250
        // Content-Type
251
        header('Content-Type: ' . $this->mimeType . '; charset=' . $this->charset);
252
253
        // Content-Length
254
        if ($this->contentLength === null) {
255
            $this->contentLength = $this->bytelen($this->body);
256
        }
257
        header('Content-Length: ' . $this->contentLength);
258
259
        // Content-Disposition
260
        if ($this->contentDisposition !== null) {
261
            header('Content-Disposition: ' . $this->contentDisposition);
262
        }
263
264
        // Content-Transfer-Encoding
265
        if ($this->contentTransferEncoding !== null) {
266
            header('Content-Transfer-Encoding: ' . $this->contentTransferEncoding);
267
        }
268
269
        // Cache-Control
270
        header('Cache-Control: ' . $this->cacheControl);
271
272
        // Pragma
273
        header('Pragma: ' . $this->pragma);
274
275
        // Expires
276
        if ($this->expires !== null) {
277
            header('Expires: ' . $this->expires);
278
        }
279
280
        // X-Content-Type-Options
281
        header("X-Content-Type-Options: nosniff");
282
283
        // Access-Control-Allow-Origin
284
        if (!empty($this->accessControlAllowOrigin)) {
285
            header('Access-Control-Allow-Origin: ' . implode(',', $this->accessControlAllowOrigin));
286
        }
287
288
        // X-Frame-Options
289
        if ($this->xframeOptions !== null) {
290
            header('X-Frame-Options: ' . $this->xframeOptions);
291
        }
292
293
        // X-XSS-Protection
294
        if ($this->xxssProtection !== null) {
295
            header('X-XSS-Protection: ' . $this->xxssProtection);
296
        }
297
298
        $this->logger->info("HTTP access occured: status code " . $this->statusCode);
0 ignored issues
show
Bug Best Practice introduced by
The property logger does not exist on WebStream\Http\Response. Since you implemented __get, consider adding a @property annotation.
Loading history...
299
    }
300
301
    /**
302
     * レスポンスボディを送出する
303
     */
304
    public function body()
305
    {
306
        if ($this->file !== null) {
307
            // バイナリ系、その他のファイルはダウンロードする
308
            ob_clean();
309
            flush();
310
            readfile($this->file);
311
        } else {
312
            // テキスト系は画面に表示する
313
            echo $this->body;
314
        }
315
    }
316
317
    /**
318
     * Mime-Type
319
     */
320
    protected $mime = [
321
        'txt'   => 'text/plain',
322
        'jpeg'  => 'image/jpeg',
323
        'jpg'   => 'image/jpeg',
324
        'gif'   => 'image/gif',
325
        'png'   => 'image/png',
326
        'tiff'  => 'image/tiff',
327
        'tif'   => 'image/tiff',
328
        'bmp'   => 'image/bmp',
329
        'ico'   => 'image/x-icon',
330
        'svg'   => 'image/svg+xml',
331
        'xml'   => 'application/xml',
332
        'xsl'   => 'application/xml',
333
        'rss'   => 'application/rss+xml',
334
        'rdf'   => 'application/rdf+xml',
335
        'atom'  => 'application/atom+xml',
336
        'zip'   => 'application/zip',
337
        'html'  => 'text/html',
338
        'htm'   => 'text/html',
339
        'css'   => 'text/css',
340
        'csv'   => 'text/csv',
341
        'tsv'   => 'text/tab-separated-values',
342
        'js'    => 'text/javascript',
343
        'jsonp' => 'text/javascript',
344
        'json'  => 'application/json',
345
        'pdf'   => 'application/pdf',
346
        'file'  => 'application/octet-stream'
347
    ];
348
349
    /**
350
     * Status
351
     */
352
    protected $status = [
353
        '100' => 'Continue',
354
        '101' => 'Switching Protocols',
355
        '102' => 'Processing',
356
        '200' => 'OK',
357
        '201' => 'Created',
358
        '202' => 'Accepted',
359
        '203' => 'Non-Authoritative Information',
360
        '204' => 'No Content',
361
        '205' => 'Reset Content',
362
        '206' => 'Partial Content',
363
        '207' => 'Multi-Status',
364
        '208' => 'Already Reported',
365
        '226' => 'IM Used',
366
        '300' => 'Multiple Choices',
367
        '301' => 'Moved Permanently',
368
        '302' => 'Found',
369
        '303' => 'See Other',
370
        '304' => 'Not Modified',
371
        '305' => 'Use Proxy',
372
        '307' => 'Temporary Redirect',
373
        '400' => 'Bad Request',
374
        '401' => 'Unauthorized',
375
        '402' => 'Payment Required',
376
        '403' => 'Forbidden',
377
        '404' => 'Not Found',
378
        '405' => 'Method Not Allowed',
379
        '406' => 'Not Acceptable',
380
        '407' => 'Proxy Authentication Required',
381
        '408' => 'Request Timeout',
382
        '409' => 'Conflict',
383
        '410' => 'Gone',
384
        '411' => 'Length Required',
385
        '412' => 'Precondition Failed',
386
        '413' => 'Request Entity Too Large',
387
        '414' => 'Request-URI Too Long',
388
        '415' => 'Unsupported Media Type',
389
        '416' => 'Requested Range Not Satisfiable',
390
        '417' => 'Expectation Failed',
391
        '418' => "I'm a teapot",
392
        '422' => 'Unprocessable Entity',
393
        '423' => 'Locked',
394
        '424' => 'Failed Dependency',
395
        '426' => 'Upgrade Required',
396
        '500' => 'Internal Server Error',
397
        '501' => 'Not Implemented',
398
        '502' => 'Bad Gateway',
399
        '503' => 'Service Unavailable',
400
        '504' => 'Gateway Timeout',
401
        '505' => 'HTTP Version Not Supported',
402
        '506' => 'Variant Also Negotiates',
403
        '507' => 'Insufficient Storage',
404
        '508' => 'Loop Detected',
405
        '510' => 'Not Extended',
406
    ];
407
408
    /**
409
     * 301 alias
410
     * @param String redirect url
0 ignored issues
show
Bug introduced by
The type WebStream\Http\redirect was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
411
     */
412
    public function movePermanently($url)
413
    {
414
        $this->setLocation($url);
415
        $this->setStatusCode(301);
416
        $this->send();
417
    }
418
419
    /**
420
     * 400 alias
421
     */
422
    public function badRequest()
423
    {
424
        $this->move(400);
425
    }
426
427
    /**
428
     * 401 alias
429
     */
430
    public function unauthorized()
431
    {
432
        $this->move(401);
433
    }
434
435
    /**
436
     * 403 alias
437
     */
438
    public function forbidden()
439
    {
440
        $this->move(403);
441
    }
442
443
    /**
444
     * 404 alias
445
     */
446
    public function notFound()
447
    {
448
        $this->move(404);
449
    }
450
451
    /**
452
     * 405 alias
453
     */
454
    public function methodNotAllowed()
455
    {
456
        $this->move(405);
457
    }
458
459
    /**
460
     * 422 alias
461
     */
462
    public function unprocessableEntity()
463
    {
464
        $this->move(422);
465
    }
466
467
    /**
468
     * 500 alias
469
     */
470
    public function internalServerError()
471
    {
472
        $this->move(500);
473
    }
474
475
    /**
476
     * 静的ファイルを表示
477
     * @param String ファイル名
478
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイル名 at position 0 could not be parsed: Unknown type name 'ファイル名' at position 0 in ファイル名.
Loading history...
479
    public function displayFile($filename)
480
    {
481
        $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
482
        $this->setType($type);
483
        $this->setContentLength(file_exists($filename) ? filesize($filename) : 0);
484
        $this->setFile($filename);
485
    }
486
487
    /**
488
     * ファイルをダウンロード
489
     * @param String ファイル名
490
     * @param String ユーザエージェント
491
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ファイル名 at position 0 could not be parsed: Unknown type name 'ファイル名' at position 0 in ファイル名.
Loading history...
492
    public function downloadFile($filename, $ua)
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ua. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
493
    {
494
        $type = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
495
        $this->setType($type);
496
        $this->setContentLength(file_exists($filename) ? filesize($filename) : 0);
497
        $this->setContentDisposition($filename);
498
        $this->setExpires(0);
499
        $this->setContentTransferEncoding('binary');
500
        if (isset($ua) && strpos($ua, 'MSIE')) {
501
            $this->setCacheControl('must-revalidate, post-check=0, pre-check=0');
502
            $this->setPragma('public');
503
        }
504
        $this->setFile($filename);
505
    }
506
507
    /**
508
     * 指定したステータスコードのページに遷移
509
     * @param Integer ステータスコード
510
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment ステータスコード at position 0 could not be parsed: Unknown type name 'ステータスコード' at position 0 in ステータスコード.
Loading history...
511
    public function move($statusCode)
512
    {
513
        if (ob_get_contents()) {
514
            ob_clean();
515
        }
516
        $statusCode = array_key_exists($statusCode, $this->status) ? $statusCode : 500;
517
        $this->setStatusCode($statusCode);
518
        $bodyMessage = $statusCode . ' ' . $this->status[$statusCode];
519
        $this->setBody($this->bodyTemplate($bodyMessage));
520
        $this->send();
521
        exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The method move() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
522
    }
523
524
    /**
525
     * レスポンス送出を開始する
526
     */
527
    public function start()
528
    {
529
        ob_start();
530
        ob_implicit_flush(false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $flag of ob_implicit_flush(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

530
        ob_implicit_flush(/** @scrutinizer ignore-type */ false);
Loading history...
531
    }
532
533
    /**
534
     * レスポンスを送出して終了する
535
     */
536
    public function end()
537
    {
538
        $body = "";
539
        if (($error = error_get_last()) === null) {
540
            $body = ob_get_clean();
541
        } else {
542
            switch ($error['type']) {
543
                case E_ERROR:
544
                case E_CORE_ERROR:
545
                case E_COMPILE_ERROR:
546
                case E_USER_ERROR:
547
                case E_RECOVERABLE_ERROR:
548
                case E_PARSE:
549
                    $this->clean();
550
                    break;
551
                case E_WARNING:
552
                case E_CORE_WARNING:
553
                case E_COMPILE_WARNING:
554
                case E_USER_WARNING:
555
                case E_STRICT:
556
                case E_NOTICE:
557
                case E_USER_NOTICE:
558
                case E_DEPRECATED:
559
                case E_USER_DEPRECATED:
560
                    $body = ob_get_clean();
561
                    break;
562
            }
563
        }
564
565
        $this->setBody($body);
566
        $this->send();
567
    }
568
569
    /**
570
     * レスポンス送出せず終了する
571
     */
572
    public function clean()
573
    {
574
        ob_end_clean();
575
    }
576
577
    /**
578
     * HTMLテンプレート
579
     * @param String 表示内容
580
     * @return String HTML
581
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment 表示内容 at position 0 could not be parsed: Unknown type name '表示内容' at position 0 in 表示内容.
Loading history...
582
    private function bodyTemplate($content)
583
    {
584
        return <<< HTML
585
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
586
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
587
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
588
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
589
    <head>
590
        <title>$content</title>
591
    </head>
592
    <body>
593
        <h1>$content</h1>
594
    </body>
595
</html>
596
HTML;
597
    }
598
}
599