@@ -142,7 +142,7 @@ class UrlUtilsTest {
142142 }
143143
144144 @Test
145- fun `splits url without query or fragment and no authority ` () {
145+ fun `splits url without query or fragment and no user info ` () {
146146 val urlDetails = UrlUtils .parse(
147147 " https://sentry.io"
148148 )
@@ -161,30 +161,179 @@ class UrlUtilsTest {
161161 assertEquals(" top" , urlDetails.fragment)
162162 }
163163
164+ // Fragment is allowed to contain '?' according to RFC 3986
164165 @Test
165- fun `no details extracted with query after fragment` () {
166+ fun `extracts details with question mark after fragment` () {
166167 val urlDetails = UrlUtils .parse(
167168 " https://user:password@sentry.io#fragment?q=1&s=2&token=secret"
168169 )
170+ assertEquals(" https://[Filtered]:[Filtered]@sentry.io" , urlDetails.url)
171+ assertNull(urlDetails.query)
172+ assertEquals(" fragment?q=1&s=2&token=secret" , urlDetails.fragment)
173+ }
174+
175+ @Test
176+ fun `extracts details with question mark after fragment without user info` () {
177+ val urlDetails = UrlUtils .parse(
178+ " https://sentry.io#fragment?q=1&s=2&token=secret"
179+ )
180+ assertEquals(" https://sentry.io" , urlDetails.url)
181+ assertNull(urlDetails.query)
182+ assertEquals(" fragment?q=1&s=2&token=secret" , urlDetails.fragment)
183+ }
184+
185+ @Test
186+ fun `no details extracted from malformed url due to invalid protocol` () {
187+ val urlDetails = UrlUtils .parse(
188+ " htps://user@sentry.io#fragment?q=1&s=2&token=secret"
189+ )
169190 assertNull(urlDetails.url)
170191 assertNull(urlDetails.query)
171192 assertNull(urlDetails.fragment)
172193 }
173194
174195 @Test
175- fun `no details extracted with query after fragment without authority ` () {
196+ fun `no details extracted from malformed url due to # symbol in fragment ` () {
176197 val urlDetails = UrlUtils .parse(
177- " https://sentry.io# fragment?q=1&s=2&token=secret "
198+ " https://example.com#hello# fragment"
178199 )
179200 assertNull(urlDetails.url)
180201 assertNull(urlDetails.query)
181202 assertNull(urlDetails.fragment)
182203 }
183204
184205 @Test
185- fun `no details extracted from malformed url ` () {
206+ fun `strips empty user info ` () {
186207 val urlDetails = UrlUtils .parse(
187- " htps://user@sentry.io#fragment?q=1&s=2&token=secret"
208+ " https://@sentry.io?query=a#fragment?q=1&s=2&token=secret"
209+ )
210+ assertEquals(" https://[Filtered]@sentry.io" , urlDetails.url)
211+ assertEquals(" query=a" , urlDetails.query)
212+ assertEquals(" fragment?q=1&s=2&token=secret" , urlDetails.fragment)
213+ }
214+
215+ @Test
216+ fun `extracts details from relative url with leading @ symbol` () {
217+ val urlDetails = UrlUtils .parse(
218+ " @@sentry.io/pages/10?query=a#fragment?q=1&s=2&token=secret"
219+ )
220+ assertEquals(" @@sentry.io/pages/10" , urlDetails.url)
221+ assertEquals(" query=a" , urlDetails.query)
222+ assertEquals(" fragment?q=1&s=2&token=secret" , urlDetails.fragment)
223+ }
224+
225+ @Test
226+ fun `extracts details from relative url with leading question mark` () {
227+ val urlDetails = UrlUtils .parse(
228+ " ?query=a#fragment?q=1&s=2&token=secret"
229+ )
230+ assertEquals(" " , urlDetails.url)
231+ assertEquals(" query=a" , urlDetails.query)
232+ assertEquals(" fragment?q=1&s=2&token=secret" , urlDetails.fragment)
233+ }
234+
235+ @Test
236+ fun `does not filter email address in path` () {
237+ val urlDetails = UrlUtils .parseNullable(
238+ " https://staging.server.com/api/v4/auth/password/reset/email@example.com"
239+ )!!
240+ assertEquals(" https://staging.server.com/api/v4/auth/password/reset/email@example.com" , urlDetails.url)
241+ assertNull(urlDetails.query)
242+ assertNull(urlDetails.fragment)
243+ }
244+
245+ @Test
246+ fun `does not filter email address in path with fragment` () {
247+ val urlDetails = UrlUtils .parseNullable(
248+ " https://staging.server.com/api/v4/auth/password/reset/email@example.com#top"
249+ )!!
250+ assertEquals(" https://staging.server.com/api/v4/auth/password/reset/email@example.com" , urlDetails.url)
251+ assertNull(urlDetails.query)
252+ assertEquals(" top" , urlDetails.fragment)
253+ }
254+
255+ @Test
256+ fun `does not filter email address in path with query and fragment` () {
257+ val urlDetails = UrlUtils .parseNullable(
258+ " https://staging.server.com/api/v4/auth/password/reset/email@example.com?a=b&c=d#top"
259+ )!!
260+ assertEquals(" https://staging.server.com/api/v4/auth/password/reset/email@example.com" , urlDetails.url)
261+ assertEquals(" a=b&c=d" , urlDetails.query)
262+ assertEquals(" top" , urlDetails.fragment)
263+ }
264+
265+ @Test
266+ fun `does not filter email address in query` () {
267+ val urlDetails = UrlUtils .parseNullable(
268+ " https://staging.server.com/?email=someone@example.com"
269+ )!!
270+ assertEquals(" https://staging.server.com/" , urlDetails.url)
271+ assertEquals(" email=someone@example.com" , urlDetails.query)
272+ }
273+
274+ @Test
275+ fun `does not filter email address in fragment` () {
276+ val urlDetails = UrlUtils .parseNullable(
277+ " https://staging.server.com#email=someone@example.com"
278+ )!!
279+ assertEquals(" https://staging.server.com" , urlDetails.url)
280+ assertEquals(" email=someone@example.com" , urlDetails.fragment)
281+ }
282+
283+ @Test
284+ fun `does not filter email address in fragment with query` () {
285+ val urlDetails = UrlUtils .parseNullable(
286+ " https://staging.server.com?q=a&b=c#email=someone@example.com"
287+ )!!
288+ assertEquals(" https://staging.server.com" , urlDetails.url)
289+ assertEquals(" q=a&b=c" , urlDetails.query)
290+ assertEquals(" email=someone@example.com" , urlDetails.fragment)
291+ }
292+
293+ @Test
294+ fun `extracts details from relative url with email in path` () {
295+ val urlDetails = UrlUtils .parse(
296+ " /emails/user@sentry.io?query=a&b=c#fragment?q=1&s=2&token=secret"
297+ )
298+ assertEquals(" /emails/user@sentry.io" , urlDetails.url)
299+ assertEquals(" query=a&b=c" , urlDetails.query)
300+ assertEquals(" fragment?q=1&s=2&token=secret" , urlDetails.fragment)
301+ }
302+
303+ @Test
304+ fun `extracts details from relative url with email in query` () {
305+ val urlDetails = UrlUtils .parse(
306+ " users/10?email=user@sentry.io&b=c#fragment?q=1&s=2&token=secret"
307+ )
308+ assertEquals(" users/10" , urlDetails.url)
309+ assertEquals(" email=user@sentry.io&b=c" , urlDetails.query)
310+ assertEquals(" fragment?q=1&s=2&token=secret" , urlDetails.fragment)
311+ }
312+
313+ @Test
314+ fun `extracts details from relative url with email in fragment` () {
315+ val urlDetails = UrlUtils .parse(
316+ " users/10?email=user@sentry.io&b=c#fragment?q=1&s=2&email=user@sentry.io"
317+ )
318+ assertEquals(" users/10" , urlDetails.url)
319+ assertEquals(" email=user@sentry.io&b=c" , urlDetails.query)
320+ assertEquals(" fragment?q=1&s=2&email=user@sentry.io" , urlDetails.fragment)
321+ }
322+
323+ @Test
324+ fun `extracts path from file url` () {
325+ val urlDetails = UrlUtils .parse(
326+ " file:///users/sentry/text.txt"
327+ )
328+ assertEquals(" file:///users/sentry/text.txt" , urlDetails.url)
329+ assertNull(urlDetails.query)
330+ assertNull(urlDetails.fragment)
331+ }
332+
333+ @Test
334+ fun `does not extract details from websockets uri` () {
335+ val urlDetails = UrlUtils .parse(
336+ " wss://example.com/socket"
188337 )
189338 assertNull(urlDetails.url)
190339 assertNull(urlDetails.query)
0 commit comments