Completed
Push — master ( c5924b...e1bbea )
by Chris
01:45
created

HttpAdapter::head()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 12
cts 12
cp 1
rs 9.0534
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 1
crap 4
1
<?php
2
3
namespace Twistor\Flysystem\Http;
4
5
use League\Flysystem\AdapterInterface;
6
use League\Flysystem\Config;
7
use League\Flysystem\Util\MimeType;
8
9
/**
10
 * Provides an adapter using PHP native HTTP functions.
11
 */
12
class HttpAdapter implements AdapterInterface
13
{
14
    /**
15
     * The base URL.
16
     *
17
     * @var string
18
     */
19
    protected $base;
20
21
    /**
22
     * @var array
23
     */
24
    protected $context;
25
26
    /**
27
     * @var bool
28
     */
29
    protected $supportsHead;
30
31
    /**
32
     * The visibility of this adapter.
33
     *
34
     * @var string
35
     */
36
    protected $visibility = AdapterInterface::VISIBILITY_PUBLIC;
37
38
    /**
39
     * Constructs an HttpAdapter object.
40
     *
41
     * @param string $base   The base URL.
42
     * @param bool   $supportsHead
43
     * @param array  $context
44
     */
45 63
    public function __construct($base, $supportsHead = true, array $context = array())
46
    {
47 63
        $this->base = $base;
48 63
        $this->supportsHead = $supportsHead;
49 63
        $this->context = $context;
50
51
        // Add in some safe defaults for SSL/TLS. Don't know why PHPUnit/Xdebug
52
        // messes this up.
53
        // @codeCoverageIgnoreStart
54
        $this->context += [
55
            'ssl' => [
56
                'verify_peer' => true,
57
                'verify_peer_name' => true,
58
                'SNI_enabled' => true,
59
                'disable_compression' => true,
60
            ],
61
        ];
62
        // @codeCoverageIgnoreEnd
63
64 63
        if (isset(parse_url($base)['user'])) {
65 3
            $this->visibility = AdapterInterface::VISIBILITY_PRIVATE;
66 3
        };
67 63
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72 3
    public function copy($path, $newpath)
73
    {
74 3
        return false;
75
    }
76
77
    /**
78
     * @inheritdoc
79
     */
80 3
    public function createDir($path, Config $config)
81
    {
82 3
        return false;
83
    }
84
85
    /**
86
     * @inheritdoc
87
     */
88 3
    public function delete($path)
89
    {
90 3
        return false;
91
    }
92
93
    /**
94
     * @inheritdoc
95
     */
96 3
    public function deleteDir($path)
97
    {
98 3
        return false;
99
    }
100
101
    /**
102
     * Returns the base path.
103
     *
104
     * @return string The base path.
105
     */
106 3
    public function getBase()
107
    {
108 3
        return $this->base;
109
    }
110
111
    /**
112
     * @inheritdoc
113
     */
114 12
    public function getMetadata($path)
115
    {
116 12
        if (false === $headers = $this->head($path)) {
117 3
            return false;
118
        }
119
120 12
        return ['type' => 'file'] + $this->parseMetadata($path, $headers);
121
    }
122
123
    /**
124
     * @inheritdoc
125
     */
126 3
    public function getMimetype($path)
127
    {
128 3
        return $this->getMetadata($path);
129
    }
130
131
    /**
132
     * @inheritdoc
133
     */
134 3
    public function getSize($path)
135
    {
136 3
        return $this->getMetadata($path);
137
    }
138
139
    /**
140
     * @inheritdoc
141
     */
142 3
    public function getTimestamp($path)
143
    {
144 3
        return $this->getMetadata($path);
145
    }
146
147
    /**
148
     * @inheritdoc
149
     */
150 3
    public function getVisibility($path)
151
    {
152
        return [
153 3
            'path' => $path,
154 3
            'visibility' => $this->visibility,
155 3
        ];
156
    }
157
158
    /**
159
     * @inheritdoc
160
     */
161 3
    public function has($path)
162
    {
163 3
        return (bool) $this->head($path);
164
    }
165
166
    /**
167
     * @inheritdoc
168
     */
169 3
    public function listContents($directory = '', $recursive = false)
170
    {
171 3
        return [];
172
    }
173
174
    /**
175
     * @inheritdoc
176
     */
177 3
    public function read($path)
178
    {
179 3
        $context = stream_context_create($this->context);
180 3
        $contents = file_get_contents($this->buildUrl($path), false, $context);
181
182 3
        if ($contents === false) {
183 3
            return false;
184
        }
185
186 3
        return compact('path', 'contents');
187
    }
188
189
    /**
190
     * @inheritdoc
191
     */
192 3
    public function readStream($path)
193
    {
194 3
        $context = stream_context_create($this->context);
195 3
        $stream = fopen($this->buildUrl($path), 'rb', false, $context);
196
197 3
        if ($stream === false) {
198 3
            return false;
199
        }
200
201
        return [
202 3
            'path' => $path,
203 3
            'stream' => $stream,
204 3
        ];
205
    }
206
207
    /**
208
     * @inheritdoc
209
     */
210 3
    public function rename($path, $newpath)
211
    {
212 3
        return false;
213
    }
214
215
    /**
216
     * Sets the HTTP context options.
217
     *
218
     * @param array $context The context options.
219
     */
220 3
    public function setContext(array $context)
221
    {
222 3
        $this->context = $context;
223 3
    }
224
225
    /**
226
     * @inheritdoc
227
     */
228 3
    public function setVisibility($path, $visibility)
229
    {
230 3
        throw new \LogicException('HttpAdapter does not support visibility. Path: ' . $path . ', visibility: ' . $visibility);
231
    }
232
233
    /**
234
     * @inheritdoc
235
     */
236 3
    public function update($path, $contents, Config $conf)
237
    {
238 3
        return false;
239
    }
240
241
    /**
242
     * @inheritdoc
243
     */
244 3
    public function updateStream($path, $resource, Config $config)
245
    {
246 3
        return false;
247
    }
248
249
    /**
250
     * @inheritdoc
251
     */
252 3
    public function write($path, $contents, Config $config)
253
    {
254 3
        return false;
255
    }
256
257
    /**
258
     * @inheritdoc
259
     */
260 3
    public function writeStream($path, $resource, Config $config)
261
    {
262 3
        return false;
263
    }
264
265
    /**
266
     * Returns the URL to perform an HTTP request.
267
     *
268
     * @param string $path
269
     *
270
     * @return string
271
     */
272 21
    protected function buildUrl($path)
273
    {
274 21
        $path = str_replace('%2F', '/', $path);
275 21
        $path = str_replace(' ', '%20', $path);
276
277 21
        return rtrim($this->base, '/') . '/' . $path;
278
    }
279
280
    /**
281
     * Performs a HEAD request.
282
     *
283
     * @param string $path
284
     *
285
     * @return array|false
286
     */
287 15
    protected function head($path)
288
    {
289 15
        $defaults = stream_context_get_options(stream_context_get_default());
290 15
        $options = $this->context;
291
292 15
        if ($this->supportsHead) {
293 15
            $options['http']['method'] = 'HEAD';
294 15
        }
295
296 15
        stream_context_set_default($options);
297
298 15
        $headers = get_headers($this->buildUrl($path), 1);
299
300 15
        stream_context_set_default($defaults);
301
302 15
        if ($headers === false || strpos($headers[0], ' 200') === false) {
303 6
            return false;
304
        }
305
306 15
        return array_change_key_case($headers);
307
    }
308
309
    /**
310
     * Parses metadata out of response headers.
311
     *
312
     * @param string $path
313
     * @param array  $headers
314
     *
315
     * @return array
316
     */
317 12
    protected function parseMetadata($path, array $headers)
318
    {
319
        $metadata = [
320 12
            'path' => $path,
321 12
            'visibility' => $this->visibility,
322 12
            'mimetype' => $this->parseMimeType($path, $headers),
323 12
        ];
324
325 12
        if (isset($headers['last-modified'])) {
326 12
            $last_modified = strtotime($headers['last-modified']);
327
328 12
            if ($last_modified !== false) {
329 12
                $metadata['timestamp'] = $last_modified;
330 12
            }
331 12
        }
332
333 12
        if (isset($headers['content-length']) && is_numeric($headers['content-length'])) {
334 12
            $metadata['size'] = (int) $headers['content-length'];
335 12
        }
336
337 12
        return $metadata;
338
    }
339
340
    /**
341
     * Parses the mimetype out of response headers.
342
     *
343
     * @param string $path
344
     * @param array  $headers
345
     *
346
     * @return string
347
     */
348 12
    protected function parseMimeType($path, array $headers)
349
    {
350 12
        if (isset($headers['content-type'])) {
351 12
            list($mimetype) = explode(';', $headers['content-type'], 2);
352
353 12
            return trim($mimetype);
354
        }
355
356
        // Remove any query strings or fragments.
357 3
        list($path) = explode('#', $path, 2);
358 3
        list($path) = explode('?', $path, 2);
359
360 3
        return MimeType::detectByFilename($path);
361
    }
362
}
363