uc_ssl.module 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. <?php
  2. //
  3. //Note about the include/exclude hooks. As an example, this module includes /cart* which essentially secures ALL cart pages with SSL. But it also implements an exclude which is called /cart Which you would think would negate the include but it DOES NOT. Even though Excludes take presedence over includes, it still allows /cart/1 for example, to be secured. Why? Because the exclude is not a wildcard match! Thus the exclude would NOT match /cart/1 because it's defined as /cart. Thus while /cart IS excluded (not secured with SSL),/cart/1 is STILL secured.
  4. //Implementation of hook_include_ssl_paths()
  5. //NOTE: Exclude takes presedence over Includes!!! So if the same path is in both hooks, then the path will be EXCLUDED from secured pages.
  6. //Since base_url has a design flaw in D4/D5/D6/D7 that seemingly will never be fixed, we fix it here.
  7. function uc_ssl_boot()
  8. {
  9. global $conf;
  10. //the $conf['https'] option MUST be enabled if mixed ssl mode is enabled. We make sure its enabled here. If it's not enabled then user
  11. //sessions will not persist across the secure and non-secure domain names resulting in the user being prompted to log in whenever they are
  12. //sent to a secure page.
  13. if (uc_ssl_switch_to_non_ssl() && !isset($conf['https']) || (isset($conf['https']) && !$conf['https']))
  14. {
  15. $conf['https'] = TRUE;
  16. }
  17. if ($GLOBALS['base_url'])
  18. {
  19. //If base_url exists and is set to a non-secure protocol while the page being called is secure, allow base_url to be changed to
  20. //the secure version of the website. Drupal for some reason doesnt have this logic anywhere for $base_url.
  21. if (uc_ssl_page_is_in_https_mode() && stristr($GLOBALS['base_url'], 'http://'))
  22. {
  23. $GLOBALS['base_url'] = str_ireplace('http://', 'https://', $GLOBALS['base_url']);
  24. }
  25. }
  26. }
  27. function uc_ssl_permission()
  28. {
  29. $perms = array(
  30. 'administer uc_ssl' => array(
  31. 'title' => t('Administer Ubercart SSL settings'),
  32. ),
  33. );
  34. return $perms;
  35. }
  36. function uc_ssl_page_is_in_https_mode()
  37. {
  38. if (isset($_SERVER['HTTPS']) && !empty($_SERVER['HTTPS']))
  39. {
  40. return TRUE;
  41. }
  42. return FALSE;
  43. }
  44. function uc_ssl_disable_overlay_module()
  45. {
  46. $modules = module_list();
  47. $base_path = base_path();
  48. if (variable_get('uc_ssl_switch_to_non_ssl', '') && in_array('overlay', $modules))
  49. {
  50. //Overlay is on, lets disable it.
  51. module_disable(array(0 => 'overlay'));
  52. }
  53. }
  54. function uc_ssl_init()
  55. {
  56. //Drupals Overlay panel is not secure! Fix that here by disable it all together. We cannot allow this. Drupal 7 is insecure by default, such a sad thing :/
  57. uc_ssl_disable_overlay_module();
  58. //menu_rebuild();
  59. //First and foremost, make sure Clean URLs are on.
  60. if (uc_ssl_clean_urls_enabled() && !drupal_is_cli())
  61. {
  62. //Setup a hook for ssl checking
  63. uc_ssl_check();
  64. //Run ssl check on every page load.
  65. uc_ssl_run();
  66. }
  67. }
  68. function uc_ssl_include_ssl_paths()
  69. {
  70. global $user;
  71. $paths = array(
  72. 'All Ubercart Cart pages' => '/cart*', //Secure ALL cart pages.
  73. //NOTE: If you remove the /user definition here, you CAN NOT use Mixed SSL Mode with D7. It HAS to exist because D7 http sessions are missing
  74. //data that https sessions require. But http sessions work with the https session variables. Thus, you MUST login with https or your sessions
  75. //will not carry between the http and https versions of your wesite. This is a D7 bug only, I believe its more of a feature than a bug.
  76. 'All User pages' => '/user*', //Secure ALL user pages.
  77. //Added support for Drupal commerce which is based upon Ubercart.
  78. 'All Drupal Commerce Checkout pages' => '/checkout*', //Secure ALL cart pages. NOTE: SEE EXCLUDES to see why the cart listing itself isnt secured!
  79. //This supports both UC and Commerce admins
  80. 'All Ubercart Cart Admin pages' => '/admin/store*', //Secure ALL UC admin pages.
  81. #'example 1' => '/cart', //Only secure the /cart and /cart/ page.
  82. #'example 2' => 'cart', //Only secure the /cart and /cart/ page.
  83. #'example 3' => 'cart/', //Only secure the /cart and /cart/ page.
  84. #'example 4' => '/cart*/something', //matches /cartThis/something and /cart/something but NOT thiscart/something etc.
  85. #'example 5' => '/cart/*/something', //Matches /cart/123/something and cart/this/something, but NOT /cart/something
  86. #'example 6' => '/cart/something', //Only secure the /cart/something and /cart/something/ page.
  87. );
  88. return $paths;
  89. }
  90. //Implementation of hook_exclude_ssl_paths()
  91. function uc_ssl_exclude_ssl_paths()
  92. {
  93. $paths = array(
  94. //These excludes no longer seem to be required for the D7 version and thus they have been removed until issues come up again.
  95. //'Exclude the cart page itself' => '/cart', //We dont need to secure the cart contents so we exclude that here
  96. //'Exclude Autocomplete' => '*autocomplete*',
  97. //'Exclude Ajax' => '*ajax*',
  98. #'example 1' => '/cart', //Only exclude the /cart and /cart/ page.
  99. #'example 2' => 'cart', //Only exclude the /cart and /cart/ page.
  100. #'example 3' => 'cart/', //Only secure the /cart and /cart/ page.
  101. #'example 4' => '/cart*/something', //matches /cartThis/something and /cart/something but NOT thiscart/something etc.
  102. #'example 5' => '/cart/*/something', //Matches /cart/123/something and cart/this/something, but NOT /cart/something
  103. #'example 6' => '/cart/something', //Only exclude the /cart/something and /cart/something/ page.
  104. );
  105. return $paths;
  106. }
  107. //Implementation of hook_exclude_ssl_paths()
  108. //This function is so you can completely ignore ANY switching at all no matter what.
  109. function uc_ssl_exclude_ssl_switch_paths()
  110. {
  111. $paths = array(
  112. #'example 1' => '/*ajax*', //Dont touch ajax paths whatsoever, this is just an example.
  113. );
  114. return $paths;
  115. }
  116. function uc_ssl_menu()
  117. {
  118. $items = array();
  119. $items['admin/config/system/uc_ssl'] = array(
  120. 'title' => 'Ubercart SSL',
  121. // 'title' => 'Ubercart SSL module settings',
  122. 'description' => 'Ubercart SSL module settings',
  123. 'page callback' => 'drupal_get_form',
  124. 'page arguments' => array('uc_ssl_admin'),
  125. 'access arguments' => array('administer uc_ssl'),
  126. 'type' => MENU_NORMAL_ITEM,
  127. 'file' => 'uc_ssl.admin.inc',
  128. );
  129. return $items;
  130. }
  131. function uc_ssl_enabled()
  132. {
  133. return variable_get('uc_ssl_status', '');
  134. }
  135. function uc_ssl_is_configured()
  136. {
  137. $query = "SELECT count(name) AS count from {variable} WHERE name = 'uc_ssl_status'";
  138. $results = db_query($query);
  139. $rows = $results->fetchAll();
  140. $rows = $rows[0];
  141. //If uc_ssl is not configured, warn the user and show a link to config it.
  142. if (!$rows->count)
  143. {
  144. $admin_link = 'http://'.$_SERVER['HTTP_HOST'].url().'admin/config/system/uc_ssl';
  145. drupal_set_message("Please configure Ubercart SSL for SSL enabled cart checkouts to work: <a href = '$admin_link'>$admin_link</a>", 'warning');
  146. }
  147. return $rows->count;
  148. }
  149. function uc_ssl_switch_to_non_ssl()
  150. {
  151. return variable_get('uc_ssl_switch_to_non_ssl', '');
  152. }
  153. function uc_ssl_path_match($path, $pages)
  154. {
  155. $result = FALSE; //This is here just to get rid of retarded php 'notice' messages. I hate php notices.
  156. if (!empty($pages))
  157. {
  158. //Trim off trailing / and then re-add it to avoid double /'s. This allows cart and cart/ to match as the same thing or cart/1 and cart/1/ to be the same.
  159. $path = trim($path, '/').'/';
  160. $regexp = '/^(';
  161. foreach ($pages AS $key => $regexp_path)
  162. {
  163. if ($regexp_path)
  164. {
  165. $regexp_path = trim($regexp_path, '/').'/';
  166. $regexp_path1 = str_replace('/', '\/', $regexp_path); //replace / with a \/
  167. $regexp_path2 = str_replace('*', '.*', $regexp_path1);//replace * with a .*
  168. $regexp .= $regexp_path2.'|'; //Append a | to signify end of this expression
  169. }
  170. }
  171. $regexp = trim($regexp, '|');
  172. $regexp .= ')$/';
  173. if (variable_get('uc_ssl_case_insensitive', 1))
  174. {
  175. $regexp .= 'i';
  176. }
  177. $result = preg_match($regexp, $path);
  178. }
  179. return $result;
  180. }
  181. function is_clean_url($path)
  182. {
  183. //Skip ANY ?q= urls since these do NOT come from a web browser when clean URLs is on. Doing this allows us to skip
  184. //ajax and autocomplete type URLs that come from within drupal itself. This is a major problem with Secure_Pages module
  185. //and we dont want the same problems so we restrict people to using ONLY clean URLs.
  186. if (!stristr($path, '?q='))
  187. {
  188. return TRUE;
  189. }
  190. return FALSE;
  191. }
  192. function uc_ssl_http_request($url)
  193. {
  194. //Handle JS type urls in the background as they are likely to be ajax type posts.
  195. if (arg(0) == 'js')
  196. {
  197. $js_response = drupal_http_request($url);
  198. return;
  199. }
  200. //Handle post type URLs. This should handle ajax POST urls such as the tax calculation/progress bars.
  201. if ($_SERVER['REQUEST_METHOD'] == 'POST' && empty($_POST['uc_ssl_post']))
  202. {
  203. $_POST['uc_ssl_post'] = 1;
  204. $options['headers'] = array('Content-Type' => 'application/x-www-form-urlencoded');
  205. $options['method'] = 'POST';
  206. $options['data'] = http_build_query($_POST, '', '&');
  207. //Drupal 7 decided to completely recode drupal_http_request so I dont know if this will cause issues or not. Report any bugs please.
  208. //$response = drupal_http_request($url, $headers, 'POST', http_build_query($_POST, '', '&'));
  209. $response = drupal_http_request($url, $options);
  210. return;
  211. }
  212. //All other types of urls are handled normally.
  213. // Reset destination variables or we'll get sent there instead of to $url
  214. unset($_REQUEST['destination'], $_GET['destination']);
  215. drupal_goto($url);
  216. return;
  217. }
  218. function uc_ssl_run()
  219. {
  220. //Set the current path without url() since it will mess up regex checks. Using url() also supports languages, base_path does not.
  221. //$current_path = trim($_SERVER['REQUEST_URI'], url());
  222. $root = url();
  223. $current_path = substr(trim($_SERVER['REQUEST_URI']),strlen($root));
  224. $ssl_domain = variable_get('uc_ssl_ssl_domain', '');
  225. $nonssl_domain = variable_get('uc_ssl_non_ssl_domain', '');
  226. //If uc_ssl is configured, run it.
  227. if (uc_ssl_is_configured() && is_clean_url($current_path))
  228. {
  229. if (uc_ssl_enabled())
  230. {
  231. //Get a list of all the URL links that should be included and excluded from SSL pages from other modules.
  232. $items = array();
  233. $exclude_secured_urls = array();
  234. $exclude_secured_urls = module_invoke('uc_ssl', 'exclude_ssl_paths', $exclude_secured_urls);
  235. foreach (module_implements('exclude_ssl_paths') as $module)
  236. {
  237. $new = module_invoke($module, 'exclude_ssl_paths', $exclude_secured_urls);
  238. if ($module != 'uc_ssl') //Skip uc_ssl since we already processed it above. Allow external modules to change uc_ssl defaults.
  239. {
  240. if (is_array($new))
  241. {
  242. $exclude_secured_urls = array_merge($exclude_secured_urls, $new);
  243. }
  244. }
  245. }
  246. $include_secured_urls = array();
  247. $include_secured_urls = module_invoke('uc_ssl', 'include_ssl_paths', $include_secured_urls);
  248. foreach (module_implements('include_ssl_paths') as $module)
  249. {
  250. $new = module_invoke($module, 'include_ssl_paths', $include_secured_urls);
  251. if ($module != 'uc_ssl') //Skip uc_ssl since we already processed it above. Allow external modules to change uc_ssl defaults.
  252. {
  253. if (is_array($new))
  254. {
  255. $include_secured_urls = array_merge($include_secured_urls, $new);
  256. }
  257. }
  258. }
  259. // Get a list of all the URL links that should not switch between HTTP/HTTPS at all.
  260. $exclude_switch_urls = array();
  261. $exclude_switch_urls = module_invoke('uc_ssl', 'exclude_ssl_switch_paths', $exclude_switch_urls);
  262. foreach (module_implements('exclude_ssl_switch_paths') as $module)
  263. {
  264. $new = module_invoke($module, 'exclude_ssl_switch_paths', $exclude_switch_urls);
  265. if ($module != 'uc_ssl') //Skip uc_ssl since we already processed it above. Allow external modules to change uc_ssl defaults.
  266. {
  267. if (is_array($new))
  268. {
  269. $exclude_switch_urls = array_merge($exclude_switch_urls, $new);
  270. }
  271. }
  272. }
  273. // make sure we return an array
  274. if (! is_array($include_secured_urls)) { $include_secured_urls = array(); }
  275. if (! is_array($exclude_secured_urls)) { $exclude_secured_urls = array(); }
  276. if (! is_array($exclude_switch_urls)) { $exclude_switch_urls = array(); }
  277. //If the path is in the exclude switch hook, dont do ANYTHING at all.
  278. if (uc_ssl_path_match($current_path, $exclude_switch_urls))
  279. {
  280. return;
  281. }
  282. //Switch to SSL no matter what the link is if Switch isnt on.
  283. if (!uc_ssl_switch_to_non_ssl())
  284. {
  285. if (!uc_ssl_page_is_in_https_mode() && !uc_ssl_path_match($current_path, $exclude_secured_urls))
  286. {
  287. uc_ssl_http_request($ssl_domain . $_SERVER['REQUEST_URI']);
  288. }
  289. }
  290. else
  291. {
  292. //If uc_ssl_switch_to_non_ssl() is enabled then we switch back to non-ssl link for non-cart links.
  293. if (uc_ssl_page_is_in_https_mode())
  294. {
  295. //The only exceptions are the links listed in the exclude list by other modules.
  296. if (uc_ssl_path_match($current_path, $exclude_secured_urls))
  297. {
  298. uc_ssl_http_request($nonssl_domain . $_SERVER['REQUEST_URI']);
  299. }
  300. if (!uc_ssl_path_match($current_path, $include_secured_urls) && uc_ssl_switch_to_non_ssl())
  301. {
  302. uc_ssl_http_request($nonssl_domain . $_SERVER['REQUEST_URI']);
  303. }
  304. }
  305. }
  306. //If on a cart url, use HTTPS and re-post/re-direct to the SSL version of the url.
  307. if (!uc_ssl_page_is_in_https_mode() && uc_ssl_path_match($current_path, $include_secured_urls) && !uc_ssl_path_match($current_path, $exclude_secured_urls))
  308. {
  309. uc_ssl_http_request($ssl_domain . $_SERVER['REQUEST_URI']);
  310. }
  311. }
  312. }
  313. }
  314. function uc_ssl_clean_urls_enabled()
  315. {
  316. $check = variable_get('clean_url', 0);
  317. if ($check == 1)
  318. {
  319. return TRUE;
  320. }
  321. drupal_set_message('Ubercart SSL (uc_ssl): The uc_ssl module REQUIRES that Clean URLs be enabled in order to work properly. This allows the module to distinguish between an Ajax call and a Web browser call as the two are handled differently so we need a way to distinguish them and this is the quickest easiest way to do so. This will also make your website URLs search engine friendly as well which is a good thing. Please enable clean urls here: <a href="?q=admin/settings/clean-urls">Clean URLs</a>', 'warn');
  322. return FALSE;
  323. }
  324. //This is a quickie hook to see if SSL works or not. It's not fool proof but it works for me.
  325. function uc_ssl_check($site = '')
  326. {
  327. error_reporting(E_ALL);
  328. if (!$site)
  329. {
  330. if (isset($_GET['uc_ssl_check']) && $_GET['uc_ssl_check'])
  331. {
  332. if (uc_ssl_page_is_in_https_mode())
  333. {
  334. echo TRUE;
  335. }
  336. else
  337. {
  338. echo FALSE;
  339. }
  340. exit;
  341. }
  342. }
  343. else
  344. {
  345. $check = drupal_http_request($site."?uc_ssl_check=1");
  346. if (isset($check->data) && $check->data == '1')
  347. {
  348. return TRUE;
  349. }
  350. //IF we are here, then the request failed for some reason, so we try to use a different method.
  351. if (ini_get('allow_url_fopen'))
  352. {
  353. if (file_get_contents($site."?uc_ssl_check=1") == '1')
  354. {
  355. return TRUE;
  356. }
  357. }
  358. //If we are here, something went wrong. Give the user some ideas on what they need to fix.
  359. drupal_set_message("Ubercart SSL (uc_ssl): The uc_ssl_check() function is returning FALSE because it was unable to contact the SSL (https) version of your website that you defined in the settings. This can be caused by 3 things. 1. Your website is not setup properly for SSL, 2. The OpenSSL extension is not enabled on in your PHP installation, 3. allow_url_fopen is not enabled in php.ini. If #2 fails, uc_ssl will try to use file_get_contents() which requires allow_url_fopen to be set to TRUE in php.ini. Hopefully these hints will help you fix this issue so that you can use uc_ssl. You can try to debug this by going to $site?uc_ssl_check=1", "error");
  360. }
  361. return FALSE;
  362. }
  363. if (!function_exists('printr'))
  364. {
  365. function printr($arr)
  366. {
  367. echo "<pre>";
  368. print_r($arr);
  369. echo "</pre>";
  370. }
  371. }