1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
|
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/download/save_package.h"
#include <stddef.h>
#include <stdint.h>
#include <array>
#include <string>
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/download/save_file_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/fake_local_frame.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "net/test/url_request/url_request_mock_http_job.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "url/gurl.h"
namespace content {
#define FPL FILE_PATH_LITERAL
#define HTML_EXTENSION ".html"
#if BUILDFLAG(IS_WIN)
#define FPL_HTML_EXTENSION L".html"
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
#define FPL_HTML_EXTENSION ".html"
#endif
namespace {
// This constant copied from save_package.cc.
#if BUILDFLAG(IS_WIN)
const uint32_t kMaxFilePathLength = MAX_PATH - 1;
const uint32_t kMaxFileNameLength = MAX_PATH - 1;
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
const uint32_t kMaxFilePathLength = PATH_MAX - 1;
const uint32_t kMaxFileNameLength = NAME_MAX;
#endif
bool HasOrdinalNumber(const base::FilePath::StringType& filename) {
base::FilePath::StringType::size_type r_paren_index =
filename.rfind(FPL(')'));
base::FilePath::StringType::size_type l_paren_index =
filename.rfind(FPL('('));
if (l_paren_index >= r_paren_index)
return false;
for (base::FilePath::StringType::size_type i = l_paren_index + 1;
i != r_paren_index; ++i) {
if (!base::IsAsciiDigit(filename[i]))
return false;
}
return true;
}
} // namespace
class SavePackageTest : public RenderViewHostImplTestHarness {
public:
bool GetGeneratedFilename(bool need_success_generate_filename,
const std::string& disposition,
const std::string& url,
bool need_htm_ext,
base::FilePath::StringType* generated_name) {
SavePackage* save_package;
if (need_success_generate_filename)
save_package = save_package_success_.get();
else
save_package = save_package_fail_.get();
return save_package->GenerateFileName(disposition, GURL(url), need_htm_ext,
generated_name);
}
GURL GetUrlToBeSaved() {
return SavePackage::GetUrlToBeSaved(contents()->GetPrimaryMainFrame());
}
protected:
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
// Do the initialization in SetUp so contents() is initialized by
// RenderViewHostImplTestHarness::SetUp.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
save_package_success_ = new SavePackage(
contents()->GetPrimaryPage(), SAVE_PAGE_TYPE_AS_COMPLETE_HTML,
temp_dir_.GetPath().AppendASCII("testfile" HTML_EXTENSION),
temp_dir_.GetPath().AppendASCII("testfile_files"));
base::FilePath::StringType long_file_name = GetLongFileName();
save_package_fail_ = new SavePackage(
contents()->GetPrimaryPage(), SAVE_PAGE_TYPE_AS_COMPLETE_HTML,
temp_dir_.GetPath().Append(long_file_name + FPL_HTML_EXTENSION),
temp_dir_.GetPath().Append(long_file_name + FPL("_files")));
}
std::unique_ptr<BrowserContext> CreateBrowserContext() override {
// Initialize the SaveFileManager instance which we will use for the tests.
save_file_manager_ = new SaveFileManager();
return RenderViewHostImplTestHarness::CreateBrowserContext();
}
void TearDown() override {
DeleteContents();
base::RunLoop().RunUntilIdle();
save_package_success_ = nullptr;
save_package_fail_ = nullptr;
RenderViewHostImplTestHarness::TearDown();
}
// Returns a path that is *almost* kMaxFilePathLength long
base::FilePath::StringType GetLongFileName() const {
size_t target_length =
kMaxFilePathLength - 9 - temp_dir_.GetPath().value().length();
return base::FilePath::StringType(target_length, FPL('a'));
}
private:
// SavePackage for successfully generating file name.
scoped_refptr<SavePackage> save_package_success_;
// SavePackage for failed generating file name.
scoped_refptr<SavePackage> save_package_fail_;
base::ScopedTempDir temp_dir_;
scoped_refptr<SaveFileManager> save_file_manager_;
};
struct GeneratedFiles {
const char* disposition;
const char* url;
const base::FilePath::CharType* expected_name;
bool need_htm_ext;
};
constexpr auto kGeneratedFiles = std::to_array<GeneratedFiles>({
// We mainly focus on testing duplicated names here, since retrieving file
// name from disposition and url has been tested in DownloadManagerTest.
// No useful information in disposition or URL, use default.
{"1.html", "http://www.savepage.com/",
FPL("saved_resource") FPL_HTML_EXTENSION, true},
// No duplicate occurs.
{"filename=1.css", "http://www.savepage.com", FPL("1.css"), false},
// No duplicate occurs.
{"filename=1.js", "http://www.savepage.com", FPL("1.js"), false},
// Append numbers for duplicated names.
{"filename=1.css", "http://www.savepage.com", FPL("1(1).css"), false},
// No duplicate occurs.
{"filename=1(1).js", "http://www.savepage.com", FPL("1(1).js"), false},
// Append numbers for duplicated names.
{"filename=1.css", "http://www.savepage.com", FPL("1(2).css"), false},
// Change number for duplicated names.
{"filename=1(1).css", "http://www.savepage.com", FPL("1(3).css"), false},
// No duplicate occurs.
{"filename=1(11).css", "http://www.savepage.com", FPL("1(11).css"), false},
// Test for case-insensitive file names.
{"filename=readme.txt", "http://www.savepage.com", FPL("readme.txt"),
false},
{"filename=readme.TXT", "http://www.savepage.com", FPL("readme(1).TXT"),
false},
{"filename=READme.txt", "http://www.savepage.com", FPL("readme(2).txt"),
false},
{"filename=Readme(1).txt", "http://www.savepage.com", FPL("readme(3).txt"),
false},
});
TEST_F(SavePackageTest, TestSuccessfullyGenerateSavePackageFilename) {
for (size_t i = 0; i < std::size(kGeneratedFiles); ++i) {
base::FilePath::StringType file_name;
bool ok = GetGeneratedFilename(true,
kGeneratedFiles[i].disposition,
kGeneratedFiles[i].url,
kGeneratedFiles[i].need_htm_ext,
&file_name);
ASSERT_TRUE(ok);
EXPECT_EQ(kGeneratedFiles[i].expected_name, file_name);
}
}
TEST_F(SavePackageTest, TestUnSuccessfullyGenerateSavePackageFilename) {
for (size_t i = 0; i < std::size(kGeneratedFiles); ++i) {
base::FilePath::StringType file_name;
bool ok = GetGeneratedFilename(false,
kGeneratedFiles[i].disposition,
kGeneratedFiles[i].url,
kGeneratedFiles[i].need_htm_ext,
&file_name);
ASSERT_FALSE(ok);
}
}
// Crashing on Windows, see http://crbug.com/79365
#if BUILDFLAG(IS_WIN)
#define MAYBE_TestLongSavePackageFilename DISABLED_TestLongSavePackageFilename
#else
#define MAYBE_TestLongSavePackageFilename TestLongSavePackageFilename
#endif
TEST_F(SavePackageTest, MAYBE_TestLongSavePackageFilename) {
const std::string base_url("http://www.google.com/");
const base::FilePath::StringType long_file_name =
GetLongFileName() + FPL(".css");
const std::string url =
base_url + base::FilePath(long_file_name).AsUTF8Unsafe();
base::FilePath::StringType filename;
// Test that the filename is successfully shortened to fit.
ASSERT_TRUE(GetGeneratedFilename(true, std::string(), url, false, &filename));
EXPECT_TRUE(filename.length() < long_file_name.length());
EXPECT_FALSE(HasOrdinalNumber(filename));
// Test that the filename is successfully shortened to fit, and gets an
// an ordinal appended.
ASSERT_TRUE(GetGeneratedFilename(true, std::string(), url, false, &filename));
EXPECT_TRUE(filename.length() < long_file_name.length());
EXPECT_TRUE(HasOrdinalNumber(filename));
// Test that the filename is successfully shortened to fit, and gets a
// different ordinal appended.
base::FilePath::StringType filename2;
ASSERT_TRUE(
GetGeneratedFilename(true, std::string(), url, false, &filename2));
EXPECT_TRUE(filename2.length() < long_file_name.length());
EXPECT_TRUE(HasOrdinalNumber(filename2));
EXPECT_NE(filename, filename2);
}
// Crashing on Windows, see http://crbug.com/79365
#if BUILDFLAG(IS_WIN)
#define MAYBE_TestLongSafePureFilename DISABLED_TestLongSafePureFilename
#else
#define MAYBE_TestLongSafePureFilename TestLongSafePureFilename
#endif
TEST_F(SavePackageTest, MAYBE_TestLongSafePureFilename) {
const base::FilePath save_dir(FPL("test_dir"));
const base::FilePath::StringType ext(FPL_HTML_EXTENSION);
base::FilePath::StringType filename = GetLongFileName();
// Test that the filename + extension doesn't exceed kMaxFileNameLength
uint32_t max_path = SavePackage::GetMaxPathLengthForDirectory(save_dir);
ASSERT_TRUE(SavePackage::TruncateBaseNameToFitPathConstraints(
save_dir, ext, max_path, &filename));
EXPECT_TRUE(filename.length() <= kMaxFileNameLength-ext.length());
}
// GetUrlToBeSaved method should return correct url to be saved.
TEST_F(SavePackageTest, TestGetUrlToBeSaved) {
GURL url = net::URLRequestMockHTTPJob::GetMockUrl("save_page/a.htm");
NavigateAndCommit(url);
EXPECT_EQ(url, GetUrlToBeSaved());
}
// GetUrlToBeSaved method sould return actual url to be saved,
// instead of the displayed url used to view source of a page.
// Ex:GetUrlToBeSaved method should return http://www.google.com
// when user types view-source:http://www.google.com
TEST_F(SavePackageTest, TestGetUrlToBeSavedViewSource) {
GURL mock_url = net::URLRequestMockHTTPJob::GetMockUrl("save_page/a.htm");
GURL view_source_url =
GURL(kViewSourceScheme + std::string(":") + mock_url.spec());
GURL actual_url = net::URLRequestMockHTTPJob::GetMockUrl("save_page/a.htm");
NavigateAndCommit(view_source_url);
EXPECT_EQ(actual_url, GetUrlToBeSaved());
EXPECT_EQ(view_source_url, contents()->GetLastCommittedURL());
}
class SavePackageFencedFrameTest : public SavePackageTest {
public:
SavePackageFencedFrameTest() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
blink::features::kFencedFrames, {{"implementation_type", "mparch"}});
}
~SavePackageFencedFrameTest() override = default;
RenderFrameHost* CreateFencedFrame(RenderFrameHost* parent) {
RenderFrameHost* fenced_frame =
RenderFrameHostTester::For(parent)->AppendFencedFrame();
return fenced_frame;
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// FakeLocalFrame implementation that records calls to
// GetSavableResourceLinks().
class FakeLocalFrameWithSavableResourceLinks : public FakeLocalFrame {
public:
explicit FakeLocalFrameWithSavableResourceLinks(RenderFrameHost* rfh) {
auto* test_host = static_cast<TestRenderFrameHost*>(rfh);
test_host->ResetLocalFrame();
Init(test_host->GetRemoteAssociatedInterfaces());
}
bool is_called() const { return is_called_; }
// FakeLocalFrame:
void GetSavableResourceLinks(
GetSavableResourceLinksCallback callback) override {
is_called_ = true;
std::move(callback).Run(nullptr);
}
private:
bool is_called_ = false;
};
// Tests that SavePackage does not create an unnecessary task that gets the
// resources links from fenced frames.
// If fenced frames become savable, this test will need to be updated.
// See https://crbug.com/1321102
TEST_F(SavePackageFencedFrameTest,
DontRequestSavableResourcesFromFencedFrames) {
NavigateAndCommit(GURL("https://www.example.com"));
// Create a fenced frame.
RenderFrameHostTester::For(contents()->GetPrimaryMainFrame())
->InitializeRenderFrameIfNeeded();
RenderFrameHost* fenced_frame_rfh =
CreateFencedFrame(contents()->GetPrimaryMainFrame());
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
scoped_refptr<SavePackage> save_package(new SavePackage(
contents()->GetPrimaryPage(), SAVE_PAGE_TYPE_AS_COMPLETE_HTML,
temp_dir.GetPath().AppendASCII("testfile" HTML_EXTENSION),
temp_dir.GetPath().AppendASCII("testfile_files")));
FakeLocalFrameWithSavableResourceLinks local_frame_for_primary(
contents()->GetPrimaryMainFrame());
local_frame_for_primary.Init(
contents()->GetPrimaryMainFrame()->GetRemoteAssociatedInterfaces());
FakeLocalFrameWithSavableResourceLinks local_frame_for_fenced_frame(
fenced_frame_rfh);
local_frame_for_fenced_frame.Init(
fenced_frame_rfh->GetRemoteAssociatedInterfaces());
EXPECT_TRUE(save_package->Init(base::DoNothing()));
ASSERT_TRUE(base::test::RunUntil([&local_frame_for_primary]() {
return local_frame_for_primary.is_called();
}));
EXPECT_FALSE(local_frame_for_fenced_frame.is_called());
}
} // namespace content
|