Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Unit tests for /lib/filelib.php.
19
 *
20
 * @package   core
21
 * @category  test
22
 * @copyright 2009 Jerome Mouneyrac
23
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24
 */
25
 
26
namespace core;
27
 
28
use core_filetypes;
29
use curl;
30
use repository;
31
 
32
defined('MOODLE_INTERNAL') || die();
33
 
34
global $CFG;
35
require_once($CFG->libdir . '/filelib.php');
36
require_once($CFG->dirroot . '/repository/lib.php');
37
 
38
/**
39
 * Unit tests for /lib/filelib.php.
40
 *
41
 * @package   core
42
 * @category  test
43
 * @copyright 2009 Jerome Mouneyrac
44
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
45
 */
46
class filelib_test extends \advanced_testcase {
11 efrain 47
    public function test_format_postdata_for_curlcall(): void {
1 efrain 48
 
49
        // POST params with just simple types.
50
        $postdatatoconvert = array( 'userid' => 1, 'roleid' => 22, 'name' => 'john');
51
        $expectedresult = "userid=1&roleid=22&name=john";
52
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
53
        $this->assertEquals($expectedresult, $postdata);
54
 
55
        // POST params with a string containing & character.
56
        $postdatatoconvert = array( 'name' => 'john&emilie', 'roleid' => 22);
57
        $expectedresult = "name=john%26emilie&roleid=22"; // Urlencode: '%26' => '&'.
58
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
59
        $this->assertEquals($expectedresult, $postdata);
60
 
61
        // POST params with an empty value.
62
        $postdatatoconvert = array( 'name' => null, 'roleid' => 22);
63
        $expectedresult = "name=&roleid=22";
64
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
65
        $this->assertEquals($expectedresult, $postdata);
66
 
67
        // POST params with complex types.
68
        $postdatatoconvert = array( 'users' => array(
69
            array(
70
                'id' => 2,
71
                'customfields' => array(
72
                    array
73
                    (
74
                        'type' => 'Color',
75
                        'value' => 'violet'
76
                    )
77
                )
78
            )
79
        )
80
        );
81
        $expectedresult = "users[0][id]=2&users[0][customfields][0][type]=Color&users[0][customfields][0][value]=violet";
82
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
83
        $this->assertEquals($expectedresult, $postdata);
84
 
85
        // POST params with other complex types.
86
        $postdatatoconvert = array ('members' =>
87
        array(
88
            array('groupid' => 1, 'userid' => 1)
89
        , array('groupid' => 1, 'userid' => 2)
90
        )
91
        );
92
        $expectedresult = "members[0][groupid]=1&members[0][userid]=1&members[1][groupid]=1&members[1][userid]=2";
93
        $postdata = format_postdata_for_curlcall($postdatatoconvert);
94
        $this->assertEquals($expectedresult, $postdata);
95
    }
96
 
11 efrain 97
    public function test_download_file_content(): void {
1 efrain 98
        global $CFG;
99
 
100
        // Test http success first.
101
        $testhtml = $this->getExternalTestFileUrl('/test.html');
102
 
103
        $contents = download_file_content($testhtml);
104
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
105
 
106
        $tofile = "$CFG->tempdir/test.html";
107
        @unlink($tofile);
108
        $result = download_file_content($testhtml, null, null, false, 300, 20, false, $tofile);
109
        $this->assertTrue($result);
110
        $this->assertFileExists($tofile);
111
        $this->assertSame(file_get_contents($tofile), $contents);
112
        @unlink($tofile);
113
 
114
        $result = download_file_content($testhtml, null, null, false, 300, 20, false, null, true);
115
        $this->assertSame($contents, $result);
116
 
117
        $response = download_file_content($testhtml, null, null, true);
118
        $this->assertInstanceOf('stdClass', $response);
119
        $this->assertSame('200', $response->status);
120
        $this->assertTrue(is_array($response->headers));
121
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
122
        $this->assertSame($contents, $response->results);
123
        $this->assertSame('', $response->error);
124
 
125
        // Test https success.
126
        $testhtml = $this->getExternalTestFileUrl('/test.html', true);
127
 
128
        $contents = download_file_content($testhtml, null, null, false, 300, 20, true);
129
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
130
 
131
        $contents = download_file_content($testhtml);
132
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
133
 
134
        // Now 404.
135
        $testhtml = $this->getExternalTestFileUrl('/test.html_nonexistent');
136
 
137
        $contents = download_file_content($testhtml);
138
        $this->assertFalse($contents);
139
        $this->assertDebuggingCalled();
140
 
141
        $response = download_file_content($testhtml, null, null, true);
142
        $this->assertInstanceOf('stdClass', $response);
143
        $this->assertSame('404', $response->status);
144
        $this->assertTrue(is_array($response->headers));
145
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 404 Not Found$|', rtrim($response->response_code));
146
        // Do not test the response starts with DOCTYPE here because some servers may return different headers.
147
        $this->assertSame('', $response->error);
148
 
149
        // Invalid url.
150
        $testhtml = $this->getExternalTestFileUrl('/test.html');
151
        $testhtml = str_replace('http://', 'ftp://', $testhtml);
152
 
153
        $contents = download_file_content($testhtml);
154
        $this->assertFalse($contents);
155
 
156
        // Test standard redirects.
157
        $testurl = $this->getExternalTestFileUrl('/test_redir.php');
158
 
159
        $contents = download_file_content("$testurl?redir=2");
160
        $this->assertSame('done', $contents);
161
 
162
        $contents = download_file_content("$testurl?redir=2&verbose=1");
163
        $this->assertSame('done', $contents);
164
 
165
        $response = download_file_content("$testurl?redir=2", null, null, true);
166
        $this->assertInstanceOf('stdClass', $response);
167
        $this->assertSame('200', $response->status);
168
        $this->assertTrue(is_array($response->headers));
169
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
170
        $this->assertSame('done', $response->results);
171
        $this->assertSame('', $response->error);
172
 
173
        $response = download_file_content("$testurl?redir=2&verbose=1", null, null, true);
174
        $this->assertInstanceOf('stdClass', $response);
175
        $this->assertSame('200', $response->status);
176
        $this->assertTrue(is_array($response->headers));
177
        $this->assertMatchesRegularExpression('|^HTTP/1\.[01] 200 OK$|', rtrim($response->response_code));
178
        $this->assertSame('done', $response->results);
179
        $this->assertSame('', $response->error);
180
 
181
        // Commented out this block if there are performance problems.
182
        /*
183
        $contents = download_file_content("$testurl?redir=6");
184
        $this->assertFalse(false, $contents);
185
        $this->assertDebuggingCalled();
186
        $response = download_file_content("$testurl?redir=6", null, null, true);
187
        $this->assertInstanceOf('stdClass', $response);
188
        $this->assertSame('0', $response->status);
189
        $this->assertTrue(is_array($response->headers));
190
        $this->assertFalse($response->results);
191
        $this->assertNotEmpty($response->error);
192
        */
193
 
194
        // Test relative redirects.
195
        $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
196
 
197
        $contents = download_file_content("$testurl");
198
        $this->assertSame('done', $contents);
199
 
200
        $contents = download_file_content("$testurl?unused=xxx");
201
        $this->assertSame('done', $contents);
202
    }
203
 
204
    /**
205
     * Test curl basics.
206
     */
11 efrain 207
    public function test_curl_basics(): void {
1 efrain 208
        global $CFG;
209
 
210
        // Test HTTP success.
211
        $testhtml = $this->getExternalTestFileUrl('/test.html');
212
 
213
        $curl = new \curl();
214
        $contents = $curl->get($testhtml);
215
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
216
        $this->assertSame(0, $curl->get_errno());
217
 
218
        $curl = new \curl();
219
        $tofile = "$CFG->tempdir/test.html";
220
        @unlink($tofile);
221
        $fp = fopen($tofile, 'w');
222
        $result = $curl->get($testhtml, array(), array('CURLOPT_FILE'=>$fp));
223
        $this->assertTrue($result);
224
        fclose($fp);
225
        $this->assertFileExists($tofile);
226
        $this->assertSame($contents, file_get_contents($tofile));
227
        @unlink($tofile);
228
 
229
        $curl = new \curl();
230
        $tofile = "$CFG->tempdir/test.html";
231
        @unlink($tofile);
232
        $result = $curl->download_one($testhtml, array(), array('filepath'=>$tofile));
233
        $this->assertTrue($result);
234
        $this->assertFileExists($tofile);
235
        $this->assertSame($contents, file_get_contents($tofile));
236
        @unlink($tofile);
237
 
238
        // Test 404 request.
239
        $curl = new \curl();
240
        $contents = $curl->get($this->getExternalTestFileUrl('/i.do.not.exist'));
241
        $response = $curl->getResponse();
242
        $this->assertSame('404 Not Found', reset($response));
243
        $this->assertSame(0, $curl->get_errno());
244
    }
245
 
246
    /**
247
     * Test a curl basic request with security enabled.
248
     */
11 efrain 249
    public function test_curl_basics_with_security_helper(): void {
1 efrain 250
        global $USER;
251
 
252
        $this->resetAfterTest();
253
 
254
        $sink = $this->redirectEvents();
255
 
256
        // Test a request with a basic hostname filter applied.
257
        $testhtml = $this->getExternalTestFileUrl('/test.html');
258
        $url = new \moodle_url($testhtml);
259
        $host = $url->get_host();
260
        set_config('curlsecurityblockedhosts', $host); // Blocks $host.
261
 
262
        // Create curl with the default security enabled. We expect this to be blocked.
263
        $curl = new \curl();
264
        $contents = $curl->get($testhtml);
265
        $expected = $curl->get_security()->get_blocked_url_string();
266
        $this->assertSame($expected, $contents);
267
        $this->assertSame(0, $curl->get_errno());
268
        $this->assertDebuggingCalled(
269
            "Blocked $testhtml: The URL is blocked. [user {$USER->id}]", DEBUG_NONE);
270
 
271
        $events = $sink->get_events();
272
        $this->assertCount(1, $events);
273
        $event = reset($events);
274
 
275
        $this->assertEquals('\core\event\url_blocked', $event->eventname);
276
        $this->assertEquals("Blocked $testhtml: $expected", $event->get_description());
277
        $this->assertEquals(\context_system::instance(), $event->get_context());
278
        $this->assertEquals($testhtml, $event->other['url']);
279
        $this->assertEventContextNotUsed($event);
280
 
281
        // Now, create a curl using the 'ignoresecurity' override.
282
        // We expect this request to pass, despite the admin setting having been set earlier.
283
        $curl = new \curl(['ignoresecurity' => true]);
284
        $contents = $curl->get($testhtml);
285
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
286
        $this->assertSame(0, $curl->get_errno());
287
 
288
        $events = $sink->get_events();
289
        $this->assertCount(1, $events);
290
 
291
        // Now, try injecting a mock security helper into curl. This will override the default helper.
292
        $mockhelper = $this->getMockBuilder('\core\files\curl_security_helper')->getMock();
293
 
294
        // Make the mock return a different string.
295
        $mockhelper->expects($this->any())->method('get_blocked_url_string')->will($this->returnValue('You shall not pass'));
296
 
297
        // And make the mock security helper block all URLs. This helper instance doesn't care about config.
298
        $mockhelper->expects($this->any())->method('url_is_blocked')->will($this->returnValue(true));
299
 
300
        $curl = new \curl(['securityhelper' => $mockhelper]);
301
        $contents = $curl->get($testhtml);
302
        $this->assertSame('You shall not pass', $curl->get_security()->get_blocked_url_string());
303
        $this->assertSame($curl->get_security()->get_blocked_url_string(), $contents);
304
        $this->assertDebuggingCalled();
305
 
306
        $events = $sink->get_events();
307
        $this->assertCount(2, $events);
308
    }
309
 
11 efrain 310
    public function test_curl_redirects(): void {
1 efrain 311
        global $CFG;
312
 
313
        $testurl = $this->getExternalTestFileUrl('/test_redir.php');
314
 
315
        $curl = new \curl();
316
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
317
        $response = $curl->getResponse();
318
        $this->assertSame('200 OK', reset($response));
319
        $this->assertSame(0, $curl->get_errno());
320
        $this->assertSame(2, $curl->info['redirect_count']);
321
        $this->assertSame('done', $contents);
322
 
323
        // All redirects are emulated now. Enabling "emulateredirects" explicitly does not have effect.
324
        $curl = new \curl();
325
        $curl->emulateredirects = true;
326
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>2));
327
        $response = $curl->getResponse();
328
        $this->assertSame('200 OK', reset($response));
329
        $this->assertSame(0, $curl->get_errno());
330
        $this->assertSame(2, $curl->info['redirect_count']);
331
        $this->assertSame('done', $contents);
332
 
333
        // All redirects are emulated now. Attempting to disable "emulateredirects" explicitly causes warning.
334
        $curl = new \curl();
335
        $curl->emulateredirects = false;
336
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS' => 2));
337
        $response = $curl->getResponse();
338
        $this->assertDebuggingCalled('Attempting to disable emulated redirects has no effect any more!');
339
        $this->assertSame('200 OK', reset($response));
340
        $this->assertSame(0, $curl->get_errno());
341
        $this->assertSame(2, $curl->info['redirect_count']);
342
        $this->assertSame('done', $contents);
343
 
344
        // This test was failing for people behind Squid proxies. Squid does not
345
        // fully support HTTP 1.1, so converts things to HTTP 1.0, where the name
346
        // of the status code is different.
347
        reset($response);
348
        if (key($response) === 'HTTP/1.0') {
349
            $responsecode302 = '302 Moved Temporarily';
350
        } else {
351
            $responsecode302 = '302 Found';
352
        }
353
 
354
        $curl = new \curl();
355
        $contents = $curl->get("$testurl?redir=3", array(), array('CURLOPT_FOLLOWLOCATION'=>0));
356
        $response = $curl->getResponse();
357
        $this->assertSame($responsecode302, reset($response));
358
        $this->assertSame(0, $curl->get_errno());
359
        $this->assertSame(302, $curl->info['http_code']);
360
        $this->assertSame('', $contents);
361
 
362
        $curl = new \curl();
363
        $contents = $curl->get("$testurl?redir=2", array(), array('CURLOPT_MAXREDIRS'=>1));
364
        $this->assertSame(CURLE_TOO_MANY_REDIRECTS, $curl->get_errno());
365
        $this->assertNotEmpty($contents);
366
 
367
        $curl = new \curl();
368
        $tofile = "$CFG->tempdir/test.html";
369
        @unlink($tofile);
370
        $fp = fopen($tofile, 'w');
371
        $result = $curl->get("$testurl?redir=1", array(), array('CURLOPT_FILE'=>$fp));
372
        $this->assertTrue($result);
373
        fclose($fp);
374
        $this->assertFileExists($tofile);
375
        $this->assertSame('done', file_get_contents($tofile));
376
        @unlink($tofile);
377
 
378
        $curl = new \curl();
379
        $tofile = "$CFG->tempdir/test.html";
380
        @unlink($tofile);
381
        $fp = fopen($tofile, 'w');
382
        $result = $curl->get("$testurl?redir=1&verbose=1", array(), array('CURLOPT_FILE' => $fp));
383
        $this->assertTrue($result);
384
        fclose($fp);
385
        $this->assertFileExists($tofile);
386
        $this->assertSame('done', file_get_contents($tofile));
387
        @unlink($tofile);
388
 
389
        $curl = new \curl();
390
        $tofile = "$CFG->tempdir/test.html";
391
        @unlink($tofile);
392
        $result = $curl->download_one("$testurl?redir=1", array(), array('filepath'=>$tofile));
393
        $this->assertTrue($result);
394
        $this->assertFileExists($tofile);
395
        $this->assertSame('done', file_get_contents($tofile));
396
        @unlink($tofile);
397
 
398
        $curl = new \curl();
399
        $tofile = "$CFG->tempdir/test.html";
400
        @unlink($tofile);
401
        $result = $curl->download_one("$testurl?redir=1&verbose=1", array(), array('filepath' => $tofile));
402
        $this->assertTrue($result);
403
        $this->assertFileExists($tofile);
404
        $this->assertSame('done', file_get_contents($tofile));
405
        @unlink($tofile);
406
    }
407
 
408
    /**
409
     * Test that redirects to blocked hosts are blocked.
410
     */
11 efrain 411
    public function test_curl_blocked_redirect(): void {
1 efrain 412
        $this->resetAfterTest();
413
 
414
        $testurl = $this->getExternalTestFileUrl('/test_redir.php');
415
 
416
        // Block a host.
417
        // Note: moodle.com is the URL redirected to when test_redir.php has the param extdest=1 set.
418
        set_config('curlsecurityblockedhosts', 'moodle.com');
419
 
420
        // Redirecting to a non-blocked host should resolve.
421
        $curl = new \curl();
422
        $contents = $curl->get("{$testurl}?redir=2");
423
        $response = $curl->getResponse();
424
        $this->assertSame('200 OK', reset($response));
425
        $this->assertSame(0, $curl->get_errno());
426
 
427
        // Redirecting to the blocked host should fail.
428
        $curl = new \curl();
429
        $blockedstring = $curl->get_security()->get_blocked_url_string();
430
        $contents = $curl->get("{$testurl}?redir=1&extdest=1");
431
        $this->assertSame($blockedstring, $contents);
432
        $this->assertSame(0, $curl->get_errno());
433
        $this->assertDebuggingCalled();
434
 
435
        // Redirecting to the blocked host after multiple successful redirects should also fail.
436
        $curl = new \curl();
437
        $contents = $curl->get("{$testurl}?redir=3&extdest=1");
438
        $this->assertSame($blockedstring, $contents);
439
        $this->assertSame(0, $curl->get_errno());
440
        $this->assertDebuggingCalled();
441
    }
442
 
11 efrain 443
    public function test_curl_relative_redirects(): void {
1 efrain 444
        // Test relative location redirects.
445
        $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
446
 
447
        $curl = new \curl();
448
        $contents = $curl->get($testurl);
449
        $response = $curl->getResponse();
450
        $this->assertSame('200 OK', reset($response));
451
        $this->assertSame(0, $curl->get_errno());
452
        $this->assertSame(1, $curl->info['redirect_count']);
453
        $this->assertSame('done', $contents);
454
 
455
        // Test different redirect types.
456
        $testurl = $this->getExternalTestFileUrl('/test_relative_redir.php');
457
 
458
        $curl = new \curl();
459
        $contents = $curl->get("$testurl?type=301");
460
        $response = $curl->getResponse();
461
        $this->assertSame('200 OK', reset($response));
462
        $this->assertSame(0, $curl->get_errno());
463
        $this->assertSame(1, $curl->info['redirect_count']);
464
        $this->assertSame('done', $contents);
465
 
466
        $curl = new \curl();
467
        $contents = $curl->get("$testurl?type=302");
468
        $response = $curl->getResponse();
469
        $this->assertSame('200 OK', reset($response));
470
        $this->assertSame(0, $curl->get_errno());
471
        $this->assertSame(1, $curl->info['redirect_count']);
472
        $this->assertSame('done', $contents);
473
 
474
        $curl = new \curl();
475
        $contents = $curl->get("$testurl?type=303");
476
        $response = $curl->getResponse();
477
        $this->assertSame('200 OK', reset($response));
478
        $this->assertSame(0, $curl->get_errno());
479
        $this->assertSame(1, $curl->info['redirect_count']);
480
        $this->assertSame('done', $contents);
481
 
482
        $curl = new \curl();
483
        $contents = $curl->get("$testurl?type=307");
484
        $response = $curl->getResponse();
485
        $this->assertSame('200 OK', reset($response));
486
        $this->assertSame(0, $curl->get_errno());
487
        $this->assertSame(1, $curl->info['redirect_count']);
488
        $this->assertSame('done', $contents);
489
 
490
        $curl = new \curl();
491
        $contents = $curl->get("$testurl?type=308");
492
        $response = $curl->getResponse();
493
        $this->assertSame('200 OK', reset($response));
494
        $this->assertSame(0, $curl->get_errno());
495
        $this->assertSame(1, $curl->info['redirect_count']);
496
        $this->assertSame('done', $contents);
497
    }
498
 
11 efrain 499
    public function test_curl_proxybypass(): void {
1 efrain 500
        global $CFG;
501
        $testurl = $this->getExternalTestFileUrl('/test.html');
502
 
503
        $oldproxy = $CFG->proxyhost;
504
        $oldproxybypass = $CFG->proxybypass;
505
 
506
        // Test without proxy bypass and inaccessible proxy.
507
        $CFG->proxyhost = 'i.do.not.exist';
508
        $CFG->proxybypass = '';
509
        $curl = new \curl();
510
        $contents = $curl->get($testurl);
511
        $this->assertNotEquals(0, $curl->get_errno());
512
        $this->assertNotEquals('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
513
 
514
        // Test with proxy bypass.
515
        $testurlhost = parse_url($testurl, PHP_URL_HOST);
516
        $CFG->proxybypass = $testurlhost;
517
        $curl = new \curl();
518
        $contents = $curl->get($testurl);
519
        $this->assertSame(0, $curl->get_errno());
520
        $this->assertSame('47250a973d1b88d9445f94db4ef2c97a', md5($contents));
521
 
522
        $CFG->proxyhost = $oldproxy;
523
        $CFG->proxybypass = $oldproxybypass;
524
    }
525
 
526
    /**
527
     * Test that duplicate lines in the curl header are removed.
528
     */
11 efrain 529
    public function test_duplicate_curl_header(): void {
1 efrain 530
        $testurl = $this->getExternalTestFileUrl('/test_post.php');
531
 
532
        $curl = new \curl();
533
        $headerdata = 'Accept: application/json';
534
        $header = [$headerdata, $headerdata];
535
        $this->assertCount(2, $header);
536
        $curl->setHeader($header);
537
        $this->assertCount(1, $curl->header);
538
        $this->assertEquals($headerdata, $curl->header[0]);
539
    }
540
 
11 efrain 541
    public function test_curl_post(): void {
1 efrain 542
        $testurl = $this->getExternalTestFileUrl('/test_post.php');
543
 
544
        // Test post request.
545
        $curl = new \curl();
546
        $contents = $curl->post($testurl, 'data=moodletest');
547
        $response = $curl->getResponse();
548
        $this->assertSame('200 OK', reset($response));
549
        $this->assertSame(0, $curl->get_errno());
550
        $this->assertSame('OK', $contents);
551
 
552
        // Test 100 requests.
553
        $curl = new \curl();
554
        $curl->setHeader('Expect: 100-continue');
555
        $contents = $curl->post($testurl, 'data=moodletest');
556
        $response = $curl->getResponse();
557
        $this->assertSame('200 OK', reset($response));
558
        $this->assertSame(0, $curl->get_errno());
559
        $this->assertSame('OK', $contents);
560
    }
561
 
11 efrain 562
    public function test_curl_file(): void {
1 efrain 563
        $this->resetAfterTest();
564
        $testurl = $this->getExternalTestFileUrl('/test_file.php');
565
 
566
        $fs = get_file_storage();
567
        $filerecord = array(
568
            'contextid' => \context_system::instance()->id,
569
            'component' => 'test',
570
            'filearea' => 'curl_post',
571
            'itemid' => 0,
572
            'filepath' => '/',
573
            'filename' => 'test.txt'
574
        );
575
        $teststring = 'moodletest';
576
        $testfile = $fs->create_file_from_string($filerecord, $teststring);
577
 
578
        // Test post with file.
579
        $data = array('testfile' => $testfile);
580
        $curl = new \curl();
581
        $contents = $curl->post($testurl, $data);
582
        $this->assertSame('OK', $contents);
583
    }
584
 
11 efrain 585
    public function test_curl_file_name(): void {
1 efrain 586
        $this->resetAfterTest();
587
        $testurl = $this->getExternalTestFileUrl('/test_file_name.php');
588
 
589
        $fs = get_file_storage();
590
        $filerecord = array(
591
            'contextid' => \context_system::instance()->id,
592
            'component' => 'test',
593
            'filearea' => 'curl_post',
594
            'itemid' => 0,
595
            'filepath' => '/',
596
            'filename' => 'test.txt'
597
        );
598
        $teststring = 'moodletest';
599
        $testfile = $fs->create_file_from_string($filerecord, $teststring);
600
 
601
        // Test post with file.
602
        $data = array('testfile' => $testfile);
603
        $curl = new \curl();
604
        $contents = $curl->post($testurl, $data);
605
        $this->assertSame('OK', $contents);
606
    }
607
 
11 efrain 608
    public function test_curl_protocols(): void {
1 efrain 609
 
610
        // HTTP and HTTPS requests were verified in previous requests. Now check
611
        // that we can selectively disable some protocols.
612
        $curl = new \curl();
613
 
614
        // Other protocols than HTTP(S) are disabled by default.
615
        $testurl = 'file:///';
616
        $curl->get($testurl);
617
        $this->assertNotEmpty($curl->error);
618
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
619
 
620
        $testurl = 'ftp://nowhere';
621
        $curl->get($testurl);
622
        $this->assertNotEmpty($curl->error);
623
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
624
 
625
        $testurl = 'telnet://somewhere';
626
        $curl->get($testurl);
627
        $this->assertNotEmpty($curl->error);
628
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
629
 
630
        // Protocols are also disabled during redirections.
631
        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
632
        $curl->get($testurl, array('proto' => 'file'));
633
        $this->assertNotEmpty($curl->error);
634
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
635
 
636
        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
637
        $curl->get($testurl, array('proto' => 'ftp'));
638
        $this->assertNotEmpty($curl->error);
639
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
640
 
641
        $testurl = $this->getExternalTestFileUrl('/test_redir_proto.php');
642
        $curl->get($testurl, array('proto' => 'telnet'));
643
        $this->assertNotEmpty($curl->error);
644
        $this->assertEquals(CURLE_UNSUPPORTED_PROTOCOL, $curl->errno);
645
    }
646
 
647
    /**
648
     * Testing prepare draft area
649
     *
650
     * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
651
     * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
652
     */
11 efrain 653
    public function test_prepare_draft_area(): void {
1 efrain 654
        global $USER, $DB;
655
 
656
        $this->resetAfterTest(true);
657
 
658
        $generator = $this->getDataGenerator();
659
        $user = $generator->create_user();
660
        $usercontext = \context_user::instance($user->id);
661
        $USER = $DB->get_record('user', array('id'=>$user->id));
662
 
663
        $repositorypluginname = 'user';
664
 
665
        $args = array();
666
        $args['type'] = $repositorypluginname;
667
        $repos = repository::get_instances($args);
668
        $userrepository = reset($repos);
669
        $this->assertInstanceOf('repository', $userrepository);
670
 
671
        $fs = get_file_storage();
672
 
673
        $syscontext = \context_system::instance();
674
        $component = 'core';
675
        $filearea  = 'unittest';
676
        $itemid    = 0;
677
        $filepath  = '/';
678
        $filename  = 'test.txt';
679
        $sourcefield = 'Copyright stuff';
680
 
681
        $filerecord = array(
682
            'contextid' => $syscontext->id,
683
            'component' => $component,
684
            'filearea'  => $filearea,
685
            'itemid'    => $itemid,
686
            'filepath'  => $filepath,
687
            'filename'  => $filename,
688
            'source'    => $sourcefield,
689
        );
690
        $ref = $fs->pack_reference($filerecord);
691
        $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
692
        $fileid = $originalfile->get_id();
693
        $this->assertInstanceOf('stored_file', $originalfile);
694
 
695
        // Create a user private file.
696
        $userfilerecord = new \stdClass;
697
        $userfilerecord->contextid = $usercontext->id;
698
        $userfilerecord->component = 'user';
699
        $userfilerecord->filearea  = 'private';
700
        $userfilerecord->itemid    = 0;
701
        $userfilerecord->filepath  = '/';
702
        $userfilerecord->filename  = 'userfile.txt';
703
        $userfilerecord->source    = 'test';
704
        $userfile = $fs->create_file_from_string($userfilerecord, 'User file content');
705
        $userfileref = $fs->pack_reference($userfilerecord);
706
 
707
        $filerefrecord = clone((object)$filerecord);
708
        $filerefrecord->filename = 'testref.txt';
709
 
710
        // Create a file reference.
711
        $fileref = $fs->create_file_from_reference($filerefrecord, $userrepository->id, $userfileref);
712
        $this->assertInstanceOf('stored_file', $fileref);
713
        $this->assertEquals($userrepository->id, $fileref->get_repository_id());
714
        $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
715
        $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
716
        $this->assertMatchesRegularExpression('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());
717
 
718
        $draftitemid = 0;
719
        file_prepare_draft_area($draftitemid, $syscontext->id, $component, $filearea, $itemid);
720
 
721
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
722
        $this->assertCount(3, $draftfiles);
723
 
724
        $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filename);
725
        $source = unserialize($draftfile->get_source());
726
        $this->assertSame($ref, $source->original);
727
        $this->assertSame($sourcefield, $source->source);
728
 
729
        $draftfileref = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $filepath, $filerefrecord->filename);
730
        $this->assertInstanceOf('stored_file', $draftfileref);
731
        $this->assertTrue($draftfileref->is_external_file());
732
 
733
        // Change some information.
734
        $author = 'Dongsheng Cai';
735
        $draftfile->set_author($author);
736
        $newsourcefield = 'Get from Flickr';
737
        $license = 'GPLv3';
738
        $draftfile->set_license($license);
739
        // If you want to really just change source field, do this.
740
        $source = unserialize($draftfile->get_source());
741
        $newsourcefield = 'From flickr';
742
        $source->source = $newsourcefield;
743
        $draftfile->set_source(serialize($source));
744
 
745
        // Save changed file.
746
        file_save_draft_area_files($draftitemid, $syscontext->id, $component, $filearea, $itemid);
747
 
748
        $file = $fs->get_file($syscontext->id, $component, $filearea, $itemid, $filepath, $filename);
749
 
750
        // Make sure it's the original file id.
751
        $this->assertEquals($fileid, $file->get_id());
752
        $this->assertInstanceOf('stored_file', $file);
753
        $this->assertSame($author, $file->get_author());
754
        $this->assertSame($license, $file->get_license());
755
        $this->assertEquals($newsourcefield, $file->get_source());
756
    }
757
 
758
    /**
759
     * Testing deleting original files.
760
     *
761
     * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
762
     * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
763
     */
11 efrain 764
    public function test_delete_original_file_from_draft(): void {
1 efrain 765
        global $USER, $DB;
766
 
767
        $this->resetAfterTest(true);
768
 
769
        $generator = $this->getDataGenerator();
770
        $user = $generator->create_user();
771
        $usercontext = \context_user::instance($user->id);
772
        $USER = $DB->get_record('user', array('id'=>$user->id));
773
 
774
        $repositorypluginname = 'user';
775
 
776
        $args = array();
777
        $args['type'] = $repositorypluginname;
778
        $repos = repository::get_instances($args);
779
        $userrepository = reset($repos);
780
        $this->assertInstanceOf('repository', $userrepository);
781
 
782
        $fs = get_file_storage();
783
        $syscontext = \context_system::instance();
784
 
785
        $filecontent = 'User file content';
786
 
787
        // Create a user private file.
788
        $userfilerecord = new \stdClass;
789
        $userfilerecord->contextid = $usercontext->id;
790
        $userfilerecord->component = 'user';
791
        $userfilerecord->filearea  = 'private';
792
        $userfilerecord->itemid    = 0;
793
        $userfilerecord->filepath  = '/';
794
        $userfilerecord->filename  = 'userfile.txt';
795
        $userfilerecord->source    = 'test';
796
        $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
797
        $userfileref = $fs->pack_reference($userfilerecord);
798
        $contenthash = $userfile->get_contenthash();
799
 
800
        $filerecord = array(
801
            'contextid' => $syscontext->id,
802
            'component' => 'core',
803
            'filearea'  => 'phpunit',
804
            'itemid'    => 0,
805
            'filepath'  => '/',
806
            'filename'  => 'test.txt',
807
        );
808
        // Create a file reference.
809
        $fileref = $fs->create_file_from_reference($filerecord, $userrepository->id, $userfileref);
810
        $this->assertInstanceOf('stored_file', $fileref);
811
        $this->assertEquals($userrepository->id, $fileref->get_repository_id());
812
        $this->assertSame($userfile->get_contenthash(), $fileref->get_contenthash());
813
        $this->assertEquals($userfile->get_filesize(), $fileref->get_filesize());
814
        $this->assertMatchesRegularExpression('#' . $userfile->get_filename(). '$#', $fileref->get_reference_details());
815
 
816
        $draftitemid = 0;
817
        file_prepare_draft_area($draftitemid, $usercontext->id, 'user', 'private', 0);
818
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid);
819
        $this->assertCount(2, $draftfiles);
820
        $draftfile = $fs->get_file($usercontext->id, 'user', 'draft', $draftitemid, $userfilerecord->filepath, $userfilerecord->filename);
821
        $draftfile->delete();
822
        // Save changed file.
823
        file_save_draft_area_files($draftitemid, $usercontext->id, 'user', 'private', 0);
824
 
825
        // The file reference should be a regular moodle file now.
826
        $fileref = $fs->get_file($syscontext->id, 'core', 'phpunit', 0, '/', 'test.txt');
827
        $this->assertFalse($fileref->is_external_file());
828
        $this->assertSame($contenthash, $fileref->get_contenthash());
829
        $this->assertEquals($filecontent, $fileref->get_content());
830
    }
831
 
832
    /**
833
     * Test avoid file merging when working with draft areas.
834
     */
11 efrain 835
    public function test_ignore_file_merging_in_draft_area(): void {
1 efrain 836
        global $USER, $DB;
837
 
838
        $this->resetAfterTest(true);
839
 
840
        $generator = $this->getDataGenerator();
841
        $user = $generator->create_user();
842
        $usercontext = \context_user::instance($user->id);
843
        $USER = $DB->get_record('user', array('id' => $user->id));
844
 
845
        $repositorypluginname = 'user';
846
 
847
        $args = array();
848
        $args['type'] = $repositorypluginname;
849
        $repos = repository::get_instances($args);
850
        $userrepository = reset($repos);
851
        $this->assertInstanceOf('repository', $userrepository);
852
 
853
        $fs = get_file_storage();
854
        $syscontext = \context_system::instance();
855
 
856
        $filecontent = 'User file content';
857
 
858
        // Create a user private file.
859
        $userfilerecord = new \stdClass;
860
        $userfilerecord->contextid = $usercontext->id;
861
        $userfilerecord->component = 'user';
862
        $userfilerecord->filearea  = 'private';
863
        $userfilerecord->itemid    = 0;
864
        $userfilerecord->filepath  = '/';
865
        $userfilerecord->filename  = 'userfile.txt';
866
        $userfilerecord->source    = 'test';
867
        $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
868
        $userfileref = $fs->pack_reference($userfilerecord);
869
        $contenthash = $userfile->get_contenthash();
870
 
871
        $filerecord = array(
872
            'contextid' => $syscontext->id,
873
            'component' => 'core',
874
            'filearea'  => 'phpunit',
875
            'itemid'    => 0,
876
            'filepath'  => '/',
877
            'filename'  => 'test.txt',
878
        );
879
        // Create a file reference.
880
        $fileref = $fs->create_file_from_reference($filerecord, $userrepository->id, $userfileref);
881
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));    // 2 because includes the '.' file.
882
 
883
        // Save using empty draft item id, all files will be deleted.
884
        file_save_draft_area_files(0, $usercontext->id, 'user', 'private', 0);
885
        $this->assertCount(0, $fs->get_area_files($usercontext->id, 'user', 'private'));
886
 
887
        // Create a file again.
888
        $userfile = $fs->create_file_from_string($userfilerecord, $filecontent);
889
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
890
 
891
        // Save without merge.
892
        file_save_draft_area_files(IGNORE_FILE_MERGE, $usercontext->id, 'user', 'private', 0);
893
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
894
        // Save again, this time including some inline text.
895
        $inlinetext = 'Some text <img src="@@PLUGINFILE@@/file.png">';
896
        $text = file_save_draft_area_files(IGNORE_FILE_MERGE, $usercontext->id, 'user', 'private', 0, null, $inlinetext);
897
        $this->assertCount(2, $fs->get_area_files($usercontext->id, 'user', 'private'));
898
        $this->assertEquals($inlinetext, $text);
899
    }
900
 
901
    /**
902
     * Testing deleting file_save_draft_area_files won't accidentally wipe unintended files.
903
     */
11 efrain 904
    public function test_file_save_draft_area_files_itemid_cannot_be_false(): void {
1 efrain 905
        global $USER, $DB;
906
        $this->resetAfterTest();
907
 
908
        $generator = $this->getDataGenerator();
909
        $user = $generator->create_user();
910
        $usercontext = \context_user::instance($user->id);
911
        $USER = $DB->get_record('user', ['id' => $user->id]);
912
 
913
        $draftitemid = 0;
914
        file_prepare_draft_area($draftitemid, $usercontext->id, 'user', 'private', 0);
915
 
916
        // Call file_save_draft_area_files with itemid false - which could only happen due to a bug.
917
        // This should throw an exception.
918
        $this->expectExceptionMessage('file_save_draft_area_files was called with $itemid false. ' .
919
                'This suggests a bug, because it would wipe all (' . $usercontext->id . ', user, private) files.');
920
        file_save_draft_area_files($draftitemid, $usercontext->id, 'user', 'private', false);
921
    }
922
 
923
    /**
924
     * Tests the strip_double_headers function in the curl class.
925
     */
11 efrain 926
    public function test_curl_strip_double_headers(): void {
1 efrain 927
        // Example from issue tracker.
928
        $mdl30648example = <<<EOF
929
HTTP/1.0 407 Proxy Authentication Required
930
Server: squid/2.7.STABLE9
931
Date: Thu, 08 Dec 2011 14:44:33 GMT
932
Content-Type: text/html
933
Content-Length: 1275
934
X-Squid-Error: ERR_CACHE_ACCESS_DENIED 0
935
Proxy-Authenticate: Basic realm="Squid proxy-caching web server"
936
X-Cache: MISS from homer.lancs.ac.uk
937
X-Cache-Lookup: NONE from homer.lancs.ac.uk:3128
938
Via: 1.0 homer.lancs.ac.uk:3128 (squid/2.7.STABLE9)
939
Connection: close
940
 
941
HTTP/1.0 200 OK
942
Server: Apache
943
X-Lb-Nocache: true
944
Cache-Control: private, max-age=15, no-transform
945
ETag: "4d69af5d8ba873ea9192c489e151bd7b"
946
Content-Type: text/html
947
Date: Thu, 08 Dec 2011 14:44:53 GMT
948
Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
949
X-Cache-Action: MISS
950
X-Cache-Age: 0
951
Vary: Cookie,X-Country,X-Ip-is-uk-combined,X-Ip-is-advertise-combined,X-Ip_is_uk_combined,X-Ip_is_advertise_combined, X-GeoIP
952
X-Cache: MISS from ww
953
 
954
<html>...
955
EOF;
956
        $mdl30648expected = <<<EOF
957
HTTP/1.0 200 OK
958
Server: Apache
959
X-Lb-Nocache: true
960
Cache-Control: private, max-age=15, no-transform
961
ETag: "4d69af5d8ba873ea9192c489e151bd7b"
962
Content-Type: text/html
963
Date: Thu, 08 Dec 2011 14:44:53 GMT
964
Set-Cookie: BBC-UID=c4de2e109c8df6a51de627cee11b214bd4fb6054a030222488317afb31b343360MoodleBot/1.0; expires=Mon, 07-Dec-15 14:44:53 GMT; path=/; domain=bbc.co.uk
965
X-Cache-Action: MISS
966
X-Cache-Age: 0
967
Vary: Cookie,X-Country,X-Ip-is-uk-combined,X-Ip-is-advertise-combined,X-Ip_is_uk_combined,X-Ip_is_advertise_combined, X-GeoIP
968
X-Cache: MISS from ww
969
 
970
<html>...
971
EOF;
972
        // For HTTP, replace the \n with \r\n.
973
        $mdl30648example = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648example);
974
        $mdl30648expected = preg_replace("~(?!<\r)\n~", "\r\n", $mdl30648expected);
975
 
976
        // Test stripping works OK.
977
        $this->assertSame($mdl30648expected, \curl::strip_double_headers($mdl30648example));
978
        // Test it does nothing to the 'plain' data.
979
        $this->assertSame($mdl30648expected, \curl::strip_double_headers($mdl30648expected));
980
 
981
        // Example from OU proxy.
982
        $httpsexample = <<<EOF
983
HTTP/1.0 200 Connection established
984
 
985
HTTP/1.1 200 OK
986
Date: Fri, 22 Feb 2013 17:14:23 GMT
987
Server: Apache/2
988
X-Powered-By: PHP/5.3.3-7+squeeze14
989
Content-Type: text/xml
990
Connection: close
991
Content-Encoding: gzip
992
Transfer-Encoding: chunked
993
 
994
<?xml version="1.0" encoding="ISO-8859-1" ?>
995
<rss version="2.0">...
996
EOF;
997
        $httpsexpected = <<<EOF
998
HTTP/1.1 200 OK
999
Date: Fri, 22 Feb 2013 17:14:23 GMT
1000
Server: Apache/2
1001
X-Powered-By: PHP/5.3.3-7+squeeze14
1002
Content-Type: text/xml
1003
Connection: close
1004
Content-Encoding: gzip
1005
Transfer-Encoding: chunked
1006
 
1007
<?xml version="1.0" encoding="ISO-8859-1" ?>
1008
<rss version="2.0">...
1009
EOF;
1010
        // For HTTP, replace the \n with \r\n.
1011
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
1012
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);
1013
 
1014
        // Test stripping works OK.
1015
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
1016
        // Test it does nothing to the 'plain' data.
1017
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));
1018
 
1019
        $httpsexample = <<<EOF
1020
HTTP/1.0 200 Connection established
1021
 
1022
HTTP/2 200 OK
1023
Date: Fri, 22 Feb 2013 17:14:23 GMT
1024
Server: Apache/2
1025
X-Powered-By: PHP/5.3.3-7+squeeze14
1026
Content-Type: text/xml
1027
Connection: close
1028
Content-Encoding: gzip
1029
Transfer-Encoding: chunked
1030
 
1031
<?xml version="1.0" encoding="ISO-8859-1" ?>
1032
<rss version="2.0">...
1033
EOF;
1034
        $httpsexpected = <<<EOF
1035
HTTP/2 200 OK
1036
Date: Fri, 22 Feb 2013 17:14:23 GMT
1037
Server: Apache/2
1038
X-Powered-By: PHP/5.3.3-7+squeeze14
1039
Content-Type: text/xml
1040
Connection: close
1041
Content-Encoding: gzip
1042
Transfer-Encoding: chunked
1043
 
1044
<?xml version="1.0" encoding="ISO-8859-1" ?>
1045
<rss version="2.0">...
1046
EOF;
1047
        // For HTTP, replace the \n with \r\n.
1048
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
1049
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);
1050
 
1051
        // Test stripping works OK.
1052
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
1053
        // Test it does nothing to the 'plain' data.
1054
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));
1055
 
1056
        $httpsexample = <<<EOF
1057
HTTP/1.0 200 Connection established
1058
 
1059
HTTP/2.1 200 OK
1060
Date: Fri, 22 Feb 2013 17:14:23 GMT
1061
Server: Apache/2
1062
X-Powered-By: PHP/5.3.3-7+squeeze14
1063
Content-Type: text/xml
1064
Connection: close
1065
Content-Encoding: gzip
1066
Transfer-Encoding: chunked
1067
 
1068
<?xml version="1.0" encoding="ISO-8859-1" ?>
1069
<rss version="2.0">...
1070
EOF;
1071
        $httpsexpected = <<<EOF
1072
HTTP/2.1 200 OK
1073
Date: Fri, 22 Feb 2013 17:14:23 GMT
1074
Server: Apache/2
1075
X-Powered-By: PHP/5.3.3-7+squeeze14
1076
Content-Type: text/xml
1077
Connection: close
1078
Content-Encoding: gzip
1079
Transfer-Encoding: chunked
1080
 
1081
<?xml version="1.0" encoding="ISO-8859-1" ?>
1082
<rss version="2.0">...
1083
EOF;
1084
        // For HTTP, replace the \n with \r\n.
1085
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
1086
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);
1087
 
1088
        // Test stripping works OK.
1089
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
1090
        // Test it does nothing to the 'plain' data.
1091
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));
1092
 
1093
        $httpsexample = <<<EOF
1094
HTTP/1.1 200 Connection established
1095
 
1096
HTTP/3 200 OK
1097
Date: Fri, 22 Feb 2013 17:14:23 GMT
1098
Server: Apache/2
1099
X-Powered-By: PHP/5.3.3-7+squeeze14
1100
Content-Type: text/xml
1101
Connection: close
1102
Content-Encoding: gzip
1103
Transfer-Encoding: chunked
1104
 
1105
<?xml version="1.0" encoding="ISO-8859-1" ?>
1106
<rss version="2.0">...
1107
EOF;
1108
        $httpsexpected = <<<EOF
1109
HTTP/3 200 OK
1110
Date: Fri, 22 Feb 2013 17:14:23 GMT
1111
Server: Apache/2
1112
X-Powered-By: PHP/5.3.3-7+squeeze14
1113
Content-Type: text/xml
1114
Connection: close
1115
Content-Encoding: gzip
1116
Transfer-Encoding: chunked
1117
 
1118
<?xml version="1.0" encoding="ISO-8859-1" ?>
1119
<rss version="2.0">...
1120
EOF;
1121
        // For HTTP, replace the \n with \r\n.
1122
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
1123
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);
1124
 
1125
        // Test stripping works OK.
1126
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
1127
        // Test it does nothing to the 'plain' data.
1128
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));
1129
 
1130
        $httpsexample = <<<EOF
1131
HTTP/2 200 Connection established
1132
 
1133
HTTP/4 200 OK
1134
Date: Fri, 22 Feb 2013 17:14:23 GMT
1135
Server: Apache/2
1136
X-Powered-By: PHP/5.3.3-7+squeeze14
1137
Content-Type: text/xml
1138
Connection: close
1139
Content-Encoding: gzip
1140
Transfer-Encoding: chunked
1141
 
1142
<?xml version="1.0" encoding="ISO-8859-1" ?>
1143
<rss version="2.0">...
1144
EOF;
1145
        $httpsexpected = <<<EOF
1146
HTTP/4 200 OK
1147
Date: Fri, 22 Feb 2013 17:14:23 GMT
1148
Server: Apache/2
1149
X-Powered-By: PHP/5.3.3-7+squeeze14
1150
Content-Type: text/xml
1151
Connection: close
1152
Content-Encoding: gzip
1153
Transfer-Encoding: chunked
1154
 
1155
<?xml version="1.0" encoding="ISO-8859-1" ?>
1156
<rss version="2.0">...
1157
EOF;
1158
        // For HTTP, replace the \n with \r\n.
1159
        $httpsexample = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexample);
1160
        $httpsexpected = preg_replace("~(?!<\r)\n~", "\r\n", $httpsexpected);
1161
 
1162
        // Test stripping works OK.
1163
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexample));
1164
        // Test it does nothing to the 'plain' data.
1165
        $this->assertSame($httpsexpected, \curl::strip_double_headers($httpsexpected));
1166
    }
1167
 
1168
    /**
1169
     * Tests the get_mimetype_description function.
1170
     */
11 efrain 1171
    public function test_get_mimetype_description(): void {
1 efrain 1172
        $this->resetAfterTest();
1173
 
1174
        // Test example type (.doc).
1175
        $this->assertEquals(get_string('application/msword', 'mimetypes'),
1176
                get_mimetype_description(array('filename' => 'test.doc')));
1177
 
1178
        // Test an unknown file type.
1179
        $this->assertEquals(get_string('document/unknown', 'mimetypes'),
1180
                get_mimetype_description(array('filename' => 'test.frog')));
1181
 
1182
        // Test a custom filetype with no lang string specified.
1183
        core_filetypes::add_type('frog', 'application/x-frog', 'document');
1184
        $this->assertEquals('application/x-frog',
1185
                get_mimetype_description(array('filename' => 'test.frog')));
1186
 
1187
        // Test custom description.
1188
        core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
1189
                array(), '', 'Froggy file');
1190
        $this->assertEquals('Froggy file',
1191
                get_mimetype_description(array('filename' => 'test.frog')));
1192
 
1193
        // Test custom description using multilang filter.
1194
        \filter_manager::reset_caches();
1195
        filter_set_global_state('multilang', TEXTFILTER_ON);
1196
        filter_set_applies_to_strings('multilang', true);
1197
        core_filetypes::update_type('frog', 'frog', 'application/x-frog', 'document',
1198
                array(), '', '<span lang="en" class="multilang">Green amphibian</span>' .
1199
                '<span lang="fr" class="multilang">Amphibian vert</span>');
1200
        $this->assertEquals('Green amphibian',
1201
                get_mimetype_description(array('filename' => 'test.frog')));
1202
    }
1203
 
1204
    /**
1205
     * Tests the get_mimetypes_array function.
1206
     */
11 efrain 1207
    public function test_get_mimetypes_array(): void {
1 efrain 1208
        $mimeinfo = get_mimetypes_array();
1209
 
1210
        // Test example MIME type (doc).
1211
        $this->assertEquals('application/msword', $mimeinfo['doc']['type']);
1212
        $this->assertEquals('document', $mimeinfo['doc']['icon']);
1213
        $this->assertEquals(array('document'), $mimeinfo['doc']['groups']);
1214
        $this->assertFalse(isset($mimeinfo['doc']['string']));
1215
        $this->assertFalse(isset($mimeinfo['doc']['defaulticon']));
1216
        $this->assertFalse(isset($mimeinfo['doc']['customdescription']));
1217
 
1218
        // Check the less common fields using other examples.
1219
        $this->assertEquals('image', $mimeinfo['png']['string']);
1220
        $this->assertEquals(true, $mimeinfo['txt']['defaulticon']);
1221
    }
1222
 
1223
    /**
1224
     * Tests for get_mimetype_for_sending function.
1225
     */
11 efrain 1226
    public function test_get_mimetype_for_sending(): void {
1 efrain 1227
        // Without argument.
1228
        $this->assertEquals('application/octet-stream', get_mimetype_for_sending());
1229
 
1230
        // Argument is null.
1231
        $this->assertEquals('application/octet-stream', get_mimetype_for_sending(null));
1232
 
1233
        // Filename having no extension.
1234
        $this->assertEquals('application/octet-stream', get_mimetype_for_sending('filenamewithoutextension'));
1235
 
1236
        // Test using the extensions listed from the get_mimetypes_array function.
1237
        $mimetypes = get_mimetypes_array();
1238
        foreach ($mimetypes as $ext => $info) {
1239
            if ($ext === 'xxx') {
1240
                $this->assertEquals('application/octet-stream', get_mimetype_for_sending('SampleFile.' . $ext));
1241
            } else {
1242
                $this->assertEquals($info['type'], get_mimetype_for_sending('SampleFile.' . $ext));
1243
            }
1244
        }
1245
    }
1246
 
1247
    /**
1248
     * Test curl agent settings.
1249
     */
11 efrain 1250
    public function test_curl_useragent(): void {
1 efrain 1251
        $curl = new testable_curl();
1252
        $options = $curl->get_options();
1253
        $this->assertNotEmpty($options);
1254
 
1255
        $moodlebot = \core_useragent::get_moodlebot_useragent();
1256
 
1257
        $curl->call_apply_opt($options);
1258
        $this->assertTrue(in_array("User-Agent: $moodlebot", $curl->header));
1259
        $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));
1260
 
1261
        $options['CURLOPT_USERAGENT'] = 'Test/1.0';
1262
        $curl->call_apply_opt($options);
1263
        $this->assertTrue(in_array('User-Agent: Test/1.0', $curl->header));
1264
        $this->assertFalse(in_array("User-Agent: $moodlebot", $curl->header));
1265
 
1266
        $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.0');
1267
        $curl->call_apply_opt();
1268
        $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));
1269
        $this->assertFalse(in_array('User-Agent: Test/1.0', $curl->header));
1270
 
1271
        $curl->set_option('CURLOPT_USERAGENT', 'AnotherUserAgent/1.1');
1272
        $options = $curl->get_options();
1273
        $curl->call_apply_opt($options);
1274
        $this->assertTrue(in_array('User-Agent: AnotherUserAgent/1.1', $curl->header));
1275
        $this->assertFalse(in_array('User-Agent: AnotherUserAgent/1.0', $curl->header));
1276
 
1277
        $curl->unset_option('CURLOPT_USERAGENT');
1278
        $curl->call_apply_opt();
1279
        $this->assertTrue(in_array("User-Agent: $moodlebot", $curl->header));
1280
 
1281
        // Finally, test it via exttests, to ensure the agent is sent properly.
1282
        $testurl = $this->getExternalTestFileUrl('/test_agent.php');
1283
        $extcurl = new \curl();
1284
 
1285
        // Matching (assert we don't receive an error, and get back the content "OK").
1286
        $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'AnotherUserAgent/1.2'));
1287
        $this->assertSame(0, $extcurl->get_errno());
1288
        $this->assertSame('OK', $contents);
1289
 
1290
        // Not matching (assert we don't receive an error, and get back empty content - not "OK").
1291
        $contents = $extcurl->get($testurl, array(), array('CURLOPT_USERAGENT' => 'NonMatchingUserAgent/1.2'));
1292
        $this->assertSame(0, $extcurl->get_errno());
1293
        $this->assertSame('', $contents);
1294
    }
1295
 
1296
    /**
1297
     * Test file_rewrite_pluginfile_urls.
1298
     */
11 efrain 1299
    public function test_file_rewrite_pluginfile_urls(): void {
1 efrain 1300
 
1301
        $syscontext = \context_system::instance();
1302
        $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
1303
 
1304
        // Do the rewrite.
1305
        $finaltext = file_rewrite_pluginfile_urls($originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0);
1306
        $this->assertStringContainsString("pluginfile.php", $finaltext);
1307
 
1308
        // Now undo.
1309
        $options = array('reverse' => true);
1310
        $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1311
 
1312
        // Compare the final text is the same that the original.
1313
        $this->assertEquals($originaltext, $finaltext);
1314
    }
1315
 
1316
    /**
1317
     * Test file_rewrite_pluginfile_urls with includetoken.
1318
     */
11 efrain 1319
    public function test_file_rewrite_pluginfile_urls_includetoken(): void {
1 efrain 1320
        global $USER, $CFG;
1321
 
1322
        $CFG->slasharguments = true;
1323
 
1324
        $this->resetAfterTest();
1325
 
1326
        $syscontext = \context_system::instance();
1327
        $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
1328
        $options = ['includetoken' => true];
1329
 
1330
        // Rewrite the content. This will generate a new token.
1331
        $finaltext = file_rewrite_pluginfile_urls(
1332
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1333
 
1334
        $token = get_user_key('core_files', $USER->id);
1335
        $expectedurl = new \moodle_url("/tokenpluginfile.php/{$token}/{$syscontext->id}/user/private/0/image.png");
1336
        $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
1337
        $this->assertEquals($expectedtext, $finaltext);
1338
 
1339
        // Do it again - the second time will use an existing token.
1340
        $finaltext = file_rewrite_pluginfile_urls(
1341
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1342
        $this->assertEquals($expectedtext, $finaltext);
1343
 
1344
        // Now undo.
1345
        $options['reverse'] = true;
1346
        $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1347
 
1348
        // Compare the final text is the same that the original.
1349
        $this->assertEquals($originaltext, $finaltext);
1350
 
1351
        // Now indicates a user different than $USER.
1352
        $user = $this->getDataGenerator()->create_user();
1353
        $options = ['includetoken' => $user->id];
1354
 
1355
        // Rewrite the content. This will generate a new token.
1356
        $finaltext = file_rewrite_pluginfile_urls(
1357
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1358
 
1359
        $token = get_user_key('core_files', $user->id);
1360
        $expectedurl = new \moodle_url("/tokenpluginfile.php/{$token}/{$syscontext->id}/user/private/0/image.png");
1361
        $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
1362
        $this->assertEquals($expectedtext, $finaltext);
1363
    }
1364
 
1365
    /**
1366
     * Test file_rewrite_pluginfile_urls with includetoken with slasharguments disabled..
1367
     */
11 efrain 1368
    public function test_file_rewrite_pluginfile_urls_includetoken_no_slashargs(): void {
1 efrain 1369
        global $USER, $CFG;
1370
 
1371
        $CFG->slasharguments = false;
1372
 
1373
        $this->resetAfterTest();
1374
 
1375
        $syscontext = \context_system::instance();
1376
        $originaltext = 'Fake test with an image <img src="@@PLUGINFILE@@/image.png">';
1377
        $options = ['includetoken' => true];
1378
 
1379
        // Rewrite the content. This will generate a new token.
1380
        $finaltext = file_rewrite_pluginfile_urls(
1381
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1382
 
1383
        $token = get_user_key('core_files', $USER->id);
1384
        $expectedurl = new \moodle_url("/tokenpluginfile.php");
1385
        $expectedurl .= "?token={$token}&file=/{$syscontext->id}/user/private/0/image.png";
1386
        $expectedtext = "Fake test with an image <img src=\"{$expectedurl}\">";
1387
        $this->assertEquals($expectedtext, $finaltext);
1388
 
1389
        // Do it again - the second time will use an existing token.
1390
        $finaltext = file_rewrite_pluginfile_urls(
1391
                $originaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1392
        $this->assertEquals($expectedtext, $finaltext);
1393
 
1394
        // Now undo.
1395
        $options['reverse'] = true;
1396
        $finaltext = file_rewrite_pluginfile_urls($finaltext, 'pluginfile.php', $syscontext->id, 'user', 'private', 0, $options);
1397
 
1398
        // Compare the final text is the same that the original.
1399
        $this->assertEquals($originaltext, $finaltext);
1400
    }
1401
 
1402
    /**
1403
     * Helpter function to create draft files
1404
     *
1405
     * @param  array  $filedata data for the file record (to not use defaults)
1406
     * @return stored_file the stored file instance
1407
     */
1408
    public static function create_draft_file($filedata = array()) {
1409
        global $USER;
1410
 
1411
        $fs = get_file_storage();
1412
 
1413
        $filerecord = array(
1414
            'component' => 'user',
1415
            'filearea'  => 'draft',
1416
            'itemid'    => isset($filedata['itemid']) ? $filedata['itemid'] : file_get_unused_draft_itemid(),
1417
            'author'    => isset($filedata['author']) ? $filedata['author'] : fullname($USER),
1418
            'filepath'  => isset($filedata['filepath']) ? $filedata['filepath'] : '/',
1419
            'filename'  => isset($filedata['filename']) ? $filedata['filename'] : 'file.txt',
1420
        );
1421
 
1422
        if (isset($filedata['contextid'])) {
1423
            $filerecord['contextid'] = $filedata['contextid'];
1424
        } else {
1425
            $usercontext = \context_user::instance($USER->id);
1426
            $filerecord['contextid'] = $usercontext->id;
1427
        }
1428
        $source = isset($filedata['source']) ? $filedata['source'] : serialize((object)array('source' => 'From string'));
1429
        $content = isset($filedata['content']) ? $filedata['content'] : 'some content here';
1430
 
1431
        $file = $fs->create_file_from_string($filerecord, $content);
1432
        $file->set_source($source);
1433
 
1434
        return $file;
1435
    }
1436
 
1437
    /**
1438
     * Test file_merge_files_from_draft_area_into_filearea
1439
     */
11 efrain 1440
    public function test_file_merge_files_from_draft_area_into_filearea(): void {
1 efrain 1441
        global $USER, $CFG;
1442
 
1443
        $this->resetAfterTest(true);
1444
        $this->setAdminUser();
1445
        $fs = get_file_storage();
1446
        $usercontext = \context_user::instance($USER->id);
1447
 
1448
        // Create a draft file.
1449
        $filename = 'data.txt';
1450
        $filerecord = array(
1451
            'filename'  => $filename,
1452
        );
1453
        $file = self::create_draft_file($filerecord);
1454
        $draftitemid = $file->get_itemid();
1455
 
1456
        $maxbytes = $CFG->userquota;
1457
        $maxareabytes = $CFG->userquota;
1458
        $options = array('subdirs' => 1,
1459
                         'maxbytes' => $maxbytes,
1460
                         'maxfiles' => -1,
1461
                         'areamaxbytes' => $maxareabytes);
1462
 
1463
        // Add new file.
1464
        file_merge_files_from_draft_area_into_filearea($draftitemid, $usercontext->id, 'user', 'private', 0, $options);
1465
 
1466
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1467
        // Directory and file.
1468
        $this->assertCount(2, $files);
1469
        $found = false;
1470
        foreach ($files as $file) {
1471
            if (!$file->is_directory()) {
1472
                $found = true;
1473
                $this->assertEquals($filename, $file->get_filename());
1474
                $this->assertEquals('some content here', $file->get_content());
1475
            }
1476
        }
1477
        $this->assertTrue($found);
1478
 
1479
        // Add two more files.
1480
        $filerecord = array(
1481
            'itemid'  => $draftitemid,
1482
            'filename'  => 'second.txt',
1483
        );
1484
        self::create_draft_file($filerecord);
1485
        $filerecord = array(
1486
            'itemid'  => $draftitemid,
1487
            'filename'  => 'third.txt',
1488
        );
1489
        $file = self::create_draft_file($filerecord);
1490
 
1491
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1492
 
1493
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1494
        $this->assertCount(4, $files);
1495
 
1496
        // Update contents of one file.
1497
        $filerecord = array(
1498
            'filename'  => 'second.txt',
1499
            'content'  => 'new content',
1500
        );
1501
        $file = self::create_draft_file($filerecord);
1502
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1503
 
1504
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1505
        $this->assertCount(4, $files);
1506
        $found = false;
1507
        foreach ($files as $file) {
1508
            if ($file->get_filename() == 'second.txt') {
1509
                $found = true;
1510
                $this->assertEquals('new content', $file->get_content());
1511
            }
1512
        }
1513
        $this->assertTrue($found);
1514
 
1515
        // Update author.
1516
        // Set different author in the current file.
1517
        foreach ($files as $file) {
1518
            if ($file->get_filename() == 'second.txt') {
1519
                $file->set_author('Nobody');
1520
            }
1521
        }
1522
        $filerecord = array(
1523
            'filename'  => 'second.txt',
1524
        );
1525
        $file = self::create_draft_file($filerecord);
1526
 
1527
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $usercontext->id, 'user', 'private', 0, $options);
1528
 
1529
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1530
        $this->assertCount(4, $files);
1531
        $found = false;
1532
        foreach ($files as $file) {
1533
            if ($file->get_filename() == 'second.txt') {
1534
                $found = true;
1535
                $this->assertEquals(fullname($USER), $file->get_author());
1536
            }
1537
        }
1538
        $this->assertTrue($found);
1539
 
1540
    }
1541
 
1542
    /**
1543
     * Test max area bytes for file_merge_files_from_draft_area_into_filearea
1544
     */
11 efrain 1545
    public function test_file_merge_files_from_draft_area_into_filearea_max_area_bytes(): void {
1 efrain 1546
        global $USER;
1547
 
1548
        $this->resetAfterTest(true);
1549
        $this->setAdminUser();
1550
        $fs = get_file_storage();
1551
 
1552
        $file = self::create_draft_file();
1553
        $options = array('subdirs' => 1,
1554
                         'maxbytes' => 5,
1555
                         'maxfiles' => -1,
1556
                         'areamaxbytes' => 10);
1557
 
1558
        // Add new file.
1559
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1560
        $usercontext = \context_user::instance($USER->id);
1561
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1562
        $this->assertCount(0, $files);
1563
    }
1564
 
1565
    /**
1566
     * Test max file bytes for file_merge_files_from_draft_area_into_filearea
1567
     */
11 efrain 1568
    public function test_file_merge_files_from_draft_area_into_filearea_max_file_bytes(): void {
1 efrain 1569
        global $USER;
1570
 
1571
        $this->resetAfterTest(true);
1572
        // The admin has no restriction for max file uploads, so use a normal user.
1573
        $user = $this->getDataGenerator()->create_user();
1574
        $this->setUser($user);
1575
        $fs = get_file_storage();
1576
 
1577
        $file = self::create_draft_file();
1578
        $options = array('subdirs' => 1,
1579
                         'maxbytes' => 1,
1580
                         'maxfiles' => -1,
1581
                         'areamaxbytes' => 100);
1582
 
1583
        // Add new file.
1584
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1585
        $usercontext = \context_user::instance($USER->id);
1586
        // Check we only get the base directory, not a new file.
1587
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1588
        $this->assertCount(1, $files);
1589
        $file = array_shift($files);
1590
        $this->assertTrue($file->is_directory());
1591
    }
1592
 
1593
    /**
1594
     * Test max file number for file_merge_files_from_draft_area_into_filearea
1595
     */
11 efrain 1596
    public function test_file_merge_files_from_draft_area_into_filearea_max_files(): void {
1 efrain 1597
        global $USER;
1598
 
1599
        $this->resetAfterTest(true);
1600
        $this->setAdminUser();
1601
        $fs = get_file_storage();
1602
 
1603
        $file = self::create_draft_file();
1604
        $options = array('subdirs' => 1,
1605
                         'maxbytes' => 1000,
1606
                         'maxfiles' => 0,
1607
                         'areamaxbytes' => 1000);
1608
 
1609
        // Add new file.
1610
        file_merge_files_from_draft_area_into_filearea($file->get_itemid(), $file->get_contextid(), 'user', 'private', 0, $options);
1611
        $usercontext = \context_user::instance($USER->id);
1612
        // Check we only get the base directory, not a new file.
1613
        $files = $fs->get_area_files($usercontext->id, 'user', 'private', 0);
1614
        $this->assertCount(1, $files);
1615
        $file = array_shift($files);
1616
        $this->assertTrue($file->is_directory());
1617
    }
1618
 
1619
    /**
1620
     * Test file_get_draft_area_info.
1621
     */
11 efrain 1622
    public function test_file_get_draft_area_info(): void {
1 efrain 1623
        global $USER;
1624
 
1625
        $this->resetAfterTest(true);
1626
        $this->setAdminUser();
1627
        $fs = get_file_storage();
1628
 
1629
        $filerecord = array(
1630
            'filename'  => 'one.txt',
1631
        );
1632
        $file = self::create_draft_file($filerecord);
1633
        $size = $file->get_filesize();
1634
        $draftitemid = $file->get_itemid();
1635
        // Add another file.
1636
        $filerecord = array(
1637
            'itemid'  => $draftitemid,
1638
            'filename'  => 'second.txt',
1639
        );
1640
        $file = self::create_draft_file($filerecord);
1641
        $size += $file->get_filesize();
1642
 
1643
        // Create directory.
1644
        $usercontext = \context_user::instance($USER->id);
1645
        $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
1646
        // Add file to directory.
1647
        $filerecord = array(
1648
            'itemid'  => $draftitemid,
1649
            'filename' => 'third.txt',
1650
            'filepath' => '/testsubdir/',
1651
        );
1652
        $file = self::create_draft_file($filerecord);
1653
        $size += $file->get_filesize();
1654
 
1655
        $fileinfo = file_get_draft_area_info($draftitemid);
1656
        $this->assertEquals(3, $fileinfo['filecount']);
1657
        $this->assertEquals($size, $fileinfo['filesize']);
1658
        $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1659
        $this->assertEquals($size, $fileinfo['filesize_without_references']);
1660
 
1661
        // Now get files from just one folder.
1662
        $fileinfo = file_get_draft_area_info($draftitemid, '/testsubdir/');
1663
        $this->assertEquals(1, $fileinfo['filecount']);
1664
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
1665
        $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
1666
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
1667
 
1668
        // Check we get the same results if we call file_get_file_area_info.
1669
        $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'draft', $draftitemid);
1670
        $this->assertEquals(3, $fileinfo['filecount']);
1671
        $this->assertEquals($size, $fileinfo['filesize']);
1672
        $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1673
        $this->assertEquals($size, $fileinfo['filesize_without_references']);
1674
    }
1675
 
1676
    /**
1677
     * Test file_get_file_area_info.
1678
     */
11 efrain 1679
    public function test_file_get_file_area_info(): void {
1 efrain 1680
        global $USER;
1681
 
1682
        $this->resetAfterTest(true);
1683
        $this->setAdminUser();
1684
        $fs = get_file_storage();
1685
 
1686
        $filerecord = array(
1687
            'filename'  => 'one.txt',
1688
        );
1689
        $file = self::create_draft_file($filerecord);
1690
        $size = $file->get_filesize();
1691
        $draftitemid = $file->get_itemid();
1692
        // Add another file.
1693
        $filerecord = array(
1694
            'itemid'  => $draftitemid,
1695
            'filename'  => 'second.txt',
1696
        );
1697
        $file = self::create_draft_file($filerecord);
1698
        $size += $file->get_filesize();
1699
 
1700
        // Create directory.
1701
        $usercontext = \context_user::instance($USER->id);
1702
        $dir = $fs->create_directory($usercontext->id, 'user', 'draft', $draftitemid, '/testsubdir/');
1703
        // Add file to directory.
1704
        $filerecord = array(
1705
            'itemid'  => $draftitemid,
1706
            'filename' => 'third.txt',
1707
            'filepath' => '/testsubdir/',
1708
        );
1709
        $file = self::create_draft_file($filerecord);
1710
        $size += $file->get_filesize();
1711
 
1712
        // Add files to user private file area.
1713
        $options = array('subdirs' => 1, 'maxfiles' => 3);
1714
        file_merge_files_from_draft_area_into_filearea($draftitemid, $file->get_contextid(), 'user', 'private', 0, $options);
1715
 
1716
        $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private');
1717
        $this->assertEquals(3, $fileinfo['filecount']);
1718
        $this->assertEquals($size, $fileinfo['filesize']);
1719
        $this->assertEquals(1, $fileinfo['foldercount']);   // Directory created.
1720
        $this->assertEquals($size, $fileinfo['filesize_without_references']);
1721
 
1722
        // Now get files from just one folder.
1723
        $fileinfo = file_get_file_area_info($usercontext->id, 'user', 'private', 0, '/testsubdir/');
1724
        $this->assertEquals(1, $fileinfo['filecount']);
1725
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize']);
1726
        $this->assertEquals(0, $fileinfo['foldercount']);   // No subdirectories inside the directory.
1727
        $this->assertEquals($file->get_filesize(), $fileinfo['filesize_without_references']);
1728
    }
1729
 
1730
    /**
1731
     * Test confirming that draft files not referenced in the editor text are removed.
1732
     */
11 efrain 1733
    public function test_file_remove_editor_orphaned_files(): void {
1 efrain 1734
        global $USER, $CFG;
1735
        $this->resetAfterTest(true);
1736
        $this->setAdminUser();
1737
 
1738
        // Create three draft files.
1739
        $filerecord = ['filename'  => 'file1.png'];
1740
        $file = self::create_draft_file($filerecord);
1741
        $draftitemid = $file->get_itemid();
1742
 
1743
        $filerecord['itemid'] = $draftitemid;
1744
 
1745
        $filerecord['filename'] = 'file2.png';
1746
        self::create_draft_file($filerecord);
1747
 
1748
        $filerecord['filename'] = 'file 3.png';
1749
        self::create_draft_file($filerecord);
1750
 
1751
        $filerecord['filename'] = 'file4.png';
1752
        self::create_draft_file($filerecord);
1753
 
1754
        // Confirm the user drafts area lists 3 files.
1755
        $fs = get_file_storage();
1756
        $usercontext = \context_user::instance($USER->id);
1757
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
1758
        $this->assertCount(4, $draftfiles);
1759
 
1760
        // Now, spoof some editor text content, referencing 2 of the files; one requiring name encoding, one not.
1761
        $editor = [
1762
            'itemid' => $draftitemid,
1763
            'text' => "
1764
                <img src=\"{$CFG->wwwroot}/draftfile.php/{$usercontext->id}/user/draft/{$draftitemid}/file%203.png\" alt=\"\">
1765
                <img src=\"{$CFG->wwwroot}/draftfile.php/{$usercontext->id}/user/draft/{$draftitemid}/file1.png\" alt=\"\">
1766
                <span>{$CFG->wwwroot}/draftfile.php/{$usercontext->id}/user/draft/{$draftitemid}/file4.png</span>"
1767
        ];
1768
 
1769
        // Run the remove orphaned drafts function and confirm that only the referenced files remain in the user drafts.
1770
        // The drafts we expect will not be removed (are referenced in the online text).
1771
        $expected = ['file1.png', 'file 3.png', 'file4.png'];
1772
        file_remove_editor_orphaned_files($editor);
1773
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'itemid', 0);
1774
        $this->assertCount(3, $draftfiles);
1775
        foreach ($draftfiles as $file) {
1776
            $this->assertContains($file->get_filename(), $expected);
1777
        }
1778
    }
1779
 
1780
    /**
1781
     * Test that all files in the draftarea are returned.
1782
     */
11 efrain 1783
    public function test_file_get_all_files_in_draftarea(): void {
1 efrain 1784
        $this->resetAfterTest();
1785
        $this->setAdminUser();
1786
 
1787
        $filerecord = ['filename' => 'basepic.jpg'];
1788
        $file = self::create_draft_file($filerecord);
1789
 
1790
        $secondrecord = [
1791
            'filename' => 'infolder.jpg',
1792
            'filepath' => '/assignment/',
1793
            'itemid' => $file->get_itemid()
1794
        ];
1795
        $file = self::create_draft_file($secondrecord);
1796
 
1797
        $thirdrecord = [
1798
            'filename' => 'deeperfolder.jpg',
1799
            'filepath' => '/assignment/pics/',
1800
            'itemid' => $file->get_itemid()
1801
        ];
1802
        $file = self::create_draft_file($thirdrecord);
1803
 
1804
        $fourthrecord = [
1805
            'filename' => 'differentimage.jpg',
1806
            'filepath' => '/secondfolder/',
1807
            'itemid' => $file->get_itemid()
1808
        ];
1809
        $file = self::create_draft_file($fourthrecord);
1810
 
1811
        // This record has the same name as the last record, but it's in a different folder.
1812
        // Just checking this is also returned.
1813
        $fifthrecord = [
1814
            'filename' => 'differentimage.jpg',
1815
            'filepath' => '/assignment/pics/',
1816
            'itemid' => $file->get_itemid()
1817
        ];
1818
        $file = self::create_draft_file($fifthrecord);
1819
 
1820
        $allfiles = file_get_all_files_in_draftarea($file->get_itemid());
1821
        $this->assertCount(5, $allfiles);
1822
        $this->assertEquals($filerecord['filename'], $allfiles[0]->filename);
1823
        $this->assertEquals($secondrecord['filename'], $allfiles[1]->filename);
1824
        $this->assertEquals($thirdrecord['filename'], $allfiles[2]->filename);
1825
        $this->assertEquals($fourthrecord['filename'], $allfiles[3]->filename);
1826
        $this->assertEquals($fifthrecord['filename'], $allfiles[4]->filename);
1827
    }
1828
 
11 efrain 1829
    public function test_file_copy_file_to_file_area(): void {
1 efrain 1830
        // Create two files in different draft areas but owned by the same user.
1831
        global $USER;
1832
        $this->resetAfterTest(true);
1833
        $this->setAdminUser();
1834
 
1835
        $filerecord = ['filename'  => 'file1.png', 'itemid' => file_get_unused_draft_itemid()];
1836
        $file1 = self::create_draft_file($filerecord);
1837
        $filerecord = ['filename'  => 'file2.png', 'itemid' => file_get_unused_draft_itemid()];
1838
        $file2 = self::create_draft_file($filerecord);
1839
 
1840
        // Confirm one file in each draft area.
1841
        $fs = get_file_storage();
1842
        $usercontext = \context_user::instance($USER->id);
1843
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
1844
        $this->assertCount(1, $draftfiles);
1845
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
1846
        $this->assertCount(1, $draftfiles);
1847
 
1848
        // Create file record.
1849
        $filerecord = [
1850
            'component' => $file2->get_component(),
1851
            'filearea' => $file2->get_filearea(),
1852
            'itemid' => $file2->get_itemid(),
1853
            'contextid' => $file2->get_contextid(),
1854
            'filepath' => '/',
1855
            'filename' => $file2->get_filename()
1856
        ];
1857
 
1858
        // Copy file2 into file1's draft area.
1859
        file_copy_file_to_file_area($filerecord, $file2->get_filename(), $file1->get_itemid());
1860
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file1->get_itemid(), 'itemid', 0);
1861
        $this->assertCount(2, $draftfiles);
1862
        $draftfiles = $fs->get_area_files($usercontext->id, 'user', 'draft', $file2->get_itemid(), 'itemid', 0);
1863
        $this->assertCount(1, $draftfiles);
1864
    }
1865
 
1866
    /**
1867
     * Test file_is_draft_areas_limit_reached
1868
     */
11 efrain 1869
    public function test_file_is_draft_areas_limit_reached(): void {
1 efrain 1870
        global $CFG;
1871
        $this->resetAfterTest(true);
1872
 
1873
        $capacity = $CFG->draft_area_bucket_capacity = 5;
1874
        $leak = $CFG->draft_area_bucket_leak = 0.2; // Leaks every 5 seconds.
1875
 
1876
        $generator = $this->getDataGenerator();
1877
        $user = $generator->create_user();
1878
 
1879
        $this->setUser($user);
1880
 
1881
        $itemids = [];
1882
        for ($i = 0; $i < $capacity; $i++) {
1883
            $itemids[$i] = file_get_unused_draft_itemid();
1884
        }
1885
 
1886
        // This test highly depends on time. We try to make sure that the test starts at the early moments on the second.
1887
        // This was not needed if MDL-37327 was implemented.
1888
        $after = time();
1889
        while (time() === $after) {
1890
            usleep(100000);
1891
        }
1892
 
1893
        // Burst up to the capacity and make sure that the bucket allows it.
1894
        $burststart = microtime();
1895
        for ($i = 0; $i < $capacity; $i++) {
1896
            if ($i) {
1897
                sleep(1); // A little delay so we have different timemodified value for files.
1898
            }
1899
            $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
1900
            self::create_draft_file([
1901
                'filename' => 'file1.png',
1902
                'itemid' => $itemids[$i],
1903
            ]);
1904
        }
1905
 
1906
        // The bucket should be full after bursting.
1907
        $this->assertTrue(file_is_draft_areas_limit_reached($user->id));
1908
 
1909
        // Calculate the time taken to burst up the bucket capacity.
1910
        $timetaken = microtime_diff($burststart, microtime());
1911
 
1912
        // The bucket leaks so it shouldn't be full after a certain time.
1913
        // Items are added into the bucket at the rate of 1 item per second.
1914
        // One item leaks from the bucket every 1/$leak seconds.
1915
        // So it takes 1/$leak - ($capacity-1) seconds for the bucket to leak one item and not be full anymore.
1916
        $milliseconds = ceil(1000000 * ((1 / $leak) - ($capacity - 1)) - ($timetaken  * 1000));
1917
        usleep($milliseconds);
1918
 
1919
        $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
1920
 
1921
        // Only one item was leaked from the bucket. So the bucket should become full again if we add a single item to it.
1922
        self::create_draft_file([
1923
            'filename' => 'file2.png',
1924
            'itemid' => $itemids[0],
1925
        ]);
1926
        $this->assertTrue(file_is_draft_areas_limit_reached($user->id));
1927
 
1928
        // The bucket leaks at a constant rate. It doesn't matter if it is filled as the result of bursting or not.
1929
        sleep(ceil(1 / $leak));
1930
        $this->assertFalse(file_is_draft_areas_limit_reached($user->id));
1931
    }
1932
 
1933
    /**
1934
     * Test text cleaning when preparing text editor data.
1935
     *
1936
     * @covers ::file_prepare_standard_editor
1937
     */
11 efrain 1938
    public function test_file_prepare_standard_editor_clean_text(): void {
1 efrain 1939
        $text = "lala <object>xx</object>";
1940
 
1941
        $syscontext = \context_system::instance();
1942
 
1943
        $object = new \stdClass();
1944
        $object->some = $text;
1945
        $object->someformat = FORMAT_PLAIN;
1946
 
1947
        $result = file_prepare_standard_editor(clone($object), 'some',
1948
            ['noclean' => false]);
1949
        $this->assertSame($text, $result->some);
1950
        $result = file_prepare_standard_editor(clone($object), 'some',
1951
            ['noclean' => true]);
1952
        $this->assertSame($text, $result->some);
1953
        $result = file_prepare_standard_editor(clone($object), 'some',
1954
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
1955
        $this->assertSame($text, $result->some);
1956
        $result = file_prepare_standard_editor(clone($object), 'some',
1957
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
1958
        $this->assertSame($text, $result->some);
1959
 
1960
        $object = new \stdClass();
1961
        $object->some = $text;
1962
        $object->someformat = FORMAT_MARKDOWN;
1963
 
1964
        $result = file_prepare_standard_editor(clone($object), 'some',
1965
            ['noclean' => false]);
1966
        $this->assertSame($text, $result->some);
1967
        $result = file_prepare_standard_editor(clone($object), 'some',
1968
            ['noclean' => true]);
1969
        $this->assertSame($text, $result->some);
1970
        $result = file_prepare_standard_editor(clone($object), 'some',
1971
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
1972
        $this->assertSame($text, $result->some);
1973
        $result = file_prepare_standard_editor(clone($object), 'some',
1974
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
1975
        $this->assertSame($text, $result->some);
1976
 
1977
        $object = new \stdClass();
1978
        $object->some = $text;
1979
        $object->someformat = FORMAT_MOODLE;
1980
 
1981
        $result = file_prepare_standard_editor(clone($object), 'some',
1982
            ['noclean' => false]);
1983
        $this->assertSame('lala xx', $result->some);
1984
        $result = file_prepare_standard_editor(clone($object), 'some',
1985
            ['noclean' => true]);
1986
        $this->assertSame($text, $result->some);
1987
        $result = file_prepare_standard_editor(clone($object), 'some',
1988
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
1989
        $this->assertSame('lala xx', $result->some);
1990
        $result = file_prepare_standard_editor(clone($object), 'some',
1991
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
1992
        $this->assertSame($text, $result->some);
1993
 
1994
        $object = new \stdClass();
1995
        $object->some = $text;
1996
        $object->someformat = FORMAT_HTML;
1997
 
1998
        $result = file_prepare_standard_editor(clone($object), 'some',
1999
            ['noclean' => false]);
2000
        $this->assertSame('lala xx', $result->some);
2001
        $result = file_prepare_standard_editor(clone($object), 'some',
2002
            ['noclean' => true]);
2003
        $this->assertSame($text, $result->some);
2004
        $result = file_prepare_standard_editor(clone($object), 'some',
2005
            ['noclean' => false, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
2006
        $this->assertSame('lala xx', $result->some);
2007
        $result = file_prepare_standard_editor(clone($object), 'some',
2008
            ['noclean' => true, 'context' => $syscontext], $syscontext, 'core', 'some', 1);
2009
        $this->assertSame($text, $result->some);
2010
    }
2011
 
2012
    /**
2013
     * Tests for file_get_typegroup to check that both arrays, and string values are accepted.
2014
     *
2015
     * @dataProvider file_get_typegroup_provider
2016
     * @param string|array $group
2017
     * @param string $expected
2018
     */
2019
    public function test_file_get_typegroup(
2020
        string|array $group,
2021
        string $expected,
2022
    ): void {
2023
        $result = file_get_typegroup('type', $group);
2024
        $this->assertContains($expected, $result);
2025
    }
2026
 
2027
    public static function file_get_typegroup_provider(): array {
2028
        return [
2029
            'Array of values' => [
2030
                ['.html', '.htm'],
2031
                'text/html',
2032
            ],
2033
            'String of comma-separated values' => [
2034
                '.html, .htm',
2035
                'text/html',
2036
            ],
2037
            'String of colon-separated values' => [
2038
                '.html : .htm',
2039
                'text/html',
2040
            ],
2041
            'String of semi-colon-separated values' => [
2042
                '.html ; .htm',
2043
                'text/html',
2044
            ],
2045
        ];
2046
    }
2047
}
2048
 
2049
/**
2050
 * Test-specific class to allow easier testing of curl functions.
2051
 *
2052
 * @copyright 2015 Dave Cooper
2053
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
2054
 */
2055
class testable_curl extends curl {
2056
    /**
2057
     * Accessor for private options array using reflection.
2058
     *
2059
     * @return array
2060
     */
2061
    public function get_options() {
2062
        // Access to private property.
2063
        $rp = new \ReflectionProperty('curl', 'options');
2064
        return $rp->getValue($this);
2065
    }
2066
 
2067
    /**
2068
     * Setter for private options array using reflection.
2069
     *
2070
     * @param array $options
2071
     */
2072
    public function set_options($options) {
2073
        // Access to private property.
2074
        $rp = new \ReflectionProperty('curl', 'options');
2075
        $rp->setValue($this, $options);
2076
    }
2077
 
2078
    /**
2079
     * Setter for individual option.
2080
     * @param string $option
2081
     * @param string $value
2082
     */
2083
    public function set_option($option, $value) {
2084
        $options = $this->get_options();
2085
        $options[$option] = $value;
2086
        $this->set_options($options);
2087
    }
2088
 
2089
    /**
2090
     * Unsets an option on the curl object
2091
     * @param string $option
2092
     */
2093
    public function unset_option($option) {
2094
        $options = $this->get_options();
2095
        unset($options[$option]);
2096
        $this->set_options($options);
2097
    }
2098
 
2099
    /**
2100
     * Wrapper to access the private \curl::apply_opt() method using reflection.
2101
     *
2102
     * @param array $options
2103
     * @return resource The curl handle
2104
     */
2105
    public function call_apply_opt($options = null) {
2106
        // Access to private method.
2107
        $rm = new \ReflectionMethod('curl', 'apply_opt');
2108
        $ch = curl_init();
2109
        return $rm->invoke($this, $ch, $options);
2110
    }
2111
}