GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#34)
by Mathias
02:33
created

VarnishSocket   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 305
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 35
lcom 1
cbo 1
dl 0
loc 305
rs 9.6
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A connect() 0 7 1
A socketConnect() 0 18 2
A authenticate() 0 23 3
A isConnected() 0 8 3
A calculateAuthToken() 0 10 1
A ensureNewline() 0 6 2
A read() 0 18 3
A readChunks() 0 23 5
A continueReading() 0 3 2
A checkSocketTimeout() 0 8 2
A readSingleChunk() 0 10 2
A write() 0 20 4
A command() 0 14 2
A close() 0 7 2
A quit() 0 8 1
1
<?php
2
3
/**
4
 * Based on the Varnish Admin Socket class used in the Terpentine extension for Magento.
5
 * @link https://github.com/nexcess/magento-turpentine
6
 *
7
 * This was in turn based on Tim Whitlock's VarnishAdminSocket.php from php-varnish
8
 * @link https://github.com/timwhitlock/php-varnish
9
 *
10
 * Pieces from both resources above were used to fit our needs.
11
 */
12
13
/**
14
 * Nexcess.net Turpentine Extension for Magento
15
 * Copyright (C) 2012  Nexcess.net L.L.C.
16
 *
17
 * This program is free software; you can redistribute it and/or modify
18
 * it under the terms of the GNU General Public License as published by
19
 * the Free Software Foundation; either version 2 of the License, or
20
 * (at your option) any later version.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25
 * GNU General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU General Public License along
28
 * with this program; if not, write to the Free Software Foundation, Inc.,
29
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
30
 */
31
32
/**
33
 * Copyright (c) 2010 Tim Whitlock.
34
 *
35
 * Permission is hereby granted, free of charge, to any person obtaining a copy
36
 * of this software and associated documentation files (the "Software"), to deal
37
 * in the Software without restriction, including without limitation the rights
38
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
39
 * copies of the Software, and to permit persons to whom the Software is
40
 * furnished to do so, subject to the following conditions:
41
 *
42
 * The above copyright notice and this permission notice shall be included in
43
 * all copies or substantial portions of the Software.
44
 *
45
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
46
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
47
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
48
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
49
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
50
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
51
 * THE SOFTWARE.
52
 */
53
54
namespace Spatie\Varnish;
55
56
class VarnishSocket
57
{
58
    /**
59
     * The socket used to connect to Varnish and a timeout in seconds.
60
     */
61
    protected $varnishSocket = null;
62
    protected $socketTimeout = 10;
63
64
    /**
65
     * Limits for reading and writing to and from the socket.
66
     */
67
    const CHUNK_SIZE = 1024;
68
    const WRITE_MAX_SIZE = 16 * self::CHUNK_SIZE;
69
70
    /**
71
     * Connect to the Varnish socket and authenticate when needed.
72
     * @param string $host
73
     * @param int $port
74
     * @param string $secret
75
     *
76
     * @return bool
77
     *
78
     * @throws \Exception
79
     */
80
    public function connect($host, $port, $secret = '')
81
    {
82
        // Open socket connection
83
        self::socketConnect($host, $port);
84
        self::authenticate($secret);
85
        return $this->isConnected();
86
    }
87
88
    /**
89
     * @param $host
90
     * @param $port
91
     * @throws \Exception
92
     */
93
    private function socketConnect($host, $port) {
94
        $this->varnishSocket = fsockopen(
95
            $host, $port,
96
            $errno, $errstr,
97
            $this->socketTimeout
98
        );
99
100
        if (! self::isConnected()) {
101
            throw new \Exception(sprintf(
102
                'Failed to connect to Varnish on %s:%d, error %d: %s',
103
                $host, $port, $errno, $errstr
104
            ));
105
        }
106
107
        // Set stream options
108
        stream_set_blocking($this->varnishSocket, true);
109
        stream_set_timeout($this->varnishSocket, $this->socketTimeout);
110
    }
111
112
    /**
113
     * @param $secret
114
     * @throws \Exception
115
     */
116
    private function authenticate($secret) {
117
        // Read first response from socket
118
        $response = $this->read();
119
120
        // Authenticate using secret if authentication is required
121
        // https://varnish-cache.org/docs/trunk/reference/varnish-cli.html#authentication-with-s
122
        if ($response->isAuthRequest()) {
123
            // Generate the authentication token based on the challenge and secret
124
            $token = $this->calculateAuthToken($response->getAuthChallenge(), $secret);
125
126
            // Authenticate using token
127
            $response = $this->command(
128
                sprintf('auth %s', $token)
129
            );
130
131
            if ($response->getCode() !== VarnishResponse::VARN_OK) {
132
                throw new \Exception(sprintf(
133
                    'Varnish admin authentication failed: %s',
134
                    $response->getContent()
135
                ));
136
            }
137
        }
138
    }
139
140
    /**
141
     * Check if we're connected to Varnish socket.
142
     *
143
     * @return bool
144
     */
145
    public function isConnected()
146
    {
147
        if (is_resource($this->varnishSocket)) {
148
            $meta = stream_get_meta_data($this->varnishSocket);
149
            return ! ($meta['eof'] || $meta['timed_out']);
150
        }
151
        return false;
152
    }
153
154
    /**
155
     * @param $challenge
156
     * @param $secret
157
     * @return string
158
     */
159
    private function calculateAuthToken($challenge, $secret) {
160
        // Ensure challenge ends with a newline
161
        $challenge = $this->ensureNewline($challenge);
162
        return hash('sha256',
163
            sprintf('%s%s%s',
164
                $challenge,
165
                $secret,
166
                $challenge
167
            ));
168
    }
169
170
    /**
171
     * @param $data
172
     * @return string
173
     */
174
    private function ensureNewline($data) {
175
        if (! preg_match('/\n$/', $data)) {
176
            $data .= "\n";
177
        }
178
        return $data;
179
    }
180
181
    /**
182
     * @return VarnishResponse
183
     *
184
     * @throws \Exception
185
     */
186
    private function read()
187
    {
188
        if (!$this->isConnected()) {
189
            throw new \Exception('Cannot read from Varnish socket because it\'s not connected');
190
        }
191
192
        // Read data from socket
193
        $response = self::readChunks();
194
195
        // Failed to get code from socket
196
        if ($response->getCode() === null) {
197
            throw new \Exception(
198
                'Failed to read response code from Varnish socket'
199
            );
200
        }
201
202
        return $response;
203
    }
204
205
    /**
206
     * @param VarnishResponse $response
207
     * @return VarnishResponse
208
     * @throws \Exception
209
     */
210
    private function readChunks(VarnishResponse $response = null) {
211
        if ($response === null) {
212
            $response = new VarnishResponse();
213
        }
214
215
        while (self::continueReading($response)) {
216
            $chunk = self::readSingleChunk();
217
218
            // Given content length
219
            if ($response->hasLength()) {
220
                $response->appendContent($chunk);
0 ignored issues
show
Bug introduced by
It seems like $chunk defined by self::readSingleChunk() on line 216 can also be of type boolean; however, Spatie\Varnish\VarnishResponse::appendContent() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
221
                continue;
222
            }
223
224
            // No content length given, expecting code + content length response
225
            if ($response->parseControlCommand($chunk)) {
226
                // Read actual content with given length
227
                return self::readChunks($response);
228
            }
229
        }
230
231
        return $response;
232
    }
233
234
    /**
235
     * Determine whether we should continue to read from the Varnish socket
236
     * - There is still data on the socket to read
237
     * - We have not reached the given content length
238
     *
239
     * @param VarnishResponse $response
240
     * @return bool
241
     */
242
    private function continueReading(VarnishResponse $response) {
243
        return ! feof($this->varnishSocket) && ! $response->finishedReading();
244
    }
245
246
    /**
247
     * Check if Varnish socket has timed out
248
     *
249
     * @throws \Exception
250
     */
251
    private function checkSocketTimeout() {
252
        $meta = stream_get_meta_data($this->varnishSocket);
253
        if ($meta['timed_out']) {
254
            throw new \Exception(
255
                'Varnish socket connection timed out'
256
            );
257
        }
258
    }
259
260
    /**
261
     * Read a single chunk from the Varnish socket
262
     *
263
     * @return bool|string
264
     * @throws \Exception
265
     */
266
    private function readSingleChunk() {
267
        $chunk = fgets($this->varnishSocket, self::CHUNK_SIZE);
268
269
        // Check for socket timeout when an empty chunk is returned
270
        if (empty($chunk)) {
271
            self::checkSocketTimeout();
272
        }
273
274
        return $chunk;
275
    }
276
277
    /**
278
     * Write data to the socket input stream.
279
     *
280
     * @param string $data
281
     *
282
     * @return VarnishSocket
283
     *
284
     * @throws \Exception
285
     */
286
    private function write($data)
287
    {
288
        if (!$this->isConnected()) {
289
            throw new \Exception('Cannot write to Varnish socket because it\'s not connected');
290
        }
291
        $data = $this->ensureNewline($data);
292
        if (strlen($data) >= self::WRITE_MAX_SIZE) {
293
            throw new \Exception(sprintf(
294
                'Data to write to Varnish socket is too large (max %d chars)',
295
                self::WRITE_MAX_SIZE
296
            ));
297
        }
298
299
        // Write data to socket
300
        $bytes = fwrite($this->varnishSocket, $data);
301
        if ($bytes !== strlen($data)) {
302
            throw new \Exception('Failed to write to Varnish socket');
303
        }
304
        return $this;
305
    }
306
307
    /**
308
     * Write a command to the socket with a trailing line break and get response straight away.
309
     *
310
     * @param string $cmd
311
     * @param int $ok
312
     *
313
     * @return VarnishResponse
314
     *
315
     * @throws \Exception
316
     */
317
    public function command($cmd, $ok = VarnishResponse::VARN_OK)
318
    {
319
        $response = $this->write($cmd)->read();
320
        if ($response->getCode() !== $ok) {
321
            throw new \Exception(
322
                sprintf(
323
                    "Command '%s' responded %d: '%s'",
324
                    $cmd, $response->getCode(), $response->getContent()
325
                ),
326
                $response->getCode()
327
            );
328
        }
329
        return $response;
330
    }
331
332
    /**
333
     * Brutal close, doesn't send quit command to varnishadm.
334
     *
335
     * @return void
336
     */
337
    public function close()
338
    {
339
        if (self::isConnected()) {
340
            fclose($this->varnishSocket);
341
        }
342
        $this->varnishSocket = null;
343
    }
344
345
    /**
346
     * Graceful close, sends quit command.
347
     *
348
     * @return void
349
     *
350
     * @throws \Exception
351
     */
352
    public function quit()
353
    {
354
        try {
355
            $this->command('quit', VarnishResponse::VARN_CLOSE);
356
        } finally {
357
            $this->close();
358
        }
359
    }
360
}
361