Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.

Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]

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