Issues (18)

Control/CheckAllTemplatesResponseController.php (2 issues)

Labels
Severity
1
<?php
2
3
namespace Sunnysideup\TemplateOverview\Control;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Cookie\CookieJar;
7
use GuzzleHttp\Exception\RequestException;
8
use GuzzleHttp\Psr7;
9
use Psr\SimpleCache\CacheInterface;
10
use SebastianBergmann\Diff\Differ;
11
use SilverStripe\Control\Controller;
12
use SilverStripe\Control\Director;
13
use SilverStripe\Control\HTTPRequest;
14
use SilverStripe\Core\Config\Config;
15
use SilverStripe\Core\Convert;
16
use SilverStripe\Core\Flushable;
17
use SilverStripe\Core\Injector\Injector;
18
use SilverStripe\Security\DefaultAdminService;
19
use SilverStripe\Security\Member;
20
use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator;
21
use SilverStripe\Security\Permission;
22
use Sunnysideup\TemplateOverview\Api\AllLinks;
23
use Sunnysideup\TemplateOverview\Api\W3cValidateApi;
24
25
/**
26
 * Class \Sunnysideup\TemplateOverview\Control\CheckAllTemplatesResponseController
27
 *
28
 */
29
class CheckAllTemplatesResponseController extends Controller implements Flushable
30
{
31
    /**
32
     * Defines methods that can be called directly.
33
     *
34
     * @var array
35
     */
36
    private static $allowed_actions = [
37
        'testone' => 'ADMIN',
38
    ];
39
40
    private static $use_default_admin = true;
41
42
    private static $username = '';
43
44
    private static $password = '';
45
46
    private static $url_segment = 'admin/templateoverviewsmoketestresponse';
47
48
    private static $use_w3_validation = false;
49
50
    private static $create_diff = false;
51
52
    private $guzzleCookieJar;
53
54
    private $guzzleClient;
55
56
    private $guzzleHasError = false;
57
58
    private $isSuccess = false;
59
60
    /**
61
     * temporary Admin used to log in.
62
     *
63
     * @var Member
64
     */
65
    private $member;
66
67
    private $rawResponse = '';
68
69
    /**
70
     * @var bool
71
     */
72
    private $debug = false;
73
74
    public static function flush()
75
    {
76
        $cache = Injector::inst()->get(CacheInterface::class . '.templateoverview');
77
        $cache->clear();
78
    }
79
80
    public static function get_user_email()
81
    {
82
        $userName = Config::inst()->get(self::class, 'username');
83
        if (! $userName) {
84
            if (Config::inst()->get(self::class, 'use_default_admin')) {
85
                $userName = DefaultAdminService::getDefaultAdminUsername();
86
            } else {
87
                $userName = '[email protected]';
88
            }
89
        }
90
91
        return $userName;
92
    }
93
94
    public static function get_password()
95
    {
96
        $password = Config::inst()->get(self::class, 'password');
97
        if (! $password) {
98
            if (Config::inst()->get(self::class, 'use_default_admin')) {
99
                $password = DefaultAdminService::getDefaultAdminPassword();
100
            } else {
101
                $cache = Injector::inst()->get(CacheInterface::class . '.templateoverview');
102
                if (! $cache->has('password')) {
103
                    $password = strtolower('aa' . substr(uniqid(), 0, 8)) . '_.,' . strtoupper('BB' . substr(uniqid(), 0, 8));
104
                    $cache->set('password', $password);
105
                }
106
107
                $password = $cache->get('password');
108
            }
109
        }
110
111
        return $password;
112
    }
113
114
    public static function get_test_user()
115
    {
116
        return Injector::inst()->get(self::class)->getTestUser();
117
    }
118
119
    /**
120
     * Main function
121
     * has two streams:
122
     * 1. check on url specified in GET variable.
123
     * 2. create a list of urls to check.
124
     */
125
    public function testone(HTTPRequest $request)
126
    {
127
        $isCMSLink = (bool) $request->getVar('iscmslink');
128
        $testURL = $request->getVar('test') ?: null;
129
130
        // 1. actually test a URL and return the data
131
        if ($testURL) {
132
            $this->guzzleSetup();
133
            $this->getTestUser();
134
            $content = $this->testURL($testURL);
135
            $this->deleteUser();
136
            $diff = 'Please install https://github.com/Kevin-Kip/meru/ to see diff.';
137
            //these echo is required!
138
            echo $content;
139
            if (! Director::is_ajax()) {
140
                $comparisonBaseURL = Config::inst()->get(self::class, 'comparision_base_url');
141
                $width = '98%';
142
                $style = 'border: none;';
143
                if ($comparisonBaseURL) {
144
                    $width = '48%';
145
                    $style = 'float: left;';
146
                    if ($this->isSuccess && ! $isCMSLink && $this->Config()->create_diff) {
147
                        $otherURL = $comparisonBaseURL . $testURL;
148
                        $testContent = str_replace(Director::absoluteBaseURL(), $comparisonBaseURL, $this->rawResponse);
149
                        $rawResponseOtherSite = @file_get_contents($otherURL);
150
                        if (class_exists(Differ::class)) {
151
                            $diff = (new Differ())->diff(
152
                                $testContent,
153
                                $rawResponseOtherSite
0 ignored issues
show
It seems like $rawResponseOtherSite can also be of type false; however, parameter $to of SebastianBergmann\Diff\Differ::diff() does only seem to accept array|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

153
                                /** @scrutinizer ignore-type */ $rawResponseOtherSite
Loading history...
154
                            );
155
                            $rawResponseOtherSite = Convert::raw2htmlatt(str_replace("'", '\\\'', $rawResponseOtherSite));
156
                            $diff = '
157
                            <iframe id="iframe2" width="' . $width . '%" height="7000" srcdoc=\'' . $rawResponseOtherSite . '\' style="float: right;"></iframe>
0 ignored issues
show
Are you sure $rawResponseOtherSite of type array|string can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

157
                            <iframe id="iframe2" width="' . $width . '%" height="7000" srcdoc=\'' . /** @scrutinizer ignore-type */ $rawResponseOtherSite . '\' style="float: right;"></iframe>
Loading history...
158
159
                            <hr style="clear: both; margin-top: 20px; padding-top: 20px;" />
160
                            <h1>Diff</h1>
161
                            <link href="/resources/vendor/sunnysideup/templateoverview/client/css/checkalltemplates.css" rel="stylesheet" type="text/css" />
162
                            ' . $diff;
163
                        }
164
                    }
165
                }
166
167
                $rawResponse = Convert::raw2htmlatt(str_replace("'", '\\\'', $this->rawResponse));
168
                echo '
169
                    <h1>Response</h1>
170
                ';
171
                echo $diff;
172
                echo '
173
                    <iframe id="iframe" width="' . $width . '" height="700" srcdoc=\'' . $rawResponse . '\' style="' . $style . '"></iframe>
174
                ';
175
            }
176
177
            return;
178
        }
179
180
        user_error('no test url provided.');
181
    }
182
183
    public function getTestUser()
184
    {
185
        if (Config::inst()->get(self::class, 'use_default_admin')) {
186
            $this->member = Injector::inst()->get(DefaultAdminService::class)->findOrCreateDefaultAdmin();
187
188
            return $this->member;
189
        }
190
191
        //Make temporary admin member
192
        $filter = ['Email' => self::get_user_email()];
193
        // @var Member|null $this->member
194
        $this->member = Member::get()
195
            ->filter($filter)
196
            ->first()
197
        ;
198
        if (empty($this->member)) {
199
            $this->member = Member::create($filter);
200
        }
201
202
        $this->member->Password = self::get_password();
203
        $this->member->LockedOutUntil = null;
204
        $this->member->FirstName = 'Test';
205
        $this->member->Surname = 'User';
206
        $this->member->write();
207
        $auth = new MemberAuthenticator();
208
        $result = $auth->checkPassword($this->member, self::get_password());
209
        if (! $result->isValid()) {
210
            user_error('Error in creating test user.', E_USER_ERROR);
211
212
            return;
213
        }
214
215
        $service = Injector::inst()->get(DefaultAdminService::class);
216
        $adminGroup = $service->findOrCreateAdminGroup();
217
        $this->member->Groups()->add($adminGroup);
218
        if (Permission::checkMember($this->member, 'ADMIN')) {
219
            user_error('No admin group exists', E_USER_ERROR);
220
221
            return;
222
        }
223
224
        return $this->member;
225
    }
226
227
    /**
228
     * @return mixed
229
     */
230
    protected function guzzleSendRequest(string $url)
231
    {
232
        $this->guzzleHasError = false;
233
        $credentials = base64_encode(self::get_user_email() . ':' . self::get_password());
234
235
        try {
236
            $response = $this->guzzleClient->request(
237
                'GET',
238
                $url,
239
                [
240
                    'cookies' => $this->guzzleCookieJar,
241
                    'headers' => [
242
                        'PHP_AUTH_USER' => self::get_user_email(),
243
                        'PHP_AUTH_PW' => self::get_password(),
244
                    ],
245
                    'auth' => [
246
                        self::get_user_email(),
247
                        self::get_password(),
248
                    ],
249
                    'Authorization' => ['Basic ' . $credentials],
250
                ]
251
            );
252
        } catch (RequestException $requestException) {
253
            $this->rawResponse = $requestException->getResponse();
254
            $this->guzzleHasError = true;
255
            //echo Psr7\str($exception->getRequest());
256
            if ($requestException->hasResponse()) {
257
                $response = $requestException->getResponse();
258
                $this->rawResponse = $response->getStatusCode() . '|' . $response->getReasonPhrase();
259
            } else {
260
                $response = null;
261
            }
262
        }
263
264
        return $response;
265
    }
266
267
    protected function isJson($string)
268
    {
269
        $obj = json_decode($string);
270
271
        return JSON_ERROR_NONE === json_last_error() && 'object' === gettype($obj);
272
    }
273
274
    /**
275
     * ECHOES the result of testing the URL....
276
     *
277
     * @param string $url
278
     */
279
    private function testURL($url)
280
    {
281
        if (strlen(trim($url)) < 1) {
282
            user_error('empty url'); //Checks for empty strings.
283
        }
284
285
        if (AllLinks::is_admin_link($url)) {
286
            $validate = false;
287
        } else {
288
            $validate = Config::inst()->get(self::class, 'use_w3_validation');
289
        }
290
291
        $testURL = Director::absoluteURL('/admin/templateoverviewloginandredirect/login/?BackURL=');
292
        $testURL .= urlencode($url);
293
        $this->guzzleSetup();
294
295
        $start = microtime(true);
296
        $response = $this->guzzleSendRequest($testURL);
297
        $end = microtime(true);
298
299
        $data = [
300
            'status' => 'success',
301
            'httpResponse' => '200',
302
            'content' => '',
303
            'responseTime' => round($end - $start, 4),
304
            'type' => '',
305
            'length' => '',
306
            'w3Content' => '',
307
        ];
308
309
        $httpResponse = $response->getStatusCode();
310
        $error = $response->getReasonPhrase();
311
        if ($this->guzzleHasError) {
312
            //we already have the body ...
313
        } else {
314
            $this->rawResponse = $response->getBody();
315
        }
316
317
        $data['httpResponse'] = $httpResponse;
318
319
        if (401 === $httpResponse) {
320
            $data['status'] = 'error';
321
            $data['content'] = 'Could not access: ' . $url;
322
323
            return json_encode($data);
324
        }
325
326
        //uncaught errors ...
327
        if ($this->rawResponse && 'Fatal error' === substr((string) $this->rawResponse, 0, 12)) {
328
            $data['status'] = 'error';
329
            $data['content'] = $this->rawResponse;
330
        } elseif (200 === $httpResponse && $this->rawResponse && strlen( (string) $this->rawResponse) < 200) {
331
            if (! $this->isJson($this->rawResponse)) {
332
                $data['status'] = 'error - no response';
333
                $data['content'] = 'SHORT RESPONSE: ' . $this->rawResponse;
334
            }
335
        }
336
337
        $data['w3Content'] = 'n/a';
338
339
        if (200 !== $httpResponse) {
340
            $data['status'] = 'error';
341
            $data['content'] .= 'unexpected response: ' . $error . $this->rawResponse;
342
        } else {
343
            $this->isSuccess = true;
344
            $data['type'] = $response->getHeaders()['Content-Type'][0] ?? 'no-content-type';
345
            $data['length'] = $response->getHeaders()['Content-Length'][0] ?? '0';
346
            if ($validate) {
347
                $w3Obj = new W3cValidateApi();
348
                $data['w3Content'] = $w3Obj->W3Validate('', $this->rawResponse);
349
            }
350
        }
351
352
        if (Director::is_ajax()) {
353
            return json_encode($data);
354
        }
355
356
        $content = '';
357
        $content .= '<p><strong>URL:</strong> ' . $url . '</p>';
358
        $content .= '<p><strong>Status:</strong> ' . $data['status'] . '</p>';
359
        $content .= '<p><strong>HTTP response:</strong> ' . $data['httpResponse'] . '</p>';
360
        $content .= '<p><strong>Content:</strong> ' . htmlspecialchars($data['content']) . '</p>';
361
        $content .= '<p><strong>Response time:</strong> ' . $data['responseTime'] . '</p>';
362
        $content .= '<p><strong>Type:</strong> ' . $data['type'] . '</p>';
363
364
        return $content . ('<p><strong>W3 Content:</strong> ' . $data['w3Content'] . '</p>');
365
    }
366
367
    /**
368
     * creates the basic curl.
369
     */
370
    private function guzzleSetup()
371
    {
372
        // $user_agent='Mozilla/5.0 (Windows NT 6.1; rv:8.0) Gecko/20100101 Firefox/8.0';
373
        // $post = $type == "GET" ? false : true;
374
        //
375
        // $strCookie = 'PHPSESSID=' . session_id() . '; path=/';
376
        // $options = array(
377
        //     CURLOPT_CUSTOMREQUEST  => $type,        //set request type post or get
378
        //     CURLOPT_POST           => $post,        //set to GET
379
        //     CURLOPT_USERAGENT      => $user_agent, //set user agent
380
        //     CURLOPT_COOKIE         => $strCookie, //set cookie file
381
        //     CURLOPT_COOKIEFILE     => "cookie.txt", //set cookie file
382
        //     CURLOPT_COOKIEJAR      => "cookie.txt", //set cookie jar
383
        //     CURLOPT_RETURNTRANSFER => true,     // return web page
384
        //     CURLOPT_HEADER         => false,    // don't return headers
385
        //     CURLOPT_FOLLOWLOCATION => true,     // follow redirects
386
        //     CURLOPT_ENCODING       => "",       // handle all encodings
387
        //     CURLOPT_AUTOREFERER    => true,     // set referer on redirect
388
        //     CURLOPT_CONNECTTIMEOUT => 120,      // timeout on connect
389
        //     CURLOPT_TIMEOUT        => 120,      // timeout on response
390
        //     CURLOPT_MAXREDIRS      => 10,       // stop after 10 redirects
391
        // );
392
        //
393
        // $this->ch = curl_init();
394
        //
395
        // curl_setopt_array($this->ch, $options);
396
397
        $this->guzzleCookieJar = new CookieJar();
398
        $this->guzzleClient = new Client(
399
            [
400
                'base_uri' => Director::baseURL(),
401
                'cookies' => true,
402
            ]
403
        );
404
    }
405
406
    private function deleteUser()
407
    {
408
        /** @var null|bool $isAdmin */
409
        $isAdmin = Config::inst()->get(self::class, 'use_default_admin');
410
        if ($isAdmin) {
411
            //do nothing;
412
        } else {
413
            $this->member->delete();
414
        }
415
    }
416
417
    // private function debugme($lineNumber, $variable = "")
418
    // {
419
    //     if ($this->debug) {
420
    //         echo "<br />" . $lineNumber . ": " . round(memory_get_usage() / 1048576) . "MB" . "=====" . print_r($variable, 1);
421
    //         ob_flush();
422
    //         flush();
423
    //     }
424
    // }
425
}
426