PageRenderTime 77ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 2ms

/includes/common.inc

https://bitbucket.org/robbiethegeek/robbie-drupal7
PHP | 7358 lines | 3086 code | 501 blank | 3771 comment | 567 complexity | 3b7b2861d9d1fab2ef0c069aa685f0bb MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. // $Id: common.inc,v 1.1236 2010/10/07 00:28:19 webchick Exp $
  3. /**
  4. * @file
  5. * Common functions that many Drupal modules will need to reference.
  6. *
  7. * The functions that are critical and need to be available even when serving
  8. * a cached page are instead located in bootstrap.inc.
  9. */
  10. /**
  11. * @defgroup php_wrappers PHP wrapper functions
  12. * @{
  13. * Functions that are wrappers or custom implementations of PHP functions.
  14. *
  15. * Certain PHP functions should not be used in Drupal. Instead, Drupal's
  16. * replacement functions should be used.
  17. *
  18. * For example, for improved or more secure UTF8-handling, or RFC-compliant
  19. * handling of URLs in Drupal.
  20. *
  21. * For ease of use and memorizing, all these wrapper functions use the same name
  22. * as the original PHP function, but prefixed with "drupal_". Beware, however,
  23. * that not all wrapper functions support the same arguments as the original
  24. * functions.
  25. *
  26. * You should always use these wrapper functions in your code.
  27. *
  28. * Wrong:
  29. * @code
  30. * $my_substring = substr($original_string, 0, 5);
  31. * @endcode
  32. *
  33. * Correct:
  34. * @code
  35. * $my_substring = drupal_substr($original_string, 0, 5);
  36. * @endcode
  37. *
  38. * @} End of "defgroup php_wrappers".
  39. */
  40. /**
  41. * Return status for saving which involved creating a new item.
  42. */
  43. define('SAVED_NEW', 1);
  44. /**
  45. * Return status for saving which involved an update to an existing item.
  46. */
  47. define('SAVED_UPDATED', 2);
  48. /**
  49. * Return status for saving which deleted an existing item.
  50. */
  51. define('SAVED_DELETED', 3);
  52. /**
  53. * The default group for system CSS files added to the page.
  54. */
  55. define('CSS_SYSTEM', -100);
  56. /**
  57. * The default group for module CSS files added to the page.
  58. */
  59. define('CSS_DEFAULT', 0);
  60. /**
  61. * The default group for theme CSS files added to the page.
  62. */
  63. define('CSS_THEME', 100);
  64. /**
  65. * The default group for JavaScript libraries, settings or jQuery plugins added
  66. * to the page.
  67. */
  68. define('JS_LIBRARY', -100);
  69. /**
  70. * The default group for module JavaScript code added to the page.
  71. */
  72. define('JS_DEFAULT', 0);
  73. /**
  74. * The default group for theme JavaScript code added to the page.
  75. */
  76. define('JS_THEME', 100);
  77. /**
  78. * Error code indicating that the request made by drupal_http_request() exceeded
  79. * the specified timeout.
  80. */
  81. define('HTTP_REQUEST_TIMEOUT', 1);
  82. /**
  83. * Constants defining cache granularity for blocks and renderable arrays.
  84. *
  85. * Modules specify the caching patterns for their blocks using binary
  86. * combinations of these constants in their hook_block_info():
  87. * $block[delta]['cache'] = DRUPAL_CACHE_PER_ROLE | DRUPAL_CACHE_PER_PAGE;
  88. * DRUPAL_CACHE_PER_ROLE is used as a default when no caching pattern is
  89. * specified. Use DRUPAL_CACHE_CUSTOM to disable standard block cache and
  90. * implement
  91. *
  92. * The block cache is cleared in cache_clear_all(), and uses the same clearing
  93. * policy than page cache (node, comment, user, taxonomy added or updated...).
  94. * Blocks requiring more fine-grained clearing might consider disabling the
  95. * built-in block cache (DRUPAL_NO_CACHE) and roll their own.
  96. *
  97. * Note that user 1 is excluded from block caching.
  98. */
  99. /**
  100. * The block should not get cached. This setting should be used:
  101. * - for simple blocks (notably those that do not perform any db query),
  102. * where querying the db cache would be more expensive than directly generating
  103. * the content.
  104. * - for blocks that change too frequently.
  105. */
  106. define('DRUPAL_NO_CACHE', -1);
  107. /**
  108. * The block is handling its own caching in its hook_block_view(). From the
  109. * perspective of the block cache system, this is equivalent to DRUPAL_NO_CACHE.
  110. * Useful when time based expiration is needed or a site uses a node access
  111. * which invalidates standard block cache.
  112. */
  113. define('DRUPAL_CACHE_CUSTOM', -2);
  114. /**
  115. * The block or element can change depending on the roles the user viewing the
  116. * page belongs to. This is the default setting for blocks, used when the block
  117. * does not specify anything.
  118. */
  119. define('DRUPAL_CACHE_PER_ROLE', 0x0001);
  120. /**
  121. * The block or element can change depending on the user viewing the page.
  122. * This setting can be resource-consuming for sites with large number of users,
  123. * and thus should only be used when DRUPAL_CACHE_PER_ROLE is not sufficient.
  124. */
  125. define('DRUPAL_CACHE_PER_USER', 0x0002);
  126. /**
  127. * The block or element can change depending on the page being viewed.
  128. */
  129. define('DRUPAL_CACHE_PER_PAGE', 0x0004);
  130. /**
  131. * The block or element is the same for every user on every page where it is visible.
  132. */
  133. define('DRUPAL_CACHE_GLOBAL', 0x0008);
  134. /**
  135. * Add content to a specified region.
  136. *
  137. * @param $region
  138. * Page region the content is added to.
  139. * @param $data
  140. * Content to be added.
  141. */
  142. function drupal_add_region_content($region = NULL, $data = NULL) {
  143. static $content = array();
  144. if (isset($region) && isset($data)) {
  145. $content[$region][] = $data;
  146. }
  147. return $content;
  148. }
  149. /**
  150. * Get assigned content for a given region.
  151. *
  152. * @param $region
  153. * A specified region to fetch content for. If NULL, all regions will be
  154. * returned.
  155. * @param $delimiter
  156. * Content to be inserted between imploded array elements.
  157. */
  158. function drupal_get_region_content($region = NULL, $delimiter = ' ') {
  159. $content = drupal_add_region_content();
  160. if (isset($region)) {
  161. if (isset($content[$region]) && is_array($content[$region])) {
  162. return implode($delimiter, $content[$region]);
  163. }
  164. }
  165. else {
  166. foreach (array_keys($content) as $region) {
  167. if (is_array($content[$region])) {
  168. $content[$region] = implode($delimiter, $content[$region]);
  169. }
  170. }
  171. return $content;
  172. }
  173. }
  174. /**
  175. * Get the name of the currently active install profile.
  176. *
  177. * When this function is called during Drupal's initial installation process,
  178. * the name of the profile that's about to be installed is stored in the global
  179. * installation state. At all other times, the standard Drupal systems variable
  180. * table contains the name of the current profile, and we can call variable_get()
  181. * to determine what one is active.
  182. *
  183. * @return $profile
  184. * The name of the install profile.
  185. */
  186. function drupal_get_profile() {
  187. global $install_state;
  188. if (isset($install_state['parameters']['profile'])) {
  189. $profile = $install_state['parameters']['profile'];
  190. }
  191. else {
  192. $profile = variable_get('install_profile', 'standard');
  193. }
  194. return $profile;
  195. }
  196. /**
  197. * Set the breadcrumb trail for the current page.
  198. *
  199. * @param $breadcrumb
  200. * Array of links, starting with "home" and proceeding up to but not including
  201. * the current page.
  202. */
  203. function drupal_set_breadcrumb($breadcrumb = NULL) {
  204. $stored_breadcrumb = &drupal_static(__FUNCTION__);
  205. if (isset($breadcrumb)) {
  206. $stored_breadcrumb = $breadcrumb;
  207. }
  208. return $stored_breadcrumb;
  209. }
  210. /**
  211. * Get the breadcrumb trail for the current page.
  212. */
  213. function drupal_get_breadcrumb() {
  214. $breadcrumb = drupal_set_breadcrumb();
  215. if (!isset($breadcrumb)) {
  216. $breadcrumb = menu_get_active_breadcrumb();
  217. }
  218. return $breadcrumb;
  219. }
  220. /**
  221. * Returns a string containing RDF namespace declarations for use in XML and
  222. * XHTML output.
  223. */
  224. function drupal_get_rdf_namespaces() {
  225. $xml_rdf_namespaces = array();
  226. // Serializes the RDF namespaces in XML namespace syntax.
  227. if (function_exists('rdf_get_namespaces')) {
  228. foreach (rdf_get_namespaces() as $prefix => $uri) {
  229. $xml_rdf_namespaces[] = 'xmlns:' . $prefix . '="' . $uri . '"';
  230. }
  231. }
  232. return count($xml_rdf_namespaces) ? "\n " . implode("\n ", $xml_rdf_namespaces) : '';
  233. }
  234. /**
  235. * Add output to the head tag of the HTML page.
  236. *
  237. * This function can be called as long the headers aren't sent. Pass no
  238. * arguments (or NULL for both) to retrieve the currently stored elements.
  239. *
  240. * @param $data
  241. * A renderable array. If the '#type' key is not set then 'html_tag' will be
  242. * added as the default '#type'.
  243. * @param $key
  244. * A unique string key to allow implementations of hook_html_head_alter() to
  245. * identify the element in $data. Required if $data is not NULL.
  246. *
  247. * @return
  248. * An array of all stored HEAD elements.
  249. *
  250. * @see theme_html_tag()
  251. */
  252. function drupal_add_html_head($data = NULL, $key = NULL) {
  253. $stored_head = &drupal_static(__FUNCTION__);
  254. if (!isset($stored_head)) {
  255. // Make sure the defaults, including Content-Type, come first.
  256. $stored_head = _drupal_default_html_head();
  257. }
  258. if (isset($data) && isset($key)) {
  259. if (!isset($data['#type'])) {
  260. $data['#type'] = 'html_tag';
  261. }
  262. $stored_head[$key] = $data;
  263. }
  264. return $stored_head;
  265. }
  266. /**
  267. * Returns elements that are always displayed in the HEAD tag of the HTML page.
  268. */
  269. function _drupal_default_html_head() {
  270. // Add default elements. Make sure the Content-Type comes first because the
  271. // IE browser may be vulnerable to XSS via encoding attacks from any content
  272. // that comes before this META tag, such as a TITLE tag.
  273. $elements['system_meta_content_type'] = array(
  274. '#type' => 'html_tag',
  275. '#tag' => 'meta',
  276. '#attributes' => array(
  277. 'http-equiv' => 'Content-Type',
  278. 'content' => 'text/html; charset=utf-8',
  279. ),
  280. // Security: This always has to be output first.
  281. '#weight' => -1000,
  282. );
  283. // Show Drupal and the major version number in the META GENERATOR tag.
  284. // Get the major version.
  285. list($version, ) = explode('.', VERSION);
  286. $elements['system_meta_generator'] = array(
  287. '#type' => 'html_tag',
  288. '#tag' => 'meta',
  289. '#attributes' => array(
  290. 'name' => 'Generator',
  291. 'content' => 'Drupal ' . $version . ' (http://drupal.org)',
  292. ),
  293. );
  294. // Also send the generator in the HTTP header.
  295. $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
  296. return $elements;
  297. }
  298. /**
  299. * Retrieve output to be displayed in the HEAD tag of the HTML page.
  300. */
  301. function drupal_get_html_head() {
  302. $elements = drupal_add_html_head();
  303. drupal_alter('html_head', $elements);
  304. return drupal_render($elements);
  305. }
  306. /**
  307. * Add a feed URL for the current page.
  308. *
  309. * This function can be called as long the HTML header hasn't been sent.
  310. *
  311. * @param $url
  312. * A url for the feed.
  313. * @param $title
  314. * The title of the feed.
  315. */
  316. function drupal_add_feed($url = NULL, $title = '') {
  317. $stored_feed_links = &drupal_static(__FUNCTION__, array());
  318. if (isset($url)) {
  319. $stored_feed_links[$url] = theme('feed_icon', array('url' => $url, 'title' => $title));
  320. drupal_add_html_head_link(array('rel' => 'alternate',
  321. 'type' => 'application/rss+xml',
  322. 'title' => $title,
  323. 'href' => $url));
  324. }
  325. return $stored_feed_links;
  326. }
  327. /**
  328. * Get the feed URLs for the current page.
  329. *
  330. * @param $delimiter
  331. * A delimiter to split feeds by.
  332. */
  333. function drupal_get_feeds($delimiter = "\n") {
  334. $feeds = drupal_add_feed();
  335. return implode($feeds, $delimiter);
  336. }
  337. /**
  338. * @name HTTP handling
  339. * @{
  340. * Functions to properly handle HTTP responses.
  341. */
  342. /**
  343. * Process a URL query parameter array to remove unwanted elements.
  344. *
  345. * @param $query
  346. * (optional) An array to be processed. Defaults to $_GET.
  347. * @param $exclude
  348. * (optional) A list of $query array keys to remove. Use "parent[child]" to
  349. * exclude nested items. Defaults to array('q').
  350. * @param $parent
  351. * Internal use only. Used to build the $query array key for nested items.
  352. *
  353. * @return
  354. * An array containing query parameters, which can be used for url().
  355. */
  356. function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') {
  357. // Set defaults, if none given.
  358. if (!isset($query)) {
  359. $query = $_GET;
  360. }
  361. // If $exclude is empty, there is nothing to filter.
  362. if (empty($exclude)) {
  363. return $query;
  364. }
  365. elseif (!$parent) {
  366. $exclude = array_flip($exclude);
  367. }
  368. $params = array();
  369. foreach ($query as $key => $value) {
  370. $string_key = ($parent ? $parent . '[' . $key . ']' : $key);
  371. if (isset($exclude[$string_key])) {
  372. continue;
  373. }
  374. if (is_array($value)) {
  375. $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key);
  376. }
  377. else {
  378. $params[$key] = $value;
  379. }
  380. }
  381. return $params;
  382. }
  383. /**
  384. * Split an URL-encoded query string into an array.
  385. *
  386. * @param $query
  387. * The query string to split.
  388. *
  389. * @return
  390. * An array of url decoded couples $param_name => $value.
  391. */
  392. function drupal_get_query_array($query) {
  393. $result = array();
  394. if (!empty($query)) {
  395. foreach (explode('&', $query) as $param) {
  396. $param = explode('=', $param);
  397. $result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
  398. }
  399. }
  400. return $result;
  401. }
  402. /**
  403. * Parse an array into a valid, rawurlencoded query string.
  404. *
  405. * This differs from http_build_query() as we need to rawurlencode() (instead of
  406. * urlencode()) all query parameters.
  407. *
  408. * @param $query
  409. * The query parameter array to be processed, e.g. $_GET.
  410. * @param $parent
  411. * Internal use only. Used to build the $query array key for nested items.
  412. *
  413. * @return
  414. * A rawurlencoded string which can be used as or appended to the URL query
  415. * string.
  416. *
  417. * @see drupal_get_query_parameters()
  418. * @ingroup php_wrappers
  419. */
  420. function drupal_http_build_query(array $query, $parent = '') {
  421. $params = array();
  422. foreach ($query as $key => $value) {
  423. $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key));
  424. // Recurse into children.
  425. if (is_array($value)) {
  426. $params[] = drupal_http_build_query($value, $key);
  427. }
  428. // If a query parameter value is NULL, only append its key.
  429. elseif (!isset($value)) {
  430. $params[] = $key;
  431. }
  432. else {
  433. // For better readability of paths in query strings, we decode slashes.
  434. $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
  435. }
  436. }
  437. return implode('&', $params);
  438. }
  439. /**
  440. * Prepare a 'destination' URL query parameter for use in combination with drupal_goto().
  441. *
  442. * Used to direct the user back to the referring page after completing a form.
  443. * By default the current URL is returned. If a destination exists in the
  444. * previous request, that destination is returned. As such, a destination can
  445. * persist across multiple pages.
  446. *
  447. * @see drupal_goto()
  448. */
  449. function drupal_get_destination() {
  450. $destination = &drupal_static(__FUNCTION__);
  451. if (isset($destination)) {
  452. return $destination;
  453. }
  454. if (isset($_GET['destination'])) {
  455. $destination = array('destination' => $_GET['destination']);
  456. }
  457. else {
  458. $path = $_GET['q'];
  459. $query = drupal_http_build_query(drupal_get_query_parameters());
  460. if ($query != '') {
  461. $path .= '?' . $query;
  462. }
  463. $destination = array('destination' => $path);
  464. }
  465. return $destination;
  466. }
  467. /**
  468. * Wrapper around parse_url() to parse a system URL string into an associative array, suitable for url().
  469. *
  470. * This function should only be used for URLs that have been generated by the
  471. * system, resp. url(). It should not be used for URLs that come from external
  472. * sources, or URLs that link to external resources.
  473. *
  474. * The returned array contains a 'path' that may be passed separately to url().
  475. * For example:
  476. * @code
  477. * $options = drupal_parse_url($_GET['destination']);
  478. * $my_url = url($options['path'], $options);
  479. * $my_link = l('Example link', $options['path'], $options);
  480. * @endcode
  481. *
  482. * This is required, because url() does not support relative URLs containing a
  483. * query string or fragment in its $path argument. Instead, any query string
  484. * needs to be parsed into an associative query parameter array in
  485. * $options['query'] and the fragment into $options['fragment'].
  486. *
  487. * @param $url
  488. * The URL string to parse, f.e. $_GET['destination'].
  489. *
  490. * @return
  491. * An associative array containing the keys:
  492. * - 'path': The path of the URL. If the given $url is external, this includes
  493. * the scheme and host.
  494. * - 'query': An array of query parameters of $url, if existent.
  495. * - 'fragment': The fragment of $url, if existent.
  496. *
  497. * @see url()
  498. * @see drupal_goto()
  499. * @ingroup php_wrappers
  500. */
  501. function drupal_parse_url($url) {
  502. $options = array(
  503. 'path' => NULL,
  504. 'query' => array(),
  505. 'fragment' => '',
  506. );
  507. // External URLs: not using parse_url() here, so we do not have to rebuild
  508. // the scheme, host, and path without having any use for it.
  509. if (strpos($url, '://') !== FALSE) {
  510. // Split off everything before the query string into 'path'.
  511. $parts = explode('?', $url);
  512. $options['path'] = $parts[0];
  513. // If there is a query string, transform it into keyed query parameters.
  514. if (isset($parts[1])) {
  515. $query_parts = explode('#', $parts[1]);
  516. parse_str($query_parts[0], $options['query']);
  517. // Take over the fragment, if there is any.
  518. if (isset($query_parts[1])) {
  519. $options['fragment'] = $query_parts[1];
  520. }
  521. }
  522. }
  523. // Internal URLs.
  524. else {
  525. // parse_url() does not support relative URLs, so make it absolute. E.g. the
  526. // relative URL "foo/bar:1" isn't properly parsed.
  527. $parts = parse_url('http://example.com/' . $url);
  528. // Strip the leading slash that was just added.
  529. $options['path'] = substr($parts['path'], 1);
  530. if (isset($parts['query'])) {
  531. parse_str($parts['query'], $options['query']);
  532. }
  533. if (isset($parts['fragment'])) {
  534. $options['fragment'] = $parts['fragment'];
  535. }
  536. }
  537. // The 'q' parameter contains the path of the current page if clean URLs are
  538. // disabled. It overrides the 'path' of the URL when present, even if clean
  539. // URLs are enabled, due to how Apache rewriting rules work.
  540. if (isset($options['query']['q'])) {
  541. $options['path'] = $options['query']['q'];
  542. unset($options['query']['q']);
  543. }
  544. return $options;
  545. }
  546. /**
  547. * Encodes a Drupal path for use in a URL.
  548. *
  549. * For aesthetic reasons slashes are not escaped.
  550. *
  551. * Note that url() takes care of calling this function, so a path passed to that
  552. * function should not be encoded in advance.
  553. *
  554. * @param $path
  555. * The Drupal path to encode.
  556. */
  557. function drupal_encode_path($path) {
  558. return str_replace('%2F', '/', rawurlencode($path));
  559. }
  560. /**
  561. * Send the user to a different Drupal page.
  562. *
  563. * This issues an on-site HTTP redirect. The function makes sure the redirected
  564. * URL is formatted correctly.
  565. *
  566. * Usually the redirected URL is constructed from this function's input
  567. * parameters. However you may override that behavior by setting a
  568. * destination in either the $_REQUEST-array (i.e. by using
  569. * the query string of an URI) This is used to direct the user back to
  570. * the proper page after completing a form. For example, after editing
  571. * a post on the 'admin/content'-page or after having logged on using the
  572. * 'user login'-block in a sidebar. The function drupal_get_destination()
  573. * can be used to help set the destination URL.
  574. *
  575. * Drupal will ensure that messages set by drupal_set_message() and other
  576. * session data are written to the database before the user is redirected.
  577. *
  578. * This function ends the request; use it instead of a return in your menu
  579. * callback.
  580. *
  581. * @param $path
  582. * A Drupal path or a full URL.
  583. * @param $options
  584. * An associative array of additional URL options to pass to url().
  585. * @param $http_response_code
  586. * Valid values for an actual "goto" as per RFC 2616 section 10.3 are:
  587. * - 301 Moved Permanently (the recommended value for most redirects)
  588. * - 302 Found (default in Drupal and PHP, sometimes used for spamming search
  589. * engines)
  590. * - 303 See Other
  591. * - 304 Not Modified
  592. * - 305 Use Proxy
  593. * - 307 Temporary Redirect (alternative to "503 Site Down for Maintenance")
  594. * Note: Other values are defined by RFC 2616, but are rarely used and poorly
  595. * supported.
  596. *
  597. * @see drupal_get_destination()
  598. * @see url()
  599. */
  600. function drupal_goto($path = '', array $options = array(), $http_response_code = 302) {
  601. // A destination in $_GET always overrides the function arguments.
  602. // We do not allow absolute URLs to be passed via $_GET, as this can be an attack vector.
  603. if (isset($_GET['destination']) && !url_is_external($_GET['destination'])) {
  604. $destination = drupal_parse_url($_GET['destination']);
  605. $path = $destination['path'];
  606. $options['query'] = $destination['query'];
  607. $options['fragment'] = $destination['fragment'];
  608. }
  609. drupal_alter('drupal_goto', $path, $options, $http_response_code);
  610. // The 'Location' HTTP header must be absolute.
  611. $options['absolute'] = TRUE;
  612. $url = url($path, $options);
  613. header('Location: ' . $url, TRUE, $http_response_code);
  614. // The "Location" header sends a redirect status code to the HTTP daemon. In
  615. // some cases this can be wrong, so we make sure none of the code below the
  616. // drupal_goto() call gets executed upon redirection.
  617. drupal_exit($url);
  618. }
  619. /**
  620. * Deliver a "site is under maintenance" message to the browser.
  621. *
  622. * Page callback functions wanting to report a "site offline" message should
  623. * return MENU_SITE_OFFLINE instead of calling drupal_site_offline(). However,
  624. * functions that are invoked in contexts where that return value might not
  625. * bubble up to menu_execute_active_handler() should call drupal_site_offline().
  626. */
  627. function drupal_site_offline() {
  628. drupal_deliver_page(MENU_SITE_OFFLINE);
  629. }
  630. /**
  631. * Deliver a "page not found" error to the browser.
  632. *
  633. * Page callback functions wanting to report a "page not found" message should
  634. * return MENU_NOT_FOUND instead of calling drupal_not_found(). However,
  635. * functions that are invoked in contexts where that return value might not
  636. * bubble up to menu_execute_active_handler() should call drupal_not_found().
  637. */
  638. function drupal_not_found() {
  639. drupal_deliver_page(MENU_NOT_FOUND);
  640. }
  641. /**
  642. * Deliver a "access denied" error to the browser.
  643. *
  644. * Page callback functions wanting to report an "access denied" message should
  645. * return MENU_ACCESS_DENIED instead of calling drupal_access_denied(). However,
  646. * functions that are invoked in contexts where that return value might not
  647. * bubble up to menu_execute_active_handler() should call drupal_access_denied().
  648. */
  649. function drupal_access_denied() {
  650. drupal_deliver_page(MENU_ACCESS_DENIED);
  651. }
  652. /**
  653. * Perform an HTTP request.
  654. *
  655. * This is a flexible and powerful HTTP client implementation. Correctly
  656. * handles GET, POST, PUT or any other HTTP requests. Handles redirects.
  657. *
  658. * @param $url
  659. * A string containing a fully qualified URI.
  660. * @param $options
  661. * (optional) An array which can have one or more of following keys:
  662. * - headers
  663. * An array containing request headers to send as name/value pairs.
  664. * - method
  665. * A string containing the request method. Defaults to 'GET'.
  666. * - data
  667. * A string containing the request body. Defaults to NULL.
  668. * - max_redirects
  669. * An integer representing how many times a redirect may be followed.
  670. * Defaults to 3.
  671. * - timeout
  672. * A float representing the maximum number of seconds the function call
  673. * may take. The default is 30 seconds. If a timeout occurs, the error
  674. * code is set to the HTTP_REQUEST_TIMEOUT constant.
  675. * - context
  676. * A context resource created with stream_context_create().
  677. * @return
  678. * An object which can have one or more of the following parameters:
  679. * - request
  680. * A string containing the request body that was sent.
  681. * - code
  682. * An integer containing the response status code, or the error code if
  683. * an error occurred.
  684. * - protocol
  685. * The response protocol (e.g. HTTP/1.1 or HTTP/1.0).
  686. * - status_message
  687. * The status message from the response, if a response was received.
  688. * - redirect_code
  689. * If redirected, an integer containing the initial response status code.
  690. * - redirect_url
  691. * If redirected, a string containing the redirection location.
  692. * - error
  693. * If an error occurred, the error message. Otherwise not set.
  694. * - headers
  695. * An array containing the response headers as name/value pairs. HTTP
  696. * header names are case-insensitive (RFC 2616, section 4.2), so for easy
  697. * access the array keys are returned in lower case.
  698. * - data
  699. * A string containing the response body that was received.
  700. */
  701. function drupal_http_request($url, array $options = array()) {
  702. $result = new stdClass();
  703. // Parse the URL and make sure we can handle the schema.
  704. $uri = @parse_url($url);
  705. if ($uri == FALSE) {
  706. $result->error = 'unable to parse URL';
  707. $result->code = -1001;
  708. return $result;
  709. }
  710. if (!isset($uri['scheme'])) {
  711. $result->error = 'missing schema';
  712. $result->code = -1002;
  713. return $result;
  714. }
  715. timer_start(__FUNCTION__);
  716. // Merge the default options.
  717. $options += array(
  718. 'headers' => array(),
  719. 'method' => 'GET',
  720. 'data' => NULL,
  721. 'max_redirects' => 3,
  722. 'timeout' => 30.0,
  723. 'context' => NULL,
  724. );
  725. // stream_socket_client() requires timeout to be a float.
  726. $options['timeout'] = (float) $options['timeout'];
  727. switch ($uri['scheme']) {
  728. case 'http':
  729. case 'feed':
  730. $port = isset($uri['port']) ? $uri['port'] : 80;
  731. $socket = 'tcp://' . $uri['host'] . ':' . $port;
  732. // RFC 2616: "non-standard ports MUST, default ports MAY be included".
  733. // We don't add the standard port to prevent from breaking rewrite rules
  734. // checking the host that do not take into account the port number.
  735. $options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
  736. break;
  737. case 'https':
  738. // Note: Only works when PHP is compiled with OpenSSL support.
  739. $port = isset($uri['port']) ? $uri['port'] : 443;
  740. $socket = 'ssl://' . $uri['host'] . ':' . $port;
  741. $options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
  742. break;
  743. default:
  744. $result->error = 'invalid schema ' . $uri['scheme'];
  745. $result->code = -1003;
  746. return $result;
  747. }
  748. if (empty($options['context'])) {
  749. $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout']);
  750. }
  751. else {
  752. // Create a stream with context. Allows verification of a SSL certificate.
  753. $fp = @stream_socket_client($socket, $errno, $errstr, $options['timeout'], STREAM_CLIENT_CONNECT, $options['context']);
  754. }
  755. // Make sure the socket opened properly.
  756. if (!$fp) {
  757. // When a network error occurs, we use a negative number so it does not
  758. // clash with the HTTP status codes.
  759. $result->code = -$errno;
  760. $result->error = trim($errstr) ? trim($errstr) : t('Error opening socket @socket', array('@socket' => $socket));
  761. // Mark that this request failed. This will trigger a check of the web
  762. // server's ability to make outgoing HTTP requests the next time that
  763. // requirements checking is performed.
  764. // See system_requirements()
  765. variable_set('drupal_http_request_fails', TRUE);
  766. return $result;
  767. }
  768. // Construct the path to act on.
  769. $path = isset($uri['path']) ? $uri['path'] : '/';
  770. if (isset($uri['query'])) {
  771. $path .= '?' . $uri['query'];
  772. }
  773. // Merge the default headers.
  774. $options['headers'] += array(
  775. 'User-Agent' => 'Drupal (+http://drupal.org/)',
  776. );
  777. // Only add Content-Length if we actually have any content or if it is a POST
  778. // or PUT request. Some non-standard servers get confused by Content-Length in
  779. // at least HEAD/GET requests, and Squid always requires Content-Length in
  780. // POST/PUT requests.
  781. $content_length = strlen($options['data']);
  782. if ($content_length > 0 || $options['method'] == 'POST' || $options['method'] == 'PUT') {
  783. $options['headers']['Content-Length'] = $content_length;
  784. }
  785. // If the server URL has a user then attempt to use basic authentication.
  786. if (isset($uri['user'])) {
  787. $options['headers']['Authorization'] = 'Basic ' . base64_encode($uri['user'] . (!empty($uri['pass']) ? ":" . $uri['pass'] : ''));
  788. }
  789. // If the database prefix is being used by SimpleTest to run the tests in a copied
  790. // database then set the user-agent header to the database prefix so that any
  791. // calls to other Drupal pages will run the SimpleTest prefixed database. The
  792. // user-agent is used to ensure that multiple testing sessions running at the
  793. // same time won't interfere with each other as they would if the database
  794. // prefix were stored statically in a file or database variable.
  795. $test_info = &$GLOBALS['drupal_test_info'];
  796. if (!empty($test_info['test_run_id'])) {
  797. $options['headers']['User-Agent'] = drupal_generate_test_ua($test_info['test_run_id']);
  798. }
  799. $request = $options['method'] . ' ' . $path . " HTTP/1.0\r\n";
  800. foreach ($options['headers'] as $name => $value) {
  801. $request .= $name . ': ' . trim($value) . "\r\n";
  802. }
  803. $request .= "\r\n" . $options['data'];
  804. $result->request = $request;
  805. // Calculate how much time is left of the original timeout value.
  806. $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
  807. if ($timeout > 0) {
  808. stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
  809. fwrite($fp, $request);
  810. }
  811. // Fetch response. Due to PHP bugs like http://bugs.php.net/bug.php?id=43782
  812. // and http://bugs.php.net/bug.php?id=46049 we can't rely on feof(), but
  813. // instead must invoke stream_get_meta_data() each iteration.
  814. $info = stream_get_meta_data($fp);
  815. $alive = !$info['eof'] && !$info['timed_out'];
  816. $response = '';
  817. while ($alive) {
  818. // Calculate how much time is left of the original timeout value.
  819. $timeout = $options['timeout'] - timer_read(__FUNCTION__) / 1000;
  820. if ($timeout <= 0) {
  821. $info['timed_out'] = TRUE;
  822. break;
  823. }
  824. stream_set_timeout($fp, floor($timeout), floor(1000000 * fmod($timeout, 1)));
  825. $chunk = fread($fp, 1024);
  826. $response .= $chunk;
  827. $info = stream_get_meta_data($fp);
  828. $alive = !$info['eof'] && !$info['timed_out'] && $chunk;
  829. }
  830. fclose($fp);
  831. if ($info['timed_out']) {
  832. $result->code = HTTP_REQUEST_TIMEOUT;
  833. $result->error = 'request timed out';
  834. return $result;
  835. }
  836. // Parse response headers from the response body.
  837. list($response, $result->data) = explode("\r\n\r\n", $response, 2);
  838. $response = preg_split("/\r\n|\n|\r/", $response);
  839. // Parse the response status line.
  840. list($protocol, $code, $status_message) = explode(' ', trim(array_shift($response)), 3);
  841. $result->protocol = $protocol;
  842. $result->status_message = $status_message;
  843. $result->headers = array();
  844. // Parse the response headers.
  845. while ($line = trim(array_shift($response))) {
  846. list($name, $value) = explode(':', $line, 2);
  847. $name = strtolower($name);
  848. if (isset($result->headers[$name]) && $name == 'set-cookie') {
  849. // RFC 2109: the Set-Cookie response header comprises the token Set-
  850. // Cookie:, followed by a comma-separated list of one or more cookies.
  851. $result->headers[$name] .= ',' . trim($value);
  852. }
  853. else {
  854. $result->headers[$name] = trim($value);
  855. }
  856. }
  857. $responses = array(
  858. 100 => 'Continue',
  859. 101 => 'Switching Protocols',
  860. 200 => 'OK',
  861. 201 => 'Created',
  862. 202 => 'Accepted',
  863. 203 => 'Non-Authoritative Information',
  864. 204 => 'No Content',
  865. 205 => 'Reset Content',
  866. 206 => 'Partial Content',
  867. 300 => 'Multiple Choices',
  868. 301 => 'Moved Permanently',
  869. 302 => 'Found',
  870. 303 => 'See Other',
  871. 304 => 'Not Modified',
  872. 305 => 'Use Proxy',
  873. 307 => 'Temporary Redirect',
  874. 400 => 'Bad Request',
  875. 401 => 'Unauthorized',
  876. 402 => 'Payment Required',
  877. 403 => 'Forbidden',
  878. 404 => 'Not Found',
  879. 405 => 'Method Not Allowed',
  880. 406 => 'Not Acceptable',
  881. 407 => 'Proxy Authentication Required',
  882. 408 => 'Request Time-out',
  883. 409 => 'Conflict',
  884. 410 => 'Gone',
  885. 411 => 'Length Required',
  886. 412 => 'Precondition Failed',
  887. 413 => 'Request Entity Too Large',
  888. 414 => 'Request-URI Too Large',
  889. 415 => 'Unsupported Media Type',
  890. 416 => 'Requested range not satisfiable',
  891. 417 => 'Expectation Failed',
  892. 500 => 'Internal Server Error',
  893. 501 => 'Not Implemented',
  894. 502 => 'Bad Gateway',
  895. 503 => 'Service Unavailable',
  896. 504 => 'Gateway Time-out',
  897. 505 => 'HTTP Version not supported',
  898. );
  899. // RFC 2616 states that all unknown HTTP codes must be treated the same as the
  900. // base code in their class.
  901. if (!isset($responses[$code])) {
  902. $code = floor($code / 100) * 100;
  903. }
  904. $result->code = $code;
  905. switch ($code) {
  906. case 200: // OK
  907. case 304: // Not modified
  908. break;
  909. case 301: // Moved permanently
  910. case 302: // Moved temporarily
  911. case 307: // Moved temporarily
  912. $location = $result->headers['location'];
  913. $options['timeout'] -= timer_read(__FUNCTION__) / 1000;
  914. if ($options['timeout'] <= 0) {
  915. $result->code = HTTP_REQUEST_TIMEOUT;
  916. $result->error = 'request timed out';
  917. }
  918. elseif ($options['max_redirects']) {
  919. // Redirect to the new location.
  920. $options['max_redirects']--;
  921. $result = drupal_http_request($location, $options);
  922. $result->redirect_code = $code;
  923. }
  924. $result->redirect_url = $location;
  925. break;
  926. default:
  927. $result->error = $status_message;
  928. }
  929. return $result;
  930. }
  931. /**
  932. * @} End of "HTTP handling".
  933. */
  934. function _fix_gpc_magic(&$item) {
  935. if (is_array($item)) {
  936. array_walk($item, '_fix_gpc_magic');
  937. }
  938. else {
  939. $item = stripslashes($item);
  940. }
  941. }
  942. /**
  943. * Helper function to strip slashes from $_FILES skipping over the tmp_name keys
  944. * since PHP generates single backslashes for file paths on Windows systems.
  945. *
  946. * tmp_name does not have backslashes added see
  947. * http://php.net/manual/en/features.file-upload.php#42280
  948. */
  949. function _fix_gpc_magic_files(&$item, $key) {
  950. if ($key != 'tmp_name') {
  951. if (is_array($item)) {
  952. array_walk($item, '_fix_gpc_magic_files');
  953. }
  954. else {
  955. $item = stripslashes($item);
  956. }
  957. }
  958. }
  959. /**
  960. * Fix double-escaping problems caused by "magic quotes" in some PHP installations.
  961. */
  962. function fix_gpc_magic() {
  963. $fixed = &drupal_static(__FUNCTION__, FALSE);
  964. if (!$fixed && ini_get('magic_quotes_gpc')) {
  965. array_walk($_GET, '_fix_gpc_magic');
  966. array_walk($_POST, '_fix_gpc_magic');
  967. array_walk($_COOKIE, '_fix_gpc_magic');
  968. array_walk($_REQUEST, '_fix_gpc_magic');
  969. array_walk($_FILES, '_fix_gpc_magic_files');
  970. $fixed = TRUE;
  971. }
  972. }
  973. /**
  974. * @defgroup validation Input validation
  975. * @{
  976. * Functions to validate user input.
  977. */
  978. /**
  979. * Verify the syntax of the given e-mail address.
  980. *
  981. * Empty e-mail addresses are allowed. See RFC 2822 for details.
  982. *
  983. * @param $mail
  984. * A string containing an e-mail address.
  985. * @return
  986. * TRUE if the address is in a valid format.
  987. */
  988. function valid_email_address($mail) {
  989. return (bool)filter_var($mail, FILTER_VALIDATE_EMAIL);
  990. }
  991. /**
  992. * Verify the syntax of the given URL.
  993. *
  994. * This function should only be used on actual URLs. It should not be used for
  995. * Drupal menu paths, which can contain arbitrary characters.
  996. * Valid values per RFC 3986.
  997. * @param $url
  998. * The URL to verify.
  999. * @param $absolute
  1000. * Whether the URL is absolute (beginning with a scheme such as "http:").
  1001. * @return
  1002. * TRUE if the URL is in a valid format.
  1003. */
  1004. function valid_url($url, $absolute = FALSE) {
  1005. if ($absolute) {
  1006. return (bool)preg_match("
  1007. /^ # Start at the beginning of the text
  1008. (?:ftp|https?|feed):\/\/ # Look for ftp, http, https or feed schemes
  1009. (?: # Userinfo (optional) which is typically
  1010. (?:(?:[\w\.\-\+!$&'\(\)*\+,;=]|%[0-9a-f]{2})+:)* # a username or a username and password
  1011. (?:[\w\.\-\+%!$&'\(\)*\+,;=]|%[0-9a-f]{2})+@ # combination
  1012. )?
  1013. (?:
  1014. (?:[a-z0-9\-\.]|%[0-9a-f]{2})+ # A domain name or a IPv4 address
  1015. |(?:\[(?:[0-9a-f]{0,4}:)*(?:[0-9a-f]{0,4})\]) # or a well formed IPv6 address
  1016. )
  1017. (?::[0-9]+)? # Server port number (optional)
  1018. (?:[\/|\?]
  1019. (?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2}) # The path and query (optional)
  1020. *)?
  1021. $/xi", $url);
  1022. }
  1023. else {
  1024. return (bool)preg_match("/^(?:[\w#!:\.\?\+=&@$'~*,;\/\(\)\[\]\-]|%[0-9a-f]{2})+$/i", $url);
  1025. }
  1026. }
  1027. /**
  1028. * @} End of "defgroup validation".
  1029. */
  1030. /**
  1031. * Register an event for the current visitor to the flood control mechanism.
  1032. *
  1033. * @param $name
  1034. * The name of an event.
  1035. * @param $window
  1036. * Optional number of seconds before this event expires. Defaults to 3600 (1
  1037. * hour). Typically uses the same value as the flood_is_allowed() $window
  1038. * parameter. Expired events are purged on cron run to prevent the flood table
  1039. * from growing indefinitely.
  1040. * @param $identifier
  1041. * Optional identifier (defaults to the current user's IP address).
  1042. */
  1043. function flood_register_event($name, $window = 3600, $identifier = NULL) {
  1044. if (!isset($identifier)) {
  1045. $identifier = ip_address();
  1046. }
  1047. db_insert('flood')
  1048. ->fields(array(
  1049. 'event' => $name,
  1050. 'identifier' => $identifier,
  1051. 'timestamp' => REQUEST_TIME,
  1052. 'expiration' => REQUEST_TIME + $window,
  1053. ))
  1054. ->execute();
  1055. }
  1056. /**
  1057. * Make the flood control mechanism forget about an event for the current visitor.
  1058. *
  1059. * @param $name
  1060. * The name of an event.
  1061. * @param $identifier
  1062. * Optional identifier (defaults to the current user's IP address).
  1063. */
  1064. function flood_clear_event($name, $identifier = NULL) {
  1065. if (!isset($identifier)) {
  1066. $identifier = ip_address();
  1067. }
  1068. db_delete('flood')
  1069. ->condition('event', $name)
  1070. ->condition('identifier', $identifier)
  1071. ->execute();
  1072. }
  1073. /**
  1074. * Checks whether user is allowed to proceed with the specified event.
  1075. *
  1076. * Events can have thresholds saying that each user can only do that event
  1077. * a certain number of times in a time window. This function verifies that the
  1078. * current user has not exceeded this threshold.
  1079. *
  1080. * @param $name
  1081. * The unique name of the event.
  1082. * @param $threshold
  1083. * The maximum number of times each user can do this event per time window.
  1084. * @param $window
  1085. * Number of seconds in the time window for this event (default is 3600
  1086. * seconds, or 1 hour).
  1087. * @param $identifier
  1088. * Unique identifier of the current user. Defaults to their IP address.
  1089. *
  1090. * @return
  1091. * TRUE if the user is allowed to proceed. FALSE if they have exceeded the
  1092. * threshold and should not be allowed to proceed.
  1093. */
  1094. function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL) {
  1095. if (!isset($identifier)) {
  1096. $identifier = ip_address();
  1097. }
  1098. $number = db_query("SELECT COUNT(*) FROM {flood} WHERE event = :event AND identifier = :identifier AND timestamp > :timestamp", array(
  1099. ':event' => $name,
  1100. ':identifier' => $identifier,
  1101. ':timestamp' => REQUEST_TIME - $window))
  1102. ->fetchField();
  1103. return ($number < $threshold);
  1104. }
  1105. /**
  1106. * @defgroup sanitization Sanitization functions
  1107. * @{
  1108. * Functions to sanitize values.
  1109. *
  1110. * See http://drupal.org/writing-secure-code for information
  1111. * on writing secure code.
  1112. */
  1113. /**
  1114. * Strips dangerous protocols (e.g. 'javascript:') from a URI.
  1115. *
  1116. * This function must be called for all URIs within user-entered input prior
  1117. * to being output to an HTML attribute value. It is often called as part of
  1118. * check_url() or filter_xss(), but those functions return an HTML-encoded
  1119. * string, so this function can be called independently when the output needs to
  1120. * be a plain-text string for passing to t(), l(), drupal_attributes(), or
  1121. * another function that will call check_plain() separately.
  1122. *
  1123. * @param $uri
  1124. * A plain-text URI that might contain dangerous protocols.
  1125. *
  1126. * @return
  1127. * A plain-text URI stripped of dangerous protocols. As with all plain-text
  1128. * strings, this return value must not be output to an HTML page without
  1129. * check_plain() being called on it. However, it can be passed to functions
  1130. * expecting plain-text strings.
  1131. *
  1132. * @see check_url()
  1133. */
  1134. function drupal_strip_dangerous_protocols($uri) {
  1135. static $allowed_protocols;
  1136. if (!isset($allowed_protocols)) {
  1137. $allowed_protocols = array_flip(variable_get('filter_allowed_protocols', array('ftp', 'http', 'https', 'irc', 'mailto', 'news', 'nntp', 'rtsp', 'sftp', 'ssh', 'telnet', 'webcal')));
  1138. }
  1139. // Iteratively remove any invalid protocol found.
  1140. do {
  1141. $before = $uri;
  1142. $colonpos = strpos($uri, ':');
  1143. if ($colonpos > 0) {
  1144. // We found a colon, possibly a protocol. Verify.
  1145. $protocol = substr($uri, 0, $colonpos);
  1146. // If a colon is preceded by a slash, question mark or hash, it cannot
  1147. // possibly be part of the URL scheme. This must be a relative URL, which
  1148. // inherits the (safe) protocol of the base document.
  1149. if (preg_match('![/?#]!', $protocol)) {
  1150. break;
  1151. }
  1152. // Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
  1153. // (URI Comparison) scheme comparison must be case-insensitive.
  1154. if (!isset($allowed_protocols[strtolower($protocol)])) {
  1155. $uri = substr($uri, $colonpos + 1);
  1156. }
  1157. }
  1158. } while ($before != $uri);
  1159. return $uri;
  1160. }
  1161. /**
  1162. * Strips dangerous protocols (e.g. 'javascript:') from a URI and encodes it for output to an HTML attribute value.
  1163. *
  1164. * @param $uri
  1165. * A plain-text URI that might contain dangerous protocols.
  1166. *
  1167. * @return
  1168. * A URI stripped of dangerous protocols and encoded for output to an HTML
  1169. * attribute value. Because it is already encoded, it should not be set as a
  1170. * value within a $attributes array passed to drupal_attributes(), because
  1171. * drupal_attributes() expects those values to be plain-text strings. To pass
  1172. * a filtered URI to drupal_attributes(), call
  1173. * drupal_strip_dangerous_protocols() instead.
  1174. *
  1175. * @see drupal_strip_dangerous_protocols()
  1176. */
  1177. function check_url($uri) {
  1178. return check_plain(drupal_strip_dangerous_protocols($uri));
  1179. }
  1180. /**
  1181. * Very permissive XSS/HTML filter for admin-only use.
  1182. *
  1183. * Use only for fields where it is impractical to use the
  1184. * whole filter system, but where some (mainly inline) mark-up
  1185. * is desired (so check_plain() is not acceptable).
  1186. *
  1187. * Allows all tags that can be used inside an HTML body, save
  1188. * for scripts and styles.
  1189. */
  1190. function filter_xss_admin($string) {
  1191. return filter_xss($string, array('a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var'));
  1192. }
  1193. /**
  1194. * Filters an HTML string to prevent cross-site-scripting (XSS) vulnerabilities.
  1195. *
  1196. * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses.
  1197. * For examples of various XSS attacks, see: http://ha.ckers.org/xss.html.
  1198. *
  1199. * This code does four things:
  1200. * - Removes characters and constructs that can trick browsers.
  1201. * - Makes sure all HTML entities are well-formed.
  1202. * - Makes sure all HTML tags and attributes are well-formed.
  1203. * - Makes sure no HTML tags contain URLs with a disallowed protocol (e.g.
  1204. * javascript:).
  1205. *
  1206. * @param $string
  1207. * The string with raw HTML in it. It will be stripped of everything that can
  1208. * cause an XSS attack.
  1209. * @param $allowed_tags
  1210. * An array of allowed tags.
  1211. *
  1212. * @return
  1213. * An XSS safe version of $string, or an empty string if $string is not
  1214. * valid UTF-8.
  1215. *
  1216. * @see drupal_validate_utf8()
  1217. * @ingroup sanitization
  1218. */
  1219. function filter_xss($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
  1220. // Only operate on valid UTF-8 strings. This is necessary to prevent cross
  1221. // site scripting issues on Internet Explorer 6.
  1222. if (!drupal_validate_utf8($string)) {
  1223. return '';
  1224. }
  1225. // Store the text format
  1226. _filter_xss_split($allowed_tags, TRUE);
  1227. // Remove NULL characters (ignored by some browsers)
  1228. $string = str_replace(chr(0), '', $string);
  1229. // Remove Netscape 4 JS entities
  1230. $string = preg_replace('%&\s*\{[^}]*(\}\s*;?|$)%', '', $string);
  1231. // Defuse all HTML entities
  1232. $string = str_replace('&', '&amp;', $string);
  1233. // Change back only well-formed entities in our whitelist
  1234. // Decimal numeric entities
  1235. $string = preg_replace('/&amp;#([0-9]+;)/', '&#\1', $string);
  1236. // Hexadecimal numeric entities
  1237. $string = preg_replace('/&amp;#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/', '&#x\1', $string);
  1238. // Named entities
  1239. $string = preg_replace('/&amp;([A-Za-z][A-Za-z0-9]*;)/', '&\1', $string);
  1240. return preg_replace_callback('%
  1241. (
  1242. <(?=[^a-zA-Z!/]) # a lone <
  1243. | # or
  1244. <!--.*?--> # a comment
  1245. | # or
  1246. <[^>]*(>|$) # a string that starts with a <, up until the > or the end of the string
  1247. | # or
  1248. > # just a >
  1249. )%x', '_filter_xss_split', $string);
  1250. }
  1251. /**
  1252. * Processes an HTML tag.
  1253. *
  1254. * @param $m
  1255. * An array with various meaning depending on the value of $store.
  1256. * If $store is TRUE then the array contains the allowed tags.
  1257. * If $store is FALSE then the array has one element, the HTML tag to process.
  1258. * @param $store
  1259. * Whether to store $m.
  1260. * @return
  1261. * If the element isn't allowed, an empty string. Otherwise, the cleaned up
  1262. * version of the HTML element.
  1263. */
  1264. function _filter_xss_split($m, $store = FALSE) {
  1265. static $allowed_html;
  1266. if ($store) {
  1267. $allowed_html = array_flip($m);
  1268. return;
  1269. }
  1270. $string = $m[1];
  1271. if (substr($string, 0, 1) != '<') {
  1272. // We matched a lone ">" character
  1273. return '&gt;';
  1274. }
  1275. elseif (strlen($string) == 1) {
  1276. // We matched a lone "<" character
  1277. return '&lt;';
  1278. }
  1279. if (!preg_match('%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%', $string, $matches)) {
  1280. // Seriously malformed
  1281. return '';
  1282. }
  1283. $slash = trim($matches[1]);
  1284. $elem = &$matches[2];
  1285. $attrlist = &$matches[3];
  1286. $comment = &$matches[4];
  1287. if ($comment) {
  1288. $elem = '!--';
  1289. }
  1290. if (!isset($allowed_html[strtolower($elem)])) {
  1291. // Disallowed HTML element
  1292. return '';
  1293. }
  1294. if ($comment) {
  1295. return $comment;
  1296. }
  1297. if ($slash != '') {
  1298. return "</$elem>";
  1299. }
  1300. // Is there a closing XHTML slash at the end of the attributes?
  1301. $attrlist = preg_replace('%(\s?)/\s*$%', '\1', $attrlist, -1, $count);
  1302. $xhtml_slash = $count ? ' /' : '';
  1303. // Clean up attributes
  1304. $attr2 = implode(' ', _filter_xss_attributes($attrlist));
  1305. $attr2 = preg_replace('/[<>]/', '', $attr2);
  1306. $attr2 = strlen($attr2) ? ' ' . $attr2 : '';
  1307. return "<$elem$attr2$xhtml_slash>";
  1308. }
  1309. /**
  1310. * Processes a string of HTML attributes.
  1311. *
  1312. * @return
  1313. * Cleaned up version of the HTML attributes.
  1314. */
  1315. function _filter_xss_attributes($attr) {
  1316. $attrarr = array();
  1317. $mode = 0;
  1318. $attrname = '';
  1319. while (strlen($attr) != 0) {
  1320. // Was the last operation successful?
  1321. $working = 0;
  1322. switch ($mode) {
  1323. case 0:
  1324. // Attribute name, href for instance
  1325. if (preg_match('/^([-a-zA-Z]+)/', $attr, $match)) {
  1326. $attrname = strtolower($match[1]);
  1327. $skip = ($attrname == 'style' || substr($attrname, 0, 2) == 'on');
  1328. $working = $mode = 1;
  1329. $attr = preg_replace('/^[-a-zA-Z]+/', '', $attr);
  1330. }
  1331. break;
  1332. case 1:
  1333. // Equals sign or valueless ("selected")
  1334. if (preg_match('/^\s*=\s*/', $attr)) {
  1335. $working = 1; $mode = 2;
  1336. $attr = preg_replace('/^\s*=\s*/', '', $attr);
  1337. break;
  1338. }
  1339. if (preg_match('/^\s+/', $attr)) {
  1340. $working = 1; $mode = 0;
  1341. if (!$skip) {
  1342. $attrarr[] = $attrname;
  1343. }
  1344. $attr = preg_replace('/^\s+/', '', $attr);
  1345. }
  1346. break;
  1347. case 2:
  1348. // Attribute value, a URL after href= for instance
  1349. if (preg_match('/^"([^"]*)"(\s+|$)/', $attr, $match)) {
  1350. $thisval = filter_xss_bad_protocol($match[1]);
  1351. if (!$skip) {
  1352. $attrarr[] = "$attrname=\"$thisval\"";
  1353. }
  1354. $working = 1;
  1355. $mode = 0;
  1356. $attr = preg_replace('/^"[^"]*"(\s+|$)/', '', $attr);
  1357. break;
  1358. }
  1359. if (preg_match("/^'([^']*)'(\s+|$)/", $attr, $match)) {
  1360. $thisval = filter_xss_bad_protocol($match[1]);
  1361. if (!$skip) {
  1362. $attrarr[] = "$attrname='$thisval'";
  1363. }
  1364. $working = 1; $mode = 0;
  1365. $attr = preg_replace("/^'[^']*'(\s+|$)/", '', $attr);
  1366. break;
  1367. }
  1368. if (preg_match("%^([^\s\"']+)(\s+|$)%", $attr, $match)) {
  1369. $thisval = filter_xss_bad_protocol($match[1]);
  1370. if (!$skip) {
  1371. $attrarr[] = "$attrname=\"$thisval\"";
  1372. }
  1373. $working = 1; $mode = 0;
  1374. $attr = preg_replace("%^[^\s\"']+(\s+|$)%", '', $attr);
  1375. }
  1376. break;
  1377. }
  1378. if ($working == 0) {
  1379. // not well formed, remove and try again
  1380. $attr = preg_replace('/
  1381. ^
  1382. (
  1383. "[^"]*("|$) # - a string that starts with a double quote, up until the next double quote or the end of the string
  1384. | # or
  1385. \'[^\']*(\'|$)| # - a string that starts with a quote, up until the next quote or the end of the string
  1386. | # or
  1387. \S # - a non-whitespace character
  1388. )* # any number of the above three
  1389. \s* # any number of whitespaces
  1390. /x', '', $attr);
  1391. $mode = 0;
  1392. }
  1393. }
  1394. // The attribute list ends with a valueless attribute like "selected".
  1395. if ($mode == 1 && !$skip) {
  1396. $attrarr[] = $attrname;
  1397. }
  1398. return $attrarr;
  1399. }
  1400. /**
  1401. * Processes an HTML attribute value and ensures it does not contain an URL with a disallowed protocol (e.g. javascript:).
  1402. *
  1403. * @param $string
  1404. * The string with the attribute value.
  1405. * @param $decode
  1406. * (Deprecated) Whether to decode entities in the $string. Set to FALSE if the
  1407. * $string is in p…

Large files files are truncated, but you can click here to view the full file