├── LICENSE ├── README.md ├── composer.json └── src └── HttpAdapter.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Chris Leppanen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flysystem HTTP Adapter 2 | 3 | [![Author](http://img.shields.io/badge/author-@chrisleppanen-blue.svg?style=flat-square)](https://twitter.com/chrisleppanen) 4 | [![Build Status](https://img.shields.io/travis/twistor/flysystem-http/master.svg?style=flat-square)](https://travis-ci.org/twistor/flysystem-http) 5 | [![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/twistor/flysystem-http.svg?style=flat-square)](https://scrutinizer-ci.com/g/twistor/flysystem-http/code-structure) 6 | [![Quality Score](https://img.shields.io/scrutinizer/g/twistor/flysystem-http.svg?style=flat-square)](https://scrutinizer-ci.com/g/twistor/flysystem-http) 7 | [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) 8 | [![Packagist Version](https://img.shields.io/packagist/v/twistor/flysystem-http.svg?style=flat-square)](https://packagist.org/packages/twistor/flysystem-http) 9 | 10 | This adapter uses basic PHP functions to access HTTP resources. It is read only. 11 | 12 | ## Installation 13 | 14 | ```bash 15 | composer require twistor/flysystem-http 16 | ``` 17 | 18 | ## Usage 19 | 20 | ```php 21 | use League\Flysystem\Filesystem; 22 | use Twistor\Flysystem\Http\HttpAdapter; 23 | 24 | $filesystem = new Filesystem(new HttpAdapter('http://example.com')); 25 | 26 | $contents = $filesystem->read('file.txt'); 27 | ``` 28 | 29 | By default, metadata will be retrieved via HEAD requests. This can be disabled. 30 | 31 | ```php 32 | use Twistor\Flysystem\Http\HttpAdapter; 33 | 34 | $supportsHead = false; 35 | 36 | $adapter = new HttpAdapter('http://example.com', $supportsHead); 37 | ``` 38 | 39 | PHP context options can be set using the third parameter. 40 | ```php 41 | use Twistor\Flysystem\Http\HttpAdapter; 42 | 43 | $context = [ 44 | 'ssl' => [ 45 | 'verify_peer' => false, 46 | 'verify_peer_name' => false, 47 | ], 48 | ]; 49 | 50 | $adapter = new HttpAdapter('http://example.com', true, $context); 51 | ``` 52 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twistor/flysystem-http", 3 | "description": "An HTTP adapter for Flysystem that uses basic PHP functions.", 4 | "keywords": [ 5 | "flysystem", 6 | "http" 7 | ], 8 | "type": "library", 9 | "license": "MIT", 10 | "homepage": "https://github.com/twistor/flysystem-http", 11 | "authors": [ 12 | { 13 | "name": "Chris Leppanen", 14 | "email": "chris.leppanen@gmail.com", 15 | "role": "Developer" 16 | } 17 | ], 18 | "require": { 19 | "league/flysystem": "^1.0.20" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": "~4.8" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "Twistor\\Flysystem\\Http\\": "src/" 27 | } 28 | }, 29 | "config": { 30 | "bin-dir": "bin" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/HttpAdapter.php: -------------------------------------------------------------------------------- 1 | base = $base; 48 | $this->supportsHead = $supportsHead; 49 | $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 | if (isset(parse_url($base)['user'])) { 65 | $this->visibility = AdapterInterface::VISIBILITY_PRIVATE; 66 | } 67 | } 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function copy($path, $newpath) 73 | { 74 | return false; 75 | } 76 | 77 | /** 78 | * @inheritdoc 79 | */ 80 | public function createDir($path, Config $config) 81 | { 82 | return false; 83 | } 84 | 85 | /** 86 | * @inheritdoc 87 | */ 88 | public function delete($path) 89 | { 90 | return false; 91 | } 92 | 93 | /** 94 | * @inheritdoc 95 | */ 96 | public function deleteDir($path) 97 | { 98 | return false; 99 | } 100 | 101 | /** 102 | * Returns the base path. 103 | * 104 | * @return string The base path 105 | */ 106 | public function getBase() 107 | { 108 | return $this->base; 109 | } 110 | 111 | /** 112 | * @inheritdoc 113 | */ 114 | public function getMetadata($path) 115 | { 116 | if (false === $headers = $this->head($path)) { 117 | return false; 118 | } 119 | 120 | return ['type' => 'file'] + $this->parseMetadata($path, $headers); 121 | } 122 | 123 | /** 124 | * @inheritdoc 125 | */ 126 | public function getMimetype($path) 127 | { 128 | return $this->getMetadata($path); 129 | } 130 | 131 | /** 132 | * @inheritdoc 133 | */ 134 | public function getSize($path) 135 | { 136 | return $this->getMetadata($path); 137 | } 138 | 139 | /** 140 | * @inheritdoc 141 | */ 142 | public function getTimestamp($path) 143 | { 144 | return $this->getMetadata($path); 145 | } 146 | 147 | /** 148 | * @inheritdoc 149 | */ 150 | public function getVisibility($path) 151 | { 152 | return [ 153 | 'path' => $path, 154 | 'visibility' => $this->visibility, 155 | ]; 156 | } 157 | 158 | /** 159 | * @inheritdoc 160 | */ 161 | public function has($path) 162 | { 163 | return (bool) $this->head($path); 164 | } 165 | 166 | /** 167 | * @inheritdoc 168 | */ 169 | public function listContents($directory = '', $recursive = false) 170 | { 171 | return []; 172 | } 173 | 174 | /** 175 | * @inheritdoc 176 | */ 177 | public function read($path) 178 | { 179 | $context = stream_context_create($this->context); 180 | $contents = file_get_contents($this->buildUrl($path), false, $context); 181 | 182 | if ($contents === false) { 183 | return false; 184 | } 185 | 186 | return compact('path', 'contents'); 187 | } 188 | 189 | /** 190 | * @inheritdoc 191 | */ 192 | public function readStream($path) 193 | { 194 | $context = stream_context_create($this->context); 195 | $stream = fopen($this->buildUrl($path), 'rb', false, $context); 196 | 197 | if ($stream === false) { 198 | return false; 199 | } 200 | 201 | return [ 202 | 'path' => $path, 203 | 'stream' => $stream, 204 | ]; 205 | } 206 | 207 | /** 208 | * @inheritdoc 209 | */ 210 | public function rename($path, $newpath) 211 | { 212 | return false; 213 | } 214 | 215 | /** 216 | * Sets the HTTP context options. 217 | * 218 | * @param array $context The context options 219 | */ 220 | public function setContext(array $context) 221 | { 222 | $this->context = $context; 223 | } 224 | 225 | /** 226 | * @inheritdoc 227 | */ 228 | public function setVisibility($path, $visibility) 229 | { 230 | throw new \LogicException('HttpAdapter does not support visibility. Path: ' . $path . ', visibility: ' . $visibility); 231 | } 232 | 233 | /** 234 | * @inheritdoc 235 | */ 236 | public function update($path, $contents, Config $conf) 237 | { 238 | return false; 239 | } 240 | 241 | /** 242 | * @inheritdoc 243 | */ 244 | public function updateStream($path, $resource, Config $config) 245 | { 246 | return false; 247 | } 248 | 249 | /** 250 | * @inheritdoc 251 | */ 252 | public function write($path, $contents, Config $config) 253 | { 254 | return false; 255 | } 256 | 257 | /** 258 | * @inheritdoc 259 | */ 260 | public function writeStream($path, $resource, Config $config) 261 | { 262 | 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 | protected function buildUrl($path) 273 | { 274 | $path = str_replace('%2F', '/', $path); 275 | $path = str_replace(' ', '%20', $path); 276 | 277 | 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 | protected function head($path) 288 | { 289 | $defaults = stream_context_get_options(stream_context_get_default()); 290 | $options = $this->context; 291 | 292 | if ($this->supportsHead) { 293 | $options['http']['method'] = 'HEAD'; 294 | } 295 | 296 | stream_context_set_default($options); 297 | 298 | $headers = get_headers($this->buildUrl($path), 1); 299 | 300 | stream_context_set_default($defaults); 301 | 302 | if ($headers === false || strpos($headers[0], ' 200') === false) { 303 | return false; 304 | } 305 | 306 | return array_change_key_case($headers); 307 | } 308 | 309 | /** 310 | * Parses the timestamp out of headers. 311 | * 312 | * @param array $headers 313 | * 314 | * @return int|false 315 | */ 316 | protected function parseTimestamp(array $headers) 317 | { 318 | if (isset($headers['last-modified'])) { 319 | return strtotime($headers['last-modified']); 320 | } 321 | 322 | return false; 323 | } 324 | 325 | /** 326 | * Parses metadata out of response headers. 327 | * 328 | * @param string $path 329 | * @param array $headers 330 | * 331 | * @return array 332 | */ 333 | protected function parseMetadata($path, array $headers) 334 | { 335 | $metadata = [ 336 | 'path' => $path, 337 | 'visibility' => $this->visibility, 338 | 'mimetype' => $this->parseMimeType($path, $headers), 339 | ]; 340 | 341 | if (false !== $timestamp = $this->parseTimestamp($headers)) { 342 | $metadata['timestamp'] = $timestamp; 343 | } 344 | 345 | if (isset($headers['content-length']) && is_numeric($headers['content-length'])) { 346 | $metadata['size'] = (int) $headers['content-length']; 347 | } 348 | 349 | return $metadata; 350 | } 351 | 352 | /** 353 | * Parses the mimetype out of response headers. 354 | * 355 | * @param string $path 356 | * @param array $headers 357 | * 358 | * @return string 359 | */ 360 | protected function parseMimeType($path, array $headers) 361 | { 362 | if (isset($headers['content-type'])) { 363 | list($mimetype) = explode(';', $headers['content-type'], 2); 364 | 365 | return trim($mimetype); 366 | } 367 | 368 | // Remove any query strings or fragments. 369 | list($path) = explode('#', $path, 2); 370 | list($path) = explode('?', $path, 2); 371 | 372 | return MimeType::detectByFilename($path); 373 | } 374 | } 375 | --------------------------------------------------------------------------------