Completed
Pull Request — master (#98)
by Hiraku
09:07
created

CopyRequest::setupAuthentication()   C

Complexity

Conditions 12
Paths 28

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 39
rs 5.1612
cc 12
eloc 25
nc 28
nop 2

How to fix   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\Util;
10
use Composer\IO;
11
use Composer\Config;
12
13
class CopyRequest
14
{
15
    private $scheme;
16
    private $user;
17
    private $pass;
18
    private $host;
19
    private $port;
20
    private $path;
21
    private $query = array();
22
23
    /** @var [string => string] */
24
    private $headers = array();
25
26
    /** @var string */
27
    private $destination;
28
29
    /** @var resource<stream<plainfile>> */
30
    private $fp;
31
32
    private $success = false;
33
34
    private static $defaultCurlOptions = array(
35
        CURLOPT_HTTPGET => true,
36
        CURLOPT_FOLLOWLOCATION => true,
37
        CURLOPT_MAXREDIRS => 20,
38
        CURLOPT_ENCODING => '',
39
    );
40
41
    private $githubDomains = array();
42
    private $gitlabDomains = array();
43
44
    private static $NSS_CIPHERS = array(
45
        'rsa_3des_sha',
46
        'rsa_des_sha',
47
        'rsa_null_md5',
48
        'rsa_null_sha',
49
        'rsa_rc2_40_md5',
50
        'rsa_rc4_128_md5',
51
        'rsa_rc4_128_sha',
52
        'rsa_rc4_40_md5',
53
        'fips_des_sha',
54
        'fips_3des_sha',
55
        'rsa_des_56_sha',
56
        'rsa_rc4_56_sha',
57
        'rsa_aes_128_sha',
58
        'rsa_aes_256_sha',
59
        'rsa_aes_128_gcm_sha_256',
60
        'dhe_rsa_aes_128_gcm_sha_256',
61
        'ecdh_ecdsa_null_sha',
62
        'ecdh_ecdsa_rc4_128_sha',
63
        'ecdh_ecdsa_3des_sha',
64
        'ecdh_ecdsa_aes_128_sha',
65
        'ecdh_ecdsa_aes_256_sha',
66
        'ecdhe_ecdsa_null_sha',
67
        'ecdhe_ecdsa_rc4_128_sha',
68
        'ecdhe_ecdsa_3des_sha',
69
        'ecdhe_ecdsa_aes_128_sha',
70
        'ecdhe_ecdsa_aes_256_sha',
71
        'ecdh_rsa_null_sha',
72
        'ecdh_rsa_128_sha',
73
        'ecdh_rsa_3des_sha',
74
        'ecdh_rsa_aes_128_sha',
75
        'ecdh_rsa_aes_256_sha',
76
        'echde_rsa_null',
77
        'ecdhe_rsa_rc4_128_sha',
78
        'ecdhe_rsa_3des_sha',
79
        'ecdhe_rsa_aes_128_sha',
80
        'ecdhe_rsa_aes_256_sha',
81
        'ecdhe_ecdsa_aes_128_gcm_sha_256',
82
        'ecdhe_rsa_aes_128_gcm_sha_256',
83
    );
84
85
    /**
86
     * @param string $url
87
     * @param string $destination
88
     * @param bool $useRedirector
89
     * @param IO\IOInterface $io
90
     * @param Config $config
91
     */
92
    public function __construct($url, $destination, $useRedirector, IO\IOInterface $io, Config $config)
93
    {
94
        $this->setURL($url);
95
        $this->setDestination($destination);
96
        $this->githubDomains = $config->get('github-domains');
0 ignored issues
show
Documentation Bug introduced by
It seems like $config->get('github-domains') of type * is incompatible with the declared type array of property $githubDomains.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
97
        $this->gitlabDomains = $config->get('gitlab-domains');
0 ignored issues
show
Documentation Bug introduced by
It seems like $config->get('gitlab-domains') of type * is incompatible with the declared type array of property $gitlabDomains.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
98
        $this->setupAuthentication($io, $useRedirector);
99
    }
100
101
    public function __destruct()
102
    {
103
        if ($this->fp) {
104
            fclose($this->fp);
105
        }
106
107
        if (!$this->success) {
108
            if (file_exists($this->destination)) {
109
                unlink($this->destination);
110
            }
111
        }
112
    }
113
114
    /**
115
     * @return string
116
     */
117
    public function getURL()
118
    {
119
        $url = self::ifOr($this->scheme, '', '://');
120
        if ($this->user) {
121
            $user = $this->user;
122
            $user .= self::ifOr($this->pass, ':');
123
            $url .= $user . '@';
124
        }
125
        $url .= self::ifOr($this->host);
126
        $url .= self::ifOr($this->port, ':');
127
        $url .= self::ifOr($this->path);
128
        $url .= self::ifOr(http_build_query($this->query), '?');
129
        return $url;
130
    }
131
132
    /**
133
     * @return string user/pass/access_token masked url
134
     */
135
    public function getMaskedURL()
136
    {
137
        $url = self::ifOr($this->scheme, '', '://');
138
        $url .= self::ifOr($this->host);
139
        $url .= self::ifOr($this->port, ':');
140
        $url .= self::ifOr($this->path);
141
        return $url;
142
    }
143
144
    private static function ifOr($str, $pre = '', $post = '')
145
    {
146
        if ($str) {
147
            return $pre . $str . $post;
148
        }
149
        return '';
150
    }
151
152
    /**
153
     * @param string $url
154
     */
155
    public function setURL($url)
156
    {
157
        $struct = parse_url($url);
158
        foreach ($struct as $key => $val) {
0 ignored issues
show
Bug introduced by
The expression $struct of type array<string,string>|false is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
159
            if ($key === 'query') {
160
                parse_str($val, $this->query);
161
            } else {
162
                $this->$key = $val;
163
            }
164
        }
165
    }
166
167
    public function addParam($key, $val)
168
    {
169
        $this->query[$key] = $val;
170
    }
171
172
    public function addHeader($key, $val)
173
    {
174
        $this->headers[strtolower($key)] = $val;
175
    }
176
177
    public function makeSuccess()
178
    {
179
        $this->success = true;
180
    }
181
182
    /**
183
     * @return array
184
     */
185
    public function getCurlOptions()
186
    {
187
        $headers = array();
188
        foreach ($this->headers as $key => $val) {
189
            $headers[] = strtr(ucwords(strtr($key, '-', ' ')), ' ', '-') . ': ' . $val;
190
        }
191
192
        $url = $this->getURL();
193
194
        $curlOpts = array(
195
            CURLOPT_URL => $url,
196
            CURLOPT_HTTPHEADER => $headers,
197
            CURLOPT_USERAGENT => ConfigFacade::getUserAgent(),
198
            CURLOPT_FILE => $this->fp,
199
            //CURLOPT_VERBOSE => true, //for debug
200
        );
201
        $curlOpts += self::$defaultCurlOptions;
202
203
        if ($ciphers = $this->nssCiphers()) {
204
            $curlOpts[CURLOPT_SSL_CIPHER_LIST] = $ciphers;
205
        }
206
        if ($proxy = $this->getProxy($url)) {
207
            $curlOpts[CURLOPT_PROXY] = $proxy;
208
        }
209
210
        return $curlOpts;
211
    }
212
213
    /**
214
     * @param IO\IOInterface $io
215
     * @param bool $useRedirector
216
     */
217
    private function setupAuthentication(IO\IOInterface $io, $useRedirector)
218
    {
219
        if (preg_match('/\.github\.com$/', $this->host)) {
220
            $authKey = 'github.com';
221
            if ($useRedirector) {
222
                if ($this->host === 'api.github.com' && preg_match('%^/repos(/[^/]+/[^/]+/)zipball(.+)$%', $this->path, $_)) {
223
                    $this->host = 'codeload.github.com';
224
                    $this->path = $_[1] . 'legacy.zip' . $_[2];
225
                }
226
            }
227
        } else {
228
            $authKey = $this->host;
229
        }
230
        if (!$io->hasAuthentication($authKey)) {
231
            if ($this->user || $this->pass) {
232
                $io->setAuthentication($authKey, $this->user, $this->pass);
233
            } else {
234
                return;
235
            }
236
        }
237
238
        $auth = $io->getAuthentication($authKey);
239
240
        // is github
241
        if (in_array($authKey, $this->githubDomains) && 'x-oauth-basic' === $auth['password']) {
242
            $this->addParam('access_token', $auth['username']);
243
            $this->user = $this->pass = null;
244
            return;
245
        }
246
        // is gitlab
247
        if (in_array($authKey, $this->gitlabDomains) && 'oauth2' === $auth['password']) {
248
            $this->addHeader('authorization', 'Bearer ' . $auth['username']);
249
            $this->user = $this->pass = null;
250
            return;
251
        }
252
        // others, includes bitbucket
253
        $this->user = $auth['username'];
254
        $this->pass = $auth['password'];
255
    }
256
257
    private function getProxy($url)
0 ignored issues
show
Coding Style introduced by
getProxy uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
258
    {
259
        if (isset($_SERVER['no_proxy'])) {
260
            $pattern = new Util\NoProxyPattern($_SERVER['no_proxy']);
261
            if ($pattern->test($url)) {
262
                return null;
263
            }
264
        }
265
266 View Code Duplication
        if ($this->scheme === 'https') {
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...
267
            if (isset($_SERVER['HTTPS_PROXY'])) {
268
                return $_SERVER['HTTPS_PROXY'];
269
            }
270
            if (isset($_SERVER['https_proxy'])) {
271
                return $_SERVER['https_proxy'];
272
            }
273
        }
274
275 View Code Duplication
        if ($this->scheme === 'http') {
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...
276
            if (isset($_SERVER['HTTP_PROXY'])) {
277
                return $_SERVER['HTTP_PROXY'];
278
            }
279
            if (isset($_SERVER['http_proxy'])) {
280
                return $_SERVER['http_proxy'];
281
            }
282
        }
283
        return null;
284
    }
285
286
    /**
287
     * enable ECC cipher suites in cURL/NSS
288
     */
289
    public static function nssCiphers()
290
    {
291
        static $cache;
292
        if (isset($cache)) {
293
            return $cache;
294
        }
295
        $ver = curl_version();
296
        if (preg_match('/^NSS.*Basic ECC$/', $ver['ssl_version'])) {
297
            return $cache = implode(',', self::$NSS_CIPHERS);
298
        }
299
        return $cache = false;
300
    }
301
302
    /**
303
     * @param string
304
     */
305
    public function setDestination($destination)
306
    {
307
        $this->destination = $destination;
308
        if (is_dir($destination)) {
309
            throw new FetchException(
310
                'The file could not be written to ' . $destination . '. Directory exists.'
311
            );
312
        }
313
314
        $this->createDir($destination);
315
316
        $this->fp = fopen($destination, 'wb');
317
        if (!$this->fp) {
318
            throw new FetchException(
319
                'The file could not be written to ' . $destination
320
            );
321
        }
322
    }
323
324
    private function createDir($fileName)
325
    {
326
        $targetdir = dirname($fileName);
327
        if (!file_exists($targetdir)) {
328
            if (!mkdir($targetdir, 0766, true)) {
329
                throw new FetchException(
330
                    'The file could not be written to ' . $fileName
331
                );
332
            }
333
        }
334
    }
335
}
336