Commit message: None
CVE: CVE-2018-6051
CWE: CWE-79
Vulnerability description: XSS Auditor in Google Chrome prior to 64.0.3282.119, did not ensure the reporting URL was in the same origin as the page it was on, which allowed a remote attacker to obtain referrer details via a crafted HTML page.

Project: Chrome

String XSSAuditor::Canonicalize(String snippet, TruncationKind treatment) {
  String decoded_snippet = FullyDecodeString(snippet, encoding_);

  if (treatment != kNoTruncation) {
    if (decoded_snippet.length() > kMaximumFragmentLengthTarget) {
      size_t position = kMaximumFragmentLengthTarget;
      while (position < decoded_snippet.length() &&
             !IsHTMLSpace(decoded_snippet[position]))
        ++position;
      decoded_snippet.Truncate(position);
    }
    if (treatment == kSrcLikeAttributeTruncation)
      TruncateForSrcLikeAttribute(decoded_snippet);
    else if (treatment == kScriptLikeAttributeTruncation)
      TruncateForScriptLikeAttribute(decoded_snippet);
    else if (treatment == kSemicolonSeparatedScriptLikeAttributeTruncation)
      TruncateForSemicolonSeparatedScriptLikeAttribute(decoded_snippet);
  }

  return decoded_snippet.RemoveCharacters(&IsNonCanonicalCharacter);
}

Project: Chrome

String XSSAuditor::CanonicalizedSnippetForJavaScript(
    const FilterTokenRequest& request) {
  String string = request.source_tracker.SourceForToken(request.token);
  size_t start_position = 0;
  size_t end_position = string.length();
  size_t found_position = kNotFound;
  size_t last_non_space_position = kNotFound;

  while (start_position < end_position) {
    while (start_position < end_position &&
           IsHTMLSpace<UChar>(string[start_position]))
      start_position++;

    if (request.should_allow_cdata)
      break;

    if (StartsHTMLOpenCommentAt(string, start_position) ||
        StartsSingleLineCommentAt(string, start_position)) {
      while (start_position < end_position &&
             !IsJSNewline(string[start_position]))
        start_position++;
    } else if (StartsMultiLineCommentAt(string, start_position)) {
      if (start_position + 2 < end_position &&
          (found_position = string.Find("*/", start_position + 2)) != kNotFound)
        start_position = found_position + 2;
      else
        start_position = end_position;
    } else
      break;
  }

  String result;
  while (start_position < end_position && !result.length()) {
    last_non_space_position = kNotFound;
    for (found_position = start_position; found_position < end_position;
         found_position++) {
      if (!request.should_allow_cdata) {
        if (StartsSingleLineCommentAt(string, found_position) ||
            StartsMultiLineCommentAt(string, found_position) ||
            StartsHTMLOpenCommentAt(string, found_position) ||
            StartsHTMLCloseCommentAt(string, found_position)) {
          break;
        }
      }
      if (string[found_position] == ',' || string[found_position] == '`')
        break;

      if (last_non_space_position != kNotFound &&
          (StartsOpeningScriptTagAt(string, found_position) ||
           StartsClosingScriptTagAt(string, found_position))) {
        found_position = last_non_space_position + 1;
        break;
      }
      if (found_position > start_position + kMaximumFragmentLengthTarget) {
        if (IsHTMLSpace<UChar>(string[found_position]))
          break;
      }
      if (!IsHTMLSpace<UChar>(string[found_position]))
        last_non_space_position = found_position;
    }
    result = Canonicalize(
        string.Substring(start_position, found_position - start_position),
        kNoTruncation);
    start_position = found_position + 1;
  }

  return result;
}

Project: Chrome

String XSSAuditor::CanonicalizedSnippetForTagName(
    const FilterTokenRequest& request) {
  String source = request.source_tracker.SourceForToken(request.token);

  unsigned start = 0;
  for (start = 0; start < source.length() && source[start] == '\0'; ++start)
    continue;

  return Canonicalize(
      source.Substring(start, request.token.GetName().size() + 1),
      kNoTruncation);
}

Project: Chrome

bool XSSAuditor::EraseAttributeIfInjected(const FilterTokenRequest& request,
                                          const QualifiedName& attribute_name,
                                          const String& replacement_value,
                                          TruncationKind treatment,
                                          HrefRestriction restriction) {
  size_t index_of_attribute = 0;
  if (!FindAttributeWithName(request.token, attribute_name, index_of_attribute))
    return false;

  const HTMLToken::Attribute& attribute =
      request.token.Attributes().at(index_of_attribute);
  if (!IsContainedInRequest(
          Canonicalize(SnippetFromAttribute(request, attribute), treatment)))
    return false;

  if (ThreadSafeMatch(attribute_name, srcAttr) ||
      (restriction == kAllowSameOriginHref &&
       ThreadSafeMatch(attribute_name, hrefAttr))) {
    if (IsLikelySafeResource(attribute.Value()))
      return false;
  } else if (ThreadSafeMatch(attribute_name, http_equivAttr)) {
    if (!IsDangerousHTTPEquiv(attribute.Value()))
      return false;
  }

  request.token.EraseValueOfAttribute(index_of_attribute);
  if (!replacement_value.IsEmpty())
    request.token.AppendToAttributeValue(index_of_attribute, replacement_value);

  return true;
}

Project: Chrome

bool XSSAuditor::EraseDangerousAttributesIfInjected(
    const FilterTokenRequest& request) {
  bool did_block_script = false;
  for (size_t i = 0; i < request.token.Attributes().size(); ++i) {
    bool erase_attribute = false;
    bool value_contains_java_script_url = false;
    const HTMLToken::Attribute& attribute = request.token.Attributes().at(i);
    if (IsNameOfInlineEventHandler(attribute.NameAsVector())) {
      erase_attribute = IsContainedInRequest(
          Canonicalize(SnippetFromAttribute(request, attribute),
                       kScriptLikeAttributeTruncation));
    } else if (IsSemicolonSeparatedAttribute(attribute)) {
      if (IsSemicolonSeparatedValueContainingJavaScriptURL(attribute.Value())) {
        value_contains_java_script_url = true;
        erase_attribute =
            IsContainedInRequest(Canonicalize(
                NameFromAttribute(request, attribute), kNoTruncation)) &&
            IsContainedInRequest(
                Canonicalize(SnippetFromAttribute(request, attribute),
                             kSemicolonSeparatedScriptLikeAttributeTruncation));
      }
    } else if (ProtocolIsJavaScript(
                   StripLeadingAndTrailingHTMLSpaces(attribute.Value()))) {
      value_contains_java_script_url = true;
      erase_attribute = IsContainedInRequest(
          Canonicalize(SnippetFromAttribute(request, attribute),
                       kScriptLikeAttributeTruncation));
    }
    if (!erase_attribute)
      continue;
    request.token.EraseValueOfAttribute(i);
    if (value_contains_java_script_url)
      request.token.AppendToAttributeValue(i, kSafeJavaScriptURL);
    did_block_script = true;
  }
  return did_block_script;
}

Project: Chrome

bool XSSAuditor::FilterBaseToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, baseTag));

  return EraseAttributeIfInjected(request, hrefAttr, String(),
                                  kSrcLikeAttributeTruncation);
}

Project: Chrome

bool XSSAuditor::FilterCharacterToken(const FilterTokenRequest& request) {
  DCHECK(script_tag_nesting_level_);
  DCHECK_NE(state_, kUninitialized);
  if (state_ == kPermittingAdjacentCharacterTokens)
    return false;

  if (state_ == kFilteringTokens && script_tag_found_in_request_) {
    String snippet = CanonicalizedSnippetForJavaScript(request);
    if (IsContainedInRequest(snippet))
      state_ = kSuppressingAdjacentCharacterTokens;
    else if (!snippet.IsEmpty())
      state_ = kPermittingAdjacentCharacterTokens;
  }
  if (state_ == kSuppressingAdjacentCharacterTokens) {
    request.token.EraseCharacters();
    request.token.AppendToCharacter(' ');
    return true;
  }
  return false;
}

Project: Chrome

bool XSSAuditor::FilterEmbedToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, embedTag));

  bool did_block_script = false;
  if (IsContainedInRequest(CanonicalizedSnippetForTagName(request))) {
    did_block_script |= EraseAttributeIfInjected(request, codeAttr, String(),
                                                 kSrcLikeAttributeTruncation);
    did_block_script |= EraseAttributeIfInjected(
        request, srcAttr, BlankURL().GetString(), kSrcLikeAttributeTruncation);
    did_block_script |= EraseAttributeIfInjected(request, typeAttr);
  }
  return did_block_script;
}

Project: Chrome

void XSSAuditor::FilterEndToken(const FilterTokenRequest& request) {
  DCHECK(script_tag_nesting_level_);
  state_ = kFilteringTokens;
  if (HasName(request.token, scriptTag)) {
    script_tag_nesting_level_--;
    DCHECK(request.should_allow_cdata || !script_tag_nesting_level_);
  }
}

Project: Chrome

bool XSSAuditor::FilterFormToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, formTag));

  return EraseAttributeIfInjected(request, actionAttr, kURLWithUniqueOrigin,
                                  kSrcLikeAttributeTruncation);
}

Project: Chrome

bool XSSAuditor::FilterFrameToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, iframeTag) || HasName(request.token, frameTag));

  bool did_block_script = EraseAttributeIfInjected(
      request, srcdocAttr, String(), kScriptLikeAttributeTruncation);
  if (IsContainedInRequest(CanonicalizedSnippetForTagName(request)))
    did_block_script |= EraseAttributeIfInjected(request, srcAttr, String(),
                                                 kSrcLikeAttributeTruncation);

  return did_block_script;
}

Project: Chrome

bool XSSAuditor::FilterInputToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, inputTag));

  return EraseAttributeIfInjected(request, formactionAttr, kURLWithUniqueOrigin,
                                  kSrcLikeAttributeTruncation);
}

Project: Chrome

bool XSSAuditor::FilterLinkToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, linkTag));

  size_t index_of_attribute = 0;
  if (!FindAttributeWithName(request.token, relAttr, index_of_attribute))
    return false;

  const HTMLToken::Attribute& attribute =
      request.token.Attributes().at(index_of_attribute);
  LinkRelAttribute parsed_attribute(attribute.Value());
  if (!parsed_attribute.IsImport())
    return false;

  return EraseAttributeIfInjected(request, hrefAttr, kURLWithUniqueOrigin,
                                  kSrcLikeAttributeTruncation,
                                  kAllowSameOriginHref);
}

Project: Chrome

bool XSSAuditor::FilterMetaToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, metaTag));

  return EraseAttributeIfInjected(request, http_equivAttr);
}

Project: Chrome

bool XSSAuditor::FilterObjectToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, objectTag));

  bool did_block_script = false;
  if (IsContainedInRequest(CanonicalizedSnippetForTagName(request))) {
    did_block_script |= EraseAttributeIfInjected(
        request, dataAttr, BlankURL().GetString(), kSrcLikeAttributeTruncation);
    did_block_script |= EraseAttributeIfInjected(request, typeAttr);
    did_block_script |= EraseAttributeIfInjected(request, classidAttr);
  }
  return did_block_script;
}

Project: Chrome

bool XSSAuditor::FilterParamToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, paramTag));

  size_t index_of_name_attribute;
  if (!FindAttributeWithName(request.token, nameAttr, index_of_name_attribute))
    return false;

  const HTMLToken::Attribute& name_attribute =
      request.token.Attributes().at(index_of_name_attribute);
  if (!HTMLParamElement::IsURLParameter(name_attribute.Value()))
    return false;

  return EraseAttributeIfInjected(request, valueAttr, BlankURL().GetString(),
                                  kSrcLikeAttributeTruncation);
}

Project: Chrome

bool XSSAuditor::FilterScriptToken(const FilterTokenRequest& request) {
  DCHECK_EQ(request.token.GetType(), HTMLToken::kStartTag);
  DCHECK(HasName(request.token, scriptTag));

  bool did_block_script = false;
  script_tag_found_in_request_ =
      IsContainedInRequest(CanonicalizedSnippetForTagName(request));
  if (script_tag_found_in_request_) {
    did_block_script |= EraseAttributeIfInjected(
        request, srcAttr, BlankURL().GetString(), kSrcLikeAttributeTruncation);
    did_block_script |= EraseAttributeIfInjected(request, SVGNames::hrefAttr,
                                                 BlankURL().GetString(),
                                                 kSrcLikeAttributeTruncation);
    did_block_script |= EraseAttributeIfInjected(request, XLinkNames::hrefAttr,
                                                 BlankURL().GetString(),
                                                 kSrcLikeAttributeTruncation);
  }
  return did_block_script;
}

Project: Chrome

bool XSSAuditor::FilterStartToken(const FilterTokenRequest& request) {
  state_ = kFilteringTokens;
  bool did_block_script = EraseDangerousAttributesIfInjected(request);

  if (HasName(request.token, scriptTag)) {
    did_block_script |= FilterScriptToken(request);
    DCHECK(request.should_allow_cdata || !script_tag_nesting_level_);
    script_tag_nesting_level_++;
  } else if (HasName(request.token, objectTag))
    did_block_script |= FilterObjectToken(request);
  else if (HasName(request.token, paramTag))
    did_block_script |= FilterParamToken(request);
  else if (HasName(request.token, embedTag))
    did_block_script |= FilterEmbedToken(request);
  else if (HasName(request.token, iframeTag) ||
           HasName(request.token, frameTag))
    did_block_script |= FilterFrameToken(request);
  else if (HasName(request.token, metaTag))
    did_block_script |= FilterMetaToken(request);
  else if (HasName(request.token, baseTag))
    did_block_script |= FilterBaseToken(request);
  else if (HasName(request.token, formTag))
    did_block_script |= FilterFormToken(request);
  else if (HasName(request.token, inputTag))
    did_block_script |= FilterInputToken(request);
  else if (HasName(request.token, buttonTag))
    did_block_script |= FilterButtonToken(request);
  else if (HasName(request.token, linkTag))
    did_block_script |= FilterLinkToken(request);

  return did_block_script;
}

Project: Chrome

static String FullyDecodeString(const String& string,
                                const WTF::TextEncoding& encoding) {
  size_t old_working_string_length;
  String working_string = string;
  do {
    old_working_string_length = working_string.length();
    working_string = Decode16BitUnicodeEscapeSequences(
        DecodeStandardURLEscapeSequences(working_string, encoding));
  } while (working_string.length() < old_working_string_length);
  working_string.Replace('+', ' ');
  return working_string;
}

Project: Chrome

static bool HasName(const HTMLToken& token, const QualifiedName& name) {
  return ThreadSafeMatch(token.GetName(), name);
}

Project: Chrome

void XSSAuditor::InitForFragment() {
  DCHECK(IsMainThread());
  DCHECK_EQ(state_, kUninitialized);
  state_ = kFilteringTokens;
  DCHECK(!is_enabled_);
}

Project: Chrome

bool XSSAuditor::IsContainedInRequest(const String& decoded_snippet) {
  if (decoded_snippet.IsEmpty())
    return false;
  if (decoded_url_.FindIgnoringCase(decoded_snippet, 0) != kNotFound)
    return true;
  if (decoded_http_body_suffix_tree_ &&
      !decoded_http_body_suffix_tree_->MightContain(decoded_snippet))
    return false;
  return decoded_http_body_.FindIgnoringCase(decoded_snippet, 0) != kNotFound;
}

Project: Chrome

static bool IsDangerousHTTPEquiv(const String& value) {
  String equiv = value.StripWhiteSpace();
  return DeprecatedEqualIgnoringCase(equiv, "refresh") ||
         DeprecatedEqualIgnoringCase(equiv, "set-cookie");
}

Project: Chrome

static bool IsHTMLQuote(UChar c) {
  return (c == '"' || c == '\'');
}

Project: Chrome

static bool IsJSNewline(UChar c) {
  return (c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029);
}

Project: Chrome

bool XSSAuditor::IsLikelySafeResource(const String& url) {
  if (url.IsEmpty() || url == BlankURL().GetString())
    return true;

  if (document_url_.Host().IsEmpty())
    return false;

  KURL resource_url(document_url_, url);
  return (document_url_.Host() == resource_url.Host() &&
          resource_url.Query().IsEmpty());
}

Project: Chrome

static bool IsNonCanonicalCharacter(UChar c) {
  return (c == '\\' || c == '0' || c == '\0' || c == '/' || c == '?' ||
          c == '%' || c >= 127);
}

Project: Chrome

static bool IsRequiredForInjection(UChar c) {
  return (c == '\'' || c == '"' || c == '<' || c == '>');
}

Project: Chrome

bool XSSAuditor::IsSafeToSendToAnotherThread() const {
  return document_url_.IsSafeToSendToAnotherThread() &&
         decoded_url_.IsSafeToSendToAnotherThread() &&
         decoded_http_body_.IsSafeToSendToAnotherThread() &&
         http_body_as_string_.IsSafeToSendToAnotherThread();
}

Project: Chrome

static bool IsSemicolonSeparatedAttribute(
    const HTMLToken::Attribute& attribute) {
  return ThreadSafeMatch(attribute.NameAsVector(), SVGNames::valuesAttr);
}

Project: Chrome

static bool IsSemicolonSeparatedValueContainingJavaScriptURL(
    const String& value) {
  Vector<String> value_list;
  value.Split(';', value_list);
  for (size_t i = 0; i < value_list.size(); ++i) {
    String stripped = StripLeadingAndTrailingHTMLSpaces(value_list[i]);
    if (ProtocolIsJavaScript(stripped))
      return true;
  }
  return false;
}

Project: Chrome

static bool IsSlash(UChar c) {
  return (c == '/' || c == '\\');
}

Project: Chrome

static bool IsTerminatingCharacter(UChar c) {
  return (c == '&' || c == '/' || c == '"' || c == '\'' || c == '<' ||
          c == '>' || c == ',');
}

Project: Chrome

String XSSAuditor::NameFromAttribute(const FilterTokenRequest& request,
                                     const HTMLToken::Attribute& attribute) {
  int start = attribute.NameRange().start - request.token.StartIndex();
  int end = attribute.ValueRange().start - request.token.StartIndex();
  return request.source_tracker.SourceForToken(request.token)
      .Substring(start, end - start);
}

Project: Chrome

void XSSAuditor::SetEncoding(const WTF::TextEncoding& encoding) {
  const size_t kMiniumLengthForSuffixTree = 512;  // FIXME: Tune this parameter.
  const int kSuffixTreeDepth = 5;

  if (!encoding.IsValid())
    return;

  encoding_ = encoding;

  decoded_url_ = Canonicalize(document_url_.GetString(), kNoTruncation);
  if (decoded_url_.Find(IsRequiredForInjection) == kNotFound)
    decoded_url_ = String();

  if (!http_body_as_string_.IsEmpty()) {
    decoded_http_body_ = Canonicalize(http_body_as_string_, kNoTruncation);
    http_body_as_string_ = String();
    if (decoded_http_body_.Find(IsRequiredForInjection) == kNotFound)
      decoded_http_body_ = String();
    if (decoded_http_body_.length() >= kMiniumLengthForSuffixTree)
      decoded_http_body_suffix_tree_ = WTF::WrapUnique(
          new SuffixTree<ASCIICodebook>(decoded_http_body_, kSuffixTreeDepth));
  }

  if (decoded_url_.IsEmpty() && decoded_http_body_.IsEmpty())
    is_enabled_ = false;
}

Project: Chrome

String XSSAuditor::SnippetFromAttribute(const FilterTokenRequest& request,
                                        const HTMLToken::Attribute& attribute) {
  int name_start = attribute.NameRange().start - request.token.StartIndex();
  int value_start = attribute.ValueRange().start - request.token.StartIndex();
  int value_end = attribute.ValueRange().end - request.token.StartIndex();
  int length = value_end - name_start;
  if (value_start == value_end)
    length += 1;
  return request.source_tracker.SourceForToken(request.token)
      .Substring(name_start, length);
}

Project: Chrome

static bool StartsHTMLCloseCommentAt(const String& string, size_t start) {
  return (start + 2 < string.length() && string[start] == '-' &&
          string[start + 1] == '-' && string[start + 2] == '>');
}

Project: Chrome

static bool StartsHTMLOpenCommentAt(const String& string, size_t start) {
  return (start + 3 < string.length() && string[start] == '<' &&
          string[start + 1] == '!' && string[start + 2] == '-' &&
          string[start + 3] == '-');
}

Project: Chrome

static bool StartsMultiLineCommentAt(const String& string, size_t start) {
  return (start + 1 < string.length() && string[start] == '/' &&
          string[start + 1] == '*');
}

Project: Chrome

static bool StartsOpeningScriptTagAt(const String& string, size_t start) {
  if (start + 6 >= string.length())
    return false;
  StringView script("<script");
  return EqualIgnoringASCIICase(StringView(string, start, script.length()),
                                script);
}

Project: Chrome

static bool StartsSingleLineCommentAt(const String& string, size_t start) {
  return (start + 1 < string.length() && string[start] == '/' &&
          string[start + 1] == '/');
}

Project: Chrome

bool ThreadSafeMatch(const Vector<UChar, inlineCapacity>& vector,
                     const QualifiedName& qname) {
  return EqualIgnoringNullity(vector, qname.LocalName().Impl());
}

Project: Chrome

static void TruncateForScriptLikeAttribute(String& decoded_snippet) {
  size_t position = 0;
  if ((position = decoded_snippet.Find("=")) != kNotFound &&
      (position = decoded_snippet.Find(IsNotHTMLSpace<UChar>, position + 1)) !=
          kNotFound &&
      (position = decoded_snippet.Find(
           IsTerminatingCharacter,
           IsHTMLQuote(decoded_snippet[position]) ? position + 1 : position)) !=
          kNotFound) {
    decoded_snippet.Truncate(position);
  }
}

Project: Chrome

static void TruncateForSemicolonSeparatedScriptLikeAttribute(
    String& decoded_snippet) {
  TruncateForScriptLikeAttribute(decoded_snippet);
  size_t position = decoded_snippet.Find(";");
  if (position != kNotFound)
    decoded_snippet.Truncate(position);
}

Project: Chrome

static void TruncateForSrcLikeAttribute(String& decoded_snippet) {
  int slash_count = 0;
  bool comma_seen = false;
  bool colon_seen = false;
  for (size_t current_length = 0, remaining_length = decoded_snippet.length();
       remaining_length; ++current_length, --remaining_length) {
    UChar current_char = decoded_snippet[current_length];
    if (current_char == ':' && !colon_seen) {
      if (remaining_length > 1 && !IsSlash(decoded_snippet[current_length + 1]))
        ++slash_count;
      if (remaining_length > 2 && !IsSlash(decoded_snippet[current_length + 2]))
        ++slash_count;
      colon_seen = true;
    }
    if (current_char == '&' || current_char == '?' || current_char == '#' ||
        (IsSlash(current_char) && (comma_seen || ++slash_count > 2)) ||
        (current_char == '<' && comma_seen) ||
        (current_char == '\'' && comma_seen) ||
        (current_char == '"' && comma_seen)) {
      decoded_snippet.Truncate(current_length);
      return;
    }
    if (current_char == ',')
      comma_seen = true;
  }
}

Project: Chrome

XSSAuditor::XSSAuditor()
    : is_enabled_(false),
      xss_protection_(kFilterReflectedXSS),
      did_send_valid_xss_protection_header_(false),
      state_(kUninitialized),
      script_tag_found_in_request_(false),
      script_tag_nesting_level_(0),
      encoding_(UTF8Encoding()) {
}