PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.1-beta.2
Jetpack – WP Security, Backup, Speed, & Growth v16.1-beta.2
16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / vendor / wp-php-toolkit / http-client / ByteStream / class-requestreadstream.php
jetpack / vendor / wp-php-toolkit / http-client / ByteStream Last commit date
class-chunkeddecoderreadstream.php 6 days ago class-chunkedencoderbytetransformer.php 6 days ago class-requestreadstream.php 6 days ago class-seekablerequestreadstream.php 6 days ago
class-requestreadstream.php
224 lines
1 <?php
2
3 namespace WordPress\HttpClient\ByteStream;
4
5 use WordPress\ByteStream\ByteStreamException;
6 use WordPress\ByteStream\ReadStream\BaseByteReadStream;
7 use WordPress\HttpClient\Client;
8 use WordPress\HttpClient\HttpClientException;
9 use WordPress\HttpClient\Request;
10 use WordPress\HttpClient\Response;
11
12 /**
13 * Streams bytes from a remote file.
14 */
15 class RequestReadStream extends BaseByteReadStream {
16
17 /**
18 * @var Client
19 */
20 private $client;
21 /**
22 * @var Request
23 */
24 private $request;
25 /**
26 * @var Response
27 */
28 private $response;
29 /**
30 * @var bool
31 */
32 private $is_enqueued = false;
33 /**
34 * @var int
35 */
36 private $remote_file_length;
37 /**
38 * @var Tracker
39 */
40 private $progress_tracker;
41
42 public function __construct( $request, $options = array() ) {
43 if ( is_string( $request ) ) {
44 $request = new Request( $request );
45 }
46 $this->client = $options['client'] ?? new Client();
47 $this->request = $request;
48 if ( isset( $options['max_lookbehind_bytes'] ) ) {
49 $this->max_lookbehind_bytes = $options['max_lookbehind_bytes'];
50 }
51 if ( isset( $options['progress_tracker'] ) ) {
52 $this->progress_tracker = $options['progress_tracker'];
53 }
54 if ( isset( $options['eagerly_enqueue'] ) ) {
55 $this->ensure_is_enqueued();
56 }
57 }
58
59 public function get_request(): Request {
60 return $this->request;
61 }
62
63 public function json() {
64 return json_decode( $this->consume_all(), true );
65 }
66
67 private function ensure_is_enqueued() {
68 if ( ! $this->is_enqueued ) {
69 $this->client->enqueue( $this->request );
70 $this->is_enqueued = true;
71 }
72 }
73
74 protected function seek_outside_of_buffer( int $target_offset ): void {
75 if ( $target_offset > $this->tell() ) {
76 $pulled = $this->pull_exactly( $target_offset - $this->tell() );
77 $this->consume( $pulled );
78 } else {
79 throw new ByteStreamException(
80 'RequestReadStream cannot seek() backwards to offset ' . $target_offset . ' outside of the in-memory data buffer. ' .
81 'You can either increase the buffer size or implement a custom SeekableRequestReadStream.'
82 );
83 }
84 }
85
86 protected function internal_pull( $max_bytes = 8096 ): string {
87 return $this->pull_until_event(
88 array(
89 'max_bytes' => $max_bytes,
90 'event' => Client::EVENT_BODY_CHUNK_AVAILABLE,
91 )
92 );
93 }
94
95 private function pull_until_event( $options = array() ) {
96 $stop_at_event = $options['event'] ?? Client::EVENT_BODY_CHUNK_AVAILABLE;
97 $this->ensure_is_enqueued();
98
99 while ( $this->client->await_next_event(
100 array(
101 'requests' => array( $this->request->latest_redirect() ),
102 )
103 ) ) {
104 $request = $this->client->get_request();
105 if ( $request->error ) {
106 throw new HttpClientException( sprintf( 'HTTP request failed: %s. Method=%s, URL=%s', $request->error->message, $request->method, $request->url ) );
107 }
108 $response = $request->response;
109 if ( ! $response ) {
110 continue;
111 }
112 if ( $request->redirected_to ) {
113 continue;
114 }
115 switch ( $this->client->get_event() ) {
116 case Client::EVENT_GOT_HEADERS:
117 $this->response = $response;
118 $content_length = $response->get_header( 'Content-Length' );
119 if ( null !== $content_length ) {
120 /**
121 * Best-effort attempt to guess the content-length of the response.
122 *
123 * Web servers often respond with a combination of Content-Length
124 * and Content-Encoding. For example, a 16kb text file may be compressed
125 * to 4kb with gzip and served with a Content-Encoding of `gzip` and a
126 * Content-Length of 4KB.
127 *
128 * If we just use that value, we'd truncate a 16KB body stream with at a
129 * Content-Length of 4KB.
130 *
131 * To correct that behavior, we're discarding the Content-Length header when
132 * it's used alongside a compressed response stream.
133 */
134 if ( ! $response->get_header( 'Content-Encoding' ) ) {
135 /**
136 * Set the content-length based on the header, but make sure it stays null
137 * when the Content-Length header is not set.
138 *
139 * Important: Don't set the content-length to 0 if the header is missing! This
140 * would tell the streaming machinery there's no body to consume.
141 */
142 $this->remote_file_length = (int) $content_length;
143 }
144 }
145 if ( Client::EVENT_GOT_HEADERS === $stop_at_event ) {
146 return true;
147 }
148 break;
149 case Client::EVENT_BODY_CHUNK_AVAILABLE:
150 if ( Client::EVENT_BODY_CHUNK_AVAILABLE === $stop_at_event ) {
151 $body_chunk = $this->client->get_response_body_chunk();
152
153 if ( $this->progress_tracker ) {
154 $bytes_downloaded = $this->bytes_already_forgotten + strlen( $this->buffer ) + strlen( $body_chunk );
155 // Arbitrarily assume 15MB if no length is provided.
156 $length = $this->remote_file_length ? $this->remote_file_length : 15 * 1024 * 1024;
157 $this->progress_tracker->set( $bytes_downloaded / $length * 100 );
158 }
159
160 return $body_chunk;
161 }
162 break;
163 case Client::EVENT_FINISHED:
164 /**
165 * If the server did not provide a Content-Length header,
166 * backfill the file length with the number of downloaded
167 * bytes.
168 */
169 if ( null === $this->remote_file_length ) {
170 $this->remote_file_length = $this->bytes_already_forgotten + strlen( $this->buffer );
171 }
172
173 return '';
174 case Client::EVENT_FAILED:
175 // TODO: Think through error handling. Errors are expected when working with
176 // the network. Should we auto retry? Make it easy for the caller to retry?
177 // Something else?
178 throw new ByteStreamException( 'HTTP request failed: ' . $this->client->get_request()->error );
179 }
180 }
181
182 return '';
183 }
184
185 public function length(): ?int {
186 return $this->remote_file_length;
187 }
188
189 public function await_response() {
190 if ( ! $this->response ) {
191 $this->pull_until_event(
192 array(
193 'event' => Client::EVENT_GOT_HEADERS,
194 )
195 );
196 }
197 if ( ! $this->response ) {
198 throw new ByteStreamException( 'HTTP request failed – never received a response' );
199 }
200
201 return $this->response;
202 }
203
204 protected function internal_reached_end_of_data(): bool {
205 return (
206 Request::STATE_FINISHED === $this->request->latest_redirect()->state &&
207 ! $this->client->has_pending_event( $this->request, Client::EVENT_BODY_CHUNK_AVAILABLE ) &&
208 ! $this->client->has_pending_event( $this->request, Client::EVENT_FINISHED ) &&
209 strlen( $this->buffer ) === $this->offset_in_current_buffer
210 );
211 }
212
213 protected function internal_close_reading(): void {
214 $latest_redirect = $this->request->latest_redirect();
215 if (
216 $latest_redirect &&
217 Request::STATE_FINISHED !== $latest_redirect->state &&
218 Request::STATE_FAILED !== $latest_redirect->state
219 ) {
220 throw new ByteStreamException( 'Cancelling the request is not implemented yet' );
221 }
222 }
223 }
224