Completed
Pull Request — master (#24)
by Hiraku
04:03
created

CurlRemoteFilesystem::createFile()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 27
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

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