PageRenderTime 81ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/includes/common.inc

https://github.com/Aerivan/aerivan
PHP | 8048 lines | 3310 code | 532 blank | 4206 comment | 632 complexity | 5804168c989b211cee04d0056c6b3b34 MD5 | raw file
Possible License(s): GPL-2.0

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

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

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