Add URL normalization test vectors and fix no-schema URL handling#39
Add URL normalization test vectors and fix no-schema URL handling#39
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves webhook signature validation by making URL normalization accept inputs without an explicit scheme (by prepending http://) and adds a parameterized test suite of cross-SDK URL normalization vectors to lock in expected signatures.
Changes:
- Prepend
http://when the input URL lacks a://scheme delimiter. - Add 15 URL normalization/signature “golden” vectors via a JUnit 5 parameterized test.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/main/java/com/didww/sdk/callback/RequestValidator.java |
Adjusts URL normalization to handle no-scheme URLs by prepending http://. |
src/test/java/com/didww/sdk/callback/RequestValidatorTest.java |
Adds parameterized test vectors to validate URL normalization + signature results across common URL variants. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (!url.matches("^[a-zA-Z]+://.*")) { | ||
| url = "http://" + url; | ||
| } | ||
| URI uri = URI.create(url); | ||
| String scheme = uri.getScheme(); | ||
| String userInfo = uri.getUserInfo() != null ? uri.getUserInfo() + "@" : ""; |
There was a problem hiding this comment.
normalizeUrl uses uri.getScheme() later to pick default ports and also includes it in the normalized URL. URI preserves the original scheme casing (e.g., HTTPS://... -> scheme "HTTPS"), but schemes are case-insensitive; this can cause signature mismatches and wrong default-port selection. Consider lowercasing the parsed scheme (and using the lowercased value consistently for both port selection and the returned normalized URL).
| if (!url.matches("^[a-zA-Z]+://.*")) { | ||
| url = "http://" + url; | ||
| } |
There was a problem hiding this comment.
String#matches recompiles the regex on every call. Since normalizeUrl runs for every validation, consider avoiding regex here (e.g., a small character scan / indexOf("://")) or using a precompiled Pattern constant. If you keep regex, [A-Za-z][A-Za-z0-9+.-]*:// is a more RFC-compatible scheme pattern than ^[a-zA-Z]+://.
…alidator - Lowercase the URI scheme after parsing to handle mixed-case schemes correctly - Replace url.matches() with a precompiled static Pattern to avoid recompiling the regex on every call
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
| URI uri = URI.create(url); | ||
| String scheme = uri.getScheme(); | ||
| String scheme = uri.getScheme().toLowerCase(); |
There was a problem hiding this comment.
uri.getScheme().toLowerCase() is locale-sensitive and can produce incorrect results under certain default locales (e.g., Turkish), which would break signature validation. Use locale-independent lowercasing (e.g., toLowerCase(Locale.ROOT)) when normalizing the scheme.
| static Stream<Object[]> urlNormalizationVectors() { | ||
| return Stream.of( | ||
| new Object[]{"http://foo.com/bar", "4d1ce2be656d20d064183bec2ab98a2ff3981f73"}, | ||
| new Object[]{"http://foo.com:80/bar", "4d1ce2be656d20d064183bec2ab98a2ff3981f73"}, |
There was a problem hiding this comment.
The method source currently returns Stream<Object[]>, which is less type-safe and harder to read/maintain than using JUnit 5's Stream<Arguments> (or a dedicated record/class). Switching to Arguments.of(url, expectedSignature) will improve clarity and avoids accidental argument-shape mistakes.
- Use toLowerCase(Locale.ROOT) for scheme normalization to avoid locale-dependent behavior (e.g. Turkish locale) - Restore SCHEME_PATTERN and no-scheme URL handling - Change Stream<Object[]> to Stream<Arguments> with Arguments.of() for type-safe JUnit 5 parameterized tests - Restore URL normalization test vectors
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| RequestValidator validator = new RequestValidator("SOMEAPIKEY"); | ||
| Map<String, String> payload = new LinkedHashMap<>(); | ||
| payload.put("id", "1dd7a68b-e235-402b-8912-fe73ee14243a"); | ||
| payload.put("status", "completed"); | ||
| payload.put("type", "orders"); | ||
|
|
There was a problem hiding this comment.
The payload setup here is duplicated across multiple tests in this class (same keys/values with only ordering differences). Extracting a small helper (e.g., a method that returns the common payload map) would reduce repetition and make it easier to update the test data consistently if the signature inputs change again.
| if (!SCHEME_PATTERN.matcher(url).find()) { | ||
| url = "http://" + url; | ||
| } | ||
| URI uri = URI.create(url); | ||
| String scheme = uri.getScheme(); | ||
| String scheme = uri.getScheme().toLowerCase(Locale.ROOT); | ||
| String userInfo = uri.getUserInfo() != null ? uri.getUserInfo() + "@" : ""; | ||
| String host = uri.getHost(); |
There was a problem hiding this comment.
normalizeUrl prepends http:// when no scheme is detected, but some inputs (e.g., /path, //foo.com/bar, or other non-host relative forms) will parse with uri.getHost() == null and the method will return a normalized URL containing the literal string null as the host. Consider explicitly validating that uri.getHost() is non-null/non-empty (and optionally handling scheme-relative URLs starting with //) and returning ""/failing validation instead of producing a canonical form with a null host.



Summary
Test plan