TestLinkHeader.cpp (15169B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include <ostream> 6 7 #include "gtest/gtest-param-test.h" 8 #include "gtest/gtest.h" 9 10 #include "mozilla/gtest/MozAssertions.h" 11 #include "nsNetUtil.h" 12 13 using namespace mozilla::net; 14 15 LinkHeader LinkHeaderSetAll(nsAString const& v) { 16 LinkHeader l; 17 l.mHref = v; 18 l.mRel = v; 19 l.mTitle = v; 20 l.mIntegrity = v; 21 l.mSrcset = v; 22 l.mSizes = v; 23 l.mType = v; 24 l.mMedia = v; 25 l.mAnchor = v; 26 l.mCrossOrigin = v; 27 l.mReferrerPolicy = v; 28 l.mAs = v; 29 l.mFetchPriority = v; 30 return l; 31 } 32 33 LinkHeader LinkHeaderSetTitle(nsAString const& v) { 34 LinkHeader l; 35 l.mHref = v; 36 l.mRel = v; 37 l.mTitle = v; 38 return l; 39 } 40 41 LinkHeader LinkHeaderSetMinimum(nsAString const& v) { 42 LinkHeader l; 43 l.mHref = v; 44 l.mRel = v; 45 return l; 46 } 47 48 void PrintTo(const nsTArray<LinkHeader>& aLinkHeaders, std::ostream* aOs) { 49 bool first = true; 50 for (const auto& header : aLinkHeaders) { 51 if (!first) { 52 *aOs << ", "; 53 } 54 first = false; 55 56 *aOs << "(mHref=" << header.mHref << ", " 57 << "mRel=" << header.mRel << ", " 58 << "mTitle=" << header.mTitle << ", " 59 << "mNonce=" << header.mNonce << ", " 60 << "mIntegrity=" << header.mIntegrity << ", " 61 << "mSrcset=" << header.mSrcset << ", " 62 << "mSizes=" << header.mSizes << ", " 63 << "mType=" << header.mType << ", " 64 << "mMedia=" << header.mMedia << ", " 65 << "mAnchor=" << header.mAnchor << ", " 66 << "mCrossOrigin=" << header.mCrossOrigin << ", " 67 << "mReferrerPolicy=" << header.mReferrerPolicy << ", " 68 << "mAs=" << header.mAs << ", " 69 << "mFetchPriority=" << header.mFetchPriority << ")"; 70 } 71 } 72 73 TEST(TestLinkHeader, MultipleLinkHeaders) 74 { 75 nsString link = 76 u"<a>; rel=a; title=a; integrity=a; imagesrcset=a; imagesizes=a; type=a; media=a; anchor=a; crossorigin=a; referrerpolicy=a; as=a; fetchpriority=a,"_ns 77 u"<b>; rel=b; title=b; integrity=b; imagesrcset=b; imagesizes=b; type=b; media=b; anchor=b; crossorigin=b; referrerpolicy=b; as=b; fetchpriority=b,"_ns 78 u"<c>; rel=c"_ns; 79 80 nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(link); 81 82 nsTArray<LinkHeader> expected; 83 expected.AppendElement(LinkHeaderSetAll(u"a"_ns)); 84 expected.AppendElement(LinkHeaderSetAll(u"b"_ns)); 85 expected.AppendElement(LinkHeaderSetMinimum(u"c"_ns)); 86 87 ASSERT_EQ(linkHeaders, expected); 88 } 89 90 // title* has to be tested separately 91 TEST(TestLinkHeader, MultipleLinkHeadersTitleStar) 92 { 93 nsString link = 94 u"<d>; rel=d; title*=UTF-8'de'd,"_ns 95 u"<e>; rel=e; title*=UTF-8'de'e; title=g,"_ns 96 u"<f>; rel=f"_ns; 97 98 nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(link); 99 100 nsTArray<LinkHeader> expected; 101 expected.AppendElement(LinkHeaderSetTitle(u"d"_ns)); 102 expected.AppendElement(LinkHeaderSetTitle(u"e"_ns)); 103 expected.AppendElement(LinkHeaderSetMinimum(u"f"_ns)); 104 105 ASSERT_EQ(linkHeaders, expected); 106 } 107 108 struct SimpleParseTestData { 109 nsString link; 110 bool valid; 111 nsString url; 112 nsString rel; 113 nsString as; 114 nsString fetchpriority; 115 116 friend void PrintTo(const SimpleParseTestData& aData, std::ostream* aOs) { 117 *aOs << "link=" << aData.link << ", valid=" << aData.valid 118 << ", url=" << aData.url << ", rel=" << aData.rel 119 << ", as=" << aData.as << ", fetchpriority=" << aData.fetchpriority 120 << ")"; 121 } 122 }; 123 124 class SimpleParseTest : public ::testing::TestWithParam<SimpleParseTestData> {}; 125 126 TEST_P(SimpleParseTest, Simple) { 127 const SimpleParseTestData test = GetParam(); 128 129 nsTArray<LinkHeader> linkHeaders = ParseLinkHeader(test.link); 130 131 EXPECT_EQ(test.valid, !linkHeaders.IsEmpty()); 132 if (test.valid) { 133 ASSERT_EQ(linkHeaders.Length(), (nsTArray<LinkHeader>::size_type)1); 134 EXPECT_EQ(test.url, linkHeaders[0].mHref); 135 EXPECT_EQ(test.rel, linkHeaders[0].mRel); 136 EXPECT_EQ(test.as, linkHeaders[0].mAs); 137 EXPECT_EQ(test.fetchpriority, linkHeaders[0].mFetchPriority); 138 } 139 } 140 141 // Some test data copied and adapted from 142 // https://source.chromium.org/chromium/chromium/src/+/main:components/link_header_util/link_header_util_unittest.cc 143 // the different behavior of the parser is commented above each test case. 144 MOZ_RUNINIT const SimpleParseTestData simple_parse_tests[] = { 145 {u"<s.css>; rel=stylesheet; fetchpriority=\"auto\""_ns, true, u"s.css"_ns, 146 u"stylesheet"_ns, u""_ns, u"auto"_ns}, 147 {u"<s.css>; rel=stylesheet; fetchpriority=\"low\""_ns, true, u"s.css"_ns, 148 u"stylesheet"_ns, u""_ns, u"low"_ns}, 149 {u"<s.css>; rel=stylesheet; fetchpriority=\"high\""_ns, true, u"s.css"_ns, 150 u"stylesheet"_ns, u""_ns, u"high"_ns}, 151 {u"<s.css>; rel=stylesheet; fetchpriority=\"foo\""_ns, true, u"s.css"_ns, 152 u"stylesheet"_ns, u""_ns, u"foo"_ns}, 153 {u"<s.css>; rel=stylesheet; fetchpriority=\"\""_ns, true, u"s.css"_ns, 154 u"stylesheet"_ns, u""_ns, u""_ns}, 155 {u"<s.css>; rel=stylesheet; fetchpriority=fooWithoutDoubleQuotes"_ns, true, 156 u"s.css"_ns, u"stylesheet"_ns, u""_ns, u"fooWithoutDoubleQuotes"_ns}, 157 {u"</images/cat.jpg>; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, 158 u"prefetch"_ns, u""_ns}, 159 {u"</images/cat.jpg>;rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, 160 u"prefetch"_ns, u""_ns}, 161 {u"</images/cat.jpg> ;rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, 162 u"prefetch"_ns, u""_ns}, 163 {u"</images/cat.jpg> ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, 164 u"prefetch"_ns, u""_ns}, 165 {u"< /images/cat.jpg> ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, 166 u"prefetch"_ns, u""_ns}, 167 {u"</images/cat.jpg > ; rel=prefetch"_ns, true, u"/images/cat.jpg"_ns, 168 u"prefetch"_ns, u""_ns}, 169 // TODO(1744051): don't ignore spaces in href 170 // {u"</images/cat.jpg wutwut> ; rel=prefetch"_ns, true, 171 // u"/images/cat.jpg wutwut"_ns, u"prefetch"_ns, u""_ns}, 172 {u"</images/cat.jpg wutwut> ; rel=prefetch"_ns, true, 173 u"/images/cat.jpgwutwut"_ns, u"prefetch"_ns, u""_ns}, 174 // TODO(1744051): don't ignore spaces in href 175 // {u"</images/cat.jpg wutwut \t > ; rel=prefetch"_ns, true, 176 // u"/images/cat.jpg wutwut"_ns, u"prefetch"_ns, u""_ns}, 177 {u"</images/cat.jpg wutwut \t > ; rel=prefetch"_ns, true, 178 u"/images/cat.jpgwutwut"_ns, u"prefetch"_ns, u""_ns}, 179 {u"</images/cat.jpg>; rel=prefetch "_ns, true, u"/images/cat.jpg"_ns, 180 u"prefetch"_ns, u""_ns}, 181 {u"</images/cat.jpg>; Rel=prefetch "_ns, true, u"/images/cat.jpg"_ns, 182 u"prefetch"_ns, u""_ns}, 183 {u"</images/cat.jpg>; Rel=PReFetCh "_ns, true, u"/images/cat.jpg"_ns, 184 u"PReFetCh"_ns, u""_ns}, 185 {u"</images/cat.jpg>; rel=prefetch; rel=somethingelse"_ns, true, 186 u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, 187 {u"</images/cat.jpg>\t\t ; \trel=prefetch \t "_ns, true, 188 u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, 189 {u"</images/cat.jpg>; rel= prefetch"_ns, true, u"/images/cat.jpg"_ns, 190 u"prefetch"_ns, u""_ns}, 191 {u"<../images/cat.jpg?dog>; rel= prefetch"_ns, true, 192 u"../images/cat.jpg?dog"_ns, u"prefetch"_ns, u""_ns}, 193 {u"</images/cat.jpg>; rel =prefetch"_ns, true, u"/images/cat.jpg"_ns, 194 u"prefetch"_ns, u""_ns}, 195 {u"</images/cat.jpg>; rel pel=prefetch"_ns, false}, 196 // different from chromium test case, because we already check for 197 // existence of "rel" parameter 198 {u"< /images/cat.jpg>"_ns, false}, 199 {u"</images/cat.jpg>; wut=sup; rel =prefetch"_ns, true, 200 u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, 201 {u"</images/cat.jpg>; wut=sup ; rel =prefetch"_ns, true, 202 u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, 203 {u"</images/cat.jpg>; wut=sup ; rel =prefetch \t ;"_ns, true, 204 u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, 205 // TODO(1744051): forbid non-whitespace characters between '>' and the first 206 // semicolon making it conform RFC 8288 Sec 3 207 // {u"</images/cat.jpg> wut=sup ; rel =prefetch \t ;"_ns, false}, 208 {u"</images/cat.jpg> wut=sup ; rel =prefetch \t ;"_ns, true, 209 u"/images/cat.jpg"_ns, u"prefetch"_ns, u""_ns}, 210 {u"< /images/cat.jpg"_ns, false}, 211 // TODO(1744051): don't ignore spaces in href 212 // {u"< http://wut.com/ sdfsdf ?sd>; rel=dns-prefetch"_ns, true, 213 // u"http://wut.com/ sdfsdf ?sd"_ns, u"dns-prefetch"_ns, u""_ns}, 214 {u"< http://wut.com/ sdfsdf ?sd>; rel=dns-prefetch"_ns, true, 215 u"http://wut.com/sdfsdf?sd"_ns, u"dns-prefetch"_ns, u""_ns}, 216 {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=dns-prefetch"_ns, true, 217 u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"dns-prefetch"_ns, u""_ns}, 218 {u"< http://wut.com/dfsdf?sdf=ghj&wer=rty>; rel=prefetch"_ns, true, 219 u"http://wut.com/dfsdf?sdf=ghj&wer=rty"_ns, u"prefetch"_ns, u""_ns}, 220 {u"< http://wut.com/dfsdf?sdf=ghj&wer=rty>;;;;; rel=prefetch"_ns, true, 221 u"http://wut.com/dfsdf?sdf=ghj&wer=rty"_ns, u"prefetch"_ns, u""_ns}, 222 {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=image"_ns, true, 223 u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"preload"_ns, u"image"_ns}, 224 {u"< http://wut.com/%20%20%3dsdfsdf?sd>; rel=preload;as=whatever"_ns, 225 true, u"http://wut.com/%20%20%3dsdfsdf?sd"_ns, u"preload"_ns, 226 u"whatever"_ns}, 227 {u"</images/cat.jpg>; rel=prefetch;"_ns, true, u"/images/cat.jpg"_ns, 228 u"prefetch"_ns, u""_ns}, 229 {u"</images/cat.jpg>; rel=prefetch ;"_ns, true, u"/images/cat.jpg"_ns, 230 u"prefetch"_ns, u""_ns}, 231 {u"</images/ca,t.jpg>; rel=prefetch ;"_ns, true, u"/images/ca,t.jpg"_ns, 232 u"prefetch"_ns, u""_ns}, 233 {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE and " 234 "backslash\""_ns, 235 true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, 236 // TODO(1744051): forbid missing end quote 237 // {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and " 238 // "backslash: \\\""_ns, false}, 239 {u"<simple.css>; rel=stylesheet; title=\"title with a DQUOTE \\\" and backslash: \\\""_ns, 240 true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, 241 {u"<simple.css>; title=\"title with a DQUOTE \\\" and backslash: \"; " 242 "rel=stylesheet; "_ns, 243 true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, 244 {u"<simple.css>; title=\'title with a DQUOTE \\\' and backslash: \'; " 245 "rel=stylesheet; "_ns, 246 true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, 247 {u"<simple.css>; title=\"title with a DQUOTE \\\" and ;backslash,: \"; " 248 "rel=stylesheet; "_ns, 249 true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, 250 {u"<simple.css>; title=\"title with a DQUOTE \' and ;backslash,: \"; " 251 "rel=stylesheet; "_ns, 252 true, u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, 253 {u"<simple.css>; title=\"\"; rel=stylesheet; "_ns, true, u"simple.css"_ns, 254 u"stylesheet"_ns, u""_ns}, 255 {u"<simple.css>; title=\"\"; rel=\"stylesheet\"; "_ns, true, 256 u"simple.css"_ns, u"stylesheet"_ns, u""_ns}, 257 // TODO(1744051): forbid missing end quote 258 // {u"<simple.css>; rel=stylesheet; title=\""_ns, false}, 259 {u"<simple.css>; rel=stylesheet; title=\""_ns, true, u"simple.css"_ns, 260 u"stylesheet"_ns, u""_ns}, 261 {u"<simple.css>; rel=stylesheet; title=\"\""_ns, true, u"simple.css"_ns, 262 u"stylesheet"_ns, u""_ns}, 263 // TODO(1744051): forbid missing end quote 264 // {u"<simple.css>; rel=\"stylesheet\"; title=\""_ns, false}, 265 {u"<simple.css>; rel=\"stylesheet\"; title=\""_ns, true, u"simple.css"_ns, 266 u"stylesheet"_ns, u""_ns}, 267 // TODO(1744051): forbid missing end quote 268 // {u"<simple.css>; rel=\";style,sheet\"; title=\""_ns, false}, 269 {u"<simple.css>; rel=\";style,sheet\"; title=\""_ns, true, u"simple.css"_ns, 270 u";style,sheet"_ns, u""_ns}, 271 // TODO(1744051): forbid missing end quote 272 // {u"<simple.css>; rel=\"bla'sdf\"; title=\""_ns, false} 273 {u"<simple.css>; rel=\"bla'sdf\"; title=\""_ns, true, u"simple.css"_ns, 274 u"bla'sdf"_ns, u""_ns}, 275 // TODO(1744051): allow explicit empty rel 276 // {u"<simple.css>; rel=\"\"; title=\"\""_ns, true, u"simple.css"_ns, 277 // u""_ns, u""_ns} 278 {u"<simple.css>; rel=\"\"; title=\"\""_ns, false}, 279 {u"<simple.css>; rel=''; title=\"\""_ns, true, u"simple.css"_ns, u"''"_ns, 280 u""_ns}, 281 {u"<simple.css>; rel=''; bla"_ns, true, u"simple.css"_ns, u"''"_ns, u""_ns}, 282 {u"<simple.css>; rel='prefetch"_ns, true, u"simple.css"_ns, u"'prefetch"_ns, 283 u""_ns}, 284 // TODO(1744051): forbid missing end quote 285 // {u"<simple.css>; rel=\"prefetch"_ns, false}, 286 {u"<simple.css>; rel=\"prefetch"_ns, true, u"simple.css"_ns, 287 u"\"prefetch"_ns, u""_ns}, 288 {u"<simple.css>; rel=\""_ns, false}, 289 {u"simple.css; rel=prefetch"_ns, false}, 290 {u"<simple.css>; rel=prefetch; rel=foobar"_ns, true, u"simple.css"_ns, 291 u"prefetch"_ns, u""_ns}, 292 }; 293 294 INSTANTIATE_TEST_SUITE_P(TestLinkHeader, SimpleParseTest, 295 testing::ValuesIn(simple_parse_tests)); 296 297 // Test anchor 298 299 struct AnchorTestData { 300 nsString baseURI; 301 // building the new anchor in combination with the baseURI 302 nsString anchor; 303 nsString href; 304 const char* resolved; 305 }; 306 307 class AnchorTest : public ::testing::TestWithParam<AnchorTestData> {}; 308 309 MOZ_RUNINIT const AnchorTestData anchor_tests[] = { 310 {u"http://example.com/path/to/index.html"_ns, u""_ns, u"page.html"_ns, 311 "http://example.com/path/to/page.html"}, 312 {u"http://example.com/path/to/index.html"_ns, 313 u"http://example.com/path/"_ns, u"page.html"_ns, 314 "http://example.com/path/page.html"}, 315 {u"http://example.com/path/to/index.html"_ns, 316 u"http://example.com/path/"_ns, u"/page.html"_ns, 317 "http://example.com/page.html"}, 318 {u"http://example.com/path/to/index.html"_ns, u".."_ns, u"page.html"_ns, 319 "http://example.com/path/page.html"}, 320 {u"http://example.com/path/to/index.html"_ns, u".."_ns, 321 u"from/page.html"_ns, "http://example.com/path/from/page.html"}, 322 {u"http://example.com/path/to/index.html"_ns, u"/hello/"_ns, 323 u"page.html"_ns, "http://example.com/hello/page.html"}, 324 {u"http://example.com/path/to/index.html"_ns, u"/hello"_ns, u"page.html"_ns, 325 "http://example.com/page.html"}, 326 {u"http://example.com/path/to/index.html"_ns, u"#necko"_ns, u"page.html"_ns, 327 "http://example.com/path/to/page.html"}, 328 {u"http://example.com/path/to/index.html"_ns, u"https://example.net/"_ns, 329 u"to/page.html"_ns, "https://example.net/to/page.html"}, 330 }; 331 332 LinkHeader LinkHeaderFromHrefAndAnchor(nsAString const& aHref, 333 nsAString const& aAnchor) { 334 LinkHeader l; 335 l.mHref = aHref; 336 l.mAnchor = aAnchor; 337 return l; 338 } 339 340 TEST_P(AnchorTest, Anchor) { 341 const AnchorTestData test = GetParam(); 342 343 LinkHeader linkHeader = LinkHeaderFromHrefAndAnchor(test.href, test.anchor); 344 345 nsCOMPtr<nsIURI> baseURI; 346 ASSERT_NS_SUCCEEDED(NS_NewURI(getter_AddRefs(baseURI), test.baseURI)); 347 348 nsCOMPtr<nsIURI> resolved; 349 ASSERT_TRUE(NS_SUCCEEDED( 350 linkHeader.NewResolveHref(getter_AddRefs(resolved), baseURI))); 351 352 ASSERT_STREQ(resolved->GetSpecOrDefault().get(), test.resolved); 353 } 354 355 INSTANTIATE_TEST_SUITE_P(TestLinkHeader, AnchorTest, 356 testing::ValuesIn(anchor_tests));