1 // Copyright (c) 2012 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 5 #include "chrome/common/content_settings_pattern.h" 6 7 #include <vector> 8 9 #include "base/memory/scoped_ptr.h" 10 #include "base/strings/string_split.h" 11 #include "base/strings/string_util.h" 12 #include "chrome/common/content_settings_pattern_parser.h" 13 #include "chrome/common/render_messages.h" 14 #include "chrome/common/url_constants.h" 15 #include "extensions/common/constants.h" 16 #include "ipc/ipc_message_utils.h" 17 #include "net/base/dns_util.h" 18 #include "net/base/net_util.h" 19 #include "url/gurl.h" 20 #include "url/url_canon.h" 21 22 namespace { 23 24 std::string GetDefaultPort(const std::string& scheme) { 25 if (scheme == content::kHttpScheme) 26 return "80"; 27 if (scheme == content::kHttpsScheme) 28 return "443"; 29 return std::string(); 30 } 31 32 // Returns true if |sub_domain| is a sub domain or equls |domain|. E.g. 33 // "mail.google.com" is a sub domain of "google.com" but "evilhost.com" is not a 34 // subdomain of "host.com". 35 bool IsSubDomainOrEqual(const std::string& sub_domain, 36 const std::string& domain) { 37 // The empty string serves as wildcard. Each domain is a subdomain of the 38 // wildcard. 39 if (domain.empty()) 40 return true; 41 const size_t match = sub_domain.rfind(domain); 42 if (match == std::string::npos || 43 (match > 0 && sub_domain[match - 1] != '.') || 44 (match + domain.length() != sub_domain.length())) { 45 return false; 46 } 47 return true; 48 } 49 50 // Compares two domain names. 51 int CompareDomainNames(const std::string& str1, const std::string& str2) { 52 std::vector<std::string> domain_name1; 53 std::vector<std::string> domain_name2; 54 55 base::SplitString(str1, '.', &domain_name1); 56 base::SplitString(str2, '.', &domain_name2); 57 58 int i1 = domain_name1.size() - 1; 59 int i2 = domain_name2.size() - 1; 60 int rv; 61 while (i1 >= 0 && i2 >= 0) { 62 // domain names are stored in puny code. So it's fine to use the compare 63 // method. 64 rv = domain_name1[i1].compare(domain_name2[i2]); 65 if (rv != 0) 66 return rv; 67 --i1; 68 --i2; 69 } 70 71 if (i1 > i2) 72 return 1; 73 74 if (i1 < i2) 75 return -1; 76 77 // The domain names are identical. 78 return 0; 79 } 80 81 typedef ContentSettingsPattern::BuilderInterface BuilderInterface; 82 83 } // namespace 84 85 // //////////////////////////////////////////////////////////////////////////// 86 // ContentSettingsPattern::Builder 87 // 88 ContentSettingsPattern::Builder::Builder(bool use_legacy_validate) 89 : is_valid_(true), 90 use_legacy_validate_(use_legacy_validate) {} 91 92 ContentSettingsPattern::Builder::~Builder() {} 93 94 BuilderInterface* ContentSettingsPattern::Builder::WithPort( 95 const std::string& port) { 96 parts_.port = port; 97 parts_.is_port_wildcard = false; 98 return this; 99 } 100 101 BuilderInterface* ContentSettingsPattern::Builder::WithPortWildcard() { 102 parts_.port = ""; 103 parts_.is_port_wildcard = true; 104 return this; 105 } 106 107 BuilderInterface* ContentSettingsPattern::Builder::WithHost( 108 const std::string& host) { 109 parts_.host = host; 110 return this; 111 } 112 113 BuilderInterface* ContentSettingsPattern::Builder::WithDomainWildcard() { 114 parts_.has_domain_wildcard = true; 115 return this; 116 } 117 118 BuilderInterface* ContentSettingsPattern::Builder::WithScheme( 119 const std::string& scheme) { 120 parts_.scheme = scheme; 121 parts_.is_scheme_wildcard = false; 122 return this; 123 } 124 125 BuilderInterface* ContentSettingsPattern::Builder::WithSchemeWildcard() { 126 parts_.scheme = ""; 127 parts_.is_scheme_wildcard = true; 128 return this; 129 } 130 131 BuilderInterface* ContentSettingsPattern::Builder::WithPath( 132 const std::string& path) { 133 parts_.path = path; 134 parts_.is_path_wildcard = false; 135 return this; 136 } 137 138 BuilderInterface* ContentSettingsPattern::Builder::WithPathWildcard() { 139 parts_.path = ""; 140 parts_.is_path_wildcard = true; 141 return this; 142 } 143 144 BuilderInterface* ContentSettingsPattern::Builder::Invalid() { 145 is_valid_ = false; 146 return this; 147 } 148 149 ContentSettingsPattern ContentSettingsPattern::Builder::Build() { 150 if (!is_valid_) 151 return ContentSettingsPattern(); 152 if (!Canonicalize(&parts_)) 153 return ContentSettingsPattern(); 154 if (use_legacy_validate_) { 155 is_valid_ = LegacyValidate(parts_); 156 } else { 157 is_valid_ = Validate(parts_); 158 } 159 if (!is_valid_) 160 return ContentSettingsPattern(); 161 162 // A pattern is invalid if canonicalization is not idempotent. 163 // This check is here because it should be checked no matter 164 // use_legacy_validate_ is. 165 PatternParts parts(parts_); 166 if (!Canonicalize(&parts)) 167 return ContentSettingsPattern(); 168 if (ContentSettingsPattern(parts_, true) != 169 ContentSettingsPattern(parts, true)) { 170 return ContentSettingsPattern(); 171 } 172 173 return ContentSettingsPattern(parts_, is_valid_); 174 } 175 176 // static 177 bool ContentSettingsPattern::Builder::Canonicalize(PatternParts* parts) { 178 // Canonicalize the scheme part. 179 const std::string scheme(StringToLowerASCII(parts->scheme)); 180 parts->scheme = scheme; 181 182 if (parts->scheme == std::string(chrome::kFileScheme) && 183 !parts->is_path_wildcard) { 184 GURL url(std::string(chrome::kFileScheme) + 185 std::string(content::kStandardSchemeSeparator) + parts->path); 186 parts->path = url.path(); 187 } 188 189 // Canonicalize the host part. 190 const std::string host(parts->host); 191 url_canon::CanonHostInfo host_info; 192 std::string canonicalized_host(net::CanonicalizeHost(host, &host_info)); 193 if (host_info.IsIPAddress() && parts->has_domain_wildcard) 194 return false; 195 canonicalized_host = net::TrimEndingDot(canonicalized_host); 196 197 parts->host = ""; 198 if ((host.find('*') == std::string::npos) && 199 !canonicalized_host.empty()) { 200 // Valid host. 201 parts->host += canonicalized_host; 202 } 203 return true; 204 } 205 206 // static 207 bool ContentSettingsPattern::Builder::Validate(const PatternParts& parts) { 208 // Sanity checks first: {scheme, port} wildcards imply empty {scheme, port}. 209 if ((parts.is_scheme_wildcard && !parts.scheme.empty()) || 210 (parts.is_port_wildcard && !parts.port.empty())) { 211 NOTREACHED(); 212 return false; 213 } 214 215 // file:// URL patterns have an empty host and port. 216 if (parts.scheme == std::string(chrome::kFileScheme)) { 217 if (parts.has_domain_wildcard || !parts.host.empty() || !parts.port.empty()) 218 return false; 219 if (parts.is_path_wildcard) 220 return parts.path.empty(); 221 return (!parts.path.empty() && 222 parts.path != "/" && 223 parts.path.find("*") == std::string::npos); 224 } 225 226 // If the pattern is for an extension URL test if it is valid. 227 if (parts.scheme == std::string(extensions::kExtensionScheme) && 228 parts.port.empty() && 229 !parts.is_port_wildcard) { 230 return true; 231 } 232 233 // Non-file patterns are invalid if either the scheme, host or port part is 234 // empty. 235 if ((parts.scheme.empty() && !parts.is_scheme_wildcard) || 236 (parts.host.empty() && !parts.has_domain_wildcard) || 237 (parts.port.empty() && !parts.is_port_wildcard)) { 238 return false; 239 } 240 241 if (parts.host.find("*") != std::string::npos) 242 return false; 243 244 // Test if the scheme is supported or a wildcard. 245 if (!parts.is_scheme_wildcard && 246 parts.scheme != std::string(content::kHttpScheme) && 247 parts.scheme != std::string(content::kHttpsScheme)) { 248 return false; 249 } 250 return true; 251 } 252 253 // static 254 bool ContentSettingsPattern::Builder::LegacyValidate( 255 const PatternParts& parts) { 256 // If the pattern is for a "file-pattern" test if it is valid. 257 if (parts.scheme == std::string(chrome::kFileScheme) && 258 !parts.is_scheme_wildcard && 259 parts.host.empty() && 260 parts.port.empty()) 261 return true; 262 263 // If the pattern is for an extension URL test if it is valid. 264 if (parts.scheme == std::string(extensions::kExtensionScheme) && 265 !parts.is_scheme_wildcard && 266 !parts.host.empty() && 267 !parts.has_domain_wildcard && 268 parts.port.empty() && 269 !parts.is_port_wildcard) 270 return true; 271 272 // Non-file patterns are invalid if either the scheme, host or port part is 273 // empty. 274 if ((!parts.is_scheme_wildcard) || 275 (parts.host.empty() && !parts.has_domain_wildcard) || 276 (!parts.is_port_wildcard)) 277 return false; 278 279 // Test if the scheme is supported or a wildcard. 280 if (!parts.is_scheme_wildcard && 281 parts.scheme != std::string(content::kHttpScheme) && 282 parts.scheme != std::string(content::kHttpsScheme)) { 283 return false; 284 } 285 return true; 286 } 287 288 // //////////////////////////////////////////////////////////////////////////// 289 // ContentSettingsPattern::PatternParts 290 // 291 ContentSettingsPattern::PatternParts::PatternParts() 292 : is_scheme_wildcard(false), 293 has_domain_wildcard(false), 294 is_port_wildcard(false), 295 is_path_wildcard(false) {} 296 297 ContentSettingsPattern::PatternParts::~PatternParts() {} 298 299 // //////////////////////////////////////////////////////////////////////////// 300 // ContentSettingsPattern 301 // 302 303 // The version of the pattern format implemented. Version 1 includes the 304 // following patterns: 305 // - [*.]domain.tld (matches domain.tld and all sub-domains) 306 // - host (matches an exact hostname) 307 // - a.b.c.d (matches an exact IPv4 ip) 308 // - [a:b:c:d:e:f:g:h] (matches an exact IPv6 ip) 309 // - file:///tmp/test.html (a complete URL without a host) 310 // Version 2 adds a resource identifier for plugins. 311 // TODO(jochen): update once this feature is no longer behind a flag. 312 const int ContentSettingsPattern::kContentSettingsPatternVersion = 1; 313 314 // TODO(markusheintz): These two constants were moved to the Pattern Parser. 315 // Remove once the dependency of the ContentSettingsBaseProvider is removed. 316 const char* ContentSettingsPattern::kDomainWildcard = "[*.]"; 317 const size_t ContentSettingsPattern::kDomainWildcardLength = 4; 318 319 // static 320 BuilderInterface* ContentSettingsPattern::CreateBuilder( 321 bool validate) { 322 return new Builder(validate); 323 } 324 325 // static 326 ContentSettingsPattern ContentSettingsPattern::FromURL( 327 const GURL& url) { 328 scoped_ptr<ContentSettingsPattern::BuilderInterface> builder( 329 ContentSettingsPattern::CreateBuilder(false)); 330 331 const GURL* local_url = &url; 332 if (url.SchemeIsFileSystem() && url.inner_url()) { 333 local_url = url.inner_url(); 334 } 335 if (local_url->SchemeIsFile()) { 336 builder->WithScheme(local_url->scheme())->WithPath(local_url->path()); 337 } else { 338 // Please keep the order of the ifs below as URLs with an IP as host can 339 // also have a "http" scheme. 340 if (local_url->HostIsIPAddress()) { 341 builder->WithScheme(local_url->scheme())->WithHost(local_url->host()); 342 } else if (local_url->SchemeIs(content::kHttpScheme)) { 343 builder->WithSchemeWildcard()->WithDomainWildcard()->WithHost( 344 local_url->host()); 345 } else if (local_url->SchemeIs(content::kHttpsScheme)) { 346 builder->WithScheme(local_url->scheme())->WithDomainWildcard()->WithHost( 347 local_url->host()); 348 } else { 349 // Unsupported scheme 350 } 351 if (local_url->port().empty()) { 352 if (local_url->SchemeIs(content::kHttpsScheme)) 353 builder->WithPort(GetDefaultPort(content::kHttpsScheme)); 354 else 355 builder->WithPortWildcard(); 356 } else { 357 builder->WithPort(local_url->port()); 358 } 359 } 360 return builder->Build(); 361 } 362 363 // static 364 ContentSettingsPattern ContentSettingsPattern::FromURLNoWildcard( 365 const GURL& url) { 366 scoped_ptr<ContentSettingsPattern::BuilderInterface> builder( 367 ContentSettingsPattern::CreateBuilder(false)); 368 369 const GURL* local_url = &url; 370 if (url.SchemeIsFileSystem() && url.inner_url()) { 371 local_url = url.inner_url(); 372 } 373 if (local_url->SchemeIsFile()) { 374 builder->WithScheme(local_url->scheme())->WithPath(local_url->path()); 375 } else { 376 builder->WithScheme(local_url->scheme())->WithHost(local_url->host()); 377 if (local_url->port().empty()) { 378 builder->WithPort(GetDefaultPort(local_url->scheme())); 379 } else { 380 builder->WithPort(local_url->port()); 381 } 382 } 383 return builder->Build(); 384 } 385 386 // static 387 ContentSettingsPattern ContentSettingsPattern::FromString( 388 const std::string& pattern_spec) { 389 scoped_ptr<ContentSettingsPattern::BuilderInterface> builder( 390 ContentSettingsPattern::CreateBuilder(false)); 391 content_settings::PatternParser::Parse(pattern_spec, builder.get()); 392 return builder->Build(); 393 } 394 395 // static 396 ContentSettingsPattern ContentSettingsPattern::LegacyFromString( 397 const std::string& pattern_spec) { 398 scoped_ptr<ContentSettingsPattern::BuilderInterface> builder( 399 ContentSettingsPattern::CreateBuilder(true)); 400 content_settings::PatternParser::Parse(pattern_spec, builder.get()); 401 return builder->Build(); 402 } 403 404 // static 405 ContentSettingsPattern ContentSettingsPattern::Wildcard() { 406 scoped_ptr<ContentSettingsPattern::BuilderInterface> builder( 407 ContentSettingsPattern::CreateBuilder(true)); 408 builder->WithSchemeWildcard()->WithDomainWildcard()->WithPortWildcard()-> 409 WithPathWildcard(); 410 return builder->Build(); 411 } 412 413 ContentSettingsPattern::ContentSettingsPattern() 414 : is_valid_(false) { 415 } 416 417 ContentSettingsPattern::ContentSettingsPattern( 418 const PatternParts& parts, 419 bool valid) 420 : parts_(parts), 421 is_valid_(valid) { 422 } 423 424 void ContentSettingsPattern::WriteToMessage(IPC::Message* m) const { 425 IPC::WriteParam(m, is_valid_); 426 IPC::WriteParam(m, parts_); 427 } 428 429 bool ContentSettingsPattern::ReadFromMessage(const IPC::Message* m, 430 PickleIterator* iter) { 431 return IPC::ReadParam(m, iter, &is_valid_) && 432 IPC::ReadParam(m, iter, &parts_); 433 } 434 435 bool ContentSettingsPattern::Matches( 436 const GURL& url) const { 437 // An invalid pattern matches nothing. 438 if (!is_valid_) 439 return false; 440 441 const GURL* local_url = &url; 442 if (url.SchemeIsFileSystem() && url.inner_url()) { 443 local_url = url.inner_url(); 444 } 445 446 // Match the scheme part. 447 const std::string scheme(local_url->scheme()); 448 if (!parts_.is_scheme_wildcard && 449 parts_.scheme != scheme) { 450 return false; 451 } 452 453 // File URLs have no host. Matches if the pattern has the path wildcard set, 454 // or if the path in the URL is identical to the one in the pattern. 455 // For filesystem:file URLs, the path used is the filesystem type, so all 456 // filesystem:file:///temporary/... are equivalent. 457 // TODO(markusheintz): Content settings should be defined for all files on 458 // a machine. Unless there is a good use case for supporting paths for file 459 // patterns, stop supporting path for file patterns. 460 if (!parts_.is_scheme_wildcard && scheme == chrome::kFileScheme) 461 return parts_.is_path_wildcard || 462 parts_.path == std::string(local_url->path()); 463 464 // Match the host part. 465 const std::string host(net::TrimEndingDot(local_url->host())); 466 if (!parts_.has_domain_wildcard) { 467 if (parts_.host != host) 468 return false; 469 } else { 470 if (!IsSubDomainOrEqual(host, parts_.host)) 471 return false; 472 } 473 474 // For chrome extensions URLs ignore the port. 475 if (parts_.scheme == std::string(extensions::kExtensionScheme)) 476 return true; 477 478 // Match the port part. 479 std::string port(local_url->port()); 480 481 // Use the default port if the port string is empty. GURL returns an empty 482 // string if no port at all was specified or if the default port was 483 // specified. 484 if (port.empty()) { 485 port = GetDefaultPort(scheme); 486 } 487 488 if (!parts_.is_port_wildcard && 489 parts_.port != port ) { 490 return false; 491 } 492 493 return true; 494 } 495 496 bool ContentSettingsPattern::MatchesAllHosts() const { 497 return parts_.has_domain_wildcard && parts_.host.empty(); 498 } 499 500 const std::string ContentSettingsPattern::ToString() const { 501 if (IsValid()) 502 return content_settings::PatternParser::ToString(parts_); 503 else 504 return std::string(); 505 } 506 507 ContentSettingsPattern::Relation ContentSettingsPattern::Compare( 508 const ContentSettingsPattern& other) const { 509 // Two invalid patterns are identical in the way they behave. They don't match 510 // anything and are represented as an empty string. So it's fair to treat them 511 // as identical. 512 if ((this == &other) || 513 (!is_valid_ && !other.is_valid_)) 514 return IDENTITY; 515 516 if (!is_valid_ && other.is_valid_) 517 return DISJOINT_ORDER_POST; 518 if (is_valid_ && !other.is_valid_) 519 return DISJOINT_ORDER_PRE; 520 521 // If either host, port or scheme are disjoint return immediately. 522 Relation host_relation = CompareHost(parts_, other.parts_); 523 if (host_relation == DISJOINT_ORDER_PRE || 524 host_relation == DISJOINT_ORDER_POST) 525 return host_relation; 526 527 Relation port_relation = ComparePort(parts_, other.parts_); 528 if (port_relation == DISJOINT_ORDER_PRE || 529 port_relation == DISJOINT_ORDER_POST) 530 return port_relation; 531 532 Relation scheme_relation = CompareScheme(parts_, other.parts_); 533 if (scheme_relation == DISJOINT_ORDER_PRE || 534 scheme_relation == DISJOINT_ORDER_POST) 535 return scheme_relation; 536 537 if (host_relation != IDENTITY) 538 return host_relation; 539 if (port_relation != IDENTITY) 540 return port_relation; 541 return scheme_relation; 542 } 543 544 bool ContentSettingsPattern::operator==( 545 const ContentSettingsPattern& other) const { 546 return Compare(other) == IDENTITY; 547 } 548 549 bool ContentSettingsPattern::operator!=( 550 const ContentSettingsPattern& other) const { 551 return !(*this == other); 552 } 553 554 bool ContentSettingsPattern::operator<( 555 const ContentSettingsPattern& other) const { 556 return Compare(other) < 0; 557 } 558 559 bool ContentSettingsPattern::operator>( 560 const ContentSettingsPattern& other) const { 561 return Compare(other) > 0; 562 } 563 564 // static 565 ContentSettingsPattern::Relation ContentSettingsPattern::CompareHost( 566 const ContentSettingsPattern::PatternParts& parts, 567 const ContentSettingsPattern::PatternParts& other_parts) { 568 if (!parts.has_domain_wildcard && !other_parts.has_domain_wildcard) { 569 // Case 1: No host starts with a wild card 570 int result = CompareDomainNames(parts.host, other_parts.host); 571 if (result == 0) 572 return ContentSettingsPattern::IDENTITY; 573 if (result < 0) 574 return ContentSettingsPattern::DISJOINT_ORDER_PRE; 575 return ContentSettingsPattern::DISJOINT_ORDER_POST; 576 } else if (parts.has_domain_wildcard && !other_parts.has_domain_wildcard) { 577 // Case 2: |host| starts with a domain wildcard and |other_host| does not 578 // start with a domain wildcard. 579 // Examples: 580 // "this" host: [*.]google.com 581 // "other" host: google.com 582 // 583 // [*.]google.com 584 // mail.google.com 585 // 586 // [*.]mail.google.com 587 // google.com 588 // 589 // [*.]youtube.com 590 // google.de 591 // 592 // [*.]youtube.com 593 // mail.google.com 594 // 595 // * 596 // google.de 597 if (IsSubDomainOrEqual(other_parts.host, parts.host)) { 598 return ContentSettingsPattern::SUCCESSOR; 599 } else { 600 if (CompareDomainNames(parts.host, other_parts.host) < 0) 601 return ContentSettingsPattern::DISJOINT_ORDER_PRE; 602 return ContentSettingsPattern::DISJOINT_ORDER_POST; 603 } 604 } else if (!parts.has_domain_wildcard && other_parts.has_domain_wildcard) { 605 // Case 3: |host| starts NOT with a domain wildcard and |other_host| starts 606 // with a domain wildcard. 607 if (IsSubDomainOrEqual(parts.host, other_parts.host)) { 608 return ContentSettingsPattern::PREDECESSOR; 609 } else { 610 if (CompareDomainNames(parts.host, other_parts.host) < 0) 611 return ContentSettingsPattern::DISJOINT_ORDER_PRE; 612 return ContentSettingsPattern::DISJOINT_ORDER_POST; 613 } 614 } else if (parts.has_domain_wildcard && other_parts.has_domain_wildcard) { 615 // Case 4: |host| and |other_host| both start with a domain wildcard. 616 // Examples: 617 // [*.]google.com 618 // [*.]google.com 619 // 620 // [*.]google.com 621 // [*.]mail.google.com 622 // 623 // [*.]youtube.com 624 // [*.]google.de 625 // 626 // [*.]youtube.com 627 // [*.]mail.google.com 628 // 629 // [*.]youtube.com 630 // * 631 // 632 // * 633 // [*.]youtube.com 634 if (parts.host == other_parts.host) { 635 return ContentSettingsPattern::IDENTITY; 636 } else if (IsSubDomainOrEqual(other_parts.host, parts.host)) { 637 return ContentSettingsPattern::SUCCESSOR; 638 } else if (IsSubDomainOrEqual(parts.host, other_parts.host)) { 639 return ContentSettingsPattern::PREDECESSOR; 640 } else { 641 if (CompareDomainNames(parts.host, other_parts.host) < 0) 642 return ContentSettingsPattern::DISJOINT_ORDER_PRE; 643 return ContentSettingsPattern::DISJOINT_ORDER_POST; 644 } 645 } 646 647 NOTREACHED(); 648 return ContentSettingsPattern::IDENTITY; 649 } 650 651 // static 652 ContentSettingsPattern::Relation ContentSettingsPattern::CompareScheme( 653 const ContentSettingsPattern::PatternParts& parts, 654 const ContentSettingsPattern::PatternParts& other_parts) { 655 if (parts.is_scheme_wildcard && !other_parts.is_scheme_wildcard) 656 return ContentSettingsPattern::SUCCESSOR; 657 if (!parts.is_scheme_wildcard && other_parts.is_scheme_wildcard) 658 return ContentSettingsPattern::PREDECESSOR; 659 660 int result = parts.scheme.compare(other_parts.scheme); 661 if (result == 0) 662 return ContentSettingsPattern::IDENTITY; 663 if (result > 0) 664 return ContentSettingsPattern::DISJOINT_ORDER_PRE; 665 return ContentSettingsPattern::DISJOINT_ORDER_POST; 666 } 667 668 // static 669 ContentSettingsPattern::Relation ContentSettingsPattern::ComparePort( 670 const ContentSettingsPattern::PatternParts& parts, 671 const ContentSettingsPattern::PatternParts& other_parts) { 672 if (parts.is_port_wildcard && !other_parts.is_port_wildcard) 673 return ContentSettingsPattern::SUCCESSOR; 674 if (!parts.is_port_wildcard && other_parts.is_port_wildcard) 675 return ContentSettingsPattern::PREDECESSOR; 676 677 int result = parts.port.compare(other_parts.port); 678 if (result == 0) 679 return ContentSettingsPattern::IDENTITY; 680 if (result > 0) 681 return ContentSettingsPattern::DISJOINT_ORDER_PRE; 682 return ContentSettingsPattern::DISJOINT_ORDER_POST; 683 } 684