Fix relative URL generation when using alternate HTTP port
authorTim Otten <totten@civicrm.org>
Sat, 22 Jul 2023 01:18:27 +0000 (18:18 -0700)
committerTim Otten <totten@civicrm.org>
Sat, 22 Jul 2023 01:25:48 +0000 (18:25 -0700)
CRM/Utils/Url.php
tests/phpunit/CRM/Utils/UrlTest.php [new file with mode: 0644]

index 5a40beb5a0a6838f91f2f40c6bc8317482607d9b..d30d3003e655311f82b5ae766e5ed084b7407902 100644 (file)
@@ -40,13 +40,19 @@ class CRM_Utils_Url {
    * Convert to a relative URL (if host/port matches).
    *
    * @param string $value
+   * @param string|null $currentHostPort
+   *   The value of HTTP_HOST. (NULL means "lookup HTTP_HOST")
    * @return string
    */
-  public static function toRelative(string $value): string {
-    $parsed = parse_url($value);
-    if (isset($_SERVER['HTTP_HOST']) && isset($parsed['host']) && $_SERVER['HTTP_HOST'] == $parsed['host']) {
-      $value = $parsed['path'];
+  public static function toRelative(string $value, ?string $currentHostPort = NULL): string {
+    $currentHostPort = $currentHostPort ?: $_SERVER['HTTP_HOST'] ?? NULL;
+
+    if (preg_match(';^(//|http://|https://)([^/]*)(.*);', $value, $m)) {
+      if ($m[2] === $currentHostPort) {
+        return $m[3];
+      }
     }
+
     return $value;
   }
 
diff --git a/tests/phpunit/CRM/Utils/UrlTest.php b/tests/phpunit/CRM/Utils/UrlTest.php
new file mode 100644 (file)
index 0000000..fdbf630
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * Class CRM_Utils_UrlTest
+ * @group headless
+ */
+class CRM_Utils_UrlTest extends CiviUnitTestCase {
+
+  public function setUp(): void {
+    parent::setUp();
+    $this->useTransaction();
+  }
+
+  /**
+   * Equal cases.
+   *
+   * @return array
+   */
+  public function relativeCases() {
+    // array(0 => $absoluteUrl, 1 => $currentHost, 2 => $expectedResult)
+    $cases = [];
+    $cases[] = ['//example.com/', 'example.com', '/'];
+    $cases[] = ['http://example.com/', 'example.com', '/'];
+    $cases[] = ['http://example.com/foo', 'example.com', '/foo'];
+    $cases[] = ['https://example.com/foo/bar', 'example.com', '/foo/bar'];
+    $cases[] = ['http://example.com/foo?bar#baz', 'example.com', '/foo?bar#baz'];
+
+    $cases[] = ['//example.com/', 'example.com:8001', '//example.com/'];
+    $cases[] = ['http://example.com/', 'example.com:8001', 'http://example.com/'];
+    $cases[] = ['http://example.com/foo', 'example.com:8001', 'http://example.com/foo'];
+    $cases[] = ['https://example.com/foo/bar', 'example.com:8001', 'https://example.com/foo/bar'];
+    $cases[] = ['http://example.com/foo?bar#baz', 'example.com:8001', 'http://example.com/foo?bar#baz'];
+
+    $cases[] = ['//example.com:8001/', 'example.com:8001', '/'];
+    $cases[] = ['http://example.com:8001/', 'example.com:8001', '/'];
+    $cases[] = ['http://example.com:8001/foo', 'example.com:8001', '/foo'];
+    $cases[] = ['https://example.com:8001/foo/bar', 'example.com:8001', '/foo/bar'];
+    $cases[] = ['http://example.com:8001/foo?bar#baz', 'example.com:8001', '/foo?bar#baz'];
+
+    $cases[] = ['//sub.example.com/', 'example.com', '//sub.example.com/'];
+    $cases[] = ['http://sub.example.com/', 'example.com', 'http://sub.example.com/'];
+    $cases[] = ['http://sub.example.com/foo', 'example.com', 'http://sub.example.com/foo'];
+
+    $cases[] = ['//sub.example.com/', 'sub.example.com', '/'];
+    $cases[] = ['http://sub.example.com/', 'sub.example.com', '/'];
+    $cases[] = ['http://sub.example.com/foo/bar', 'sub.example.com', '/foo/bar'];
+
+    $cases[] = ['http://127.0.0.1/foo/bar', 'sub.example.com', 'http://127.0.0.1/foo/bar'];
+    $cases[] = ['https://127.0.0.1/foo/bar', '127.0.0.1', '/foo/bar'];
+
+    $cases[] = ['http://[2001:aaaa:bbbb:cccc:dddd:eeee:ffff:9999]:8001/foo/bar', 'sub.example.com', 'http://[2001:aaaa:bbbb:cccc:dddd:eeee:ffff:9999]:8001/foo/bar'];
+    $cases[] = ['https://[2001:aaaa:bbbb:cccc:dddd:eeee:ffff:9999]:8001/foo/bar', '[2001:aaaa:bbbb:cccc:dddd:eeee:ffff:9999]:8001', '/foo/bar'];
+
+    // return CRM_Utils_Array::subset($cases, [2]);
+    return $cases;
+  }
+
+  /**
+   * Test relative URL conversions.
+   *
+   * @param string $absoluteUrl
+   * @param string $currentHost
+   * @param string $expectedResult
+   *
+   * @dataProvider relativeCases
+   */
+  public function testEquals($absoluteUrl, $currentHost, $expectedResult) {
+    $actual = CRM_Utils_Url::toRelative($absoluteUrl, $currentHost);
+    $this->assertEquals($expectedResult, $actual, "Within \"$currentHost\", \"$absoluteUrl\" should render as \"$expectedResult\"");
+  }
+
+}