// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/common/unique_name_helper.h" #include #include #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_piece.h" #include "base/strings/string_util.h" #include "base/unguessable_token.h" #include "crypto/sha2.h" namespace content { namespace { bool g_preserve_stable_unique_name_for_testing = false; using FrameAdapter = UniqueNameHelper::FrameAdapter; class PendingChildFrameAdapter : public UniqueNameHelper::FrameAdapter { public: explicit PendingChildFrameAdapter(FrameAdapter* parent) : parent_(parent) {} // FrameAdapter overrides: bool IsMainFrame() const override { return false; } bool IsCandidateUnique(base::StringPiece name) const override { return parent_->IsCandidateUnique(name); } int GetSiblingCount() const override { // Note: no adjustment is required here: since this adapter is an internal // helper, the parent FrameAdapter it delegates to won't know about this // child to include it in the count. return parent_->GetChildCount(); } int GetChildCount() const override { NOTREACHED(); return 0; } std::vector CollectAncestorNames( BeginPoint begin_point, bool (*should_stop)(base::StringPiece)) const override { DCHECK_EQ(BeginPoint::kParentFrame, begin_point); return parent_->CollectAncestorNames(BeginPoint::kThisFrame, should_stop); } std::vector GetFramePosition(BeginPoint begin_point) const override { DCHECK_EQ(BeginPoint::kParentFrame, begin_point); return parent_->GetFramePosition(BeginPoint::kThisFrame); } private: FrameAdapter* const parent_; }; constexpr char kFramePathPrefix[] = "") && (kFramePathPrefixLength + kFramePathSuffixLength) < name.size(); } std::string GenerateCandidate(const FrameAdapter* frame) { std::string new_name(kFramePathPrefix); std::vector ancestor_names = frame->CollectAncestorNames( FrameAdapter::BeginPoint::kParentFrame, &IsNameWithFramePath); std::reverse(ancestor_names.begin(), ancestor_names.end()); // Note: This checks ancestor_names[0] twice, but it's nicer to do the name // extraction here rather than passing another function pointer to // CollectAncestorNames(). if (!ancestor_names.empty() && IsNameWithFramePath(ancestor_names[0])) { ancestor_names[0] = ancestor_names[0].substr(kFramePathPrefixLength, ancestor_names[0].size() - kFramePathPrefixLength - kFramePathSuffixLength); } new_name += base::JoinString(ancestor_names, "/"); new_name += "/-->"; // NOTE: This name might not be unique - see http://crbug.com/588800. return new_name; } std::string GenerateFramePosition(const FrameAdapter* frame) { std::string position_string(""; if (frame->IsCandidateUnique(candidate)) break; candidate.resize(current_length); } return candidate; } std::string CalculateNameInternal(const FrameAdapter* frame, base::StringPiece name) { if (!name.empty() && frame->IsCandidateUnique(name) && name != "_blank") return name.as_string(); std::string candidate = GenerateCandidate(frame); if (frame->IsCandidateUnique(candidate)) return candidate; std::string likely_unique_suffix = GenerateFramePosition(frame); return AppendUniqueSuffix(frame, candidate, likely_unique_suffix); } std::string CalculateFrameHash(base::StringPiece name) { DCHECK_GT(name.size(), kMaxRequestedNameSize); std::string hashed_name; uint8_t result[crypto::kSHA256Length]; crypto::SHA256HashString(name, result, arraysize(result)); hashed_name += ""; return hashed_name; } std::string CalculateNewName(const FrameAdapter* frame, base::StringPiece name) { std::string hashed_name; // By default, |name| is the browsing context name, which can be arbitrarily // long. Since the generated name is part of history entries and FrameState, // hash pathologically long names to avoid using a lot of memory. if (name.size() > kMaxRequestedNameSize) { hashed_name = CalculateFrameHash(name); name = hashed_name; } return CalculateNameInternal(frame, name); } } // namespace UniqueNameHelper::FrameAdapter::~FrameAdapter() {} UniqueNameHelper::Replacement::Replacement(std::string old_name, std::string new_name) : old_name(std::move(old_name)), new_name(std::move(new_name)) {} UniqueNameHelper::UniqueNameHelper(FrameAdapter* frame) : frame_(frame) {} UniqueNameHelper::~UniqueNameHelper() {} std::string UniqueNameHelper::GenerateNameForNewChildFrame( const std::string& name, bool is_created_by_script) const { std::string unique_name_of_new_child; // The deterministic part of unique name should be included if // 1. The new subframe is not created by script or // 2. The new subframe is created by script, but we are still asked for the // old, stable part for layout tests (via // |g_preserve_stable_unique_name_for_testing|). if (!is_created_by_script || g_preserve_stable_unique_name_for_testing) { PendingChildFrameAdapter adapter(frame_); unique_name_of_new_child = CalculateNewName(&adapter, name); } // The random part of unique name is only included for subframes created from // scripts. if (is_created_by_script) { unique_name_of_new_child += kDynamicFrameMarker; unique_name_of_new_child += base::UnguessableToken::Create().ToString(); unique_name_of_new_child += "-->"; } return unique_name_of_new_child; } void UniqueNameHelper::UpdateName(const std::string& name) { // Don't update the unique name if it should remain frozen. if (frozen_) return; // The unique name of the main frame is always the empty string. if (frame_->IsMainFrame()) return; // It's important to clear this before calculating a new name, as the // calculation checks for collisions with existing unique names. unique_name_.clear(); unique_name_ = CalculateNewName(frame_, name); } // |replacements| is used for two purposes: // - when processing a non-frame path unique name that exceeds the max size, // this collection records the original name and the hashed name. // - when processing a frame path unique name, this collection is used to fix up // ancestor frames in the frame path with an updated unique name. // std::string UniqueNameHelper::UpdateLegacyNameFromV24( std::string legacy_name, std::vector* replacements) { if (IsNameWithFramePath(legacy_name)) { // Frame paths can embed ancestor's unique names. Since the contract of this // function is that names must be updated beginning from the root of the // tree and go down from there, it is impossible for a frame path to contain // a unique name (which needs a replacement) that has not already been seen // and inserted into |replacements|. size_t index = 0; for (const auto& replacement : *replacements) { size_t next_index = legacy_name.find(replacement.old_name); if (next_index == std::string::npos) continue; legacy_name.replace(next_index, replacement.old_name.size(), replacement.new_name); index = next_index - (replacement.old_name.size() - replacement.new_name.size()); } return legacy_name; } if (legacy_name.size() > kMaxRequestedNameSize) { std::string hashed_name = CalculateFrameHash(legacy_name); // Suppose 'aaa' and 'caaab' are unique names in the same tree. A // hypothetical frame path might look like: // --> // // In this case, it's important to avoid matching 'aaa' against the // substring in 'caaab'. To try to avoid this, the search and the // replacement strings are wrapped in '/' to try to match the path delimiter // in generated frame paths. // // However, nothing prevents a browsing context name from containing a // literal '/', which could lead to an ambiguous parse. Consider the case // where 'aaa', 'bbb', and 'aaa/bbb' are unique names in the same tree. The // following frame path is ambiguous: // --> // // While it's possible to use the depth of the frame tree as a hint for // disambiguating this, the number of ways to split up the frame path // quickly becomes quite large. This code takes the simple approach and // simply aims to implement a best effort update, accepting that there may // be some names that are updated incorrectly. std::string original_string = "/"; original_string += legacy_name; original_string += "/"; std::string new_string = "/"; new_string += hashed_name; new_string += "/"; replacements->emplace_back(std::move(original_string), std::move(new_string)); return hashed_name; } return legacy_name; } std::string UniqueNameHelper::CalculateLegacyNameForTesting( const FrameAdapter* frame, const std::string& name) { return CalculateNameInternal(frame, name); } // static void UniqueNameHelper::PreserveStableUniqueNameForTesting() { g_preserve_stable_unique_name_for_testing = true; } std::string UniqueNameHelper::ExtractStableNameForTesting( const std::string& unique_name) { size_t i = unique_name.rfind(kDynamicFrameMarker); if (i == std::string::npos) return unique_name; return unique_name.substr(0, i); } } // namespace content