Passed
Push — master ( 122326...34fd24 )
by Aurimas
14:24
created

PackagistAdder::checkRequirements()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 13
nc 3
nop 0
dl 0
loc 25
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
namespace Thruster\Tool\ProjectGenerator\Console;
4
5
use Symfony\Component\Console\Output\OutputInterface;
6
7
/**
8
 * Class PackagistAdder
9
 *
10
 * @package Thruster\Tool\ProjectGenerator\Console
11
 * @author  Aurimas Niekis <[email protected]>
12
 */
13
class PackagistAdder
14
{
15
    /** @var string */
16
    private $pauth;
17
18
    /** @var OutputInterface */
19
    private $output;
20
21
    /** @var string */
22
    private $session;
23
24
    public function __construct(string $pauth = null)
25
    {
26
        if (null === $pauth) {
27
            $this->pauth = getenv('PACKAGIST_AUTH');
28
        } else {
29
            $this->pauth = $pauth;
30
        }
31
    }
32
33
    public function execute(OutputInterface $output): int
34
    {
35
        $this->output = $output;
36
37
        if (!$this->pauth) {
38
            $output->writeln('<error>No valid PACKAGIST_AUTH provided!</error>');
39
40
            return 1;
41
        }
42
43
        if (false === $this->checkRequirements()) {
44
            return 1;
45
        }
46
47
        $url = $this->parseConfigFile();
48
        if (false === $url) {
49
            $output->writeln('<error>No valid git remote origins found!</error>');
50
51
            return 1;
52
        }
53
54
        if (false === $this->fetchCSRFToken()) {
55
            $output->writeln('<error>No valid CSRF token found!</error>');
56
57
            return 1;
58
        }
59
60
        if (false === $this->validateUrl($url)) {
61
            $output->writeln('<error>No valid package found in "' . $url . '"</error>');
62
63
            return 1;
64
        }
65
66
        if (false === $this->submitPackage($url)) {
67
            $output->writeln('<error>Error creating package!</error>');
68
69
            return 1;
70
        }
71
72
73
        $output->writeln('');
74
        $output->writeln('<info>Done.</info>');
75
76
        return 0;
77
    }
78
79
    private function checkRequirements()
80
    {
81
        if (false === file_exists($this->getGitConfigFile())) {
82
            $this->output->writeln(
83
                sprintf(
84
                    '<error>`.git/config` file not found in "%s"</error>',
85
                    getcwd()
86
                )
87
            );
88
89
            return false;
90
        }
91
92
        if (false === file_exists($this->getComposerJsonFile())) {
93
            $this->output->writeln(
94
                sprintf(
95
                    '<error>`composer.json` file not found in "%s"</error>',
96
                    getcwd()
97
                )
98
            );
99
100
            return false;
101
        }
102
103
        return true;
104
    }
105
106
    private function getGitConfigFile(): string
107
    {
108
        return getcwd() . '/.git/config';
109
    }
110
111
    private function getComposerJsonFile(): string
112
    {
113
        return getcwd() . '/composer.json';
114
    }
115
116
    private function parseConfigFile()
117
    {
118
        $file = $this->getGitConfigFile();
119
120
        $ini = parse_ini_file($file, true);
121
122
        foreach ($ini as $group => $config) {
123
            if (0 === strpos($group, 'remote')) {
124
                return $config['url'];
125
            }
126
        }
127
128
        return false;
129
    }
130
131
    private function validateUrl($url): bool
132
    {
133
        $ch = curl_init();
134
135
        curl_setopt($ch, CURLOPT_URL, 'https://packagist.org/packages/fetch-info');
136
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
137
138
        $a = 'package[repository]=' . urlencode($url) . '&package[_token]=' . urlencode($this->token);
139
        curl_setopt(
140
            $ch,
141
            CURLOPT_POSTFIELDS,
142
            $a
143
        );
144
        curl_setopt($ch, CURLOPT_POST, 1);
145
146
        $headers   = [];
147
        $headers[] = 'Cookie: pauth=' . $this->pauth . '; packagist=' . $this->session . ';';
148
        $headers[] = 'Origin: https://packagist.org';
149
        $headers[] = 'X-Requested-With: XMLHttpRequest';
150
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
151
152
        $result = curl_exec($ch);
153
        if (curl_errno($ch)) {
154
            $this->output->writeln(
155
                sprintf(
156
                    '<error>Curl error when trying to validate url "%s"</error>',
157
                    curl_error($ch)
158
                )
159
            );
160
161
            return false;
162
        }
163
164
        curl_close($ch);
165
166
        $result = json_decode($result, true);
167
168
        if (JSON_ERROR_NONE !== json_last_error()) {
169
            $this->output->writeln(
170
                sprintf(
171
                    '<error>Error parsing JSON response: %s</error>',
172
                    json_last_error_msg()
173
                )
174
            );
175
176
            return false;
177
        }
178
179
        return $result['status'] === 'success';
180
    }
181
182
    private function submitPackage($url)
183
    {
184
        $ch = curl_init();
185
186
        curl_setopt($ch, CURLOPT_URL, 'https://packagist.org/packages/submit');
187
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
188
189
        $a = 'package[repository]=' . urlencode($url) . '&package[_token]=' . urlencode($this->token);
190
        curl_setopt(
191
            $ch,
192
            CURLOPT_POSTFIELDS,
193
            $a
194
        );
195
        curl_setopt($ch, CURLOPT_POST, 1);
196
197
        $headers   = [];
198
        $headers[] = 'Cookie: pauth=' . $this->pauth . '; packagist=' . $this->session . ';';
199
        $headers[] = 'Origin: https://packagist.org';
200
        $headers[] = 'X-Requested-With: XMLHttpRequest';
201
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
202
        curl_setopt($ch, CURLOPT_HEADER, 1);
203
204
        $result = curl_exec($ch);
205
        if (curl_errno($ch)) {
206
            $this->output->writeln(
207
                sprintf(
208
                    '<error>Curl error when trying to validate url "%s"</error>',
209
                    curl_error($ch)
210
                )
211
            );
212
213
            return false;
214
        }
215
216
        curl_close($ch);
217
218
        $line = strtok($result, "\n");
219
220
        return false !== strpos($line, '302');
221
    }
222
223
    private function fetchCSRFToken()
224
    {
225
        $ch = curl_init();
226
227
        curl_setopt($ch, CURLOPT_URL, "https://packagist.org/packages/submit");
228
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
229
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "GET");
230
231
        $headers = array();
232
        $headers[] = 'Cookie: pauth=' . $this->pauth . ';';
233
        $headers[] = 'Origin: https://packagist.org';
234
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
235
        curl_setopt($ch, CURLOPT_HEADER, 1);
236
237
        $result = curl_exec($ch);
238
        if (curl_errno($ch)) {
239
            $this->output->writeln(
240
                sprintf(
241
                    '<error>Curl error when trying to fetch CSRF token "%s"</error>',
242
                    curl_error($ch)
243
                )
244
            );
245
246
            return false;
247
        }
248
249
        curl_close($ch);
250
251
        if (preg_match('/packagist=([^;]+)/', $result, $matches)) {
252
            $this->session = $matches[1];
253
        } else {
254
            return false;
255
        }
256
257
        if (preg_match('/name="package\[_token\]" class=" form-control" value="([^"]+)"/', $result, $matches)) {
258
            $this->token = $matches[1];
0 ignored issues
show
Bug Best Practice introduced by
The property token does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
259
260
            return true;
261
        } else {
262
            return false;
263
        }
264
    }
265
}
266