Completed
Push — master ( 1f6bdc...410881 )
by Hiraku
02:16
created

CurlRemoteFilesystem::fetch()   F

Complexity

Conditions 15
Paths 512

Size

Total Lines 64
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 34
CRAP Score 20.5844

Importance

Changes 9
Bugs 2 Features 1
Metric Value
c 9
b 2
f 1
dl 0
loc 64
ccs 34
cts 48
cp 0.7083
rs 3.8254
cc 15
eloc 40
nc 512
nop 5
crap 20.5844

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * hirak/prestissimo
4
 * @author Hiraku NAKANO
5
 * @license MIT https://github.com/hirak/prestissimo
6
 */
7
namespace Hirak\Prestissimo;
8
9
use Composer\Config;
10
use Composer\IO;
11
use Composer\Downloader;
12
use Composer\Util;
13
14
/**
15
 * yet another implementation about Composer\Util\RemoteFilesystem
16
 * non thread safe
17
 */
18
class CurlRemoteFilesystem extends Util\RemoteFilesystem
19
{
20
    protected $io;
21
    protected $config;
22
    protected $options;
23
24
    protected $retryAuthFailure = true;
25
26
    protected $pluginConfig;
27
28
    private $_lastHeaders = array();
29
30
    // global flags
31
    private $_retry = false;
32
    private $_degradedMode = false;
33
34
    /** @var Aspects\JoinPoint */
35
    public $onPreDownload;
36
37
    /** @var Aspects\JoinPoint */
38
    public $onPostDownload;
39
40
    /**
41
     * @param IO\IOInterface $io
42
     * @param Config $config
43
     * @param array $options
44
     */
45 8
    public function __construct(IO\IOInterface $io, Config $config, array $options = array())
46
    {
47 8
        $this->io = $io;
48 8
        $this->config = $config;
49 8
        $this->options = $options;
50 8
    }
51
52 8
    public function setPluginConfig(array $pluginConfig)
53
    {
54 8
        $this->pluginConfig = $pluginConfig;
55 8
    }
56
57
    /**
58
     * Copy the remote file in local.
59
     *
60
     * @param string $origin    host/domain text
61
     * @param string $fileUrl   targeturl
62
     * @param string $fileName  the local filename
63
     * @param bool   $progress  Display the progression
64
     * @param array  $options   Additional context options
65
     *
66
     * @return bool true
67
     */
68 4
    public function copy($origin, $fileUrl, $fileName, $progress=true, $options=array())
69
    {
70 4
        $that = $this; // for PHP5.3
71
72
        return $this->fetch($origin, $fileUrl, $progress, $options, function ($ch, $request) use ($that, $fileName) {
73 4
            $outputFile = new OutputFile($fileName);
74 4
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
75 4
            curl_setopt($ch, CURLOPT_FILE, $outputFile->getPointer());
76
77 4
            list(, $response) = $result = $that->exec($ch, $request);
78
79 2
            curl_setopt($ch, CURLOPT_FILE, STDOUT);
80
81 2
            if (200 !== $response->info['http_code']) {
82 1
                $outputFile->setFailure();
83 1
            }
84
85 2
            return $result;
86 4
        });
87
    }
88
89
    /**
90
     * Get the content.
91
     *
92
     * @param string $origin The origin URL
93
     * @param string $fileUrl   The file URL
94
     * @param bool   $progress  Display the progression
95
     * @param array  $options   Additional context options
96
     *
97
     * @return bool|string The content
98
     */
99 1
    public function getContents($origin, $fileUrl, $progress=true, $options=array())
100
    {
101 1
        $that = $this; // for PHP5.3
102
103 1
        return $this->fetch($origin, $fileUrl, $progress, $options, function ($ch, $request) use ($that) {
104
            // This order is important.
105 1
            curl_setopt($ch, CURLOPT_FILE, STDOUT);
106 1
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
107
108 1
            return $that->exec($ch, $request);
109 1
        });
110
    }
111
112 5
    protected function fetch($origin, $fileUrl, $progress, $options, $exec)
113
    {
114
        do {
115 5
            $this->_retry = false;
116
117 5
            $request = new Aspects\HttpGetRequest($origin, $fileUrl, $this->io);
118 5
            $request->setSpecial(array(
119 5
                'github' => $this->config->get('github-domains') ?: array(),
120 5
                'gitlab' => $this->config->get('gitlab-domains') ?: array(),
121 5
            ));
122 5
            $this->onPreDownload = Factory::getPreEvent($request);
123 5
            $this->onPostDownload = Factory::getPostEvent($request);
124 5
            if ($this->_degradedMode) {
125
                $this->onPreDownload->attach(new Aspects\AspectDegradedMode);
126
            }
127
128 5
            $options += $this->options;
129
            // override
130 5
            if ('github' === $request->special && isset($options['github-token'])) {
131
                $request->query['access_token'] = $options['github-token'];
132
            }
133 5
            if ('gitlab' === $request->special && isset($options['gitlab-token'])) {
134
                $request->query['access_token'] = $options['gitlab-token'];
135
            }
136
137 5
            if ($this->io->isDebug()) {
138
                $this->io->write('Downloading ' . $fileUrl);
139
            }
140
141 5
            if ($progress) {
142 4
                $this->io->write("    Downloading: <comment>Connecting...</comment>", false);
143 4
                $request->curlOpts[CURLOPT_NOPROGRESS] = false;
144 4
                $request->curlOpts[CURLOPT_PROGRESSFUNCTION] = array($this, 'progress');
145 4
            } else {
146 1
                $request->curlOpts[CURLOPT_NOPROGRESS] = true;
147 1
                $request->curlOpts[CURLOPT_PROGRESSFUNCTION] = null;
148
            }
149
150 5
            $this->onPreDownload->notify();
151
152 5
            $opts = $request->getCurlOpts();
153 5
            $ch = Factory::getConnection($origin, isset($opts[CURLOPT_USERPWD]));
154
155 5
            if ($this->pluginConfig['insecure']) {
156
                $opts[CURLOPT_SSL_VERIFYPEER] = false;
157
            }
158 5
            if (! empty($pluginConfig['userAgent'])) {
159
                $opts[CURLOPT_USERAGENT] = $pluginConfig['userAgent'];
160
            }
161 5
            if (! empty($pluginConfig['capath'])) {
162
                $opts[CURLOPT_CAPATH] = $pluginConfig['capath'];
163
            }
164
165 5
            curl_setopt_array($ch, $opts);
166
167 5
            list($execStatus, ) = $exec($ch, $request);
168 3
        } while ($this->_retry);
169
170 3
        if ($progress) {
171 2
            $this->io->overwrite("    Downloading: <comment>100%</comment>");
172 2
        }
173
174 3
        return $execStatus;
175
    }
176
177
    /**
178
     * Retrieve the options set in the constructor
179
     *
180
     * @return array Options
181
     */
182 1
    public function getOptions()
183
    {
184 1
        return $this->options;
185
    }
186
187
    /**
188
     * Returns the headers of the last request
189
     *
190
     * @return array
191
     */
192 1
    public function getLastHeaders()
193
    {
194 1
        return $this->_lastHeaders;
195
    }
196
197
    /**
198
     * @internal
199
     * @param resource $ch
200
     * @param Aspects\HttpGetRequest $request
201
     * @return array {int, Aspects\HttpGetResponse}
202
     */
203 5
    public function exec($ch, $request)
204
    {
205 5
        $this->_lastHeaders = array();
206 5
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'processHeader'));
207 5
        $execStatus = curl_exec($ch);
208
209 5
        $response = new Aspects\HttpGetResponse(
210 5
            curl_errno($ch),
211 5
            curl_error($ch),
212 5
            curl_getinfo($ch)
213 5
        );
214 5
        $this->onPostDownload->setResponse($response);
215 5
        $this->onPostDownload->notify();
216
217 5
        if ($response->needAuth()) {
218 3
            $this->promptAuth($request, $response);
219 1
        }
220
221 3
        return array($execStatus, $response);
222
    }
223
224
    /**
225
     * @internal
226
     */
227 4
    public function progress()
228
    {
229
        // @codeCoverageIgnoreStart
230
        if (PHP_VERSION_ID >= 50500) {
231
            list(, $downBytesMax, $downBytes, , ) = func_get_args();
232
        } else {
233
            list($downBytesMax, $downBytes, , ) = func_get_args();
234
        }
235
        // @codeCoverageIgnoreEnd
236
237 4
        if ($downBytesMax <= 0 || $downBytesMax < $downBytes) {
238 1
            return 0;
239
        }
240
241 4
        $progression = intval($downBytes / $downBytesMax * 100);
242 4
        $this->io->overwrite("    Downloading: <comment>$progression%</comment>", false);
243 4
        return 0;
244
    }
245
246
    /**
247
     * @internal
248
     * @param resource $ch
249
     * @param string $header
250
     * @return int
251
     */
252 5
    public function processHeader($ch, $header)
253
    {
254 5
        $this->_lastHeaders[] = trim($header);
255 5
        return strlen($header);
256
    }
257
258 3
    protected function promptAuth(Aspects\HttpGetRequest $req, Aspects\HttpGetResponse $res)
259
    {
260 3
        $io = $this->io;
261 3
        $httpCode = $res->info['http_code'];
262
263 3 View Code Duplication
        if ('github' === $req->special) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
264
            $message = "\nCould not fetch {$req->getURL()}, please create a GitHub OAuth token ";
265
            if (404 === $httpCode) {
266
                $message .= 'to access private repos';
267
            } else {
268
                $message .= 'to go over the API rate limit';
269
            }
270
            $github = new Util\GitHub($io, $this->config, null);
271
            if ($github->authorizeOAuth($req->origin)) {
272
                $this->_retry = true;
273
                return;
274
            }
275
            if ($io->isInteractive() &&
276
                $github->authorizeOAuthInteractively($req->origin, $message)) {
277
                $this->_retry = true;
278
                return;
279
            }
280
281
            throw new Downloader\TransportException(
282
                "Could not authenticate against $req->origin",
283
                401
284
            );
285
        }
286
        
287 3 View Code Duplication
        if ('gitlab' === $req->special) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
288
            $message = "\nCould not fetch {$req->getURL()}, enter your $req->origin credentials ";
289
            if (401 === $httpCode) {
290
                $message .= 'to access private repos';
291
            } else {
292
                $message .= 'to go over the API rate limit';
293
            }
294
            $gitlab = new Util\GitLab($io, $this->config, null);
295
            if ($gitlab->authorizeOAuth($req->origin)) {
296
                $this->_retry = true;
297
                return;
298
            }
299
            if ($io->isInteractive() &&
300
                $gitlab->authorizeOAuthInteractively($req->origin, $message)) {
301
                $this->_retry = true;
302
                return;
303
            }
304
305
            throw new Downloader\TransportException(
306
                "Could not authenticate against $req->origin",
307
                401
308
            );
309
        }
310
311
        // 404s are only handled for github
312 3
        if (404 === $httpCode) {
313 1
            return;
314
        }
315
316
        // fail if the console is not interactive
317 2
        if (!$io->isInteractive()) {
318
            switch ($httpCode) {
319 2
                case 401:
320 1
                    $message = "The '{$req->getURL()}' URL required authentication.\nYou must be using the interactive console to authenticate";
321 1
                    break;
322 1
                case 403:
323 1
                    $message = "The '{$req->getURL()}' URL could not be accessed.";
324 1
                    break;
325
            }
326 2
            throw new Downloader\TransportException($message, $httpCode);
327
        }
328
329
        // fail if we already have auth
330
        if ($io->hasAuthentication($req->origin)) {
331
            throw new Downloader\TransportException(
332
                "Invalid credentials for '{$req->getURL()}', aborting.",
333
                $res->info['http_code']
334
            );
335
        }
336
337
        $io->overwrite("    Authentication required (<info>$req->host</info>):");
338
        $username = $io->ask('      Username: ');
339
        $password = $io->askAndHideAnswer('      Password: ');
340
        $io->setAuthentication($req->origin, $username, $password);
341
        $this->_retry = true;
342
    }
343
}
344