File indexing completed on 2024-11-24 05:19:04
0001 <?php 0002 /** 0003 * file server - part of Opendesktop.org platform project <https://www.opendesktop.org>. 0004 * 0005 * Copyright (c) 2016 pling GmbH. 0006 * 0007 * This program is free software: you can redistribute it and/or modify 0008 * it under the terms of the GNU Affero General Public License as 0009 * published by the Free Software Foundation, either version 3 of the 0010 * License, or (at your option) any later version. 0011 * 0012 * This program is distributed in the hope that it will be useful, 0013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0015 * GNU Affero General Public License for more details. 0016 * 0017 * You should have received a copy of the GNU Affero General Public License 0018 * along with this program. If not, see <https://www.gnu.org/licenses/>. 0019 */ 0020 0021 namespace Ocs\Url; 0022 0023 use DateTime; 0024 use Exception; 0025 0026 class UrlSigner 0027 { 0028 0029 /** 0030 * Sign a URL 0031 * 0032 * @param string $url 0033 * @param string $private_key 0034 * @param int $expireMin 0035 * 0036 * @return string Signed URL 0037 * @throws Exception 0038 */ 0039 public static function getSignedUrl(string $url, string $private_key, int $expireMin = 30): string 0040 { 0041 $join = parse_url($url, PHP_URL_QUERY) ? '&' : '?'; 0042 $expiration = self::getExpirationTimestamp($expireMin); 0043 0044 return $url . $join . 'signature=' . self::getUrlSignature($url, $private_key, $expiration) . '&expires=' . $expiration; 0045 } 0046 0047 /** 0048 * @throws Exception 0049 */ 0050 private static function getExpirationTimestamp($expiration): string 0051 { 0052 if (is_int($expiration)) { 0053 $expiration = (new DateTime())->modify((int)$expiration . ' minutes'); 0054 } 0055 0056 if (!$expiration instanceof DateTime) { 0057 throw new Exception('Expiration date must be an instance of DateTime or an integer'); 0058 } 0059 0060 if (!self::isFuture($expiration->getTimestamp())) { 0061 throw new Exception('Expiration date must be in the future'); 0062 } 0063 0064 return (string)$expiration->getTimestamp(); 0065 } 0066 0067 private static function isFuture($timestamp): bool 0068 { 0069 return ((int)$timestamp) >= (new DateTime())->getTimestamp(); 0070 } 0071 0072 /** 0073 * Get the signature for the given URL 0074 * 0075 * @param string $url 0076 * @param string $private_key 0077 * @param $expiration 0078 * 0079 * @return string URL signature string 0080 */ 0081 private static function getUrlSignature(string $url, string $private_key, $expiration): string 0082 { 0083 return md5($url . ':' . $expiration . ':' . $private_key); 0084 } 0085 0086 /** 0087 * Check that the given URL is correctly signed 0088 * 0089 * @param string $url 0090 * @param string $private_key 0091 * 0092 * @return bool True if URL contains valid signature, false otherwise 0093 */ 0094 public static function verifySignedUrl(string $url, string $private_key): bool 0095 { 0096 0097 $param_expires = preg_quote('expires'); 0098 if (!preg_match($regex1 = "/(:?&|\?)?{$param_expires}=([0-9]{10})/", $url, $matches)) { 0099 return false; 0100 } 0101 // Get the expires param 0102 $expiration = $matches[2]; 0103 0104 0105 if (!self::isFuture($expiration)) { 0106 return false; 0107 } 0108 $param_name = preg_quote('signature'); 0109 if (!preg_match($regex2 = "/(:?&|\?)?{$param_name}=([0-9a-f]{32})/", $url, $matches)) { 0110 return false; 0111 } 0112 // Get the signature param 0113 $passed_sig = $matches[2]; 0114 0115 // Strip signature from the given URL 0116 $url = preg_replace($regex1, '', $url); 0117 $url = preg_replace($regex2, '', $url); 0118 0119 // Check that the given signature matches the correct one 0120 return self::getUrlSignature($url, $private_key, $expiration) === $passed_sig; 0121 } 0122 }