Completed
Pull Request — master (#32)
by Nikhil
02:28
created

CurlRemoteFilesystem::fetch()   F

Complexity

Conditions 15
Paths 512

Size

Total Lines 64
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 240

Importance

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

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\Util;
12
13
/**
14
 * yet another implementation about Composer\Util\RemoteFilesystem
15
 * non thread safe
16
 */
17
class CurlRemoteFilesystem extends Util\RemoteFilesystem
18
{
19
    protected $io;
20
    protected $config;
21
    protected $options;
22
23
    protected $retryAuthFailure = true;
24
25
    protected $pluginConfig;
26
27
    private $_lastHeaders = array();
28
29
    // global flags
30
    private $_retry = false;
31
    private $_degradedMode = false;
32
33
    /** @var Aspects\JoinPoint */
34
    public $onPreDownload;
35
36
    /** @var Aspects\JoinPoint */
37
    public $onPostDownload;
38
39
    /**
40
     * @param IO\IOInterface $io
41
     * @param Config $config
42
     * @param array $options
43
     */
44
    public function __construct(IO\IOInterface $io, Config $config, array $options = array())
45
    {
46
        $this->io = $io;
47
        $this->config = $config;
48
        $this->options = $options;
49
    }
50
51
    public function setPluginConfig(array $pluginConfig)
52
    {
53
        $this->pluginConfig = $pluginConfig;
54
    }
55
56
    /**
57
     * Copy the remote file in local.
58
     *
59
     * @param string $origin    host/domain text
60
     * @param string $fileUrl   targeturl
61
     * @param string $fileName  the local filename
62
     * @param bool   $progress  Display the progression
63
     * @param array  $options   Additional context options
64
     *
65
     * @return bool true
66
     */
67
    public function copy($origin, $fileUrl, $fileName, $progress=true, $options=array())
68
    {
69
        $that = $this; // for PHP5.3
70
71
        return $this->fetch($origin, $fileUrl, $progress, $options, function ($ch, $request) use ($that, $fileName) {
72
            $outputFile = new OutputFile($fileName);
73
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
74
            curl_setopt($ch, CURLOPT_FILE, $outputFile->getPointer());
75
76
            list(, $response) = $result = $that->exec($ch, $request);
77
78
            curl_setopt($ch, CURLOPT_FILE, STDOUT);
79
80
            if (200 !== $response->info['http_code']) {
81
                $outputFile->setFailure();
82
            }
83
84
            return $result;
85
        });
86
    }
87
88
    /**
89
     * Get the content.
90
     *
91
     * @param string $origin The origin URL
92
     * @param string $fileUrl   The file URL
93
     * @param bool   $progress  Display the progression
94
     * @param array  $options   Additional context options
95
     *
96
     * @return bool|string The content
97
     */
98
    public function getContents($origin, $fileUrl, $progress=true, $options=array())
99
    {
100
        $that = $this; // for PHP5.3
101
102
        return $this->fetch($origin, $fileUrl, $progress, $options, function ($ch, $request) use ($that) {
103
            // This order is important.
104
            curl_setopt($ch, CURLOPT_FILE, STDOUT);
105
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
106
107
            return $that->exec($ch, $request);
108
        });
109
    }
110
111
    protected function fetch($origin, $fileUrl, $progress, $options, $exec)
112
    {
113
        do {
114
            $this->_retry = false;
115
116
            $request = new Aspects\HttpGetRequest($origin, $fileUrl, $this->io);
117
            $request->setSpecial(array(
118
                'github' => $this->config->get('github-domains') ?: array(),
119
                'gitlab' => $this->config->get('gitlab-domains') ?: array(),
120
            ));
121
            $this->onPreDownload = Factory::getPreEvent($request);
122
            $this->onPostDownload = Factory::getPostEvent($request);
123
            if ($this->_degradedMode) {
124
                $this->onPreDownload->attach(new Aspects\AspectDegradedMode);
125
            }
126
127
            $options += $this->options;
128
            // override
129
            if ('github' === $request->special && isset($options['github-token'])) {
130
                $request->query['access_token'] = $options['github-token'];
131
            }
132
            if ('gitlab' === $request->special && isset($options['gitlab-token'])) {
133
                $request->query['access_token'] = $options['gitlab-token'];
134
            }
135
136
            if ($this->io->isDebug()) {
137
                $this->io->write('Downloading ' . $fileUrl);
138
            }
139
140
            if ($progress) {
141
                $this->io->write("    Downloading: <comment>Connecting...</comment>", false);
142
                $request->curlOpts[CURLOPT_NOPROGRESS] = false;
143
                $request->curlOpts[CURLOPT_PROGRESSFUNCTION] = array($this, 'progress');
144
            } else {
145
                $request->curlOpts[CURLOPT_NOPROGRESS] = true;
146
                $request->curlOpts[CURLOPT_PROGRESSFUNCTION] = null;
147
            }
148
149
            $this->onPreDownload->notify();
150
151
            $opts = $request->getCurlOpts();
152
            $ch = Factory::getConnection($origin, isset($opts[CURLOPT_USERPWD]));
153
154
            if ($this->pluginConfig['insecure']) {
155
                $opts[CURLOPT_SSL_VERIFYPEER] = false;
156
            }
157
            if (! empty($pluginConfig['userAgent'])) {
158
                $opts[CURLOPT_USERAGENT] = $pluginConfig['userAgent'];
159
            }
160
            if (! empty($pluginConfig['capath'])) {
161
                $opts[CURLOPT_CAPATH] = $pluginConfig['capath'];
162
            }
163
164
            curl_setopt_array($ch, $opts);
165
166
            list($execStatus, ) = $exec($ch, $request);
167
        } while ($this->_retry);
168
169
        if ($progress) {
170
            $this->io->overwrite("    Downloading: <comment>100%</comment>");
171
        }
172
173
        return $execStatus;
174
    }
175
176
    /**
177
     * Retrieve the options set in the constructor
178
     *
179
     * @return array Options
180
     */
181
    public function getOptions()
182
    {
183
        return $this->options;
184
    }
185
186
    /**
187
     * Returns the headers of the last request
188
     *
189
     * @return array
190
     */
191
    public function getLastHeaders()
192
    {
193
        return $this->_lastHeaders;
194
    }
195
196
    /**
197
     * @internal
198
     * @param resource $ch
199
     * @param Aspects\HttpGetRequest $request
200
     * @return array {int, Aspects\HttpGetResponse}
201
     */
202
    public function exec($ch, $request)
203
    {
204
        $this->_lastHeaders = array();
205
        curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, 'processHeader'));
206
        $execStatus = curl_exec($ch);
207
208
        $response = new Aspects\HttpGetResponse(
209
            curl_errno($ch),
210
            curl_error($ch),
211
            curl_getinfo($ch)
212
        );
213
        $this->onPostDownload->setResponse($response);
214
        $this->onPostDownload->notify();
215
216
        if ($response->needAuth()) {
217
            $this->promptAuth($request, $response);
218
        }
219
220
        return array($execStatus, $response);
221
    }
222
223
    /**
224
     * @internal
225
     */
226
    public function progress()
227
    {
228
        // @codeCoverageIgnoreStart
229
        if (PHP_VERSION_ID >= 50500) {
230
            list(, $downBytesMax, $downBytes, , ) = func_get_args();
231
        } else {
232
            list($downBytesMax, $downBytes, , ) = func_get_args();
233
        }
234
        // @codeCoverageIgnoreEnd
235
236
        if ($downBytesMax <= 0 || $downBytesMax < $downBytes) {
237
            return 0;
238
        }
239
240
        $progression = intval($downBytes / $downBytesMax * 100);
241
        $this->io->overwrite("    Downloading: <comment>$progression%</comment>", false);
242
        return 0;
243
    }
244
245
    /**
246
     * @internal
247
     * @param resource $ch
248
     * @param string $header
249
     * @return int
250
     */
251
    public function processHeader($ch, $header)
252
    {
253
        $this->_lastHeaders[] = trim($header);
254
        return strlen($header);
255
    }
256
257
    protected function promptAuth(Aspects\HttpGetRequest $req, Aspects\HttpGetResponse $res)
258
    {
259
        $io = $this->io;
260
        $httpCode = $res->info['http_code'];
261
262 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...
263
            $message = "\nCould not fetch {$req->getURL()}, please create a GitHub OAuth token ";
264
            if (404 === $httpCode) {
265
                $message .= 'to access private repos';
266
            } else {
267
                $message .= 'to go over the API rate limit';
268
            }
269
            $github = new Util\GitHub($io, $this->config, null);
270
            if ($github->authorizeOAuth($req->origin)) {
271
                $this->_retry = true;
272
                return;
273
            }
274
            if ($io->isInteractive() &&
275
                $github->authorizeOAuthInteractively($req->origin, $message)) {
276
                $this->_retry = true;
277
                return;
278
            }
279
280
            throw new Downloader\TransportException(
281
                "Could not authenticate against $req->origin",
282
                401
283
            );
284
        }
285
        
286 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...
287
            $message = "\nCould not fetch {$req->getURL()}, enter your $req->origin credentials ";
288
            if (401 === $httpCode) {
289
                $message .= 'to access private repos';
290
            } else {
291
                $message .= 'to go over the API rate limit';
292
            }
293
            $gitlab = new Util\GitLab($io, $this->config, null);
294
            if ($gitlab->authorizeOAuth($req->origin)) {
295
                $this->_retry = true;
296
                return;
297
            }
298
            if ($io->isInteractive() &&
299
                $gitlab->authorizeOAuthInteractively($req->origin, $message)) {
300
                $this->_retry = true;
301
                return;
302
            }
303
304
            throw new Downloader\TransportException(
305
                "Could not authenticate against $req->origin",
306
                401
307
            );
308
        }
309
310
        // 404s are only handled for github
311
        if (404 === $httpCode) {
312
            return;
313
        }
314
315
        // fail if the console is not interactive
316
        if (!$io->isInteractive()) {
317
            switch ($httpCode) {
318
                case 401:
319
                    $message = "The '{$req->getURL()}' URL required authentication.\nYou must be using the interactive console to authenticate";
320
                    break;
321
                case 403:
322
                    $message = "The '{$req->getURL()}' URL could not be accessed.";
323
                    break;
324
            }
325
            throw new Downloader\TransportException($message, $httpCode);
326
        }
327
328
        // fail if we already have auth
329
        if ($io->hasAuthentication($req->origin)) {
330
            throw new Downloader\TransportException(
331
                "Invalid credentials for '{$req->getURL()}', aborting.",
332
                $res->info['http_code']
333
            );
334
        }
335
336
        $io->overwrite("    Authentication required (<info>$req->host</info>):");
337
        $username = $io->ask('      Username: ');
338
        $password = $io->askAndHideAnswer('      Password: ');
339
        $io->setAuthentication($req->origin, $username, $password);
340
        $this->_retry = true;
341
    }
342
}
343