url_param_filterer_unittest.cc 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "components/url_param_filter/core/url_param_filterer.h"
  5. #include <string>
  6. #include "base/test/metrics/histogram_tester.h"
  7. #include "base/test/scoped_feature_list.h"
  8. #include "components/url_param_filter/core/features.h"
  9. #include "components/url_param_filter/core/url_param_classifications_loader.h"
  10. #include "components/url_param_filter/core/url_param_filter_classification.pb.h"
  11. #include "components/url_param_filter/core/url_param_filter_test_helper.h"
  12. #include "testing/gtest/include/gtest/gtest.h"
  13. #include "third_party/abseil-cpp/absl/types/optional.h"
  14. namespace url_param_filter {
  15. class UrlParamFiltererTest : public ::testing::Test {};
  16. TEST_F(UrlParamFiltererTest, FilterUrlEmptyClassifications) {
  17. GURL source = GURL{"http://source.xyz"};
  18. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  19. // If no classifications are passed in, don't modify the destination URL.
  20. FilterResult result = FilterUrl(source, expected, ClassificationMap(),
  21. FilterClassification::USE_CASE_UNKNOWN);
  22. ASSERT_EQ(result.filtered_url, expected);
  23. ASSERT_EQ(result.filtered_param_count, 0);
  24. ASSERT_EQ(result.experimental_status,
  25. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  26. }
  27. TEST_F(UrlParamFiltererTest, FilterUrlNoChanges) {
  28. GURL source = GURL{"http://source.xyz"};
  29. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  30. ClassificationMap classification_map = CreateClassificationMapForTesting(
  31. {{SourceKey("source.xyz"), {"plzblock"}},
  32. {DestinationKey("destination.xyz"), {"plzblock1"}}});
  33. // If classifications are passed in, but the destination URL doesn't contain
  34. // any blocked params, don't modify it.
  35. FilterResult result = FilterUrl(source, expected, classification_map,
  36. FilterClassification::USE_CASE_UNKNOWN);
  37. ASSERT_EQ(result.filtered_url, expected);
  38. ASSERT_EQ(result.filtered_param_count, 0);
  39. ASSERT_EQ(result.experimental_status,
  40. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  41. }
  42. TEST_F(UrlParamFiltererTest, FilterUrlSourceBlocked) {
  43. GURL source = GURL{"https://source.xyz"};
  44. GURL destination = GURL{"https://destination.xyz?plzblock=123&nochange=asdf"};
  45. ClassificationMap classification_map = CreateClassificationMapForTesting(
  46. {{SourceKey("source.xyz"), {"plzblock"}}});
  47. // Navigations from source.xyz with a param called plzblock should have that
  48. // param removed, regardless of destination.
  49. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  50. FilterResult result = FilterUrl(source, destination, classification_map,
  51. FilterClassification::USE_CASE_UNKNOWN);
  52. ASSERT_EQ(result.filtered_url, expected);
  53. ASSERT_EQ(result.filtered_param_count, 1);
  54. ASSERT_EQ(result.experimental_status,
  55. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  56. }
  57. TEST_F(UrlParamFiltererTest, FilterUrlSourceBlockedNoValue) {
  58. GURL source = GURL{"https://source.xyz"};
  59. GURL destination = GURL{"https://destination.xyz?plzblock&nochange"};
  60. ClassificationMap classification_map = CreateClassificationMapForTesting(
  61. {{SourceKey("source.xyz"), {"plzblock"}}});
  62. // Navigations from source.xyz with a param called plzblock should have that
  63. // param removed, regardless of missing a value.
  64. GURL expected = GURL{"https://destination.xyz?nochange"};
  65. FilterResult result = FilterUrl(source, destination, classification_map,
  66. FilterClassification::USE_CASE_UNKNOWN);
  67. ASSERT_EQ(result.filtered_url, expected);
  68. ASSERT_EQ(result.filtered_param_count, 1);
  69. ASSERT_EQ(result.experimental_status,
  70. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  71. }
  72. TEST_F(UrlParamFiltererTest, FilterUrlMultipleSourceBlocked) {
  73. GURL source = GURL{"https://source.xyz"};
  74. GURL destination =
  75. GURL{"https://destination.xyz?plzblock=123&plzblock1=321&nochange=asdf"};
  76. ClassificationMap classification_map = CreateClassificationMapForTesting(
  77. {{SourceKey("source.xyz"), {"plzblock", "plzblock1"}}});
  78. // Navigations from source.xyz with a param called plzblock or plzblock1
  79. // should have those params removed, regardless of destination.
  80. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  81. FilterResult result = FilterUrl(source, destination, classification_map,
  82. FilterClassification::USE_CASE_UNKNOWN);
  83. ASSERT_EQ(result.filtered_url, expected);
  84. ASSERT_EQ(result.filtered_param_count, 2);
  85. ASSERT_EQ(result.experimental_status,
  86. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  87. }
  88. TEST_F(UrlParamFiltererTest, FilterUrlDestinationBlocked) {
  89. GURL source = GURL{"https://source.xyz"};
  90. GURL destination = GURL{"https://destination.xyz?plzblock=123&nochange=asdf"};
  91. ClassificationMap classification_map = CreateClassificationMapForTesting(
  92. {{DestinationKey("destination.xyz"), {"plzblock"}}});
  93. // Navigations to destination.xyz with a param called plzblock should have
  94. // that param removed, regardless of source.
  95. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  96. FilterResult result = FilterUrl(source, destination, classification_map,
  97. FilterClassification::USE_CASE_UNKNOWN);
  98. ASSERT_EQ(result.filtered_url, expected);
  99. ASSERT_EQ(result.filtered_param_count, 1);
  100. ASSERT_EQ(result.experimental_status,
  101. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  102. }
  103. TEST_F(UrlParamFiltererTest, FilterUrlMultipleDestinationBlocked) {
  104. GURL source = GURL{"https://source.xyz"};
  105. GURL destination =
  106. GURL{"https://destination.xyz?plzblock=123&plzblock1=321&nochange=asdf"};
  107. ClassificationMap classification_map = CreateClassificationMapForTesting(
  108. {{DestinationKey("destination.xyz"), {"plzblock", "plzblock1"}}});
  109. // Navigations to destination.xyz with a param called plzblock and/or
  110. // plzblock1 should have those param removed, regardless of source.
  111. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  112. FilterResult result = FilterUrl(source, destination, classification_map,
  113. FilterClassification::USE_CASE_UNKNOWN);
  114. ASSERT_EQ(result.filtered_url, expected);
  115. ASSERT_EQ(result.filtered_param_count, 2);
  116. ASSERT_EQ(result.experimental_status,
  117. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  118. }
  119. TEST_F(UrlParamFiltererTest, FilterUrlSourceWildcardBlocked) {
  120. GURL source = GURL{"https://source.xyz"};
  121. GURL destination = GURL{"https://destination.xyz?plzblock=123&nochange=asdf"};
  122. ClassificationMap classification_map = CreateClassificationMapForTesting(
  123. {{SourceWildcardKey("source"), {"plzblock"}}});
  124. // Navigations from source.* with a param called plzblock should have
  125. // that param removed, regardless of destination.
  126. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  127. FilterResult result = FilterUrl(source, destination, classification_map,
  128. FilterClassification::USE_CASE_UNKNOWN);
  129. ASSERT_EQ(result.filtered_url, expected);
  130. ASSERT_EQ(result.filtered_param_count, 1);
  131. ASSERT_EQ(result.experimental_status,
  132. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  133. }
  134. TEST_F(UrlParamFiltererTest, FilterUrlSourceWildcardeTLDHasTrailingDotBlock) {
  135. // eTLD has a trailing dot, which is weird but legal.
  136. GURL source = GURL{"https://source.xyz."};
  137. GURL destination = GURL{"https://destination.xyz?plzblock=123&nochange=asdf"};
  138. ClassificationMap classification_map = CreateClassificationMapForTesting(
  139. {{SourceWildcardKey("source"), {"plzblock"}}});
  140. // Navigations from source.* with a param called plzblock should have
  141. // that param removed, regardless of destination.
  142. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  143. FilterResult result = FilterUrl(source, destination, classification_map,
  144. FilterClassification::USE_CASE_UNKNOWN);
  145. ASSERT_EQ(result.filtered_url, expected);
  146. ASSERT_EQ(result.filtered_param_count, 1);
  147. ASSERT_EQ(result.experimental_status,
  148. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  149. }
  150. TEST_F(UrlParamFiltererTest, FilterUrlSourceWildcardeTLDUnparsableNotBlocked) {
  151. // eTLD has two trailing dots, which means we cannot parse it.
  152. GURL source = GURL{"https://source.xyz.."};
  153. GURL destination = GURL{"https://destination.xyz?plzblock=123&nochange=asdf"};
  154. ClassificationMap classification_map = CreateClassificationMapForTesting(
  155. {{SourceWildcardKey("source"), {"plzblock"}}});
  156. // Since the eTLD is unparsable, we should leave the URL intact.
  157. GURL expected = GURL{"https://destination.xyz?plzblock=123&nochange=asdf"};
  158. FilterResult result = FilterUrl(source, destination, classification_map,
  159. FilterClassification::USE_CASE_UNKNOWN);
  160. ASSERT_EQ(result.filtered_url, expected);
  161. ASSERT_EQ(result.filtered_param_count, 0);
  162. ASSERT_EQ(result.experimental_status,
  163. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  164. }
  165. TEST_F(UrlParamFiltererTest, FilterUrlMultipleSourceWildcardBlocked) {
  166. GURL source = GURL{"https://source.xyz"};
  167. GURL destination =
  168. GURL{"https://destination.xyz?plzblock=123&plzblock1=321&nochange=asdf"};
  169. ClassificationMap classification_map = CreateClassificationMapForTesting(
  170. {{SourceWildcardKey("source"), {"plzblock", "plzblock1"}}});
  171. // Navigations to destination.xyz with a param called plzblock and/or
  172. // plzblock1 should have those param removed, regardless of source.
  173. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  174. FilterResult result = FilterUrl(source, destination, classification_map,
  175. FilterClassification::USE_CASE_UNKNOWN);
  176. ASSERT_EQ(result.filtered_url, expected);
  177. ASSERT_EQ(result.filtered_param_count, 2);
  178. ASSERT_EQ(result.experimental_status,
  179. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  180. }
  181. TEST_F(UrlParamFiltererTest,
  182. FilterUrlSourceAndSourceWildcardAndDestinationBlocked) {
  183. GURL source = GURL{"https://source.xyz"};
  184. GURL destination = GURL{
  185. "https://"
  186. "destination.xyz?plzblock=123&plzblock1=321&plzblockwildcard=456&"
  187. "nochange=asdf"};
  188. ClassificationMap classification_map = CreateClassificationMapForTesting(
  189. {{SourceKey("source.xyz"), {"plzblock"}},
  190. {DestinationKey("destination.xyz"), {"plzblock1"}},
  191. {SourceWildcardKey("source"), {"plzblockwildcard"}}});
  192. // Both source and destination have associated URL param filtering rules. Only
  193. // nochange should remain.
  194. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  195. FilterResult result = FilterUrl(source, destination, classification_map,
  196. FilterClassification::USE_CASE_UNKNOWN);
  197. ASSERT_EQ(result.filtered_url, expected);
  198. ASSERT_EQ(result.filtered_param_count, 3);
  199. ASSERT_EQ(result.experimental_status,
  200. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  201. }
  202. TEST_F(UrlParamFiltererTest, FilterUrlSourceAndDestinationAsIPBlocked) {
  203. GURL source = GURL{"https://127.0.0.1"};
  204. GURL destination =
  205. GURL{"https://123.0.0.1?plzblock=123&plzblock1=321&nochange=asdf"};
  206. ClassificationMap classification_map = CreateClassificationMapForTesting({
  207. {SourceKey("127.0.0.1"), {"plzblock"}},
  208. {DestinationKey("123.0.0.1"), {"plzblock1"}},
  209. });
  210. // Both source and destination have associated URL param filtering rules. Only
  211. // nochange should remain.
  212. GURL expected = GURL{"https://123.0.0.1?nochange=asdf"};
  213. FilterResult result = FilterUrl(source, destination, classification_map,
  214. FilterClassification::USE_CASE_UNKNOWN);
  215. ASSERT_EQ(result.filtered_url, expected);
  216. ASSERT_EQ(result.filtered_param_count, 2);
  217. ASSERT_EQ(result.experimental_status,
  218. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  219. }
  220. TEST_F(UrlParamFiltererTest, FilterUrlSourceAndDestinationAsIPv6Blocked) {
  221. GURL source = GURL{"https://[::1]"};
  222. GURL destination = GURL{
  223. "https://"
  224. "[2001:db8:ac10:fe01::]?plzblock=123&plzblock1=321&nochange=asdf"};
  225. ClassificationMap classification_map = CreateClassificationMapForTesting(
  226. {{SourceKey("[::1]"), {"plzblock"}},
  227. {DestinationKey("[2001:db8:ac10:fe01::]"), {"plzblock1"}}});
  228. // Both source and destination have associated URL param filtering rules. Only
  229. // nochange should remain.
  230. GURL expected = GURL{"https://[2001:db8:ac10:fe01::]?nochange=asdf"};
  231. FilterResult result = FilterUrl(source, destination, classification_map,
  232. FilterClassification::USE_CASE_UNKNOWN);
  233. ASSERT_EQ(result.filtered_url, expected);
  234. ASSERT_EQ(result.filtered_param_count, 2);
  235. ASSERT_EQ(result.experimental_status,
  236. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  237. }
  238. TEST_F(UrlParamFiltererTest,
  239. FilterUrlSourceAndDestinationMixedIPv6AndIPv4Blocked) {
  240. GURL source = GURL{"https://127.0.0.1"};
  241. GURL destination = GURL{
  242. "https://"
  243. "[2001:db8:ac10:fe01::]?plzblock=123&plzblock1=321&nochange=asdf"};
  244. ClassificationMap classification_map = CreateClassificationMapForTesting(
  245. {{SourceKey("127.0.0.1"), {"plzblock"}},
  246. {DestinationKey("[2001:db8:ac10:fe01::]"), {"plzblock1"}}});
  247. // Both source and destination have associated URL param filtering rules. Only
  248. // nochange should remain.
  249. GURL expected = GURL{"https://[2001:db8:ac10:fe01::]?nochange=asdf"};
  250. FilterResult result = FilterUrl(source, destination, classification_map,
  251. FilterClassification::USE_CASE_UNKNOWN);
  252. ASSERT_EQ(result.filtered_url, expected);
  253. ASSERT_EQ(result.filtered_param_count, 2);
  254. ASSERT_EQ(result.experimental_status,
  255. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  256. }
  257. TEST_F(UrlParamFiltererTest, FilterUrlSourceWildcardIgnoredForIPv4Source) {
  258. GURL source = GURL{"https://127.0.0.1"};
  259. GURL destination =
  260. GURL{"https://123.0.0.1?plzblock=123&plzblock1=321&nochange=asdf"};
  261. ClassificationMap classification_map = CreateClassificationMapForTesting(
  262. {{SourceWildcardKey("127.0.0.1"), {"plzblock", "plzblock1"}}});
  263. // Even if we somehow have a classification map with an IP address wildcard
  264. // rule, we should not filter based on that because it is not possible to get
  265. // the eTLD + 1 of an IP address.
  266. GURL expected =
  267. GURL{"https://123.0.0.1?plzblock=123&plzblock1=321&nochange=asdf"};
  268. FilterResult result = FilterUrl(source, destination, classification_map,
  269. FilterClassification::USE_CASE_UNKNOWN);
  270. ASSERT_EQ(result.filtered_url, expected);
  271. ASSERT_EQ(result.filtered_param_count, 0);
  272. ASSERT_EQ(result.experimental_status,
  273. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  274. }
  275. TEST_F(UrlParamFiltererTest, FilterUrlSourceWildcardIgnoredForIPv6Source) {
  276. GURL source = GURL{"https://[::1]"};
  277. GURL destination =
  278. GURL{"https://123.0.0.1?plzblock=123&plzblock1=321&nochange=asdf"};
  279. ClassificationMap classification_map = CreateClassificationMapForTesting(
  280. {{SourceWildcardKey("[::1]"), {"plzblock", "plzblock1"}}});
  281. // Even if we somehow have a classification map with an IP address wildcard
  282. // rule, we should not filter based on that because it is not possible to get
  283. // the eTLD + 1 of an IP address.
  284. GURL expected =
  285. GURL{"https://123.0.0.1?plzblock=123&plzblock1=321&nochange=asdf"};
  286. FilterResult result = FilterUrl(source, destination, classification_map,
  287. FilterClassification::USE_CASE_UNKNOWN);
  288. ASSERT_EQ(result.filtered_url, expected);
  289. ASSERT_EQ(result.filtered_param_count, 0);
  290. ASSERT_EQ(result.experimental_status,
  291. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  292. }
  293. TEST_F(
  294. UrlParamFiltererTest,
  295. FilterUrlSourceAndSourceWildcardAndDestinationBlockedCheckOrderingPreserved) {
  296. GURL source = GURL{"https://source.xyz"};
  297. GURL destination = GURL{
  298. "https://"
  299. "destination.xyz?plzblock=123&plzblock1=321&nochange=asdf&"
  300. "plzblockwildcard=456&laternochange="
  301. "fdsa"};
  302. ClassificationMap classification_map = CreateClassificationMapForTesting(
  303. {{SourceKey("source.xyz"), {"plzblock"}},
  304. {DestinationKey("destination.xyz"), {"plzblock1"}},
  305. {SourceWildcardKey("source"), {"plzblockwildcard"}}});
  306. // Both source and destination have associated URL param filtering rules. Only
  307. // nochange should remain.
  308. GURL expected =
  309. GURL{"https://destination.xyz?nochange=asdf&laternochange=fdsa"};
  310. FilterResult result = FilterUrl(source, destination, classification_map,
  311. FilterClassification::USE_CASE_UNKNOWN);
  312. ASSERT_EQ(result.filtered_url, expected);
  313. ASSERT_EQ(result.filtered_param_count, 3);
  314. ASSERT_EQ(result.experimental_status,
  315. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  316. }
  317. TEST_F(UrlParamFiltererTest, FilterUrlSubdomainsApplied) {
  318. GURL source = GURL{"https://subdomain.source.xyz"};
  319. GURL destination = GURL{
  320. "https://"
  321. "subdomain.destination.xyz?plzblock=123&plzblock1=321&plzblockwildcard="
  322. "678&nochange=asdf"};
  323. ClassificationMap classification_map = CreateClassificationMapForTesting(
  324. {{SourceKey("source.xyz"), {"plzblock"}},
  325. {DestinationKey("destination.xyz"), {"plzblock1"}},
  326. {SourceWildcardKey("source"), {"plzblockwildcard"}}});
  327. GURL expected = GURL{"https://subdomain.destination.xyz?nochange=asdf"};
  328. FilterResult result = FilterUrl(source, destination, classification_map,
  329. FilterClassification::USE_CASE_UNKNOWN);
  330. ASSERT_EQ(result.filtered_url, expected);
  331. ASSERT_EQ(result.filtered_param_count, 3);
  332. ASSERT_EQ(result.experimental_status,
  333. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  334. }
  335. TEST_F(UrlParamFiltererTest, FilterUrlCaseIgnored) {
  336. GURL source = GURL{"https://source.xyz"};
  337. GURL destination = GURL{
  338. "https://"
  339. "destination.xyz?PlZbLoCk=123&PLZBLOCK1=321&plzBlockWildcard=876&"
  340. "nochange=asdf"};
  341. ClassificationMap classification_map = CreateClassificationMapForTesting(
  342. {{SourceKey("source.xyz"), {"plzblock"}},
  343. {DestinationKey("destination.xyz"), {"plzblock1"}},
  344. {SourceWildcardKey("source"), {"plzblockwildcard"}}});
  345. // The disallowed params PlZbLoCk and PLZBLOCK1 should be removed.
  346. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  347. FilterResult result = FilterUrl(source, destination, classification_map,
  348. FilterClassification::USE_CASE_UNKNOWN);
  349. ASSERT_EQ(result.filtered_url, expected);
  350. ASSERT_EQ(result.filtered_param_count, 3);
  351. ASSERT_EQ(result.experimental_status,
  352. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  353. }
  354. TEST_F(UrlParamFiltererTest, FilterUrlWithNestedUrl) {
  355. GURL source = GURL{"https://source.xyz"};
  356. GURL destination = GURL{
  357. "https://"
  358. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  359. "3Fplzblock1%"
  360. "3D123%26nochange%3Dasdf&PLZBLOCK1=321&plzblockWildcard=789&nochange="
  361. "asdf"};
  362. ClassificationMap classification_map = CreateClassificationMapForTesting(
  363. {{SourceKey("source.xyz"), {"plzblock"}},
  364. {DestinationKey("destination.xyz"), {"plzblock1"}},
  365. {DestinationKey("source.xyz"), {"plzblock1"}},
  366. {SourceWildcardKey("source"), {"plzblockwildcard"}}});
  367. // The nested URL pattern is commonly observed; we do not want the parameter
  368. // to leak.
  369. GURL expected = GURL{
  370. "https://"
  371. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  372. "3Fnochange%"
  373. "3Dasdf&nochange=asdf"};
  374. FilterResult result = FilterUrl(source, destination, classification_map,
  375. FilterClassification::USE_CASE_UNKNOWN);
  376. ASSERT_EQ(result.filtered_url, expected);
  377. ASSERT_EQ(result.filtered_param_count, 3);
  378. ASSERT_EQ(result.experimental_status,
  379. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  380. }
  381. TEST_F(UrlParamFiltererTest, FilterUrlWithNestedUrlNotNeedingFiltering) {
  382. GURL source = GURL{"https://source.xyz"};
  383. GURL destination = GURL{
  384. "https://"
  385. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  386. "3Fnochange%"
  387. "3Dasdf&PLZBLOCK1=321&nochange=asdf"};
  388. ClassificationMap classification_map = CreateClassificationMapForTesting(
  389. {{SourceKey("source.xyz"), {"plzblock"}},
  390. {DestinationKey("destination.xyz"), {"plzblock1"}},
  391. {DestinationKey("source.xyz"), {"plzblock1"}},
  392. {SourceWildcardKey("source"), {"plzblock1"}}});
  393. // The nested URL does not have filtered parameters and should be left alone.
  394. GURL expected = GURL{
  395. "https://"
  396. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  397. "3Fnochange%"
  398. "3Dasdf&nochange=asdf"};
  399. FilterResult result = FilterUrl(source, destination, classification_map,
  400. FilterClassification::USE_CASE_UNKNOWN);
  401. ASSERT_EQ(result.filtered_url, expected);
  402. ASSERT_EQ(result.filtered_param_count, 1);
  403. }
  404. TEST_F(UrlParamFiltererTest, FilterUrlWithNestedUrlAndDuplicates) {
  405. GURL source = GURL{"https://source.xyz"};
  406. GURL destination = GURL{
  407. "https://"
  408. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  409. "3Fplzblock1%"
  410. "3D123%26nochange%3Dasdf%26plzblock1%3D123&PLZBLOCK1=321&nochange=asdf&"
  411. "PLZBLOCK1=321"};
  412. ClassificationMap classification_map = CreateClassificationMapForTesting(
  413. {{SourceKey("source.xyz"), {"plzblock"}},
  414. {DestinationKey("destination.xyz"), {"plzblock1"}},
  415. {DestinationKey("source.xyz"), {"plzblock1"}}});
  416. // The nested URL pattern is commonly observed; we do not want the parameter
  417. // to leak.
  418. GURL expected = GURL{
  419. "https://"
  420. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  421. "3Fnochange%"
  422. "3Dasdf&nochange=asdf"};
  423. FilterResult result = FilterUrl(source, destination, classification_map,
  424. FilterClassification::USE_CASE_UNKNOWN);
  425. ASSERT_EQ(result.filtered_url, expected);
  426. ASSERT_EQ(result.filtered_param_count, 4);
  427. ASSERT_EQ(result.experimental_status,
  428. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  429. }
  430. TEST_F(UrlParamFiltererTest, FeatureDeactivated) {
  431. GURL source = GURL{"http://source.xyz"};
  432. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  433. // When the feature is not explicitly activated, the 2-parameter version of
  434. // the function should be inert.
  435. GURL result = FilterUrl(source, expected).filtered_url;
  436. ASSERT_EQ(result, expected);
  437. }
  438. TEST_F(UrlParamFiltererTest, FeatureDeactivatedUseCaseVariant) {
  439. GURL source = GURL{"http://source.xyz"};
  440. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  441. // When the feature is not explicitly activated, the 3-parameter version of
  442. // the function should be inert.
  443. FilterResult result =
  444. FilterUrl(source, expected, FilterClassification::CROSS_OTR);
  445. ASSERT_EQ(result.filtered_url, expected);
  446. ASSERT_EQ(result.filtered_param_count, 0);
  447. ASSERT_EQ(result.experimental_status,
  448. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  449. }
  450. TEST_F(UrlParamFiltererTest, FeatureActivatedNoQueryString) {
  451. GURL source = GURL{"http://source.xyz"};
  452. GURL destination = GURL{"https://destination.xyz"};
  453. std::string encoded_classification =
  454. CreateBase64EncodedFilterParamClassificationForTesting(
  455. {{"source.xyz", {"plzblock"}}}, {{"destination.xyz", {"plzblock1"}}});
  456. base::test::ScopedFeatureList scoped_feature_list;
  457. // With the flag set, the URL should be filtered.
  458. scoped_feature_list.InitAndEnableFeatureWithParameters(
  459. features::kIncognitoParamFilterEnabled,
  460. {{"classifications", encoded_classification}});
  461. GURL expected = GURL{"https://destination.xyz"};
  462. FilterResult result = FilterUrl(source, destination);
  463. ASSERT_EQ(result.filtered_url, expected);
  464. ASSERT_EQ(result.filtered_param_count, 0);
  465. ASSERT_EQ(result.experimental_status,
  466. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  467. }
  468. TEST_F(UrlParamFiltererTest, FeatureActivatedAllRemoved) {
  469. GURL source = GURL{"http://source.xyz"};
  470. GURL destination =
  471. GURL{"https://destination.xyz?plzblock=adf&plzblock1=asdffdsa"};
  472. std::string encoded_classification =
  473. CreateBase64EncodedFilterParamClassificationForTesting(
  474. {{"source.xyz", {"plzblock"}}}, {{"destination.xyz", {"plzblock1"}}});
  475. base::test::ScopedFeatureList scoped_feature_list;
  476. // With the flag set, the URL should be filtered.
  477. scoped_feature_list.InitAndEnableFeatureWithParameters(
  478. features::kIncognitoParamFilterEnabled,
  479. {{"classifications", encoded_classification}});
  480. GURL expected = GURL{"https://destination.xyz"};
  481. FilterResult result = FilterUrl(source, destination);
  482. ASSERT_EQ(result.filtered_url, expected);
  483. ASSERT_EQ(result.filtered_param_count, 2);
  484. ASSERT_EQ(result.experimental_status,
  485. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  486. }
  487. TEST_F(UrlParamFiltererTest, FeatureActivatedSourceAndDestinationRemoval) {
  488. GURL source = GURL{"http://source.xyz"};
  489. GURL destination =
  490. GURL{"https://destination.xyz?plzblock=1&plzblock1=2&nochange=asdf"};
  491. std::string encoded_classification =
  492. CreateBase64EncodedFilterParamClassificationForTesting(
  493. {{"source.xyz", {"plzblock"}}}, {{"destination.xyz", {"plzblock1"}}});
  494. base::test::ScopedFeatureList scoped_feature_list;
  495. // With the flag set, the URL should be filtered.
  496. scoped_feature_list.InitAndEnableFeatureWithParameters(
  497. features::kIncognitoParamFilterEnabled,
  498. {{"classifications", encoded_classification}});
  499. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  500. FilterResult result = FilterUrl(source, destination);
  501. ASSERT_EQ(result.filtered_url, expected);
  502. ASSERT_EQ(result.filtered_param_count, 2);
  503. ASSERT_EQ(result.experimental_status,
  504. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  505. }
  506. TEST_F(UrlParamFiltererTest, CrossOtrUseCase) {
  507. GURL source = GURL{"http://source.xyz"};
  508. GURL destination =
  509. GURL{"https://destination.xyz?plzblock=1&plzblock1=2&nochange=asdf"};
  510. std::string encoded_classification =
  511. CreateBase64EncodedFilterParamClassificationForTesting(
  512. {{"source.xyz", {{FilterClassification::CROSS_OTR, {"plzblock"}}}}},
  513. {{"destination.xyz",
  514. {{FilterClassification::CROSS_OTR, {"plzblock1"}}}}});
  515. base::test::ScopedFeatureList scoped_feature_list;
  516. // With the flag set, the URL should be filtered.
  517. scoped_feature_list.InitAndEnableFeatureWithParameters(
  518. features::kIncognitoParamFilterEnabled,
  519. {{"classifications", encoded_classification}});
  520. GURL expected = GURL{"https://destination.xyz?nochange=asdf"};
  521. FilterResult result =
  522. FilterUrl(source, destination, FilterClassification::CROSS_OTR);
  523. ASSERT_EQ(result.filtered_url, expected);
  524. ASSERT_EQ(result.filtered_param_count, 2);
  525. ASSERT_EQ(result.experimental_status,
  526. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  527. }
  528. TEST_F(UrlParamFiltererTest, MismatchedUseCases) {
  529. GURL source = GURL{"http://source.xyz"};
  530. GURL destination =
  531. GURL{"https://destination.xyz?plzblock=1&plzblock1=2&nochange=asdf"};
  532. std::string encoded_classification =
  533. CreateBase64EncodedFilterParamClassificationForTesting(
  534. {{"source.xyz", {{FilterClassification::CROSS_OTR, {"plzblock"}}}}},
  535. {{"destination.xyz",
  536. {{FilterClassification::CROSS_OTR, {"plzblock1"}}}}});
  537. base::test::ScopedFeatureList scoped_feature_list;
  538. // With the flag set, the URL could be filtered.
  539. scoped_feature_list.InitAndEnableFeatureWithParameters(
  540. features::kIncognitoParamFilterEnabled,
  541. {{"classifications", encoded_classification}});
  542. FilterResult result =
  543. FilterUrl(source, destination, FilterClassification::CROSS_SITE_NO_3PC);
  544. // There are no rules for CROSS_SITE_NO_3PC, so the URL should not change.
  545. ASSERT_EQ(result.filtered_url, destination);
  546. ASSERT_EQ(result.filtered_param_count, 0);
  547. ASSERT_EQ(result.experimental_status,
  548. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  549. }
  550. TEST_F(UrlParamFiltererTest, MixedUseCases) {
  551. GURL source = GURL{"http://source.xyz"};
  552. GURL destination =
  553. GURL{"https://destination.xyz?plzblock=1&plzblock1=2&nochange=asdf"};
  554. std::string encoded_classification =
  555. CreateBase64EncodedFilterParamClassificationForTesting(
  556. {{"source.xyz",
  557. {{FilterClassification::CROSS_SITE_NO_3PC, {"plzblock"}}}}},
  558. {{"destination.xyz",
  559. {{FilterClassification::CROSS_OTR, {"plzblock1"}}}}});
  560. base::test::ScopedFeatureList scoped_feature_list;
  561. // With the flag set, the URL could be filtered.
  562. scoped_feature_list.InitAndEnableFeatureWithParameters(
  563. features::kIncognitoParamFilterEnabled,
  564. {{"classifications", encoded_classification}});
  565. GURL expected = GURL{"https://destination.xyz?plzblock1=2&nochange=asdf"};
  566. FilterResult result =
  567. FilterUrl(source, destination, FilterClassification::CROSS_SITE_NO_3PC);
  568. // The only rule for CROSS_SITE_NO_3PC is to remove `plzblock` from
  569. // source.xyz.
  570. ASSERT_EQ(result.filtered_url, expected);
  571. ASSERT_EQ(result.filtered_param_count, 1);
  572. ASSERT_EQ(result.experimental_status,
  573. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  574. }
  575. TEST_F(UrlParamFiltererTest, MultipleUseCases) {
  576. GURL source = GURL{"http://source.xyz"};
  577. GURL destination = GURL{
  578. "https://"
  579. "destination.xyz?blockotrsource=1&blockno3pcdest=1&plzblock=1&plzblock1="
  580. "2&nochange=asdf"};
  581. std::string encoded_classification =
  582. CreateBase64EncodedFilterParamClassificationForTesting(
  583. {{"source.xyz",
  584. {{FilterClassification::CROSS_OTR, {"blockotrsource"}},
  585. {FilterClassification::CROSS_SITE_NO_3PC, {"plzblock"}}}}},
  586. {{"destination.xyz",
  587. {{FilterClassification::CROSS_SITE_NO_3PC, {"blockno3pcdest"}},
  588. {FilterClassification::CROSS_OTR, {"plzblock1"}}}}});
  589. base::test::ScopedFeatureList scoped_feature_list;
  590. // With the flag set, the URL could be filtered.
  591. scoped_feature_list.InitAndEnableFeatureWithParameters(
  592. features::kIncognitoParamFilterEnabled,
  593. {{"classifications", encoded_classification}});
  594. GURL expected = GURL{
  595. "https://destination.xyz?blockotrsource=1&plzblock1=2&nochange=asdf"};
  596. FilterResult result =
  597. FilterUrl(source, destination, FilterClassification::CROSS_SITE_NO_3PC);
  598. // `blockotrsource` should be left alone, but `blockno3pcdest` should not.
  599. ASSERT_EQ(result.filtered_url, expected);
  600. ASSERT_EQ(result.filtered_param_count, 2);
  601. ASSERT_EQ(result.experimental_status,
  602. ClassificationExperimentStatus::NON_EXPERIMENTAL);
  603. }
  604. TEST_F(UrlParamFiltererTest, ExperimentalClassifications) {
  605. GURL source = GURL{"http://source.xyz"};
  606. GURL destination = GURL{
  607. "https://"
  608. "destination.xyz?srcexperimental=1&destexperimental=2&noblock=1&plzblock="
  609. "1&plzblockdest=1"};
  610. ClassificationMap classification_map = CreateClassificationMapForTesting(
  611. {{SourceKey("source.xyz"), {"plzblock"}},
  612. {DestinationKey("destination.xyz"), {"plzblockdest"}}});
  613. // In addition to the default `plzblock`, also set `srcexperimental` as an
  614. // experiment-driven parameter.
  615. auto key = SourceKey("source.xyz");
  616. classification_map[key][FilterClassification::USE_CASE_UNKNOWN]
  617. ["srcexperimental"] =
  618. ClassificationExperimentStatus::EXPERIMENTAL;
  619. // In addition to the default `plzblockdest`, also set `destexperimental` as
  620. // an experiment-driven parameter.
  621. classification_map[DestinationKey("destination.xyz")]
  622. [FilterClassification::USE_CASE_UNKNOWN]
  623. ["destexperimental"] =
  624. ClassificationExperimentStatus::EXPERIMENTAL;
  625. FilterResult result = FilterUrl(source, destination, classification_map,
  626. FilterClassification::USE_CASE_UNKNOWN);
  627. // `blockotrsource` should be left alone, but `blockno3pcdest` should not.
  628. GURL expected = GURL{"https://destination.xyz?noblock=1"};
  629. ASSERT_EQ(result.filtered_url, expected);
  630. ASSERT_EQ(result.filtered_param_count, 4);
  631. ASSERT_EQ(result.experimental_status,
  632. ClassificationExperimentStatus::EXPERIMENTAL);
  633. }
  634. TEST_F(UrlParamFiltererTest, ExperimentalClassificationsWithNestedUrl) {
  635. GURL source = GURL{"https://source.xyz"};
  636. GURL destination = GURL{
  637. "https://"
  638. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  639. "3Fplzblock1%"
  640. "3D123%26destexperimental%3D1%26nochange%3Dasdf&PLZBLOCK1=321&nochange="
  641. "asdf"};
  642. ClassificationMap classification_map = CreateClassificationMapForTesting(
  643. {{SourceKey("source.xyz"), {"plzblock"}},
  644. {DestinationKey("destination.xyz"), {"plzblock1"}},
  645. {DestinationKey("source.xyz"), {"plzblock1"}}});
  646. // In addition to the default params, also set `destexperimental` as
  647. // an experiment-driven parameter and verify the nested URL correctly results
  648. // in `ClassificationExperimentStatus::EXPERIMENTAL`.
  649. auto key = DestinationKey("destination.xyz");
  650. classification_map[key][FilterClassification::USE_CASE_UNKNOWN]
  651. ["destexperimental"] =
  652. ClassificationExperimentStatus::EXPERIMENTAL;
  653. GURL expected = GURL{
  654. "https://"
  655. "subdomain.source.xyz?destination=https%3A%2F%2Fdestination.xyz%2F%"
  656. "3Fnochange%"
  657. "3Dasdf&nochange=asdf"};
  658. FilterResult result = FilterUrl(source, destination, classification_map,
  659. FilterClassification::USE_CASE_UNKNOWN);
  660. ASSERT_EQ(result.filtered_url, expected);
  661. ASSERT_EQ(result.filtered_param_count, 3);
  662. ASSERT_EQ(result.experimental_status,
  663. ClassificationExperimentStatus::EXPERIMENTAL);
  664. }
  665. namespace internal {
  666. class UrlParamFiltererInternalTest : public ::testing::Test {};
  667. TEST_F(UrlParamFiltererInternalTest, GetLabelFromHostname) {
  668. struct {
  669. std::string url;
  670. absl::optional<std::string> expected;
  671. } kTestCases[]{
  672. // Normal cases.
  673. {.url = "https://site.xyz?param=123&param2=456", .expected = "site"},
  674. {.url = "http://a.com?param=123&param2=456", .expected = "a"},
  675. {.url = "https://b.co.uk?param=123&param2=456", .expected = "b"},
  676. {.url = "https://subdomain.c.xyz?param=123&param2=456", .expected = "c"},
  677. // Trailing dots.
  678. {.url = "https://a.com./", .expected = "a"},
  679. {.url = "https://b.gov.uk./", .expected = "b"},
  680. {.url = "https://c.co.", .expected = "c"},
  681. // Unparsable.
  682. {.url = "https://a.com../", .expected = absl::nullopt},
  683. {.url = "https://b.gov.uk..", .expected = absl::nullopt},
  684. {.url = "http://192.168.0.1", .expected = absl::nullopt},
  685. {.url = "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/",
  686. .expected = absl::nullopt},
  687. {.url = "http://localhost", .expected = absl::nullopt},
  688. {.url = "", .expected = absl::nullopt},
  689. {.url = "http://localhost.", .expected = absl::nullopt},
  690. {.url = "http://", .expected = absl::nullopt},
  691. {.url = "file:///C:/file.html", .expected = absl::nullopt},
  692. {.url = "http://...", .expected = absl::nullopt},
  693. {.url = "http:////Comment", .expected = absl::nullopt},
  694. {.url = "http://.com", .expected = absl::nullopt},
  695. };
  696. for (const auto& test_case : kTestCases) {
  697. ASSERT_EQ(GetLabelFromHostname(GURL{test_case.url}), test_case.expected);
  698. }
  699. }
  700. } // namespace internal
  701. } // namespace url_param_filter