feat(UrlUtils): Resolve more URL formats
Resolves more formats of URLs correctly: absolute, root-relative, relative, parent-relative, and URLs with query strings and fragments.
This commit is contained in:
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
BUILD_DIR="$ROOT_DIR/build/url_utils"
|
||||
BINARY="$BUILD_DIR/UrlUtilsTest"
|
||||
PLATFORMIO_DIR="${PLATFORMIO_CORE_DIR:-$HOME/.platformio}"
|
||||
ARDUINO_FRAMEWORK_DIR="$PLATFORMIO_DIR/packages/framework-arduinoespressif32"
|
||||
|
||||
mkdir -p "$BUILD_DIR"
|
||||
|
||||
SOURCES=(
|
||||
"$ROOT_DIR/test/url_utils/UrlUtilsTest.cpp"
|
||||
"$ROOT_DIR/src/util/UrlUtils.cpp"
|
||||
)
|
||||
|
||||
CXXFLAGS=(
|
||||
-std=c++20
|
||||
-O2
|
||||
-Wall
|
||||
-Wextra
|
||||
-pedantic
|
||||
-fno-exceptions
|
||||
-DARDUINO_USB_MODE=1
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
-DDESTRUCTOR_CLOSES_FILE=1
|
||||
-I"$ROOT_DIR/test/shims"
|
||||
-I"$ROOT_DIR"
|
||||
-I"$ROOT_DIR/src"
|
||||
-I"$ARDUINO_FRAMEWORK_DIR/cores/esp32"
|
||||
-I"$ARDUINO_FRAMEWORK_DIR/variants/esp32c3"
|
||||
)
|
||||
|
||||
c++ "${CXXFLAGS[@]}" "${SOURCES[@]}" -o "$BINARY"
|
||||
|
||||
"$BINARY" "$@"
|
||||
@@ -0,0 +1,149 @@
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include "../../src/util/UrlUtils.h"
|
||||
|
||||
static int testsPassed = 0;
|
||||
static int testsFailed = 0;
|
||||
|
||||
#define ASSERT_EQ(a, b) \
|
||||
do { \
|
||||
if ((a) != (b)) { \
|
||||
fprintf(stderr, " FAIL: %s:%d: %s != %s\n", __FILE__, __LINE__, #a, #b); \
|
||||
testsFailed++; \
|
||||
return; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define PASS() testsPassed++
|
||||
|
||||
void testAbsoluteUrlRemainsUnchanged() {
|
||||
printf("testAbsoluteUrlRemainsUnchanged...\n");
|
||||
ASSERT_EQ(
|
||||
UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "https://example.com/books/download/test.epub"),
|
||||
"https://example.com/books/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "//cdn.example.com/books/test.epub"),
|
||||
"https://cdn.example.com/books/test.epub");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testRootRelativeUrlUsesHostRoot() {
|
||||
printf("testRootRelativeUrlUsesHostRoot...\n");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "/books/download/test.epub"),
|
||||
"https://catalog.example.com/books/download/test.epub");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testRelativeUrlUsesFeedDirectory() {
|
||||
printf("testRelativeUrlUsesFeedDirectory...\n");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "download/test.epub"),
|
||||
"https://catalog.example.com/opds/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/sub", "download/test.epub"),
|
||||
"https://catalog.example.com/opds/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/sub/", "download/test.epub"),
|
||||
"https://catalog.example.com/opds/sub/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "images//cover.jpg"),
|
||||
"https://catalog.example.com/opds/images//cover.jpg");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "subdir/"),
|
||||
"https://catalog.example.com/opds/subdir/");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testParentRelativeUrlResolvesCorrectly() {
|
||||
printf("testParentRelativeUrlResolvesCorrectly...\n");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/sub/feed.xml", "../download/test.epub"),
|
||||
"https://catalog.example.com/opds/download/test.epub");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testUrlsWithQueryAndFragmentResolveCorrectly() {
|
||||
printf("testUrlsWithQueryAndFragmentResolveCorrectly...\n");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/sub?auth=1", "download/test.epub"),
|
||||
"https://catalog.example.com/opds/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/sub/#section", "download/test.epub?format=epub#start"),
|
||||
"https://catalog.example.com/opds/sub/download/test.epub?format=epub#start");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml?auth=1#top", "download/test.epub"),
|
||||
"https://catalog.example.com/opds/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "/books?id=/../cover"),
|
||||
"https://catalog.example.com/books?id=/../cover");
|
||||
ASSERT_EQ(
|
||||
UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "download/test.epub?next=/../cover#frag/../x"),
|
||||
"https://catalog.example.com/opds/download/test.epub?next=/../cover#frag/../x");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml", "download/test.epub#frag?x=1"),
|
||||
"https://catalog.example.com/opds/download/test.epub#frag?x=1");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testRootRelativeUrlsWithQueryAndFragmentResolveCorrectly() {
|
||||
printf("testRootRelativeUrlsWithQueryAndFragmentResolveCorrectly...\n");
|
||||
ASSERT_EQ(
|
||||
UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml?auth=1", "/books/download/test.epub?format=epub"),
|
||||
"https://catalog.example.com/books/download/test.epub?format=epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/sub/#section", "/books/download/test.epub#start"),
|
||||
"https://catalog.example.com/books/download/test.epub#start");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/sub?auth=1#top",
|
||||
"/books/download/test.epub?format=epub#start"),
|
||||
"https://catalog.example.com/books/download/test.epub?format=epub#start");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testHostOnlyBaseUrlsWithQueryAndFragmentResolveCorrectly() {
|
||||
printf("testHostOnlyBaseUrlsWithQueryAndFragmentResolveCorrectly...\n");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com?auth=1", "download/test.epub"),
|
||||
"https://catalog.example.com/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com#top", "/books/download/test.epub"),
|
||||
"https://catalog.example.com/books/download/test.epub");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com?auth=1", "?page=2"), "https://catalog.example.com?page=2");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com#top", "#latest"), "https://catalog.example.com#latest");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testQueryAndFragmentReferencesAreRfcCompliant() {
|
||||
printf("testQueryAndFragmentReferencesAreRfcCompliant...\n");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml?auth=1", "#top"),
|
||||
"https://catalog.example.com/opds/feed.xml?auth=1#top");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com/opds/feed.xml?auth=1#old", "?page=2"),
|
||||
"https://catalog.example.com/opds/feed.xml?page=2");
|
||||
ASSERT_EQ(UrlUtils::buildUrl("https://catalog.example.com?auth=1#old", "#latest"),
|
||||
"https://catalog.example.com?auth=1#latest");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testExtractHostnameHandlesPortsAndIpv6() {
|
||||
printf("testExtractHostnameHandlesPortsAndIpv6...\n");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("https://catalog.example.com:8080/path"), "catalog.example.com");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("catalog.example.com:8080/opds"), "catalog.example.com");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("https://user:pass@[2001:db8::1]:8080/path?x=1#frag"), "2001:db8::1");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("http://[fe80::1234]#top"), "fe80::1234");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void testExtractHostnameRejectsMalformedInputs() {
|
||||
printf("testExtractHostnameRejectsMalformedInputs...\n");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("https:///path"), "");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("http://[]"), "");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("http://@/x"), "");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("http://[::1"), "");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("http://[::1]x"), "");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("mailto:user@example.com"), "");
|
||||
ASSERT_EQ(UrlUtils::extractHostname("urn:isbn:1234567890"), "");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
printf("=== OPDS URL Utils Tests ===\n\n");
|
||||
|
||||
testAbsoluteUrlRemainsUnchanged();
|
||||
testRootRelativeUrlUsesHostRoot();
|
||||
testRelativeUrlUsesFeedDirectory();
|
||||
testParentRelativeUrlResolvesCorrectly();
|
||||
testUrlsWithQueryAndFragmentResolveCorrectly();
|
||||
testRootRelativeUrlsWithQueryAndFragmentResolveCorrectly();
|
||||
testHostOnlyBaseUrlsWithQueryAndFragmentResolveCorrectly();
|
||||
testQueryAndFragmentReferencesAreRfcCompliant();
|
||||
testExtractHostnameHandlesPortsAndIpv6();
|
||||
testExtractHostnameRejectsMalformedInputs();
|
||||
|
||||
printf("\n=== Results: %d passed, %d failed ===\n", testsPassed, testsFailed);
|
||||
return testsFailed > 0 ? 1 : 0;
|
||||
}
|
||||
Reference in New Issue
Block a user