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 }