Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
TestRateLimitCommand
0.00% covered (danger)
0.00%
0 / 47
0.00% covered (danger)
0.00%
0 / 4
42
0.00% covered (danger)
0.00%
0 / 1
 buildOptionParser
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 1
12
 getBaseUrl
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 generateRandomIp
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2declare(strict_types=1);
3
4namespace App\Command;
5
6use Cake\Command\Command;
7use Cake\Console\Arguments;
8use Cake\Console\ConsoleIo;
9use Cake\Console\ConsoleOptionParser;
10
11/**
12 * TestRateLimitCommand class
13 *
14 * This command is used to test rate limiting by making multiple requests to a specified URL.
15 */
16class TestRateLimitCommand extends Command
17{
18    /**
19     * Build the option parser for the command.
20     *
21     * @param \Cake\Console\ConsoleOptionParser $parser The option parser to be defined.
22     * @return \Cake\Console\ConsoleOptionParser The option parser instance.
23     */
24    protected function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
25    {
26        $parser
27            ->addOption('url', [
28                'short' => 'u',
29                'help' => 'URL to test (default: /en/users/login)',
30                'default' => '/en/users/login',
31            ])
32            ->addOption('attempts', [
33                'short' => 'a',
34                'help' => 'Number of attempts (default: 10)',
35                'default' => '10',
36            ]);
37
38        return $parser;
39    }
40
41    /**
42     * Execute the command.
43     *
44     * @param \Cake\Console\Arguments $args The command arguments.
45     * @param \Cake\Console\ConsoleIo $io The console io.
46     * @return int|null The exit code or null for success.
47     */
48    public function execute(Arguments $args, ConsoleIo $io): ?int
49    {
50        $url = $args->getOption('url');
51        $attempts = (int)$args->getOption('attempts');
52
53        $baseUrl = $this->getBaseUrl();
54        $fullUrl = $baseUrl . $url;
55
56        $io->out(sprintf('Testing rate limit on URL: %s', $fullUrl));
57        $io->out(sprintf('Number of attempts: %d', $attempts));
58        $io->hr();
59
60        for ($i = 0; $i < $attempts; $i++) {
61            $ch = curl_init($fullUrl);
62            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
63            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
64            curl_setopt($ch, CURLOPT_FAILONERROR, true);
65
66            // Set headers to simulate a browser request
67            curl_setopt($ch, CURLOPT_HTTPHEADER, [
68                'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' .
69                    '(KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
70                'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
71                'Accept-Language: en-US,en;q=0.5',
72                'Connection: keep-alive',
73                'Upgrade-Insecure-Requests: 1',
74                'X-Forwarded-For: ' . $this->generateRandomIp(),
75            ]);
76
77            $response = curl_exec($ch);
78
79            if ($response === false) {
80                $error = curl_error($ch);
81                $errno = curl_errno($ch);
82                $io->error(sprintf('Attempt %d failed. Error (%d): %s', $i + 1, $errno, $error));
83            } else {
84                $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
85                $io->out(sprintf('Attempt %d: HTTP Code: %d', $i + 1, $httpCode));
86                $io->out(sprintf('Response: %s', substr($response, 0, 100) . '...'));
87            }
88
89            curl_close($ch);
90            $io->hr();
91
92            usleep(100000); // 0.1 second delay
93        }
94
95        return static::CODE_SUCCESS;
96    }
97
98    /**
99     * Get the base URL for the requests.
100     *
101     * @return string The base URL.
102     */
103    private function getBaseUrl(): string
104    {
105        return 'http://willowcms:80';
106    }
107
108    /**
109     * Generate a random IP address.
110     *
111     * @return string A randomly generated IP address.
112     */
113    private function generateRandomIp(): string
114    {
115        return mt_rand(1, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(0, 255) . '.' . mt_rand(0, 255);
116    }
117}