Skip to content
WebTestBase.php 116 KiB
Newer Older
 * @file
 * Definition of Drupal\simpletest\WebTestBase.
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Database\Database;
use Drupal\Core\Database\ConnectionNotDefinedException;
use PDO;
use stdClass;
use DOMDocument;
use DOMXPath;
use SimpleXMLElement;
  /**
   * The profile to install as a basis for testing.
   *
   * @var string
   */
  protected $profile = 'testing';
  /**
   * The URL currently loaded in the internal browser.
   *
   * @var string
   */
  protected $url;

  /**
   * The handle of the current cURL connection.
   *
   * @var resource
   */
  protected $curlHandle;

  /**
   * The headers of the page currently loaded in the internal browser.
   *
   * @var Array
   */
  protected $headers;

  /**
   * The content of the page currently loaded in the internal browser.
   *
   * @var string
   */
  protected $content;

  /**
   * The content of the page currently loaded in the internal browser (plain text version).
   *
   * @var string
   */
  protected $plainTextContent;

  /**
   * The value of the Drupal.settings JavaScript variable for the page currently loaded in the internal browser.
   *
   * @var Array
   */
  protected $drupalSettings;

  /**
   * The parsed version of the page.
   *
   * @var SimpleXMLElement
   */
  protected $elements = NULL;

  /**
   * The current user logged in using the internal browser.
   *
   * @var bool
   */
  protected $loggedInUser = FALSE;

  /**
   * The current cookie file used by cURL.
   *
   * We do not reuse the cookies in further runs, so we do not need a file
   * but we still need cookie handling, so we set the jar to NULL.
   */
  protected $cookieFile = NULL;

  /**
   * Additional cURL options.
   *
   * Drupal\simpletest\WebTestBase itself never sets this but always obeys what is
   * set.
   */
  protected $additionalCurlOptions = array();

  /**
   * The original user, before it was changed to a clean uid = 1 for testing purposes.
   *
   * @var object
   */
  protected $originalUser = NULL;

  /**
   * The original shutdown handlers array, before it was cleaned for testing purposes.
   *
   * @var array
   */
  protected $originalShutdownCallbacks = array();

  /**
   * HTTP authentication method
   */
  protected $httpauth_method = CURLAUTH_BASIC;

  /**
   * HTTP authentication credentials (<username>:<password>).
   */
  protected $httpauth_credentials = NULL;

  /**
   * The current session name, if available.
   */
  protected $session_name = NULL;

  /**
   * The current session ID, if available.
   */
  protected $session_id = NULL;

  /**
   * Whether the files were copied to the test files directory.
   */
  protected $generatedTestFiles = FALSE;

  /**
   * The maximum number of redirects to follow when handling responses.
   */
  protected $maximumRedirects = 5;

  /**
   * The number of redirects followed during the handling of a request.
   */
  protected $redirect_count;

   * Constructor for Drupal\simpletest\WebTestBase.
   */
  function __construct($test_id = NULL) {
    parent::__construct($test_id);
    $this->skipClasses[__CLASS__] = TRUE;
  }

  /**
   * Get a node from the database based on its title.
   *
   *   A node title, usually generated by $this->randomName().
   *   (optional) Whether to reset the entity cache.
   *   A node entity matching $title.
  function drupalGetNodeByTitle($title, $reset = FALSE) {
      drupal_container()->get('plugin.manager.entity')->getStorageController('node')->resetCache();
    }
    $nodes = entity_load_multiple_by_properties('node', array('title' => $title));
    // Load the first node returned from the database.
    $returned_node = reset($nodes);
    return $returned_node;
  }

  /**
   * Creates a node based on default settings.
   *
   * @param array $settings
   *   (optional) An associative array of settings for the node, as used in
   *   entity_create(). Override the defaults by specifying the key and value
   *   in the array, for example:
   *   @code
   *     $this->drupalCreateNode(array(
   *       'title' => t('Hello, world!'),
   *       'type' => 'article',
   *     ));
   *   @endcode
   *   The following defaults are provided:
   *   - body: Random string using the default filter format:
   *     @code
   *       $settings['body'][LANGUAGE_NOT_SPECIFIED][0] = array(
   *         'value' => $this->randomName(32),
   *         'format' => filter_default_format(),
   *       );
   *     @endcode
   *   - title: Random string.
   *   - comment: COMMENT_NODE_OPEN.
   *   - changed: REQUEST_TIME.
   *   - promote: NODE_NOT_PROMOTED.
   *   - log: Empty string.
   *   - status: NODE_PUBLISHED.
   *   - sticky: NODE_NOT_STICKY.
   *   - type: 'page'.
   *   - langcode: LANGUAGE_NOT_SPECIFIED. (If a 'langcode' key is provided in
   *     the array, this language code will also be used for a randomly
   *     generated body field for that language, and the body for
   *     LANGUAGE_NOT_SPECIFIED will remain empty.)
   *   - uid: The currently logged in user, or the user running test.
   *   - revision: 1. (Backwards-compatible binary flag indicating whether a
   *     new revision should be created; use 1 to specify a new revision.)
   *
   * @return Drupal\node\Node
   *   The created node entity.
   */
  protected function drupalCreateNode(array $settings = array()) {
    // Populate defaults array.
      'body'      => array(LANGUAGE_NOT_SPECIFIED => array(array())),
      'title'     => $this->randomName(8),
      'status'    => NODE_PUBLISHED,
      'sticky'    => NODE_NOT_STICKY,
      'langcode'  => LANGUAGE_NOT_SPECIFIED,
    // Add in comment settings for nodes.
    if (module_exists('comment')) {
      $settings += array(
        'comment' => COMMENT_NODE_OPEN,
      );
    }

    // Use the original node's created time for existing nodes.
    if (isset($settings['created']) && !isset($settings['date'])) {
      $settings['date'] = format_date($settings['created'], 'custom', 'Y-m-d H:i:s O');

    // If the node's user uid is not specified manually, use the currently
    // logged in user if available, or else the user running the test.
    if (!isset($settings['uid'])) {
      if ($this->loggedInUser) {
        $settings['uid'] = $this->loggedInUser->uid;
      }
      else {
        global $user;
        $settings['uid'] = $user->uid;
      }
    }

    // Merge body field value and format separately.
    $body = array(
      'value' => $this->randomName(32),
      'format' => filter_default_format(),
    if (empty($settings['body'][$settings['langcode']])) {
      $settings['body'][$settings['langcode']][0] = array();
    }
    $settings['body'][$settings['langcode']][0] += $body;
    $node = entity_create('node', $settings);
    if (!empty($settings['revision'])) {
      $node->setNewRevision();
    }
    // Small hack to link revisions to our test user.
    db_update('node_revision')
      ->fields(array('uid' => $node->uid))
      ->condition('vid', $node->vid)
      ->execute();
    return $node;
  }

  /**
   * Creates a custom content type based on default settings.
   *
   *   An array of settings to change from the defaults.
   *   Example: 'type' => 'foo'.
  protected function drupalCreateContentType($settings = array()) {
    // Find a non-existent random type name.
      $name = strtolower($this->randomName(8));
    // Populate defaults array.
    $defaults = array(
      'type' => $name,
      'name' => $name,
      'description' => '',
      'help' => '',
      'title_label' => 'Title',
      'body_label' => 'Body',
      'has_title' => 1,
      'has_body' => 1,
    );
    // Imposed values for a custom type.
    $forced = array(
      'orig_type' => '',
      'old_type' => '',
      'module' => 'node',
      'custom' => 1,
      'modified' => 1,
      'locked' => 0,
    );
    $type = $forced + $settings + $defaults;
    $this->assertEqual($saved_type, SAVED_NEW, t('Created content type %type.', array('%type' => $type->type)));
    // Reset permissions so that permissions for this content type are available.
    $this->checkPermissions(array(), TRUE);

  /**
   * Creates a block instance based on default settings.
   *
   * Note: Until this can be done programmatically, the active user account
   * must have permission to administer blocks.
   *
   * @param string $plugin_id
   *   The plugin ID of the block type for this block instance.
   * @param array $values
   *   (optional) An associative array of values for the block entity.
   *   Override the defaults by specifying the key and value in the array, for
   *   example:
   *   @code
   *     $this->drupalPlaceBlock('system_powered_by_block', array(
   *     ));
   *   @endcode
   *   The following defaults are provided:
   *   - machine_name: Random string.
   *   - region: 'sidebar_first'.
   *   - theme: The default theme.
   * @param array $settings
   *   (optional) An associative array of plugin-specific settings.
   * @return \Drupal\block\Plugin\Core\Entity\Block
   *   The block entity.
   *
   * @todo
   *   Add support for creating custom block instances.
   */
  protected function drupalPlaceBlock($plugin_id, array $values = array(), array $settings = array()) {
    $values += array(
      'plugin' => $plugin_id,
      'label' => $this->randomName(8),
      'theme' => variable_get('theme_default', 'stark'),
      'machine_name' => strtolower($this->randomName(8)),
      'settings' => $settings,
    // Build the ID out of the theme and machine_name.
    $values['id'] = $values['theme'] . '.' . $values['machine_name'];
    $block = entity_create('block', $values);
    $block->save();
    return $block;
  /**
   * Get a list files that can be used in tests.
   *
   * @param $type
   *   File type, possible values: 'binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'.
   * @param $size
   *   File size in bytes to match. Please check the tests/files folder.
   * @return
   *   List of files that match filter.
  protected function drupalGetTestFiles($type, $size = NULL) {
    if (empty($this->generatedTestFiles)) {
      // Generate binary test files.
      $lines = array(64, 1024);
      $count = 0;
      foreach ($lines as $line) {
        simpletest_generate_file('binary-' . $count++, 64, $line, 'binary');
      }

      // Generate text test files.
      $lines = array(16, 256, 1024, 2048, 20480);
      $count = 0;
      foreach ($lines as $line) {
        simpletest_generate_file('text-' . $count++, 64, $line);
      }

      // Copy other test files from simpletest.
      $original = drupal_get_path('module', 'simpletest') . '/files';
      $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
      foreach ($files as $file) {
        file_unmanaged_copy($file->uri, variable_get('file_public_path', conf_path() . '/files'));
      $this->generatedTestFiles = TRUE;
    }

    $files = array();
    // Make sure type is valid.
    if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
      $files = file_scan_directory('public://', '/' . $type . '\-.*/');

      // If size is set then remove any files that are not of that size.
      if ($size !== NULL) {
        foreach ($files as $file) {
          }
        }
      }
    }
    usort($files, array($this, 'drupalCompareFiles'));
    return $files;
  }

  /**
   * Compare two files based on size and file name.
  protected function drupalCompareFiles($file1, $file2) {
    $compare_size = filesize($file1->uri) - filesize($file2->uri);
    if ($compare_size) {
      // Sort by file size.
      return $compare_size;
      // The files were the same size, so sort alphabetically.
      return strnatcmp($file1->name, $file2->name);
   * Create a user with a given set of permissions.
   * @param array $permissions
   *   Array of permission names to assign to user. Note that the user always
   *   has the default permissions derived from the "authenticated users" role.
   *
   * @return object|false
   *   A fully loaded user object with pass_raw property, or FALSE if account
  protected function drupalCreateUser(array $permissions = array()) {
    // Create a role with the given permission set, if any.
    $rid = FALSE;
    if ($permissions) {
      $rid = $this->drupalCreateRole($permissions);
      if (!$rid) {
        return FALSE;
      }
    }

    // Create a user assigned to that role.
    $edit = array();
    $edit['name']   = $this->randomName();
    $edit['mail']   = $edit['name'] . '@example.com';
    $edit['pass']   = user_password();
    $edit['status'] = 1;
    if ($rid) {
      $edit['roles'] = array($rid => $rid);
    }
    $account = entity_create('user', $edit);
    $account->save();

    $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login'));
    if (empty($account->uid)) {
      return FALSE;
    }

    // Add the raw password so that we can log in as this user.
    $account->pass_raw = $edit['pass'];
    return $account;
  }

  /**
   * Internal helper function; Create a role with specified permissions.
   *
   *   Array of permission names to assign to role.
   * @param string $rid
   *   (optional) The role ID (machine name). Defaults to a random name.
   * @param string $name
   *   (optional) The label for the role. Defaults to a random string.
   *
   * @return string
   *   Role ID of newly created role, or FALSE if role creation failed.
  protected function drupalCreateRole(array $permissions, $rid = NULL, $name = NULL) {
    // Generate a random, lowercase machine name if none was passed.
    if (!isset($rid)) {
      $rid = strtolower($this->randomName(8));
    }
    // Generate a random label.
    if (!isset($name)) {
      $name = $this->randomString(8);
    // Check the all the permissions strings are valid.
    if (!$this->checkPermissions($permissions)) {
      return FALSE;
    }

    $result = user_role_save($role);

    $this->assertIdentical($result, SAVED_NEW, t('Created role ID @rid with name @name.', array(
      '@name' => var_export($role->name, TRUE),
      '@rid' => var_export($role->rid, TRUE),
    )), t('Role'));

    if ($result === SAVED_NEW) {
      // Grant the specified permissions to the role, if any.
      if (!empty($permissions)) {
        user_role_grant_permissions($role->rid, $permissions);

        $assigned_permissions = db_query('SELECT permission FROM {role_permission} WHERE rid = :rid', array(':rid' => $role->rid))->fetchCol();
        $missing_permissions = array_diff($permissions, $assigned_permissions);
        if (!$missing_permissions) {
          $this->pass(t('Created permissions: @perms', array('@perms' => implode(', ', $permissions))), t('Role'));
        }
        else {
          $this->fail(t('Failed to create permissions: @perms', array('@perms' => implode(', ', $missing_permissions))), t('Role'));
        }
      }
  /**
   * Check to make sure that the array of permissions are valid.
   *
   * @param $permissions
   *   Permissions to check.
   * @param $reset
   *   Reset cached available permissions.
   * @return
   *   TRUE or FALSE depending on whether the permissions are valid.
  protected function checkPermissions(array $permissions, $reset = FALSE) {
    $available = &drupal_static(__FUNCTION__);
      $available = array_keys(module_invoke_all('permission'));
    }

    $valid = TRUE;
    foreach ($permissions as $permission) {
      if (!in_array($permission, $available)) {
        $this->fail(t('Invalid permission %permission.', array('%permission' => $permission)), t('Role'));
        $valid = FALSE;
      }
    }
    return $valid;
  }

   * Log in a user with the internal browser.
   *
   * If a user is already logged in, then the current user is logged out before
   * logging in the specified user.
   *
   * Please note that neither the global $user nor the passed-in user object is
   * populated with data of the logged in user. If you need full access to the
   * user object after logging in, it must be updated manually. If you also need
   * access to the plain-text password of the user (set by drupalCreateUser()),
   * e.g. to log in the same user again, then it must be re-assigned manually.
   * For example:
   * @code
   *   // Create a user.
   *   $account = $this->drupalCreateUser(array());
   *   $this->drupalLogin($account);
   *   // Load real user object.
   *   $pass_raw = $account->pass_raw;
   *   $account = user_load($account->uid);
   *   $account->pass_raw = $pass_raw;
   * @endcode
   *   User object representing the user to log in.
  protected function drupalLogin($user) {
      $this->drupalLogout();
    }

    $edit = array(
      'name' => $user->name,
      'pass' => $user->pass_raw
    );
    $this->drupalPost('user', $edit, t('Log in'));

    // If a "log out" link appears on the page, it is almost certainly because
    // the login was successful.
    $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $user->name)), t('User login'));
  /**
   * Generate a token for the currently logged in user.
   */
  protected function drupalGetToken($value = '') {
    $private_key = drupal_get_private_key();
    return drupal_hmac_base64($value, $this->session_id . $private_key);
  /*
   * Logs a user out of the internal browser, then check the login page to confirm logout.
   */
  protected function drupalLogout() {
    // Make a request to the logout page, and redirect to the user page, the
    // idea being if you were properly logged out you should be seeing a login
    // screen.
    $this->drupalGet('user/logout', array('query' => array('destination' => 'user')));
    $this->assertResponse(200, t('User was logged out.'));
    $pass = $this->assertField('name', t('Username field found.'), t('Logout'));
    $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout'));

  /**
   * Sets up a Drupal site for running functional and integration tests.
   *
   * Generates a random database prefix and installs Drupal with the specified
   * installation profile in Drupal\simpletest\WebTestBase::$profile into the
   * prefixed database. Afterwards, installs any additional modules specified by
   * the test.
   *
   * After installation all caches are flushed and several configuration values
   * are reset to the values of the parent site executing the test, since the
   * default values may be incompatible with the environment in which tests are
   * being executed.
   *
   * @param ...
   *   List of modules to enable for the duration of the test. This can be
   *   either a single array or a variable number of string arguments.
   *
   * @see Drupal\simpletest\WebTestBase::prepareDatabasePrefix()
   * @see Drupal\simpletest\WebTestBase::changeDatabasePrefix()
   * @see Drupal\simpletest\WebTestBase::prepareEnvironment()
   */
  protected function setUp() {
    // When running tests through the Simpletest UI (vs. on the command line),
    // Simpletest's batch conflicts with the installer's batch. Batch API does
    // not support the concept of nested batches (in which the nested is not
    // progressive), so we need to temporarily pretend there was no batch.
    // Backup the currently running Simpletest batch.
    $this->originalBatch = batch_get();

    // Create the database prefix for this test.
    $this->prepareDatabasePrefix();

    // Prepare the environment for running tests.
    $this->prepareEnvironment();
    if (!$this->setupEnvironment) {
      return FALSE;
    }
    // Reset all statics and variables to perform tests in a clean environment.
    $conf = array();
    drupal_static_reset();

    // Change the database prefix.
    // All static variables need to be reset before the database prefix is
    // changed, since Drupal\Core\Utility\CacheArray implementations attempt to
    // write back to persistent caches when they are destructed.
    $this->changeDatabasePrefix();
    if (!$this->setupDatabasePrefix) {
      return FALSE;
    }
    // Set the 'simpletest_parent_profile' variable to add the parent profile's
    // search path to the child site's search paths.
    // @see drupal_system_listing()
    $conf['simpletest_parent_profile'] = $this->originalProfile;

    // Set installer parameters.
    // @see install.php, install.core.inc
    $connection_info = Database::getConnectionInfo();
    $this->root_user = (object) array(
      'name' => 'admin',
      'mail' => 'admin@example.com',
      'pass_raw' => $this->randomName(),
    );
    $settings = array(
      'interactive' => FALSE,
      'parameters' => array(
        'profile' => $this->profile,
        'langcode' => 'en',
      ),
      'forms' => array(
        'install_settings_form' => $connection_info['default'],
        'install_configure_form' => array(
          'site_name' => 'Drupal',
          'site_mail' => 'simpletest@example.com',
          'account' => array(
            'name' => $this->root_user->name,
            'mail' => $this->root_user->mail,
            'pass' => array(
              'pass1' => $this->root_user->pass_raw,
              'pass2' => $this->root_user->pass_raw,
            ),
          ),
          // form_type_checkboxes_value() requires NULL instead of FALSE values
          // for programmatic form submissions to disable a checkbox.
          'update_status_module' => array(
            1 => NULL,
            2 => NULL,
          ),
        ),
      ),
    );

    // Replace the global $user session with an anonymous user to resemble a
    // regular installation.
    $user = drupal_anonymous_user();

    // Reset the static batch to remove Simpletest's batch operations.
    $batch = &batch_get();
    $batch = array();
    $variables = array(
      'file_public_path' =>  $this->public_files_directory,
      'file_private_path' =>  $this->private_files_directory,
      'file_temporary_path' =>  $this->temp_files_directory,
      'locale_translate_file_directory' =>  $this->translation_files_directory,
    );
    foreach ($variables as $name => $value) {
      $GLOBALS['conf'][$name] = $value;
    }
    // Execute the non-interactive installer.
    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
    install_drupal($settings);
    foreach ($variables as $name => $value) {
      variable_set($name, $value);
    }

    // Restore the original Simpletest batch.
    $batch = &batch_get();
    $batch = $this->originalBatch;
    // Revert install_begin_request() cache service overrides.
    // Set 'parent_profile' of simpletest to add the parent profile's
    // search path to the child site's search paths.
    // @see drupal_system_listing()
    config('simpletest.settings')->set('parent_profile', $this->originalProfile)->save();
    // Collect modules to install.
    $class = get_class($this);
    while ($class) {
      if (property_exists($class, 'modules')) {
        $modules = array_merge($modules, $class::$modules);
      }
      $class = get_parent_class($class);
    }
      $success = module_enable($modules, TRUE);
      $this->assertTrue($success, t('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
    // Reset/rebuild all data structures after enabling the modules.
    $this->resetAll();
    // Use the test mail class instead of the default mail handler class.
    variable_set('mail_system', array('default-system' => 'Drupal\Core\Mail\VariableLog'));
    drupal_set_time_limit($this->timeLimit);
    // Temporary fix so that when running from run-tests.sh we don't get an
    // empty current path which would indicate we're on the home page.
    $path = current_path();
    if (empty($path)) {
      _current_path('run-tests');
    }
  /**
   * Reset all data structures after having enabled new modules.
   *
   * This method is called by Drupal\simpletest\WebTestBase::setUp() after enabling
   * the requested modules. It must be called again when additional modules
   * are enabled later.
   */
  protected function resetAll() {
    // Clear all database and static caches and rebuild data structures.
    drupal_flush_all_caches();

    // Reload global $conf array and permissions.
    $this->refreshVariables();
    $this->checkPermissions(array(), TRUE);
  }

  /**
   * Refresh the in-memory set of variables. Useful after a page request is made
   * that changes a variable in a different thread.
   *
   * In other words calling a settings page with $this->drupalPost() with a changed
   * value would update a variable to reflect that change, but in the thread that
   * made the call (thread running the test) the changed variable would not be
   * picked up.
   *
   * This method clears the variables cache and loads a fresh copy from the database
   * to ensure that the most up-to-date set of variables is loaded.
   */
  protected function refreshVariables() {
    $conf = variable_initialize();
    drupal_container()->get('config.factory')->reset();
  /**
   * Delete created files and temporary files directory, delete the tables created by setUp(),
   * and reset the database prefix.
   */
  protected function tearDown() {
    // Destroy the testing kernel.
    if (isset($this->kernel)) {
      $this->kernel->shutdown();
    }
    // Ensure that internal logged in variable and cURL options are reset.
    $this->loggedInUser = FALSE;
    $this->additionalCurlOptions = array();
    // Close the CURL handler.
    $this->curlClose();
   * If the simpletest_httpauth_credentials variable is set, this function will
   * add HTTP authentication headers. This is necessary for testing sites that
   * are protected by login credentials from public access.
   * See the description of $curl_options for other options.
    if (!isset($this->curlHandle)) {
      $this->curlHandle = curl_init();

      // Some versions/configurations of cURL break on a NULL cookie jar, so
      // supply a real file.
      if (empty($this->cookieFile)) {
        $this->cookieFile = $this->public_files_directory . '/cookie.jar';
      }

        CURLOPT_COOKIEJAR => $this->cookieFile,
        CURLOPT_SSL_VERIFYPEER => FALSE, // Required to make the tests run on HTTPS.
        CURLOPT_SSL_VERIFYHOST => FALSE, // Required to make the tests run on HTTPS.
        CURLOPT_HEADERFUNCTION => array(&$this, 'curlHeaderCallback'),
        CURLOPT_USERAGENT => $this->databasePrefix,
        $curl_options[CURLOPT_HTTPAUTH] = $this->httpauth_method;
        $curl_options[CURLOPT_USERPWD] = $this->httpauth_credentials;
      // curl_setopt_array() returns FALSE if any of the specified options
      // cannot be set, and stops processing any further options.
      $result = curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
      if (!$result) {
        throw new \UnexpectedValueException('One or more cURL options could not be set.');
      }

      // By default, the child session name should be the same as the parent.
      $this->session_name = session_name();
    // We set the user agent header on each request so as to use the current
    // time and a new uniqid.
    if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) {
      curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
    }
   * Initializes and executes a cURL request.
   *   An associative array of cURL options to set, where the keys are constants
   *   defined by the cURL library. For a list of valid options, see
   *   http://www.php.net/manual/function.curl-setopt.php
   *   FALSE if this is an initial request, TRUE if this request is the result
   *   of a redirect.
   *
   *   The content returned from the call to curl_exec().
   *
   * @see curlInitialize()
  protected function curlExec($curl_options, $redirect = FALSE) {

    // cURL incorrectly handles URLs with a fragment by including the
    // fragment in the request to the server, causing some web servers
    // to reject the request citing "400 - Bad Request". To prevent
    // this, we strip the fragment from the request.
    // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
    if (!empty($curl_options[CURLOPT_URL]) && strpos($curl_options[CURLOPT_URL], '#')) {
      $original_url = $curl_options[CURLOPT_URL];
      $curl_options[CURLOPT_URL] = strtok($curl_options[CURLOPT_URL], '#');
    }

    $url = empty($curl_options[CURLOPT_URL]) ? curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL) : $curl_options[CURLOPT_URL];
    if (!empty($curl_options[CURLOPT_POST])) {
      // This is a fix for the Curl library to prevent Expect: 100-continue
      // headers in POST requests, that may cause unexpected HTTP response
      // codes from some webservers (like lighttpd that returns a 417 error
      // code). It is done by setting an empty "Expect" header field that is
      // not overwritten by Curl.
      $curl_options[CURLOPT_HTTPHEADER][] = 'Expect:';
    }
    curl_setopt_array($this->curlHandle, $this->additionalCurlOptions + $curl_options);
    if (!$redirect) {
      // Reset headers, the session ID and the redirect counter.
      $this->session_id = NULL;
      $this->headers = array();
      $this->redirect_count = 0;
    }

    $content = curl_exec($this->curlHandle);
    $status = curl_getinfo($this->curlHandle, CURLINFO_HTTP_CODE);

    // cURL incorrectly handles URLs with fragments, so instead of
    // letting cURL handle redirects we take of them ourselves to
    // to prevent fragments being sent to the web server as part
    // of the request.
    // TODO: Remove this for Drupal 8, since fixed in curl 7.20.0.
    if (in_array($status, array(300, 301, 302, 303, 305, 307)) && $this->redirect_count < $this->maximumRedirects) {
      if ($this->drupalGetHeader('location')) {
        $this->redirect_count++;
        $curl_options = array();
        $curl_options[CURLOPT_URL] = $this->drupalGetHeader('location');
        $curl_options[CURLOPT_HTTPGET] = TRUE;
        return $this->curlExec($curl_options, TRUE);
      }
    }
    $this->drupalSetContent($content, isset($original_url) ? $original_url : curl_getinfo($this->curlHandle, CURLINFO_EFFECTIVE_URL));
    $message_vars = array(
      '!method' => !empty($curl_options[CURLOPT_NOBODY]) ? 'HEAD' : (empty($curl_options[CURLOPT_POSTFIELDS]) ? 'GET' : 'POST'),
      '@url' => isset($original_url) ? $original_url : $url,