← All changes
|
vendor/wp-php-toolkit/reprint-server/src/class-gzip-output-stream.php
+116
-0
16.2-beta
→
16.3-beta
View file →
| @@ -1,0 +1,116 @@ | ||
| 1 | +<?php | |
| 2 | + | |
| 3 | +// Declared here rather than in export.php so it can live in this package's | |
| 4 | +// namespace. export.php is a global-namespace file of endpoint functions that | |
| 5 | +// callers reach by unqualified name. | |
| 6 | + | |
| 7 | +namespace WordPress\Reprint\Server; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * Incremental response writer. | |
| 11 | + * | |
| 12 | + * PHP 7.0 and newer can gzip each bounded write without buffering the whole | |
| 13 | + * response. PHP 5.6 lacks the incremental zlib API, so it sends the same | |
| 14 | + * multipart stream without a Content-Encoding header. | |
| 15 | + * | |
| 16 | + * PHP-side gzip also gives outbound WAF rules that do not decode | |
| 17 | + * Content-Encoding the encoded byte stream; server-level compression can run | |
| 18 | + * after those rules inspect the body. | |
| 19 | + */ | |
| 20 | +class GzipOutputStream | |
| 21 | +{ | |
| 22 | + private $deflate_ctx; | |
| 23 | + /** @var bool */ | |
| 24 | + private $enabled = true; | |
| 25 | + | |
| 26 | + public function __construct(bool $enabled = true) | |
| 27 | + { | |
| 28 | + $this->enabled = $enabled | |
| 29 | + && function_exists('deflate_init') | |
| 30 | + && function_exists('deflate_add'); | |
| 31 | + if ($this->enabled) { | |
| 32 | + $this->deflate_ctx = deflate_init(ZLIB_ENCODING_GZIP, ["level" => 6]); | |
| 33 | + if ($this->deflate_ctx === false) { | |
| 34 | + throw new \RuntimeException( | |
| 35 | + "deflate_init() failed — zlib may be misconfigured" | |
| 36 | + ); | |
| 37 | + } | |
| 38 | + if (!headers_sent()) { | |
| 39 | + @header("Content-Encoding: gzip"); | |
| 40 | + } | |
| 41 | + } | |
| 42 | + } | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * Writes data without forcing a sync point. | |
| 46 | + * | |
| 47 | + * Uses ZLIB_NO_FLUSH so the compressor can build back-references across | |
| 48 | + * multiple write() calls, producing significantly better compression | |
| 49 | + * ratios than ZLIB_SYNC_FLUSH on every call. Data still flows out | |
| 50 | + * whenever zlib's internal buffer fills — the decompressor on the other | |
| 51 | + * end will decompress incrementally. | |
| 52 | + * | |
| 53 | + * Call sync() after each complete multipart part to guarantee the client | |
| 54 | + * can decompress everything emitted so far. | |
| 55 | + */ | |
| 56 | + public function write(string $data): void | |
| 57 | + { | |
| 58 | + if (!$this->enabled) { | |
| 59 | + echo $data; | |
| 60 | + return; | |
| 61 | + } | |
| 62 | + $compressed = deflate_add( | |
| 63 | + $this->deflate_ctx, | |
| 64 | + $data, | |
| 65 | + ZLIB_NO_FLUSH | |
| 66 | + ); | |
| 67 | + if ($compressed === false) { | |
| 68 | + throw new \RuntimeException("deflate_add() failed during gzip write"); | |
| 69 | + } | |
| 70 | + if ($compressed !== "") { | |
| 71 | + echo $compressed; | |
| 72 | + } | |
| 73 | + } | |
| 74 | + | |
| 75 | + /** | |
| 76 | + * Forces a sync flush so the client can decompress all data written so far. | |
| 77 | + */ | |
| 78 | + public function sync(): void | |
| 79 | + { | |
| 80 | + if (!$this->enabled) { | |
| 81 | + flush(); | |
| 82 | + return; | |
| 83 | + } | |
| 84 | + $compressed = deflate_add( | |
| 85 | + $this->deflate_ctx, | |
| 86 | + "", | |
| 87 | + ZLIB_SYNC_FLUSH | |
| 88 | + ); | |
| 89 | + if ($compressed === false) { | |
| 90 | + throw new \RuntimeException("deflate_add() failed during gzip sync"); | |
| 91 | + } | |
| 92 | + if ($compressed !== "") { | |
| 93 | + echo $compressed; | |
| 94 | + } | |
| 95 | + flush(); | |
| 96 | + } | |
| 97 | + | |
| 98 | + /** | |
| 99 | + * Finalizes the gzip stream with ZLIB_FINISH. | |
| 100 | + */ | |
| 101 | + public function finish(): void | |
| 102 | + { | |
| 103 | + if (!$this->enabled) { | |
| 104 | + flush(); | |
| 105 | + return; | |
| 106 | + } | |
| 107 | + $final = deflate_add($this->deflate_ctx, "", ZLIB_FINISH); | |
| 108 | + if ($final === false) { | |
| 109 | + throw new \RuntimeException("deflate_add() failed during gzip finish"); | |
| 110 | + } | |
| 111 | + if ($final !== "") { | |
| 112 | + echo $final; | |
| 113 | + } | |
| 114 | + flush(); | |
| 115 | + } | |
| 116 | +} | |