PageRenderTime 76ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 2ms

/includes/common.inc

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

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