Skip to content

Commit 046dc25

Browse files
authored
Merge pull request #1 from neighbourhoodie/m2
RectorPHP rules to upgrade phpseclib 2.0 to 3.0
2 parents 8fb10d4 + 34de30f commit 046dc25

68 files changed

Lines changed: 2567 additions & 2 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/composer.lock
2+
/composer.phar
3+
/tests/.phpunit.result.cache
4+
/vendor/

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ vendor/bin/rector process src --dry-run
3333
vendor/bin/rector process src
3434
```
3535
The files in the `src/` directory will either be full on modified or (in the case of `--dry-run`) the changes that would be made will be previewed.
36+
37+
## Rule specifics
38+
39+
Details on specific rules can be found [here](./src/README.MD).

composer.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
"require": {
66
"rector/rector": "^2.0.0"
77
},
8+
"require-dev": {
9+
"phpunit/phpunit": "12.x"
10+
},
811
"license": "MIT",
912
"autoload": {
1013
"psr-4": {
11-
"phpseclib\\phpseclib3Rector\\": "src/"
14+
"phpseclib\\phpseclib3Rector\\": "src/",
15+
"phpseclib\\phpseclib3Rector\\Tests\\": "tests/"
1216
}
1317
},
1418
"authors": [

rector.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use phpseclib\phpseclib3Rector\Set;
7+
8+
return RectorConfig::configure()
9+
->withSets([Set::PATH]);

src/CreateKey.php

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpseclib\phpseclib3Rector;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Name;
9+
use PhpParser\NodeTraverser;
10+
use PhpParser\Node\Expr\Assign;
11+
use PhpParser\Node\Expr\FuncCall;
12+
use PhpParser\Node\Expr\New_;
13+
use PhpParser\Node\Expr\StaticCall;
14+
use PhpParser\Node\Expr\Variable;
15+
use PhpParser\Node\Expr\MethodCall;
16+
use PhpParser\Node\Expr\Cast\String_;
17+
use PhpParser\Node\Stmt\UseUse;
18+
use PhpParser\Node\Stmt\Expression;
19+
20+
use Rector\Rector\AbstractRector;
21+
22+
final class CreateKey extends AbstractRector
23+
{
24+
private ?bool $hasExtract = null;
25+
private ?string $currentFilePath = null;
26+
private array $rsaVarsUsedInExtract = [];
27+
28+
public function getNodeTypes(): array
29+
{
30+
return [
31+
UseUse::class,
32+
Expression::class,
33+
];
34+
}
35+
36+
// We only continue when there is an extract(->createKey())
37+
private function containsExtract(array $nodes): bool
38+
{
39+
foreach ($nodes as $node) {
40+
if ($node instanceof Expression && ($node->expr instanceof FuncCall || $node->expr instanceof MethodCall)) {
41+
$funcName = $node->expr->name;
42+
if ($funcName instanceof Name && $funcName->toString() === 'extract') {
43+
$arg = $node->expr->args[0]->value ?? null;
44+
45+
if (
46+
$arg instanceof MethodCall &&
47+
$this->isName($arg->name, 'createKey') &&
48+
$arg->var instanceof Variable
49+
) {
50+
return true;
51+
}
52+
}
53+
}
54+
55+
// Recurse into class/method bodies
56+
if (property_exists($node, 'stmts') && is_array($node->stmts)) {
57+
if ($this->containsExtract($node->stmts)) {
58+
return true;
59+
}
60+
}
61+
}
62+
return false;
63+
}
64+
65+
public function refactor(Node $node): null|Node|int|array
66+
{
67+
$filePath = $this->file->getFilePath();
68+
if ($this->currentFilePath !== $filePath) {
69+
$this->hasExtract = null;
70+
$this->currentFilePath = $filePath;
71+
}
72+
73+
if ($this->hasExtract === null) {
74+
$this->hasExtract = $this->containsExtract($this->file->getNewStmts());
75+
}
76+
77+
// This is a job for PublicKeyLoaderRule
78+
if (! $this->hasExtract) {
79+
return null;
80+
}
81+
82+
// Detect extract($rsa->createKey(...))
83+
if (
84+
$node instanceof Expression &&
85+
$node->expr instanceof FuncCall &&
86+
$this->isName($node->expr->name, 'extract')
87+
) {
88+
$arg = $node->expr->args[0]->value ?? null;
89+
if (
90+
$arg instanceof MethodCall &&
91+
$this->isName($arg->name, 'createKey') &&
92+
$arg->var instanceof Variable &&
93+
is_string($arg->var->name)
94+
) {
95+
$this->rsaVarsUsedInExtract[$arg->var->name] = true;
96+
}
97+
}
98+
99+
// Replace use phpseclib\Crypt\RSA -> use phpseclib3\Crypt\RSA
100+
if ($node instanceof UseUse) {
101+
if ($this->isName($node->name, 'phpseclib\Crypt\RSA')) {
102+
$node->name = new Name('phpseclib3\Crypt\RSA');
103+
$node->alias = null;
104+
return $node;
105+
}
106+
}
107+
108+
if (! $node instanceof Expression) {
109+
return null;
110+
}
111+
$expr = $node->expr;
112+
113+
// Remove new RSA() assignment
114+
if ($expr instanceof Assign &&
115+
$expr->var instanceof Variable &&
116+
$expr->expr instanceof New_ &&
117+
$this->isName($node->expr->expr->class, 'phpseclib\Crypt\RSA')
118+
) {
119+
$varName = is_string($expr->var->name) ? $expr->var->name : null;
120+
// new RSA() is removed only when it is used in extract()
121+
if ($varName !== null && isset($this->rsaVarsUsedInExtract[$varName])) {
122+
return NodeTraverser::REMOVE_NODE;
123+
}
124+
}
125+
126+
// Match: extract(...)
127+
if (! $expr instanceof FuncCall) {
128+
return null;
129+
}
130+
if (! $this->isName($expr->name, 'extract')) {
131+
return null;
132+
}
133+
// extract() must have arguments
134+
if (! isset($expr->args[0])) {
135+
return null;
136+
}
137+
// Argument must be a method -> $rsa->createKey(...)
138+
$arg = $expr->args[0]->value;
139+
if (! ($arg instanceof MethodCall) || ! ($this->isName($arg->name, 'createKey'))) {
140+
return null;
141+
}
142+
143+
// Extract variable name from: $rsa->createKey(...)
144+
$varName = $arg->var instanceof Variable ? $arg->var->name : null;
145+
if (
146+
! is_string($varName) ||
147+
! isset($this->rsaVarsUsedInExtract[$varName])
148+
) {
149+
return null;
150+
}
151+
152+
// refactor to $rsa = RSA::createKey(2048);
153+
$originalPrivateKeyExpr = new Expression(
154+
new Assign(
155+
// or $varName if you want to keep the original one
156+
new Variable('privateKey'),
157+
new StaticCall(
158+
new Name('RSA'),
159+
'createKey',
160+
$arg->args
161+
)
162+
)
163+
);
164+
165+
// $publickey = $privatekey->getPublicKey();
166+
$publicKeyExpr = new Expression(
167+
new Assign(
168+
new Variable('publicKey'),
169+
new MethodCall(
170+
new Variable('privateKey'),
171+
'getPublicKey',
172+
)
173+
)
174+
);
175+
176+
// $privatekey = (string) $privatekey;
177+
$privateKeyString = new Expression(
178+
new Assign(
179+
new Variable('privateKey'),
180+
new String_(
181+
new Variable('privateKey'),
182+
)
183+
)
184+
);
185+
// $publickey = (string) $publickey;
186+
$publicKeySting = new Expression(
187+
new Assign(
188+
new Variable('publicKey'),
189+
new String_(
190+
new Variable('publicKey'),
191+
)
192+
)
193+
);
194+
195+
return [
196+
$originalPrivateKeyExpr,
197+
$publicKeyExpr,
198+
$privateKeyString,
199+
$publicKeySting,
200+
];
201+
}
202+
}

src/HashLength.php

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace phpseclib\phpseclib3Rector;
6+
7+
use Rector\Rector\AbstractRector;
8+
9+
use PhpParser\Node;
10+
use PhpParser\Node\Name;
11+
use PhpParser\Node\Identifier;
12+
use PhpParser\Node\Expr\MethodCall;
13+
use PhpParser\Node\Expr\New_;
14+
use PhpParser\Node\Expr\Assign;
15+
use PhpParser\Node\Expr\Variable;
16+
use PhpParser\Node\Stmt\UseUse;
17+
use PhpParser\Node\Stmt\Expression;
18+
19+
final class HashLength extends AbstractRector
20+
{
21+
private array $hashVarNames = [];
22+
23+
public function getNodeTypes(): array
24+
{
25+
return [
26+
UseUse::class,
27+
MethodCall::class,
28+
Expression::class,
29+
];
30+
}
31+
32+
public function refactor(Node $node): ?Node
33+
{
34+
// Replace use phpseclib\Crypt\Hash -> use phpseclib3\Crypt\Hash
35+
if ($node instanceof UseUse) {
36+
if ($this->isName($node->name, 'phpseclib\Crypt\Hash')) {
37+
$node->name = new Name('phpseclib3\Crypt\Hash');
38+
return $node;
39+
}
40+
}
41+
42+
// Remember the variable name
43+
if (
44+
$node instanceof Expression &&
45+
$node->expr instanceof Assign &&
46+
$node->expr->var instanceof Variable &&
47+
$node->expr->expr instanceof New_ &&
48+
$this->isName($node->expr->expr->class, 'phpseclib\Crypt\Hash')
49+
) {
50+
$this->hashVarNames[$node->expr->var->name] = true;
51+
}
52+
53+
if (! $node instanceof MethodCall) {
54+
return null;
55+
}
56+
if(
57+
! $node->var instanceof Variable ||
58+
! is_string($node->var->name) ||
59+
! isset($this->hashVarNames[$node->var->name])
60+
) {
61+
return null;
62+
}
63+
64+
// Replace method call getLength() -> getLengthInBytes()
65+
if ($this->isName($node->name, 'getLength')) {
66+
$node->name = new Identifier('getLengthInBytes');
67+
}
68+
69+
return $node;
70+
}
71+
}

0 commit comments

Comments
 (0)