Completed
Push — master ( 742fbd...1ef1a7 )
by Hiraku
02:11
created

CurlRemoteFilesystem::processHeader()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 5
ccs 0
cts 5
cp 0
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
crap 2
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 = null, 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($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...
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 $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_SSL_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 $this->_lastHeaders;
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
        $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
     * @param  resource $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...
226
     * @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...
227
     * @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...
228
     * @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...
229
     * @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...
230
     */
231
    public function progress()
232
    {
233
        // @codeCoverageIgnoreStart
234
        if (PHP_VERSION_ID >= 50500) {
235
            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...
236
        } else {
237
            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...
238
        }
239
        // @codeCoverageIgnoreEnd
240
241
        if ($downBytesMax <= 0 || $downBytesMax < $downBytes) {
242
            return 0;
243
        }
244
245
        $progression = intval($downBytes / $downBytesMax * 100);
246
        $this->io->overwrite("    Downloading: <comment>$progression%</comment>", false);
247
        return 0;
248
    }
249
250
    /**
251
     * @internal
252
     * @param resource $ch
253
     * @param string $header
254
     * @return int
255
     */
256
    public function processHeader($ch, $header)
257
    {
258
        $this->_lastHeaders[] = trim($header);
259
        return strlen($header);
260
    }
261
262
    protected function promptAuth(Aspects\HttpGetRequest $req, Aspects\HttpGetResponse $res)
263
    {
264
        $io = $this->io;
265
        $httpCode = $res->info['http_code'];
266
267 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...
268
            $message = "\nCould not fetch {$req->getURL()}, please create a GitHub OAuth token ";
269
            if (404 === $httpCode) {
270
                $message .= 'to access private repos';
271
            } else {
272
                $message .= 'to go over the API rate limit';
273
            }
274
            $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...
275
            if ($github->authorizeOAuth($req->origin)) {
276
                $this->_retry = true;
277
                return;
278
            }
279
            if ($io->isInteractive() &&
280
                $github->authorizeOAuthInteractively($req->origin, $message)) {
281
                $this->_retry = true;
282
                return;
283
            }
284
285
            throw new Downloader\TransportException(
286
                "Could not authenticate against $req->origin",
287
                401
288
            );
289
        }
290
        
291 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...
292
            $message = "\nCould not fetch {$req->getURL()}, enter your $req->origin credentials ";
293
            if (401 === $httpCode) {
294
                $message .= 'to access private repos';
295
            } else {
296
                $message .= 'to go over the API rate limit';
297
            }
298
            $gitlab = new Util\GitLab($io, $this->config, null);
299
            if ($gitlab->authorizeOAuth($req->origin)) {
300
                $this->_retry = true;
301
                return;
302
            }
303
            if ($io->isInteractive() &&
304
                $gitlab->authorizeOAuthInteractively($req->origin, $message)) {
305
                $this->_retry = true;
306
                return;
307
            }
308
309
            throw new Downloader\TransportException(
310
                "Could not authenticate against $req->origin",
311
                401
312
            );
313
        }
314
315
        // 404s are only handled for github
316
        if (404 === $httpCode) {
317
            return;
318
        }
319
320
        // fail if the console is not interactive
321
        if (!$io->isInteractive()) {
322
            switch ($httpCode) {
323
                case 401:
324
                    $message = "The '{$req->getURL()}' URL required authentication.\nYou must be using the interactive console to authenticate";
325
                    break;
326
                case 403:
327
                    $message = "The '{$req->getURL()}' URL could not be accessed.";
328
                    break;
329
            }
330
            throw new Downloader\TransportException($message, $httpCode);
331
        }
332
333
        // fail if we already have auth
334
        if ($io->hasAuthentication($req->origin)) {
335
            throw new Downloader\TransportException(
336
                "Invalid credentials for '{$req->getURL()}', aborting.",
337
                $res->info['http_code']
338
            );
339
        }
340
341
        $io->overwrite("    Authentication required (<info>$req->host</info>):");
342
        $username = $io->ask('      Username: ');
343
        $password = $io->askAndHideAnswer('      Password: ');
344
        $io->setAuthentication($req->origin, $username, $password);
345
        $this->_retry = true;
346
    }
347
}
348