|
3 | 3 | import io.sentry.ISpan; |
4 | 4 | import io.sentry.SpanDataConvention; |
5 | 5 | import io.sentry.protocol.Request; |
6 | | -import java.net.MalformedURLException; |
7 | | -import java.net.URL; |
8 | | -import java.util.regex.Matcher; |
9 | | -import java.util.regex.Pattern; |
| 6 | +import java.net.URI; |
10 | 7 | import org.jetbrains.annotations.ApiStatus; |
11 | 8 | import org.jetbrains.annotations.NotNull; |
12 | 9 | import org.jetbrains.annotations.Nullable; |
|
15 | 12 | public final class UrlUtils { |
16 | 13 |
|
17 | 14 | public static final @NotNull String SENSITIVE_DATA_SUBSTITUTE = "[Filtered]"; |
18 | | - private static final @NotNull Pattern AUTH_REGEX = Pattern.compile("(.+://)(.*@)(.*)"); |
19 | 15 |
|
20 | 16 | public static @Nullable UrlDetails parseNullable(final @Nullable String url) { |
21 | | - if (url == null) { |
22 | | - return null; |
23 | | - } |
24 | | - |
25 | | - return parse(url); |
| 17 | + return url == null ? null : parse(url); |
26 | 18 | } |
27 | 19 |
|
28 | 20 | public static @NotNull UrlDetails parse(final @NotNull String url) { |
29 | | - if (isAbsoluteUrl(url)) { |
30 | | - return splitAbsoluteUrl(url); |
31 | | - } else { |
32 | | - return splitRelativeUrl(url); |
33 | | - } |
34 | | - } |
35 | | - |
36 | | - private static boolean isAbsoluteUrl(@NotNull String url) { |
37 | | - return url.contains("://"); |
38 | | - } |
39 | | - |
40 | | - private static @NotNull UrlDetails splitRelativeUrl(final @NotNull String url) { |
41 | | - final int queryParamSeparatorIndex = url.indexOf("?"); |
42 | | - final int fragmentSeparatorIndex = url.indexOf("#"); |
43 | | - |
44 | | - final @Nullable String baseUrl = |
45 | | - extractBaseUrl(url, queryParamSeparatorIndex, fragmentSeparatorIndex); |
46 | | - final @Nullable String query = |
47 | | - extractQuery(url, queryParamSeparatorIndex, fragmentSeparatorIndex); |
48 | | - final @Nullable String fragment = extractFragment(url, fragmentSeparatorIndex); |
| 21 | + try { |
| 22 | + URI uri = new URI(url); |
| 23 | + if (uri.isAbsolute() && !isValidAbsoluteUrl(uri)) { |
| 24 | + return new UrlDetails(null, null, null); |
| 25 | + } |
49 | 26 |
|
50 | | - return new UrlDetails(baseUrl, query, fragment); |
51 | | - } |
| 27 | + final @NotNull String schemeAndSeparator = |
| 28 | + uri.getScheme() == null ? "" : (uri.getScheme() + "://"); |
| 29 | + final @NotNull String authority = uri.getRawAuthority() == null ? "" : uri.getRawAuthority(); |
| 30 | + final @NotNull String path = uri.getRawPath() == null ? "" : uri.getRawPath(); |
| 31 | + final @Nullable String query = uri.getRawQuery(); |
| 32 | + final @Nullable String fragment = uri.getRawFragment(); |
52 | 33 |
|
53 | | - private static @Nullable String extractBaseUrl( |
54 | | - final @NotNull String url, |
55 | | - final int queryParamSeparatorIndex, |
56 | | - final int fragmentSeparatorIndex) { |
57 | | - if (queryParamSeparatorIndex >= 0) { |
58 | | - return url.substring(0, queryParamSeparatorIndex).trim(); |
59 | | - } else if (fragmentSeparatorIndex >= 0) { |
60 | | - return url.substring(0, fragmentSeparatorIndex).trim(); |
61 | | - } else { |
62 | | - return url; |
63 | | - } |
64 | | - } |
| 34 | + final @NotNull String filteredUrl = schemeAndSeparator + filterUserInfo(authority) + path; |
65 | 35 |
|
66 | | - private static @Nullable String extractQuery( |
67 | | - final @NotNull String url, |
68 | | - final int queryParamSeparatorIndex, |
69 | | - final int fragmentSeparatorIndex) { |
70 | | - if (queryParamSeparatorIndex > 0) { |
71 | | - if (fragmentSeparatorIndex > 0 && fragmentSeparatorIndex > queryParamSeparatorIndex) { |
72 | | - return url.substring(queryParamSeparatorIndex + 1, fragmentSeparatorIndex).trim(); |
73 | | - } else { |
74 | | - return url.substring(queryParamSeparatorIndex + 1).trim(); |
75 | | - } |
76 | | - } else { |
77 | | - return null; |
78 | | - } |
79 | | - } |
80 | | - |
81 | | - private static @Nullable String extractFragment( |
82 | | - final @NotNull String url, final int fragmentSeparatorIndex) { |
83 | | - if (fragmentSeparatorIndex > 0) { |
84 | | - return url.substring(fragmentSeparatorIndex + 1).trim(); |
85 | | - } else { |
86 | | - return null; |
| 36 | + return new UrlDetails(filteredUrl, query, fragment); |
| 37 | + } catch (Exception e) { |
| 38 | + return new UrlDetails(null, null, null); |
87 | 39 | } |
88 | 40 | } |
89 | 41 |
|
90 | | - private static @NotNull UrlDetails splitAbsoluteUrl(final @NotNull String url) { |
| 42 | + private static boolean isValidAbsoluteUrl(final @NotNull URI uri) { |
91 | 43 | try { |
92 | | - final @NotNull String filteredUrl = urlWithAuthRemoved(url); |
93 | | - final @NotNull URL urlObj = new URL(url); |
94 | | - final @NotNull String baseUrl = baseUrlOnly(filteredUrl); |
95 | | - if (baseUrl.contains("#")) { |
96 | | - // url considered malformed because it has fragment |
97 | | - return new UrlDetails(null, null, null); |
98 | | - } else { |
99 | | - final @Nullable String query = urlObj.getQuery(); |
100 | | - final @Nullable String fragment = urlObj.getRef(); |
101 | | - return new UrlDetails(baseUrl, query, fragment); |
102 | | - } |
103 | | - } catch (MalformedURLException e) { |
104 | | - return new UrlDetails(null, null, null); |
| 44 | + uri.toURL(); |
| 45 | + } catch (Exception e) { |
| 46 | + return false; |
105 | 47 | } |
| 48 | + return true; |
106 | 49 | } |
107 | 50 |
|
108 | | - private static @NotNull String urlWithAuthRemoved(final @NotNull String url) { |
109 | | - final @NotNull Matcher userInfoMatcher = AUTH_REGEX.matcher(url); |
110 | | - if (userInfoMatcher.matches() && userInfoMatcher.groupCount() == 3) { |
111 | | - final @NotNull String userInfoString = userInfoMatcher.group(2); |
112 | | - final @NotNull String replacementString = |
113 | | - userInfoString.contains(":") |
114 | | - ? (SENSITIVE_DATA_SUBSTITUTE + ":" + SENSITIVE_DATA_SUBSTITUTE + "@") |
115 | | - : (SENSITIVE_DATA_SUBSTITUTE + "@"); |
116 | | - return userInfoMatcher.group(1) + replacementString + userInfoMatcher.group(3); |
117 | | - } else { |
| 51 | + private static @NotNull String filterUserInfo(final @NotNull String url) { |
| 52 | + if (!url.contains("@")) { |
118 | 53 | return url; |
119 | 54 | } |
120 | | - } |
121 | | - |
122 | | - private static @NotNull String baseUrlOnly(final @NotNull String url) { |
123 | | - final int queryParamSeparatorIndex = url.indexOf("?"); |
124 | | - |
125 | | - if (queryParamSeparatorIndex >= 0) { |
126 | | - return url.substring(0, queryParamSeparatorIndex).trim(); |
127 | | - } else { |
128 | | - final int fragmentSeparatorIndex = url.indexOf("#"); |
129 | | - if (fragmentSeparatorIndex >= 0) { |
130 | | - return url.substring(0, fragmentSeparatorIndex).trim(); |
131 | | - } else { |
132 | | - return url; |
133 | | - } |
| 55 | + if (url.startsWith("@")) { |
| 56 | + return SENSITIVE_DATA_SUBSTITUTE + url; |
134 | 57 | } |
| 58 | + final @NotNull String userInfo = url.substring(0, url.indexOf('@')); |
| 59 | + final @NotNull String filteredUserInfo = |
| 60 | + userInfo.contains(":") |
| 61 | + ? (SENSITIVE_DATA_SUBSTITUTE + ":" + SENSITIVE_DATA_SUBSTITUTE) |
| 62 | + : SENSITIVE_DATA_SUBSTITUTE; |
| 63 | + return filteredUserInfo + url.substring(url.indexOf('@')); |
135 | 64 | } |
136 | 65 |
|
137 | 66 | public static final class UrlDetails { |
|
0 commit comments