/includes/common.inc
PHP | 7966 lines | 3275 code | 529 blank | 4162 comment | 622 complexity | e56cf64dca7391404a7e2f2571bd6d2d MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, AGPL-1.0
Large files files are truncated, but you can click here to view the full file
- <?php
- /**
- * @file
- * Common functions that many Drupal modules will need to reference.
- *
- * The functions that are critical and need to be available even when serving
- * a cached page are instead located in bootstrap.inc.
- */
- /**
- * @defgroup php_wrappers PHP wrapper functions
- * @{
- * Functions that are wrappers or custom implementations of PHP functions.
- *
- * Certain PHP functions should not be used in Drupal. Instead, Drupal's
- * replacement functions should be used.
- *
- * For example, for improved or more secure UTF8-handling, or RFC-compliant
- * handling of URLs in Drupal.
- *
- * For ease of use and memorizing, all these wrapper functions use the same name
- * as the original PHP function, but prefixed with "drupal_". Beware, however,
- * that not all wrapper functions support the same arguments as the original
- * functions.
- *
- * You should always use these wrapper functions in your code.
- *
- * Wrong:
- * @code
- * $my_substring = substr($original_string, 0, 5);
- * @endcode
- *
- * Correct:
- * @code
- * $my_substring = drupal_substr($original_string, 0, 5);
- * @endcode
- *
- * @}
- */
- /**
- * Return status for saving which involved creating a new item.
- */
- define('SAVED_NEW', 1);
- /**
- * Return status for saving which involved an update to an existing item.
- */
- define('SAVED_UPDATED', 2);
- /**
- * Return status for saving which deleted an existing item.
- */
- define('SAVED_DELETED', 3);
- /**
- * The default group for system CSS files added to the page.
- */
- define('CSS_SYSTEM', -100);
- /**
- * The default group for module CSS files added to the page.
- */
- define('CSS_DEFAULT', 0);
- /**
- * The default group for theme CSS files added to the page.
- */
- define('CSS_THEME', 100);
- /**
- * The default group for JavaScript and jQuery libraries added to the page.
- */
- define('JS_LIBRARY', -100);
- /**
- * The default group for module JavaScript code added to the page.
- */
- define('JS_DEFAULT', 0);
- /**
- * The default group for theme JavaScript code added to the page.
- */
- define('JS_THEME', 100);
- /**
- * Error code indicating that the request exceeded the specified timeout.
- *
- * @see drupal_http_request()
- */
- define('HTTP_REQUEST_TIMEOUT', -1);
- /**
- * Constants defining cache granularity for blocks and renderable arrays.
- *
- * Modules specify the caching patterns for their blocks using binary
- * combinations of these constants in their hook_block_info():
- * $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE;
- * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is
- * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
- * implement
- *
- * The block cache is cleared in cache_clear_all(), and uses the same clearing
- * policy than page cache (node, comment, user, taxonomy added or updated...).
- * Blocks requiring more fine-grained clearing might consider disabling the
- * built-in block cache (DRUPAL_NO_CACHE) and roll their own.
- *
- * Note that user 1 is excluded from block caching.
- */
- /**
- * The block should not get cached.
- *
- * This setting should be used:
- * - For simple blocks (notably those that do not perform any db query), where
- * querying the db cache would be more expensive than directly generating the
- * content.
- * - For blocks that change too frequently.
- */
- define('DRUPAL_NO_CACHE', -1);
- /**
- * The block is handling its own caching in its hook_block_view().
- *
- * From the perspective of the block cache system, this is equivalent to
- * DRUPAL_NO_CACHE. Useful when time based expiration is needed or a site uses
- * a node access which invalidates standard block cache.
- */
- define('DRUPAL_CACHE_CUSTOM', -2);
- /**
- * The block or element can change depending on the user's roles.
- *
- * This is the default setting for blocks, used when the block does not specify
- * anything.
- */
- define('DRUPAL_CACHE_PER_ROLE', 0x0001);
- /**
- * The block or element can change depending on the user.
- *
- * This setting can be resource-consuming for sites with large number of users,
- * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
- */
- define('DRUPAL_CACHE_PER_USER', 0x0002);
- /**
- * The block or element can change depending on the page being viewed.
- */
- define('DRUPAL_CACHE_PER_PAGE', 0x0004);
- /**
- * The block or element is the same for every user and page that it is visible.
- */
- define('DRUPAL_CACHE_GLOBAL', 0x0008);
- /**
- * Adds content to a specified region.
- *
- * @param $region
- * Page region the content is added to.
- * @param $data
- * Content to be added.
- */
- function drupal_add_region_content($region = NULL, $data = NULL) {
- static $content = array();
- if (isset($region) && isset($data)) {
- $content[$region][] = $data;
- }
- return $content;
- }
- /**
- * Gets assigned content for a given region.
- *
- * @param $region
- * A specified region to fetch content for. If NULL, all regions will be
- * returned.
- * @param $delimiter
- * Content to be inserted between imploded array elements.
- */
- function drupal_get_region_content($region = NULL, $delimiter = ' ') {
- $content = drupal_add_region_content();
- if (isset($region)) {
- if (isset($content[$region]) && is_array($content[$region])) {
- return implode($delimiter, $content[$region]);
- }
- }
- else {
- foreach (array_keys($content) as $region) {
- if (is_array($content[$region])) {
- $content[$region] = implode($delimiter, $content[$region]);
- }
- }
- return $content;
- }
- }
- /**
- * Gets the name of the currently active install profile.
- *
- * When this function is called during Drupal's initial installation process,
- * the name of the profile that's about to be installed is stored in the global
- * installation state. At all other times, the standard Drupal systems variable
- * table contains the name of the current profile, and we can call
- * variable_get() to determine what one is active.
- *
- * @return $profile
- * The name of the install profile.
- */
- function drupal_get_profile() {
- global $install_state;
- if (isset($install_state['parameters']['profile'])) {
- $profile = $install_state['parameters']['profile'];
- }
- else {
- $profile = variable_get('install_profile', 'standard');
- }
- return $profile;
- }
- /**
- * Sets the breadcrumb trail for the current page.
- *
- * @param $breadcrumb
- * Array of links, starting with "home" and proceeding up to but not including
- * the current page.
- */
- function drupal_set_breadcrumb($breadcrumb = NULL) {
- $stored_breadcrumb = &drupal_static(__FUNCTION__);
- if (isset($breadcrumb)) {
- $stored_breadcrumb = $breadcrumb;
- }
- return $stored_breadcrumb;
- }
- /**
- * Gets the breadcrumb trail for the current page.
- */
- function drupal_get_breadcrumb() {
- $breadcrumb = drupal_set_breadcrumb();
- if (!isset($breadcrumb)) {
- $breadcrumb = menu_get_active_breadcrumb();
- }
- return $breadcrumb;
- }
- /**
- * Returns a string containing RDF namespace declarations for use in XML and
- * XHTML output.
- */
- function drupal_get_rdf_namespaces() {
- $xml_rdf_namespaces = array();
- // Serializes the RDF namespaces in XML namespace syntax.
- if (function_exists('rdf_get_namespaces')) {
- foreach (rdf_get_namespaces() as $prefix => $uri) {
- $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
- }
- }
- return count($xml_rdf_namespaces) ? "\n " . implode("\n ", $xml_rdf_namespaces) : '';
- }
- /**
- * Adds output to the HEAD tag of the HTML page.
- *
- * This function can be called as long the headers aren't sent. Pass no
- * arguments (or NULL for both) to retrieve the currently stored elements.
- *
- * @param $data
- * A renderable array. If the '#type' key is not set then 'html_tag' will be
- * added as the default '#type'.
- * @param $key
- * A unique string key to allow implementations of hook_html_head_alter() to
- * identify the element in $data. Required if $data is not NULL.
- *
- * @return
- * An array of all stored HEAD elements.
- *
- * @see theme_html_tag()
- */
- function drupal_add_html_head($data = NULL, $key = NULL) {
- $stored_head = &drupal_static(__FUNCTION__);
- if (!isset($stored_head)) {
- // Make sure the defaults, including Content-Type, come first.
- $stored_head = _drupal_default_html_head();
- }
- if (isset($data) && isset($key)) {
- if (!isset($data['#type'])) {
- $data['#type'] = 'html_tag';
- }
- $stored_head[$key] = $data;
- }
- return $stored_head;
- }
- /**
- * Returns elements that are always displayed in the HEAD tag of the HTML page.
- */
- function _drupal_default_html_head() {
- // Add default elements. Make sure the Content-Type comes first because the
- // IE browser may be vulnerable to XSS via encoding attacks from any content
- // that comes before this META tag, such as a TITLE tag.
- $elements['system_meta_content_type'] = array(
- '#type' => 'html_tag',
- '#tag' => 'meta',
- '#attributes' => array(
- 'http-equiv' => 'Content-Type',
- 'content' => 'text/html; charset=utf-8',
- ),
- // Security: This always has to be output first.
- '#weight' => -1000,
- );
- // Show Drupal and the major version number in the META GENERATOR tag.
- // Get the major version.
- list($version, ) = explode('.', VERSION);
- $elements['system_meta_generator'] = array(
- '#type' => 'html_tag',
- '#tag' => 'meta',
- '#attributes' => array(
- 'name' => 'Generator',
- 'content' => 'Drupal ' . $version . ' (http://drupal.org)',
- ),
- );
- // Also send the generator in the HTTP header.
- $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
- return $elements;
- }
- /**
- * Retrieves output to be displayed in the HEAD tag of the HTML page.
- */
- function drupal_get_html_head() {
- $elements = drupal_add_html_head();
- drupal_alter('html_head', $elements);
- return drupal_render($elements);
- }
- /**
- * Adds a feed URL for the current page.
- *
- * This function can be called as long the HTML header hasn't been sent.
- *
- * @param $url
- * An internal system path or a fully qualified external URL of the feed.
- * @param $title
- * The title of the feed.
- */
- function drupal_add_feed($url = NULL, $title = '') {
- $stored_feed_links = &drupal_static(__FUNCTION__, array());
- if (isset($url)) {
- $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
- drupal_add_html_head_link(array(
- 'rel' => 'alternate',
- 'type' => 'application/rss+xml',
- 'title' => $title,
- // Force the URL to be absolute, for consistency with other <link> tags
- // output by Drupal.
- 'href' => url($url, array('absolute' => TRUE)),
- ));
- }
- return $stored_feed_links;
- }
- /**
- * Gets the feed URLs for the current page.
- *
- * @param $delimiter
- * A delimiter to split feeds by.
- */
- function drupal_get_feeds($delimiter = "\n") {
- $feeds = drupal_add_feed();
- return implode($feeds, $delimiter);
- }
- /**
- * @defgroup http_handling HTTP handling
- * @{
- * Functions to properly handle HTTP responses.
- */
- /**
- * Processes a URL query parameter array to remove unwanted elements.
- *
- * @param $query
- * (optional) An array to be processed. Defaults to $_GET.
- * @param $exclude
- * (optional) A list of $query array keys to remove. Use "parent[child]" to
- * exclude nested items. Defaults to array('q').
- * @param $parent
- * Internal use only. Used to build the $query array key for nested items.
- *
- * @return
- * An array containing query parameters, which can be used for url().
- */
- function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') {
- // Set defaults, if none given.
- if (!isset($query)) {
- $query = $_GET;
- }
- // If $exclude is empty, there is nothing to filter.
- if (empty($exclude)) {
- return $query;
- }
- elseif (!$parent) {
- $exclude = array_flip($exclude);
- }
- $params = array();
- foreach ($query as $key => $value) {
- $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
- if (isset($exclude[$string_key])) {
- continue;
- }
- if (is_array($value)) {
- $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
- }
- else {
- $params[$key] = $value;
- }
- }
- return $params;
- }
- /**
- * Splits a URL-encoded query string into an array.
- *
- * @param $query
- * The query string to split.
- *
- * @return
- * An array of url decoded couples $param_name => $value.
- */
- function drupal_get_query_array($query) {
- $result = array();
- if (!empty($query)) {
- foreach (explode('&', $query) as $param) {
- $param = explode('=', $param);
- $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
- }
- }
- return $result;
- }
- /**
- * Parses an array into a valid, rawurlencoded query string.
- *
- * This differs from http_build_query() as we need to rawurlencode() (instead of
- * urlencode()) all query parameters.
- *
- * @param $query
- * The query parameter array to be processed, e.g. $_GET.
- * @param $parent
- * Internal use only. Used to build the $query array key for nested items.
- *
- * @return
- * A rawurlencoded string which can be used as or appended to the URL query
- * string.
- *
- * @see drupal_get_query_parameters()
- * @ingroup php_wrappers
- */
- function drupal_http_build_query(array $query, $parent = '') {
- $params = array();
- foreach ($query as $key => $value) {
- $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key));
- // Recurse into children.
- if (is_array($value)) {
- $params[] = drupal_http_build_query($value, $key);
- }
- // If a query parameter value is NULL, only append its key.
- elseif (!isset($value)) {
- $params[] = $key;
- }
- else {
- // For better readability of paths in query strings, we decode slashes.
- $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
- }
- }
- return implode('&', $params);
- }
- /**
- * Prepares a 'destination' URL query parameter for use with drupal_goto().
- *
- * Used to direct the user back to the referring page after completing a form.
- * By default the current URL is returned. If a destination exists in the
- * previous request, that destination is returned. As such, a destination can
- * persist across multiple pages.
- *
- * @see drupal_goto()
- */
- function drupal_get_destination() {
- $destination = &drupal_static(__FUNCTION__);
- if (isset($destination)) {
- return $destination;
- }
- if (isset($_GET['destination'])) {
- $destination = array('destination' => $_GET['destination']);
- }
- else {
- $path = $_GET['q'];
- $query = drupal_http_build_query(drupal_get_query_parameters());
- if ($query != '') {
- $path .= '?' . $query;
- }
- $destination = array('destination' => $path);
- }
- return $destination;
- }
- /**
- * Parses a system URL string into an associative array suitable for url().
- *
- * This function should only be used for URLs that have been generated by the
- * system, resp. url(). It should not be used for URLs that come from external
- * sources, or URLs that link to external resources.
- *
- * The returned array contains a 'path' that may be passed separately to url().
- * For example:
- * @code
- * $options = drupal_parse_url($_GET['destination']);
- * $my_url = url($options['path'], $options);
- * $my_link = l('Example link', $options['path'], $options);
- * @endcode
- *
- * This is required, because url() does not support relative URLs containing a
- * query string or fragment in its $path argument. Instead, any query string
- * needs to be parsed into an associative query parameter array in
- * $options['query'] and the fragment into $options['fragment'].
- *
- * @param $url
- * The URL string to parse, f.e. $_GET['destination'].
- *
- * @return
- * An associative array containing the keys:
- * - 'path': The path of the URL. If the given $url is external, this includes
- * the scheme and host.
- * - 'query': An array of query parameters of $url, if existent.
- * - 'fragment': The fragment of $url, if existent.
- *
- * @see url()
- * @see drupal_goto()
- * @ingroup php_wrappers
- */
- function drupal_parse_url($url) {
- $options = array(
- 'path' => NULL,
- 'query' => array(),
- 'fragment' => '',
- );
- // External URLs: not using parse_url() here, so we do not have to rebuild
- // the scheme, host, and path without having any use for it.
- if (strpos($url, '://') !== FALSE) {
- // Split off everything before the query string into 'path'.
- $parts = explode('?', $url);
- $options['path'] = $parts[0];
- // If there is a query string, transform it into keyed query parameters.
- if (isset($parts[1])) {
- $query_parts = explode('#', $parts[1]);
- parse_str($query_parts[0], $options['query']);
- // Take over the fragment, if there is any.
- if (isset($query_parts[1])) {
- $options['fragment'] = $query_parts[1];
- }
- }
- }
- // Internal URLs.
- else {
- // parse_url() does not support relative URLs, so make it absolute. E.g. the
- // relative URL "foo/bar:1" isn't properly parsed.
- $parts = parse_url('http://example.com/' . $url);
- // Strip the leading slash that was just added.
- $options['path'] = substr($parts['path'], 1);
- if (isset($parts['query'])) {
- parse_str($parts['query'], $options['query']);
- }
- if (isset($parts['fragment'])) {
- $options['fragment'] = $parts['fragment'];
- }
- }
- // The 'q' parameter contains the path of the current page if clean URLs are
- // disabled. It overrides the 'path' of the URL when present, even if clean
- // URLs are enabled, due to how Apache rewriting rules work.
- if (isset($options['query']['q'])) {
- $options['path'] = $options['query']['q'];
- unset($options['query']['q']);
- }
- return $options;
- }
- /**
- * Encodes a Drupal path for use in a URL.
- *
- * For aesthetic reasons slashes are not escaped.
- *
- * Note that url() takes care of calling this function, so a path passed to that
- * function should not be encoded in advance.
- *
- * @param $path
- * The Drupal path to encode.
- */
- function drupal_encode_path($path) {
- return str_replace('%2F', '/', rawurlencode($path));
- }
- /**
- * Sends the user to a different Drupal page.
- *
- * This issues an on-site HTTP redirect. The function makes sure the redirected
- * URL is formatted correctly.
- *
- * Usually the redirected URL is constructed from this function's input
- * parameters. However you may override that behavior by setting a
- * destination in either the $_REQUEST-array (i.e. by using
- * the query string of an URI) This is used to direct the user back to
- * the proper page after completing a form. For example, after editing
- * a post on the 'admin/content'-page or after having logged on using the
- * 'user login'-block in a sidebar. The function drupal_get_destination()
- * can be used to help set the destination URL.
- *
- * Drupal will ensure that messages set by drupal_set_message() and other
- * session data are written to the database before the user is redirected.
- *
- * This function ends the request; use it instead of a return in your menu
- * callback.
- *
- * @param $path
- * A Drupal path or a full URL.
- * @param $options
- * An associative array of additional URL options to pass to url().
- * @param $http_response_code
- * Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
- * - 301 Moved Permanently (the recommended value for most redirects)
- * - 302 Found (default in Drupal and PHP, sometimes used for spamming search
- * engines)
- * - 303 See Other
- * - 304 Not Modified
- * - 305 Use Proxy
- * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
- * Note: Other values are defined by RFC 2616, but are rarely used and poorly
- * supported.
- *
- * @see drupal_get_destination()
- * @see url()
- */
- function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
- // A destination in $_GET always overrides the function arguments.
- // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
- if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
- $destination = drupal_parse_url($_GET['destination']);
- $path = $destination['path'];
- $options['query'] = $destination['query'];
- $options['fragment'] = $destination['fragment'];
- }
- drupal_alter('drupal_goto', $path, $options, $http_response_code);
- // The 'Location' HTTP header must be absolute.
- $options['absolute'] = TRUE;
- $url = url($path, $options);
- header('Location: ' . $url, TRUE, $http_response_code);
- // The "Location" header sends a redirect status code to the HTTP daemon. In
- // some cases this can be wrong, so we make sure none of the code below the
- // drupal_goto() call gets executed upon redirection.
- drupal_exit($url);
- }
- /**
- * Delivers a "site is under maintenance" message to the browser.
- *
- * Page callback functions wanting to report a "site offline" message should
- * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
- * functions that are invoked in contexts where that return value might not
- * bubble up to menu_execute_active_handler() should call drupal_site_offline().
- */
- function drupal_site_offline() {
- drupal_deliver_page(MENU_SITE_OFFLINE);
- }
- /**
- * Delivers a "page not found" error to the browser.
- *
- * Page callback functions wanting to report a "page not found" message should
- * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
- * functions that are invoked in contexts where that return value might not
- * bubble up to menu_execute_active_handler() should call drupal_not_found().
- */
- function drupal_not_found() {
- drupal_deliver_page(MENU_NOT_FOUND);
- }
- /**
- * Delivers an "access denied" error to the browser.
- *
- * Page callback functions wanting to report an "access denied" message should
- * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
- * functions that are invoked in contexts where that return value might not
- * bubble up to menu_execute_active_handler() should call
- * drupal_access_denied().
- */
- function drupal_access_denied() {
- drupal_deliver_page(MENU_ACCESS_DENIED);
- }
- /**
- * Performs an HTTP request.
- *
- * This is a flexible and powerful HTTP client implementation. Correctly
- * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
- *
- * @param $url
- * A string containing a fully qualified URI.
- * @param array $options
- * (optional) An array that can have one or more of the following elements:
- * - headers: An array containing request headers to send as name/value pairs.
- * - method: A string containing the request method. Defaults to 'GET'.
- * - data: A string containing the request body, formatted as
- * 'param=value¶m=value&...'. Defaults to NULL.
- * - max_redirects: An integer representing how many times a redirect
- * may be followed. Defaults to 3.
- * - timeout: A float representing the maximum number of seconds the function
- * call may take. The default is 30 seconds. If a timeout occurs, the error
- * code is set to the HTTP_REQUEST_TIMEOUT constant.
- * - context: A context resource created with stream_context_create().
- *
- * @return object
- * An object that can have one or more of the following components:
- * - request: A string containing the request body that was sent.
- * - code: An integer containing the response status code, or the error code
- * if an error occurred.
- * - protocol: The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
- * - status_message: The status message from the response, if a response was
- * received.
- * - redirect_code: If redirected, an integer containing the initial response
- * status code.
- * - redirect_url: If redirected, a string containing the URL of the redirect
- * target.
- * - error: If an error occurred, the error message. Otherwise not set.
- * - headers: An array containing the response headers as name/value pairs.
- * HTTP header names are case-insensitive (RFC 2616, section 4.2), so for
- * easy access the array keys are returned in lower case.
- * - data: A string containing the response body that was received.
- */
- function drupal_http_request($url, array $options = array()) {
- $result = new stdClass();
- // Parse the URL and make sure we can handle the schema.
- $uri = @parse_url($url);
- if ($uri == FALSE) {
- $result->error = 'unable to parse URL';
- $result->code = -1001;
- return $result;
- }
- if (!isset($uri['scheme'])) {
- $result->error = 'missing schema';
- $result->code = -1002;
- return $result;
- }
- timer_start(__FUNCTION__);
- // Merge the default options.
- $options += array(
- 'headers' => array(),
- 'method' => 'GET',
- 'data' => NULL,
- 'max_redirects' => 3,
- 'timeout' => 30.0,
- 'context' => NULL,
- );
- // stream_socket_client() requires timeout to be a float.
- $options['timeout'] = (float) $options['timeout'];
- switch ($uri['scheme']) {
- case 'http':
- case 'feed':
- $port = isset($uri['port']) ? $uri['port'] : 80;
- $socket = 'tcp://' . $uri['host'] . ':' . $port;
- // RFC 2616: "non-standard ports MUST, default ports MAY be included".
- // We don't add the standard port to prevent from breaking rewrite rules
- // checking the host that do not take into account the port number.
- $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
- break;
- case 'https':
- // Note: Only works when PHP is compiled with OpenSSL support.
- $port = isset($uri['port']) ? $uri['port'] : 443;
- $socket = 'ssl://' . $uri['host'] . ':' . $port;
- $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
- break;
- default:
- $result->error = 'invalid schema ' . $uri['scheme'];
- $result->code = -1003;
- return $result;
- }
- if (empty($options['context'])) {
- $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
- }
- else {
- // Create a stream with context. Allows verification of a SSL certificate.
- $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);
- }
- // Make sure the socket opened properly.
- if (!$fp) {
- // When a network error occurs, we use a negative number so it does not
- // clash with the HTTP status codes.
- $result->code = -$errno;
- $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
- // Mark that this request failed. This will trigger a check of the web
- // server's ability to make outgoing HTTP requests the next time that
- // requirements checking is performed.
- // See system_requirements().
- variable_set('drupal_http_request_fails', TRUE);
- return $result;
- }
- // Construct the path to act on.
- $path = isset($uri['path']) ? $uri['path'] : '/';
- if (isset($uri['query'])) {
- $path .= '?' . $uri['query'];
- }
- // Merge the default headers.
- $options['headers'] += array(
- 'User-Agent' => 'Drupal (+http://drupal.org/)',
- );
- // Only add Content-Length if we actually have any content or if it is a POST
- // or PUT request. Some non-standard servers get confused by Content-Length in
- // at least HEAD/GET requests, and Squid always requires Content-Length in
- // POST/PUT requests.
- $content_length = strlen($options['data']);
- if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
- $options['headers']['Content-Length'] = $content_length;
- }
- // If the server URL has a user then attempt to use basic authentication.
- if (isset($uri['user'])) {
- $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (isset($uri['pass']) ? ':' . $uri['pass'] : ''));
- }
- // If the database prefix is being used by SimpleTest to run the tests in a copied
- // database then set the user-agent header to the database prefix so that any
- // calls to other Drupal pages will run the SimpleTest prefixed database. The
- // user-agent is used to ensure that multiple testing sessions running at the
- // same time won't interfere with each other as they would if the database
- // prefix were stored statically in a file or database variable.
- $test_info = &$GLOBALS['drupal_test_info'];
- if (!empty($test_info['test_run_id'])) {
- $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
- }
- $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
- foreach ($options['headers'] as $name => $value) {
- $request .= $name . ': ' . trim($value) . "\r\n";
- }
- $request .= "\r\n" . $options['data'];
- $result->request = $request;
- // Calculate how much time is left of the original timeout value.
- $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
- if ($timeout > 0) {
- stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
- fwrite($fp, $request);
- }
- // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
- // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
- // instead must invoke stream_get_meta_data() each iteration.
- $info = stream_get_meta_data($fp);
- $alive = !$info['eof'] && !$info['timed_out'];
- $response = '';
- while ($alive) {
- // Calculate how much time is left of the original timeout value.
- $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
- if ($timeout <= 0) {
- $info['timed_out'] = TRUE;
- break;
- }
- stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
- $chunk = fread($fp, 1024);
- $response .= $chunk;
- $info = stream_get_meta_data($fp);
- $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
- }
- fclose($fp);
- if ($info['timed_out']) {
- $result->code = HTTP_REQUEST_TIMEOUT;
- $result->error = 'request timed out';
- return $result;
- }
- // Parse response headers from the response body.
- // Be tolerant of malformed HTTP responses that separate header and body with
- // \n\n or \r\r instead of \r\n\r\n.
- list($response, $result->data) = preg_split("/\r\n\r\n|\n\n|\r\r/", $response, 2);
- $response = preg_split("/\r\n|\n|\r/", $response);
- // Parse the response status line.
- list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
- $result->protocol = $protocol;
- $result->status_message = $status_message;
- $result->headers = array();
- // Parse the response headers.
- while ($line = trim(array_shift($response))) {
- list($name, $value) = explode(':', $line, 2);
- $name = strtolower($name);
- if (isset($result->headers[$name]) && $name == 'set-cookie') {
- // RFC 2109: the Set-Cookie response header comprises the token Set-
- // Cookie:, followed by a comma-separated list of one or more cookies.
- $result->headers[$name] .= ',' . trim($value);
- }
- else {
- $result->headers[$name] = trim($value);
- }
- }
- $responses = array(
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative Information',
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content',
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found',
- 303 => 'See Other',
- 304 => 'Not Modified',
- 305 => 'Use Proxy',
- 307 => 'Temporary Redirect',
- 400 => 'Bad Request',
- 401 => 'Unauthorized',
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required',
- 408 => 'Request Time-out',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed',
- 413 => 'Request Entity Too Large',
- 414 => 'Request-URI Too Large',
- 415 => 'Unsupported Media Type',
- 416 => 'Requested range not satisfiable',
- 417 => 'Expectation Failed',
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Time-out',
- 505 => 'HTTP Version not supported',
- );
- // RFC 2616 states that all unknown HTTP codes must be treated the same as the
- // base code in their class.
- if (!isset($responses[$code])) {
- $code = floor($code / 100) * 100;
- }
- $result->code = $code;
- switch ($code) {
- case 200: // OK
- case 304: // Not modified
- break;
- case 301: // Moved permanently
- case 302: // Moved temporarily
- case 307: // Moved temporarily
- $location = $result->headers['location'];
- $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
- if ($options['timeout'] <= 0) {
- $result->code = HTTP_REQUEST_TIMEOUT;
- $result->error = 'request timed out';
- }
- elseif ($options['max_redirects']) {
- // Redirect to the new location.
- $options['max_redirects']--;
- $result = drupal_http_request($location, $options);
- $result->redirect_code = $code;
- }
- if (!isset($result->redirect_url)) {
- $result->redirect_url = $location;
- }
- break;
- default:
- $result->error = $status_message;
- }
- return $result;
- }
- /**
- * @} End of "HTTP handling".
- */
- /**
- * Strips slashes from a string or array of strings.
- *
- * Callback for array_walk() within fix_gpx_magic().
- *
- * @param $item
- * An individual string or array of strings from superglobals.
- */
- function _fix_gpc_magic(&$item) {
- if (is_array($item)) {
- array_walk($item, '_fix_gpc_magic');
- }
- else {
- $item = stripslashes($item);
- }
- }
- /**
- * Strips slashes from $_FILES items.
- *
- * Callback for array_walk() within fix_gpc_magic().
- *
- * The tmp_name key is skipped keys since PHP generates single backslashes for
- * file paths on Windows systems.
- *
- * @param $item
- * An item from $_FILES.
- * @param $key
- * The key for the item within $_FILES.
- *
- * @see http://php.net/manual/en/features.file-upload.php#42280
- */
- function _fix_gpc_magic_files(&$item, $key) {
- if ($key != 'tmp_name') {
- if (is_array($item)) {
- array_walk($item, '_fix_gpc_magic_files');
- }
- else {
- $item = stripslashes($item);
- }
- }
- }
- /**
- * Fixes double-escaping caused by "magic quotes" in some PHP installations.
- *
- * @see _fix_gpc_magic()
- * @see _fix_gpc_magic_files()
- */
- function fix_gpc_magic() {
- static $fixed = FALSE;
- if (!$fixed && ini_get('magic_quotes_gpc')) {
- array_walk($_GET, '_fix_gpc_magic');
- array_walk($_POST, '_fix_gpc_magic');
- array_walk($_COOKIE, '_fix_gpc_magic');
- array_walk($_REQUEST, '_fix_gpc_magic');
- array_walk($_FILES, '_fix_gpc_magic_files');
- }
- $fixed = TRUE;
- }
- /**
- * @defgroup validation Input validation
- * @{
- * Functions to validate user input.
- */
- /**
- * Verifies the syntax of the given e-mail address.
- *
- * Empty e-mail addresses are allowed. See RFC 2822 for details.
- *
- * @param $mail
- * A string containing an e-mail address.
- *
- * @return
- * TRUE if the address is in a valid format.
- */
- function valid_email_address($mail) {
- return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
- }
- /**
- * Verifies the syntax of the given URL.
- *
- * This function should only be used on actual URLs. It should not be used for
- * Drupal menu paths, which can contain arbitrary characters.
- * Valid values per RFC 3986.
- * @param $url
- * The URL to verify.
- * @param $absolute
- * Whether the URL is absolute (beginning with a scheme such as "http:").
- *
- * @return
- * TRUE if the URL is in a valid format.
- */
- function valid_url($url, $absolute = FALSE) {
- if ($absolute) {
- return (bool)preg_match("
- /^ # Start at the beginning of the text
- (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes
- (?: # Userinfo (optional) which is typically
- (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
- (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
- )?
- (?:
- (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
- |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
- )
- (?::[0-9]+)? # Server port number (optional)
- (?:[\/|\?]
- (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
- *)?
- $/xi", $url);
- }
- else {
- return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
- }
- }
- /**
- * @} End of "defgroup validation".
- */
- /**
- * Registers an event for the current visitor to the flood control mechanism.
- *
- * @param $name
- * The name of an event.
- * @param $window
- * Optional number of seconds before this event expires. Defaults to 3600 (1
- * hour). Typically uses the same value as the flood_is_allowed() $window
- * parameter. Expired events are purged on cron run to prevent the flood table
- * from growing indefinitely.
- * @param $identifier
- * Optional identifier (defaults to the current user's IP address).
- */
- function flood_register_event($name, $window = 3600, $identifier = NULL) {
- if (!isset($identifier)) {
- $identifier = ip_address();
- }
- db_insert('flood')
- ->fields(array(
- 'event' => $name,
- 'identifier' => $identifier,
- 'timestamp' => REQUEST_TIME,
- 'expiration' => REQUEST_TIME + $window,
- ))
- ->execute();
- }
- /**
- * Makes the flood control mechanism forget an event for the current visitor.
- *
- * @param $name
- * The name of an event.
- * @param $identifier
- * Optional identifier (defaults to the current user's IP address).
- */
- function flood_clear_event($name, $identifier = NULL) {
- if (!isset($identifier)) {
- $identifier = ip_address();
- }
- db_delete('flood')
- ->condition('event', $name)
- ->condition('identifier', $identifier)
- ->execute();
- }
- /**
- * Checks whether a user is allowed to proceed with the specified event.
- *
- * Events can have thresholds saying that each user can only do that event
- * a certain number of times in a time window. This function verifies that the
- * current user has not exceeded this threshold.
- *
- * @param $name
- * The unique name of the event.
- * @param $threshold
- * The maximum number of times each user can do this event per time window.
- * @param $window
- * Number of seconds in the time window for this event (default is 3600
- * seconds, or 1 hour).
- * @param $identifier
- * Unique identifier of the current user. Defaults to their IP address.
- *
- * @return
- * TRUE if the user is allowed to proceed. FALSE if they have exceeded the
- * threshold and should not be allowed to proceed.
- */
- function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) {
- if (!isset($identifier)) {
- $identifier = ip_address();
- }
- $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array(
- ':event' => $name,
- ':identifier' => $identifier,
- ':timestamp' => REQUEST_TIME - $window))
- ->fetchField();
- return ($number < $threshold);
- }
- /**
- * @defgroup sanitization Sanitization functions
- * @{
- * Functions to sanitize values.
- *
- * See http://drupal.org/writing-secure-code for information
- * on writing secure code.
- */
- /**
- * Strips dangerous protocols (e.g. 'javascript:') from a URI.
- *
- * This function must be called for all URIs within user-entered input prior
- * to being output to an HTML attribute value. It is often called as part of
- * check_url() or filter_xss(), but those functions return an HTML-encoded
- * string, so this function can be called independently when the output needs to
- * be a plain-text string for passing to t(), l(), drupal_attributes(), or
- * another function that will call check_plain() separately.
- *
- * @param $uri
- * A plain-text URI that might contain dangerous protocols.
- *
- * @return
- * A plain-text URI stripped of dangerous protocols. As with all plain-text
- * strings, this return value must not be output to an HTML page without
- * check_plain() being called on it. However, it can be passed to functions
- * expecting plain-text strings.
- *
- * @see check_url()
- */
- function drupal_strip_dangerous_protocols($uri) {
- static $allowed_protocols;
- if (!isset($allowed_protocols)) {
- $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'tel', 'telnet', 'webcal')));
- }
- // Iteratively remove any invalid protocol found.
- do {
- $before = $uri;
- $colonpos = strpos($uri, ':');
- if ($colonpos > 0) {
- // We found a colon, possibly a protocol. Verify.
- $protocol = substr($uri, 0, $colonpos);
- // If a colon is preceded by a slash, question mark or hash, it cannot
- // possibly be part of the URL scheme. This must be a relative URL, which
- // inherits the (safe) protocol of the base document.
- if (preg_match('![/?#]!', $protocol)) {
- break;
- }
- // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
- // (URI Comparison) scheme comparison must be case-insensitive.
- if (!isset($allowed_protocols[strtolower($protocol)])) {
- $uri = substr($uri, $colonpos + 1);
- }
- }
- } while ($before != $uri);
- return $uri;
- }
- /**
- * Strips dangerous protocols from a URI and encodes it for output to HTML.
- *
- * @param $uri
- * A plain-text URI that might contain dangerous protocols.
- *
- * @return
- * A URI stripped of dangerous protocols and encoded for output to an HTML
- * attribute value. Because it is already encoded, it should not be set as a
- * value within a $attributes array passed to drupal_attributes(), because
- * drupal_attributes() expects those values to be plain-text strings. To pass
- * a filtered URI to drupal_attributes(), call
- * drupal_strip_dangerous_protocols() instead.
- *
- * @see drupal_strip_dangerous_protocols()
- */
- function check_url($uri) {
- return check_plain(drupal_strip_dangerous_protocols($uri));
- }
- /**
- * Applies a very permissive XSS/HTML filter for admin-only use.
- *
- * Use only for fields where it is impractical to use the
- * whole filter system, but where some (mainly inline) mark-up
- * is desired (so check_plain() is not acceptable).
- *
- * Allows all tags that can be used inside an HTML body, save
- * for scripts and styles.
- */
- function filter_xss_admin($string) {
- return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'article', 'aside', 'b', 'bdi', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'command', 'dd', 'del', 'details', 'dfn', 'div', 'dl', 'dt', 'em', 'figcaption', 'figure', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'mark', 'menu', 'meter', 'nav', 'ol', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'small', 'span', 'strong', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'time', 'tr', 'tt', 'u', 'ul', 'var', 'wbr'));
- }
- /**
- * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities.
- *
- * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
- * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
- *
- * This code does four things:
- * - Removes characters and constructs that can trick browsers.
- * - Makes sure all HTML entities are well-formed.
- * - Makes sure all HTML tags and attributes are well-formed.
- * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
- * javascript:).
- *
- * @param $string
- * The string with raw HTML in it. It will be stripped of everything that can
- * cause an XSS attack.
- * @param $allowed_tags
- * An array of allowed tags.
- *
- * @return
- * An XSS safe version of $string, or an empty string if $string is not
- * valid UTF-8.
- *
- * @see drupal_validate_utf8()
- * @ingroup sanitization
- */
- function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
- // Only operate on valid UTF-8 strings. This is necessary to prevent cross
- // site scripting issues on Internet Explorer 6.
- if (!drupal_validate_utf8($string)) {
- return '';
- }
- // Store the text format.
- _filter_xss_split($allowed_tags, TRUE);
- // Remove NULL characters (ignored by some browsers).
- $string = str_replace(chr(0), '', $string);
- // Remove Netscape 4 JS entities.
- $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
- // Defuse all HTML entities.
- $string = str_replace('&', '&', $string);
- // Change back only well-formed entities in our whitelist:
- // Decimal numeric entities.
- $string = preg_replace('/&#([0-9]+;)/', '&#\1', $string);
- // Hexadecimal numeric entities.
- $string = preg_replace('/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
- // Named entities.
- $string = preg_replace('/&([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
- return preg_replace_callback('%
- (
- <(?=[^a-zA-Z!/]) # a lone <
- | # or
- <!--.*?--> # a comment
- | # or
- <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
- | # or
- > # just a >
- )%x', '_filter_xss_split', $string);
- }
- /**
- * Processes an HTML tag.
- *
- * @param $m
- * An array with various meaning depending on the value of $store.
- * If $store is TRUE then the array contains the allowed tags.
- * If $store is FALSE then the array has one element, the HTML tag to process.
- * @param $store
- * Whether to store $m.
- *
- * @return
- * If the element isn't allowed, an empty string. Otherwise, the cleaned up
- * version of the HTML element.
- */
- function _filter_xss_split($m, $store = FALSE) {
- static $allowed_html;
- if ($store) {
- $allowed_html = array_flip($m);
- return;
- }
- $string = $m[1];
- if (substr($string, 0, 1) != '<') {
- // We matched a lone ">" character.
- return '>';
- }
- elseif (strlen($string) == 1) {
- // We matched a lone "<" character.
- return '<';
- }
- if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
- // Seriously malformed.
- return '';
- }
- $slash = trim($matches[1]);
- $elem = &$matches[2];
- $attrlist = &$matches[3];
- $comment = &$matches[4];
- if ($comment) {
- $elem = '!--';
- }
- if (!isset($allowed_html[strtolower($elem)])) {
- // Disallowed HTML element.
- return '';
- }
- if ($comment) {
- return $comment;
- }
- if ($slash != '') {
- return "</$elem>";
- }
- // Is there a closing XHTML slash at the end of the attributes?
- $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
- $xhtml_slash = $count ? ' /' : '';
- // Clean up attributes.
- $attr2 = implode(' ', _filter_xss_attributes($attrlist));
- $attr2 = preg_replace('/[<>]/', '', $attr2);
- $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
- return "<$elem$attr2$xhtml_slash>";
- }
- /**
- * Processes a string of HTML attributes.
- *
- * @return
- * Cleaned up version of the HTML attributes.
- */
- function _filter_xss_attributes($attr) {
- $attrarr = array();
- $mode = 0;
- $attrname = '';
- while (strlen($attr) != 0) {
- // Was the last operation successful?
- $working = 0;
- switch ($mode) {
- case 0:
- // Attribute name, href for instance.
- if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
- $attrname = strtolower($match[1]);
- $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
- $working = $mode = 1;
- $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
- }
- break;
- case 1:
- // Equals sign or valueless ("selected").
- if (preg_match('/^\s*=\s*/', $attr)) {
- $working = 1; $mode = 2;
- $attr = preg_replace('/^\s*=\s*/', '', $attr);
- break;
- }
- if (preg_match('/^\s+/', $attr)) {
- $working = 1; $mode = 0;
- if (!$skip) {
- $attrarr[] = $attrname;
- }
- $attr = preg_replace('/^\s+/', '', $attr);
- }
- break;
- case 2:
- // Attribute value, a URL after href= for instance.
- if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
- $thisval = filter_xss_bad_protocol($match[1]);
- if (!$skip) {
- $attrarr[] = "$attrname=\"$thisval\"";
- }
- $working = 1;
- $mode = 0;
- $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
- break;
- }
- if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
- $thisval = filter_xss_bad_protocol($match[1]);
- if (!$skip) {
- $attrarr[] = "$attrname='$thisval'";
- }
- $working = 1; $mode = 0;
- $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
- break;
- }
- if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
- $thisval = filter_xss_bad_protocol($match[1]);
- if (!$skip) {
- $attrarr[] = "$attrname=\"$thisval\"";
- }
- $working = 1; $mode = 0;
- $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
- }
- break;
- }
- if ($working == 0) {
- // Not well formed; remove and try again.
- $attr = preg_replace('/
- ^
- (
- "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
- | # or
- \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
- |…
Large files files are truncated, but you can click here to view the full file