Completed
Pull Request — master (#80)
by Hiraku
02:32
created

HttpGetRequest   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 255
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 88.98%

Importance

Changes 15
Bugs 6 Features 1
Metric Value
wmc 41
c 15
b 6
f 1
lcom 1
cbo 3
dl 0
loc 255
ccs 113
cts 127
cp 0.8898
rs 8.2769

11 Methods

Rating   Name   Duplication   Size   Complexity  
A setOr() 0 8 2
A __construct() 0 13 4
A importURL() 0 20 3
A processRFSOption() 0 6 2
A setConfig() 0 4 1
B nssCiphers() 0 19 5
A getURL() 0 20 4
B promptAuthWithUtil() 0 19 5
A genUA() 0 16 3
B getCurlOpts() 0 30 6
B promptAuth() 0 25 6

How to fix   Complexity   

Complex Class

Complex classes like HttpGetRequest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HttpGetRequest, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * hirak/prestissimo
4
 * @author Hiraku NAKANO
5
 * @license MIT https://github.com/hirak/prestissimo
6
 */
7
namespace Hirak\Prestissimo\Aspects;
8
9
use Composer\IO;
10
use Composer\Composer;
11
use Composer\Config as CConfig;
12
use Composer\Downloader;
13
14
/**
15
 * Simple Container for http-get request
16
 */
17
class HttpGetRequest
18
{
19
    public $origin;
20
    public $scheme = 'http';
21
    public $host = 'example.com';
22
    public $port = 80;
23
    public $path = '/';
24
25
    public $query = array();
26
    public $headers = array();
27
28
    public $curlOpts = array();
29
30
    public $username = null;
31
    public $password = null;
32
33
    public $maybePublic = false;
34
    public $verbose = false;
35
36
    /** @var CConfig */
37
    protected $config;
38
39
    /** @internal */
40
    const TOKEN_LABEL = 'access_token';
41
42
    /**
43
     * normalize url and authentication info
44
     * @param string $origin domain text
45
     * @param string $url
46
     * @param IO\IOInterface $io
47
     */
48 26
    public function __construct($origin, $url, IO\IOInterface $io)
49
    {
50 26
        $this->origin = $origin;
51 26
        $this->importURL($url);
52
53 26
        if ($this->username && $this->password) {
54 3
            $io->setAuthentication($origin, $this->username, $this->password);
55 26
        } elseif ($io->hasAuthentication($origin)) {
56 2
            $auth = $io->getAuthentication($origin);
57 2
            $this->username = $auth['username'];
58 2
            $this->password = $auth['password'];
59 2
        }
60 26
    }
61
62
    /**
63
     * @param string $url
64
     */
65 26
    public function importURL($url)
66
    {
67 26
        $struct = parse_url($url);
68
        // @codeCoverageIgnoreStart
69
        if (!$struct) {
70
            throw new \InvalidArgumentException("$url is not valid URL");
71
        }
72
        // @codeCoverageIgnoreEnd
73
74 26
        $this->scheme = self::setOr($struct, 'scheme');
75 26
        $this->host = self::setOr($struct, 'host');
76 26
        $this->port = self::setOr($struct, 'port');
77 26
        $this->path = self::setOr($struct, 'path');
78 26
        $this->username = self::setOr($struct, 'user');
79 26
        $this->password = self::setOr($struct, 'pass');
80
81 26
        if (!empty($struct['query'])) {
82 3
            parse_str($struct['query'], $this->query);
83 3
        }
84 26
    }
85
86
87
    /**
88
     * @param array $struct
89
     * @param string $key
90
     * @param string $default
91
     * @return mixed
92
     */
93 26
    private static function setOr(array $struct, $key, $default = null)
94
    {
95 26
        if (!empty($struct[$key])) {
96 26
            return $struct[$key];
97
        }
98
99 26
        return $default;
100
    }
101
102
    /**
103
     * process option for RemortFileSystem
104
     * @param array $options
105
     * @return void
106
     */
107 2
    public function processRFSOption(array $options)
108
    {
109 2
        if (isset($options[static::TOKEN_LABEL])) {
110 2
            $this->query['access_token'] = $options[static::TOKEN_LABEL];
111 2
        }
112 2
    }
113
114
    /**
115
     * @return array
116
     */
117 4
    public function getCurlOpts()
118
    {
119 4
        $headers = $this->headers;
120 4
        if ($this->username && $this->password) {
121 1
            foreach ($headers as $i => $header) {
122
                if (0 === strpos($header, 'Authorization:')) {
123
                    unset($headers[$i]);
124
                }
125 1
            }
126 1
            $headers[] = 'Authorization: Basic ' . base64_encode("$this->username:$this->password");
127 1
        }
128
129 4
        $curlOpts = $this->curlOpts + array(
130 4
            CURLOPT_HTTPGET => true,
131 4
            CURLOPT_FOLLOWLOCATION => true,
132 4
            CURLOPT_MAXREDIRS => 20,
133 4
            CURLOPT_ENCODING => 'gzip',
134 4
            CURLOPT_HTTPHEADER => $headers,
135 4
            CURLOPT_USERAGENT => $this->genUA(),
136 4
            CURLOPT_VERBOSE => (bool)$this->verbose,
137 4
            CURLOPT_URL => $this->getUrl(),
138 4
        );
139 4
        unset($curlOpts[CURLOPT_USERPWD]);
140
141 4
        if ($ciphers = $this->nssCiphers()) {
142
            $curlOpts[CURLOPT_SSL_CIPHER_LIST] = $ciphers;
143
        }
144
145 4
        return $curlOpts;
146
    }
147
148
    /**
149
     * enable ECC cipher suites in cURL/NSS
150
     */
151 4
    public function nssCiphers()
152
    {
153 4
        static $cache;
154 4
        if (isset($cache)) {
155 3
            return $cache;
156
        }
157 2
        $ver = curl_version();
158 2
        if (preg_match('/^NSS.*Basic ECC$/', $ver['ssl_version'])) {
159
            $ciphers = array();
160
            foreach (new \SplFileObject(__DIR__ . '/../../res/nss_ciphers.txt') as $line) {
161
                $line = trim($line);
162
                if ($line) {
163
                    $ciphers[] = $line;
164
                }
165
            }
166
            return $cache = implode(',', $ciphers);
167
        }
168 2
        return $cache = false;
169
    }
170
171 12
    public function getURL()
172
    {
173 12
        $url = '';
174 12
        if ($this->scheme) {
175 12
            $url .= "$this->scheme://";
176 12
        }
177 12
        $url .= $this->host;
178
179 12
        if ($this->port) {
180 3
            $url .= ":$this->port";
181 3
        }
182
183 12
        $url .= $this->path;
184
185 12
        if ($this->query) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->query of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
186 3
            $url .= '?' . http_build_query($this->query);
187 3
        }
188
189 12
        return $url;
190
    }
191
192 2
    public function setConfig(CConfig $config)
193
    {
194 2
        $this->config = $config;
195 2
    }
196
197 3
    public function promptAuth(HttpGetResponse $res, IO\IOInterface $io)
198
    {
199 3
        $httpCode = $res->info['http_code'];
200
        // 404s are only handled for github
201 3
        if (404 === $httpCode) {
202
            return false;
203
        }
204
205
        // fail if the console is not interactive
206 3
        if (!$io->isInteractive() && ($httpCode === 401 || $httpCode === 403)) {
207 1
            $message = "The '{$this->getURL()}' URL required authentication.\nYou must be using the interactive console to authenticate";
208 1
            throw new Downloader\TransportException($message, $httpCode);
209
        }
210
211
        // fail if we already have auth
212 2
        if ($io->hasAuthentication($this->origin)) {
213 1
            throw new Downloader\TransportException("Invalid credentials for '{$this->getURL()}', aborting.", $httpCode);
214
        }
215
216 1
        $io->overwrite("    Authentication required (<info>$this->host</info>):");
217 1
        $username = $io->ask('      Username: ');
218 1
        $password = $io->askAndHideAnswer('      Password: ');
219 1
        $io->setAuthentication($this->origin, $username, $password);
220 1
        return true;
221
    }
222
223
    /**
224
     * @internal
225
     * @param int $privateCode 404|403
226
     * @param Composer\Util\GitHub|Composer\Util\GitLab $util
227
     * @param HttpGetResponse $res
228
     * @param IO\IOInterface $io
229
     * @throws Composer\Downloader\TransportException
230
     * @return bool
231
     */
232 2
    public function promptAuthWithUtil($privateCode, $util, HttpGetResponse $res, IO\IOInterface $io)
233
    {
234 2
        $httpCode = $res->info['http_code'];
235 2
        $message = "\nCould not fetch {$this->getURL()}, enter your $this->origin credentials ";
236 2
        if ($privateCode === $httpCode) {
237 1
            $message .= 'to access private repos';
238 1
        } else {
239 1
            $message .= 'to go over the API rate limit';
240
        }
241 2
        if ($util->authorizeOAuth($this->origin)) {
242 1
            return true;
243
        }
244 2
        if ($io->isInteractive() &&
245 2
            $util->authorizeOAuthInteractively($this->origin, $message)) {
246 1
            return true;
247
        }
248
249 1
        throw new Downloader\TransportException("Could not authenticate against $this->origin", $httpCode);
250
    }
251
252
    /**
253
     * @return string
254
     */
255 4
    public static function genUA()
256
    {
257 4
        static $ua;
258 4
        if ($ua) {
259 3
            return $ua;
260
        }
261 2
        $phpVersion = defined('HHVM_VERSION') ? 'HHVM ' . HHVM_VERSION : 'PHP ' . PHP_VERSION;
262
263 2
        return $ua = sprintf(
264 2
            'Composer/%s (%s; %s; %s)',
265 2
            str_replace('@package_version@', 'source', Composer::VERSION),
266 2
            php_uname('s'),
267 2
            php_uname('r'),
268
            $phpVersion
269 2
        );
270
    }
271
}
272