Completed
Push — master ( af1a9d...ee1d8c )
by Hiraku
02:13
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 6
Bugs 1 Features 1
Metric Value
c 6
b 1
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\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
    // global flags
29
    private $retry = false;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
30
    private $degradedMode = false;
0 ignored issues
show
Comprehensibility introduced by
Consider using a different property name as you override a private property of the parent class.
Loading history...
31
32
    /** @var Aspects\JoinPoint */
33
    public $onPreDownload;
34
35
    /** @var Aspects\JoinPoint */
36
    public $onPostDownload;
37
38
    /**
39
     * @param IO\IOInterface $io
40
     * @param Config $config
41
     * @param array $options
42
     */
43
    public function __construct(IO\IOInterface $io, Config $config = null, array $options = array())
44
    {
45
        $this->io = $io;
46
        $this->config = $config;
47
        $this->options = $options;
48
    }
49
50
    public function setPluginConfig(array $pluginConfig)
51
    {
52
        $this->pluginConfig = $pluginConfig;
53
    }
54
55
    /**
56
     * Copy the remote file in local.
57
     *
58
     * @param string $origin    host/domain text
59
     * @param string $fileUrl   targeturl
60
     * @param string $fileName  the local filename
61
     * @param bool   $progress  Display the progression
62
     * @param array  $options   Additional context options
63
     *
64
     * @return bool true
65
     */
66
    public function copy($origin, $fileUrl, $fileName, $progress=true, $options=array())
67
    {
68
        $that = $this; // for PHP5.3
69
70
        return $this->fetch($origin, $fileUrl, $progress, $options, function ($ch, $request) use ($that, $fileName) {
71
            $fp = $that->createFile($fileName);
72
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
73
            curl_setopt($ch, CURLOPT_FILE, $fp);
74
75
            list($execStatus, $response) = $result = $that->exec($ch, $request);
0 ignored issues
show
Unused Code introduced by
The assignment to $execStatus is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
76
77
            curl_setopt($ch, CURLOPT_FILE, STDOUT);
78
            fclose($fp);
79
80
            if (200 !== $response->info['http_code']) {
81
                unlink($fileName);
82
            }
83
84
            return $result;
85
        });
86
    }
87
88
    /**
89
     * Get the content.
90
     *
91
     * @param string $originUrl The origin URL
0 ignored issues
show
Documentation introduced by
There is no parameter named $originUrl. Did you maybe mean $origin?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
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
            if (empty($opts[CURLOPT_USERPWD])) {
153
                unset($opts[CURLOPT_USERPWD]);
154
            }
155
            $ch = Factory::getConnection($origin, isset($opts[CURLOPT_USERPWD]));
156
157
            if ($this->pluginConfig['insecure']) {
158
                $opts[CURLOPT_VERIFYPEER] = false;
159
            }
160
            if (! empty($pluginConfig['capath'])) {
161
                $opts[CURLOPT_CAPATH] = $pluginConfig['capath'];
162
            }
163
164
            curl_setopt_array($ch, $opts);
165
166
            list($execStatus, $response) = $exec($ch, $request);
0 ignored issues
show
Unused Code introduced by
The assignment to $response is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
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 parent::getLastHeaders();
194
    }
195
196
    /**
197
     * @internal
198
     * @param resource<curl> $ch
0 ignored issues
show
Documentation introduced by
The doc-type resource<curl> could not be parsed: Expected "|" or "end of type", but got "<" at position 8. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
199
     * @param Aspects\HttpGetRequest $request
200
     * @return array(int, Aspects\HttpGetResponse)
0 ignored issues
show
Documentation introduced by
The doc-type array(int, could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
201
     */
202
    public function exec($ch, $request)
203
    {
204
        $execStatus = curl_exec($ch);
205
206
        $response = new Aspects\HttpGetResponse(
207
            curl_errno($ch),
208
            curl_error($ch),
209
            curl_getinfo($ch)
210
        );
211
        $this->onPostDownload->setResponse($response);
212
        $this->onPostDownload->notify();
213
214
        if ($response->needAuth()) {
215
            $this->promptAuth($request, $response);
216
        }
217
218
        return array($execStatus, $response);
219
    }
220
221
    /**
222
     * @param  resource<curl> $ch
0 ignored issues
show
Bug introduced by
There is no parameter named $ch. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
Documentation introduced by
The doc-type resource<curl> could not be parsed: Expected "|" or "end of type", but got "<" at position 8. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
223
     * @param  int $downBytesMax
0 ignored issues
show
Bug introduced by
There is no parameter named $downBytesMax. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
224
     * @param  int $downBytes
0 ignored issues
show
Bug introduced by
There is no parameter named $downBytes. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
225
     * @param  int $upBytesMax
0 ignored issues
show
Bug introduced by
There is no parameter named $upBytesMax. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
226
     * @param  int $upBytes
0 ignored issues
show
Bug introduced by
There is no parameter named $upBytes. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
227
     */
228
    public function progress()
229
    {
230
        // @codeCoverageIgnoreStart
231
        if (PHP_VERSION_ID >= 50500) {
232
            list($ch, $downBytesMax, $downBytes, $upBytesMax, $upBytes) = func_get_args();
0 ignored issues
show
Unused Code introduced by
The assignment to $ch is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $upBytesMax is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $upBytes is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
233
        } else {
234
            list($downBytesMax, $downBytes, $upBytesMax, $upBytes) = func_get_args();
0 ignored issues
show
Unused Code introduced by
The assignment to $upBytesMax is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $upBytes is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
235
        }
236
        // @codeCoverageIgnoreEnd
237
238
        if ($downBytesMax <= 0 || $downBytesMax < $downBytes) {
239
            return 0;
240
        }
241
242
        $progression = intval($downBytes / $downBytesMax * 100);
243
        $this->io->overwrite("    Downloading: <comment>$progression%</comment>", false);
244
        return 0;
245
    }
246
247
    public static function createFile($fileName)
248
    {
249
        if (is_dir($fileName)) {
250
            throw new Downloader\TransportException(
251
                "The file could not be written to $fileName. Directory exists."
252
            );
253
        }
254
255
        $dir = dirname($fileName);
256
        if (!file_exists($dir)) {
257
            $created = mkdir($dir, 0766, true);
258
            if (!$created) {
259
                throw new Downloader\TransportException(
260
                    "The file could not be written to $fileName."
261
                );
262
            }
263
        }
264
265
        $file = fopen($fileName, 'wb');
266
        if (!$file) {
267
            throw new Downloader\TransportException(
268
                "The file could not be written to $fileName."
269
            );
270
        }
271
272
        return $file;
273
    }
274
275
    protected function promptAuth(Aspects\HttpGetRequest $req, Aspects\HttpGetResponse $res)
276
    {
277
        $io = $this->io;
278
        $httpCode = $res->info['http_code'];
279
280 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...
281
            $message = "\nCould not fetch {$req->getURL()}, please create a GitHub OAuth token ";
282
            if (404 === $httpCode) {
283
                $message .= 'to access private repos';
284
            } else {
285
                $message .= 'to go over the API rate limit';
286
            }
287
            $github = new Util\GitHub($io, $this->config, null);
0 ignored issues
show
Bug introduced by
It seems like $this->config can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
288
            if ($github->authorizeOAuth($req->origin)) {
289
                $this->retry = true;
290
                return;
291
            }
292
            if ($io->isInteractive() &&
293
                $github->authorizeOAuthInteractively($req->origin, $message)) {
294
                $this->retry = true;
295
                return;
296
            }
297
298
            throw new Downloader\TransportException(
299
                "Could not authenticate against $req->origin",
300
                401
301
            );
302
        }
303
        
304 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...
305
            $message = "\nCould not fetch {$req->getURL()}, enter your $req->origin credentials ";
306
            if (401 === $httpCode) {
307
                $message .= 'to access private repos';
308
            } else {
309
                $message .= 'to go over the API rate limit';
310
            }
311
            $gitlab = new Util\GitLab($io, $this->config, null);
0 ignored issues
show
Bug introduced by
It seems like $this->config can be null; however, __construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
312
            if ($gitlab->authorizeOAuth($req->origin)) {
313
                $this->retry = true;
314
                return;
315
            }
316
            if ($io->isInteractive() &&
317
                $gitlab->authorizeOAuthInteractively($req->origin, $message)) {
318
                $this->retry = true;
319
                return;
320
            }
321
322
            throw new Downloader\TransportException(
323
                "Could not authenticate against $req->origin",
324
                401
325
            );
326
        }
327
328
        // 404s are only handled for github
329
        if (404 === $httpCode) {
330
            return;
331
        }
332
333
        // fail if the console is not interactive
334
        if (!$io->isInteractive()) {
335
            switch ($httpCode) {
336
                case 401:
337
                    $message = "The '{$req->getURL()}' URL required authentication.\nYou must be using the interactive console to authenticate";
338
                    break;
339
                case 403:
340
                    $message = "The '{$req->getURL()}' URL could not be accessed.";
341
                    break;
342
            }
343
            throw new Downloader\TransportException($message, $httpCode);
344
        }
345
346
        // fail if we already have auth
347
        if ($io->hasAuthentication($req->origin)) {
348
            throw new Downloader\TransportException(
349
                "Invalid credentials for '{$req->getURL()}', aborting.",
350
                $res->info['http_code']
351
            );
352
        }
353
354
        $io->overwrite("    Authentication required (<info>$req->host</info>):");
355
        $username = $io->ask('      Username: ');
356
        $password = $io->askAndHideAnswer('      Password: ');
357
        $io->setAuthentication($req->origin, $username, $password);
358
        $this->retry = true;
359
    }
360
}
361