diff --git a/chrome/browser/resources/ash/print_preview/native_layer.ts b/chrome/browser/resources/ash/print_preview/native_layer.ts index 926a8b736ce0bf..c1f7e021efafce 100644 --- a/chrome/browser/resources/ash/print_preview/native_layer.ts +++ b/chrome/browser/resources/ash/print_preview/native_layer.ts @@ -144,7 +144,7 @@ export interface NativeLayer { * @return Promise that resolves with the unique ID of * the preview UI when the preview has been generated. */ - getPreview(printTicket: string): Promise; + getPreview(printTicket: string): Promise; /** * Opens the OS's printer manager dialog. For Chrome OS, open the printing @@ -215,7 +215,7 @@ export class NativeLayerImpl implements NativeLayer { } getPreview(printTicket: string) { - return sendWithPromise('getPreview', printTicket); + return sendWithPromise('getPreview', printTicket); } managePrinters() { diff --git a/chrome/browser/resources/ash/print_preview/ui/plugin_proxy.ts b/chrome/browser/resources/ash/print_preview/ui/plugin_proxy.ts index afbf6c857e7223..ddee96f9850cf6 100644 --- a/chrome/browser/resources/ash/print_preview/ui/plugin_proxy.ts +++ b/chrome/browser/resources/ash/print_preview/ui/plugin_proxy.ts @@ -23,7 +23,7 @@ export interface PluginProxy { * @param index The preview index to load. * @return The created plugin. */ - createPlugin(previewUid: number, index: number): PdfPlugin; + createPlugin(previewUid: string, index: number): PdfPlugin; /** * @param previewUid Unique identifier of preview. @@ -33,7 +33,7 @@ export interface PluginProxy { * @param modifiable Whether the document is modifiable. */ resetPrintPreviewMode( - previewUid: number, index: number, color: boolean, pages: number[], + previewUid: string, index: number, color: boolean, pages: number[], modifiable: boolean): void; /** @@ -58,7 +58,7 @@ export interface PluginProxy { * @param pageIndex The page index to load. * @param index The preview index. */ - loadPreviewPage(previewUid: number, pageIndex: number, index: number): void; + loadPreviewPage(previewUid: string, pageIndex: number, index: number): void; setKeyEventCallback(keyEventCallback: (e: KeyboardEvent) => void): void; @@ -81,7 +81,7 @@ export class PluginProxyImpl implements PluginProxy { return !!this.plugin_; } - createPlugin(previewUid: number, index: number) { + createPlugin(previewUid: string, index: number) { assert(!this.plugin_); const srcUrl = this.getPreviewUrl_(previewUid, index); this.plugin_ = pdfCreateOutOfProcessPlugin( @@ -99,12 +99,12 @@ export class PluginProxyImpl implements PluginProxy { * @param previewUid Unique identifier of preview. * @param index Page index for plugin. */ - private getPreviewUrl_(previewUid: number, index: number): string { + private getPreviewUrl_(previewUid: string, index: number): string { return `chrome-untrusted://print/${previewUid}/${index}/print.pdf`; } resetPrintPreviewMode( - previewUid: number, index: number, color: boolean, pages: number[], + previewUid: string, index: number, color: boolean, pages: number[], modifiable: boolean) { this.plugin_!.resetPrintPreviewMode( this.getPreviewUrl_(previewUid, index), color, pages, modifiable); @@ -126,7 +126,7 @@ export class PluginProxyImpl implements PluginProxy { this.plugin_!.style.pointerEvents = eventsEnabled ? 'auto' : 'none'; } - loadPreviewPage(previewUid: number, pageIndex: number, index: number) { + loadPreviewPage(previewUid: string, pageIndex: number, index: number) { this.plugin_!.loadPreviewPage( this.getPreviewUrl_(previewUid, pageIndex), index); } diff --git a/chrome/browser/resources/ash/print_preview/ui/preview_area.ts b/chrome/browser/resources/ash/print_preview/ui/preview_area.ts index d2d45ea63ba8ec..f13a53314033fc 100644 --- a/chrome/browser/resources/ash/print_preview/ui/preview_area.ts +++ b/chrome/browser/resources/ash/print_preview/ui/preview_area.ts @@ -319,7 +319,7 @@ export class PrintPreviewPreviewAreaElement extends * @param previewUid The unique identifier of the preview. * @param index The index of the page to preview. */ - private onPreviewStart_(previewUid: number, index: number) { + private onPreviewStart_(previewUid: string, index: number) { if (!this.pluginProxy_.pluginReady()) { const plugin = this.pluginProxy_.createPlugin(previewUid, index); this.pluginProxy_.setKeyEventCallback(this.keyEventCallback_!); @@ -398,7 +398,7 @@ export class PrintPreviewPreviewAreaElement extends * preview is a response to. */ private onPagePreviewReady_( - pageIndex: number, previewUid: number, previewResponseId: number) { + pageIndex: number, previewUid: string, previewResponseId: number) { if (this.inFlightRequestId_ !== previewResponseId) { return; } @@ -741,7 +741,7 @@ export class PrintPreviewPreviewAreaElement extends * @return Promise that resolves when the preview has been * generated. */ - private getPreview_(): Promise { + private getPreview_(): Promise { this.inFlightRequestId_++; const ticket: PreviewTicket = { pageRange: this.getSettingValue('ranges'), diff --git a/chrome/browser/resources/pdf/pdf_viewer_print.ts b/chrome/browser/resources/pdf/pdf_viewer_print.ts index bdff2adea6361e..f633eeb8251aa5 100644 --- a/chrome/browser/resources/pdf/pdf_viewer_print.ts +++ b/chrome/browser/resources/pdf/pdf_viewer_print.ts @@ -78,8 +78,8 @@ export class PdfViewerPrintElement extends PdfViewerBaseElement { const paths = url.pathname.split('/'); assert(paths.length === 4); assert(paths[3] === 'print.pdf'); - // Valid Print Preview UI ID - assert(!Number.isNaN(parseInt(paths[1]!))); + // Valid Print Preview UI token. + assert(/^[0-9A-F]{32}$/.test(paths[1]!)); // Valid page index (can be negative for PDFs). assert(!Number.isNaN(parseInt(paths[2]!))); return url.toString(); diff --git a/chrome/browser/resources/print_preview/native_layer.ts b/chrome/browser/resources/print_preview/native_layer.ts index fd5803ae7250d5..94bf268ccd9b0d 100644 --- a/chrome/browser/resources/print_preview/native_layer.ts +++ b/chrome/browser/resources/print_preview/native_layer.ts @@ -120,7 +120,7 @@ export interface NativeLayer { * @return Promise that resolves with the unique ID of * the preview UI when the preview has been generated. */ - getPreview(printTicket: string): Promise; + getPreview(printTicket: string): Promise; /** * Opens the OS's printer manager dialog. For Chrome OS, open the printing @@ -194,7 +194,7 @@ export class NativeLayerImpl implements NativeLayer { } getPreview(printTicket: string) { - return sendWithPromise('getPreview', printTicket); + return sendWithPromise('getPreview', printTicket); } managePrinters() { diff --git a/chrome/browser/resources/print_preview/ui/plugin_proxy.ts b/chrome/browser/resources/print_preview/ui/plugin_proxy.ts index afbf6c857e7223..ddee96f9850cf6 100644 --- a/chrome/browser/resources/print_preview/ui/plugin_proxy.ts +++ b/chrome/browser/resources/print_preview/ui/plugin_proxy.ts @@ -23,7 +23,7 @@ export interface PluginProxy { * @param index The preview index to load. * @return The created plugin. */ - createPlugin(previewUid: number, index: number): PdfPlugin; + createPlugin(previewUid: string, index: number): PdfPlugin; /** * @param previewUid Unique identifier of preview. @@ -33,7 +33,7 @@ export interface PluginProxy { * @param modifiable Whether the document is modifiable. */ resetPrintPreviewMode( - previewUid: number, index: number, color: boolean, pages: number[], + previewUid: string, index: number, color: boolean, pages: number[], modifiable: boolean): void; /** @@ -58,7 +58,7 @@ export interface PluginProxy { * @param pageIndex The page index to load. * @param index The preview index. */ - loadPreviewPage(previewUid: number, pageIndex: number, index: number): void; + loadPreviewPage(previewUid: string, pageIndex: number, index: number): void; setKeyEventCallback(keyEventCallback: (e: KeyboardEvent) => void): void; @@ -81,7 +81,7 @@ export class PluginProxyImpl implements PluginProxy { return !!this.plugin_; } - createPlugin(previewUid: number, index: number) { + createPlugin(previewUid: string, index: number) { assert(!this.plugin_); const srcUrl = this.getPreviewUrl_(previewUid, index); this.plugin_ = pdfCreateOutOfProcessPlugin( @@ -99,12 +99,12 @@ export class PluginProxyImpl implements PluginProxy { * @param previewUid Unique identifier of preview. * @param index Page index for plugin. */ - private getPreviewUrl_(previewUid: number, index: number): string { + private getPreviewUrl_(previewUid: string, index: number): string { return `chrome-untrusted://print/${previewUid}/${index}/print.pdf`; } resetPrintPreviewMode( - previewUid: number, index: number, color: boolean, pages: number[], + previewUid: string, index: number, color: boolean, pages: number[], modifiable: boolean) { this.plugin_!.resetPrintPreviewMode( this.getPreviewUrl_(previewUid, index), color, pages, modifiable); @@ -126,7 +126,7 @@ export class PluginProxyImpl implements PluginProxy { this.plugin_!.style.pointerEvents = eventsEnabled ? 'auto' : 'none'; } - loadPreviewPage(previewUid: number, pageIndex: number, index: number) { + loadPreviewPage(previewUid: string, pageIndex: number, index: number) { this.plugin_!.loadPreviewPage( this.getPreviewUrl_(previewUid, pageIndex), index); } diff --git a/chrome/browser/resources/print_preview/ui/preview_area.ts b/chrome/browser/resources/print_preview/ui/preview_area.ts index 0fdbc9fea5fdae..45fa4d5aa8bf63 100644 --- a/chrome/browser/resources/print_preview/ui/preview_area.ts +++ b/chrome/browser/resources/print_preview/ui/preview_area.ts @@ -283,7 +283,7 @@ export class PrintPreviewPreviewAreaElement extends * @param previewUid The unique identifier of the preview. * @param index The index of the page to preview. */ - private onPreviewStart_(previewUid: number, index: number) { + private onPreviewStart_(previewUid: string, index: number) { if (!this.pluginProxy_.pluginReady()) { const plugin = this.pluginProxy_.createPlugin(previewUid, index); this.pluginProxy_.setKeyEventCallback(this.keyEventCallback_!); @@ -363,7 +363,7 @@ export class PrintPreviewPreviewAreaElement extends * preview is a response to. */ private onPagePreviewReady_( - pageIndex: number, previewUid: number, previewResponseId: number) { + pageIndex: number, previewUid: string, previewResponseId: number) { if (this.inFlightRequestId_ !== previewResponseId) { return; } @@ -704,7 +704,7 @@ export class PrintPreviewPreviewAreaElement extends * @return Promise that resolves when the preview has been * generated. */ - private getPreview_(): Promise { + private getPreview_(): Promise { assert(this.destination); this.inFlightRequestId_++; const ticket: PreviewTicket = { diff --git a/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc b/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc index 902bfdf5c785b6..8cc737a1244f23 100644 --- a/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc +++ b/chrome/browser/ui/webui/log_web_ui_url_browsertest.cc @@ -12,6 +12,7 @@ #include "base/strings/strcat.h" #include "base/strings/utf_string_conversions.h" #include "base/test/metrics/histogram_tester.h" +#include "base/unguessable_token.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" @@ -94,8 +95,9 @@ IN_PROC_BROWSER_TEST_F(LogWebUIUrlTest, TestDinoPage) { #if !BUILDFLAG(IS_ANDROID) IN_PROC_BROWSER_TEST_F(LogWebUIUrlTest, TestChromeUntrustedPage) { - RunTest(u"", GURL(base::StrCat( - {chrome::kChromeUIUntrustedPrintURL, "1/1/print.pdf"}))); + RunTest(u"", GURL(base::StrCat({chrome::kChromeUIUntrustedPrintURL, + base::UnguessableToken::Create().ToString(), + "/1/print.pdf"}))); } #endif diff --git a/chrome/browser/ui/webui/print_preview/parse_data_path.cc b/chrome/browser/ui/webui/print_preview/parse_data_path.cc index 92c2a622ad98fb..b5b70b4fbb2907 100644 --- a/chrome/browser/ui/webui/print_preview/parse_data_path.cc +++ b/chrome/browser/ui/webui/print_preview/parse_data_path.cc @@ -17,7 +17,7 @@ namespace printing { std::optional ParseDataPath( const std::string& path) { PrintPreviewIdAndPageIndex parsed = { - .ui_id = -1, + .ui_token = std::nullopt, .page_index = 0, }; @@ -36,7 +36,9 @@ std::optional ParseDataPath( return std::nullopt; } - if (!base::StringToInt(url_substr[0], &parsed.ui_id) || parsed.ui_id < 0) { + parsed.ui_token = + base::UnguessableToken::DeserializeFromString(url_substr[0]); + if (!parsed.ui_token) { return std::nullopt; } diff --git a/chrome/browser/ui/webui/print_preview/parse_data_path.h b/chrome/browser/ui/webui/print_preview/parse_data_path.h index b4a289d631e6ab..ed38c4fc8a68d3 100644 --- a/chrome/browser/ui/webui/print_preview/parse_data_path.h +++ b/chrome/browser/ui/webui/print_preview/parse_data_path.h @@ -8,12 +8,14 @@ #include #include +#include "base/unguessable_token.h" + namespace printing { // Result of `ParseDataPath()`. struct PrintPreviewIdAndPageIndex { - // Print Preview UI ID. - int ui_id; + // Per-PrintPreviewUI unguessable token, or nullopt for `test.pdf` paths. + std::optional ui_token; // Zero-based page index, or `COMPLETE_PREVIEW_DOCUMENT_INDEX` for a // print-ready PDF. @@ -24,10 +26,10 @@ struct PrintPreviewIdAndPageIndex { // chrome-untrusted://print/). // // The format for requesting preview PDF data is as follows: -// chrome-untrusted://print///print.pdf +// chrome-untrusted://print///print.pdf // -// Example: -// chrome-untrusted://print/123/10/print.pdf +// `ui_token` is the 32-character uppercase-hex serialization of the +// PrintPreviewUI's unguessable token. std::optional ParseDataPath( const std::string& path); diff --git a/chrome/browser/ui/webui/print_preview/parse_data_path_unittest.cc b/chrome/browser/ui/webui/print_preview/parse_data_path_unittest.cc index 8b59878135979d..922826c76e0d57 100644 --- a/chrome/browser/ui/webui/print_preview/parse_data_path_unittest.cc +++ b/chrome/browser/ui/webui/print_preview/parse_data_path_unittest.cc @@ -6,16 +6,18 @@ #include +#include "base/unguessable_token.h" #include "testing/gtest/include/gtest/gtest.h" namespace printing { TEST(ParseDataPathTest, ParseDataPath) { + base::UnguessableToken token = base::UnguessableToken::Create(); std::optional parsed = - ParseDataPath("3/4/print.pdf"); + ParseDataPath(token.ToString() + "/4/print.pdf"); ASSERT_TRUE(parsed); - EXPECT_EQ(parsed->ui_id, 3); + EXPECT_EQ(parsed->ui_token, token); EXPECT_EQ(parsed->page_index, 4); } @@ -24,25 +26,27 @@ TEST(ParseDataPathTest, ParseDataPathTest) { ParseDataPath("1/1/test.pdf"); ASSERT_TRUE(parsed); - EXPECT_EQ(parsed->ui_id, -1); + EXPECT_FALSE(parsed->ui_token); EXPECT_EQ(parsed->page_index, 0); } TEST(ParseDataPathTest, ParseDataPathValid) { - EXPECT_TRUE(ParseDataPath("1/2/print.pdf")); + base::UnguessableToken token = base::UnguessableToken::Create(); + EXPECT_TRUE(ParseDataPath(token.ToString() + "/2/print.pdf")); } TEST(ParseDataPathTest, ParseDataPathInvalid) { + base::UnguessableToken token = base::UnguessableToken::Create(); // Doesn't end in print.pdf EXPECT_FALSE(ParseDataPath("pdf/browser_api.js")); - // Doesn't have both page index and UI ID. + // Doesn't have both page index and UI token. EXPECT_FALSE(ParseDataPath("1/print.pdf")); - // Non-integer UI ID + // Token isn't a valid serialized UnguessableToken. EXPECT_FALSE(ParseDataPath("foo/0/print.pdf")); - // UI ID < 0 - EXPECT_FALSE(ParseDataPath("-1/0/print.pdf")); + // Integer rather than UI token. + EXPECT_FALSE(ParseDataPath("3/0/print.pdf")); // Non-integer page index - EXPECT_FALSE(ParseDataPath("1/foo/print.pdf")); + EXPECT_FALSE(ParseDataPath(token.ToString() + "/foo/print.pdf")); } } // namespace printing diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc index 2777f1c5f93f59..a4203a04928941 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_handler.cc +++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.cc @@ -22,6 +22,7 @@ #include "base/json/json_reader.h" #include "base/lazy_instance.h" #include "base/time/time.h" +#include "base/unguessable_token.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/app_mode/app_mode_utils.h" @@ -960,13 +961,16 @@ PrintPreviewHandler::GetRequestParams() { return dialog_controller->GetRequestParams(preview_web_contents()); } -void PrintPreviewHandler::OnPrintPreviewReady(int preview_uid, int request_id) { +void PrintPreviewHandler::OnPrintPreviewReady( + const base::UnguessableToken& preview_token, + int request_id) { std::string callback_id = GetCallbackId(request_id); if (callback_id.empty()) { return; } - ResolveJavascriptCallback(base::Value(callback_id), base::Value(preview_uid)); + ResolveJavascriptCallback(base::Value(callback_id), + base::Value(preview_token.ToString())); } void PrintPreviewHandler::OnPrintPreviewFailed(int request_id) { @@ -1039,9 +1043,10 @@ void PrintPreviewHandler::SendPageLayoutReady( base::Value(all_pages_have_custom_orientation)); } -void PrintPreviewHandler::SendPagePreviewReady(int page_index, - int preview_uid, - int preview_request_id) { +void PrintPreviewHandler::SendPagePreviewReady( + int page_index, + const base::UnguessableToken& preview_token, + int preview_request_id) { // With print compositing, by the time compositing finishes and this method // gets called, the print preview may have failed. Since the failure message // may have arrived first, check for this case and bail out instead of @@ -1055,7 +1060,8 @@ void PrintPreviewHandler::SendPagePreviewReady(int page_index, } FireWebUIListener("page-preview-ready", base::Value(page_index), - base::Value(preview_uid), base::Value(preview_request_id)); + base::Value(preview_token.ToString()), + base::Value(preview_request_id)); } void PrintPreviewHandler::OnPrintPreviewCancelled(int request_id) { diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler.h b/chrome/browser/ui/webui/print_preview/print_preview_handler.h index 4ab20dba80b735..94afbfc5884c08 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_handler.h +++ b/chrome/browser/ui/webui/print_preview/print_preview_handler.h @@ -32,6 +32,7 @@ namespace base { class TimeTicks; +class UnguessableToken; } // namespace base @@ -72,7 +73,8 @@ class PrintPreviewHandler : public content::WebUIMessageHandler { void OnInvalidPrinterSettings(int request_id); // Called when print preview is ready. - void OnPrintPreviewReady(int preview_uid, int request_id); + void OnPrintPreviewReady(const base::UnguessableToken& preview_token, + int request_id); // Called when a print request is cancelled due to its initiator closing. void OnPrintRequestCancelled(); @@ -96,7 +98,7 @@ class PrintPreviewHandler : public content::WebUIMessageHandler { // Notify the WebUI that the page preview is ready. void SendPagePreviewReady(int page_index, - int preview_uid, + const base::UnguessableToken& preview_token, int preview_request_id); // Notifies PDF Printer Handler that |path| was selected. Used for tests. diff --git a/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc b/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc index 4117145c6c43ff..4ab7fc1fd99453 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc +++ b/chrome/browser/ui/webui/print_preview/print_preview_handler_unittest.cc @@ -29,6 +29,7 @@ #include "base/test/metrics/histogram_tester.h" #include "base/test/scoped_feature_list.h" #include "base/test/values_test_util.h" +#include "base/unguessable_token.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/printing/print_preview_dialog_controller.h" @@ -1292,7 +1293,8 @@ TEST_F(PrintPreviewHandlerTest, SendPreviewUpdates) { std::optional ui_value = preview_params.FindInt(kPreviewUIID); ASSERT_TRUE(ui_value.has_value()); - int preview_ui_id = ui_value.value(); + + base::UnguessableToken preview_token = base::UnguessableToken::Create(); // Simulate renderer responses: PageLayoutReady, PageCountReady, // PagePreviewReady, and OnPrintPreviewReady will be called in that order. @@ -1320,11 +1322,11 @@ TEST_F(PrintPreviewHandlerTest, SendPreviewUpdates) { AssertWebUIEventFired(*web_ui()->call_data().back(), "page-count-ready"); // Page at index 0 is ready. - handler()->SendPagePreviewReady(0, preview_ui_id, preview_request_id); + handler()->SendPagePreviewReady(0, preview_token, preview_request_id); AssertWebUIEventFired(*web_ui()->call_data().back(), "page-preview-ready"); // Print preview is ready. - handler()->OnPrintPreviewReady(preview_ui_id, preview_request_id); + handler()->OnPrintPreviewReady(preview_token, preview_request_id); CheckWebUIResponse(*web_ui()->call_data().back(), callback_id_in, true); // Renderer responses have been as expected. @@ -1340,7 +1342,7 @@ TEST_F(PrintPreviewHandlerTest, SendPreviewUpdates) { EXPECT_EQ(message_count, web_ui()->call_data().size()); handler()->SendPageCountReady(1, -1, 0); EXPECT_EQ(message_count, web_ui()->call_data().size()); - handler()->OnPrintPreviewReady(0, 0); + handler()->OnPrintPreviewReady(preview_token, 0); EXPECT_EQ(message_count, web_ui()->call_data().size()); // Handler should have tried to kill the renderer for each of these. diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc index 18593bc686a7f7..829c420fc7478d 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_ui.cc +++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.cc @@ -152,6 +152,15 @@ PrintPreviewRequestIdMap& GetPrintPreviewRequestIdMap() { base::LazyInstance>::DestructorAtExit g_print_preview_ui_id_map = LAZY_INSTANCE_INITIALIZER; +// Mapping from per-PrintPreviewUI unguessable token to the integer id stored +// in `g_print_preview_ui_id_map`. Only accessed on the UI thread. +using PrintPreviewTokenMap = base::flat_map; + +PrintPreviewTokenMap& GetPrintPreviewTokenMap() { + static base::NoDestructor map; + return *map; +} + void AddPrintPreviewStrings(content::WebUIDataSource* source) { static constexpr webui::LocalizedString kLocalizedStrings[] = { {"advancedSettingsDialogConfirm", @@ -498,8 +507,10 @@ void PrintPreviewUI::ClearPreviewUIId() { receiver_.reset(); PrintPreviewDataService::GetInstance()->RemoveEntry(*id_); GetPrintPreviewRequestIdMap().erase(*id_); + GetPrintPreviewTokenMap().erase(*token_); g_print_preview_ui_id_map.Get().Remove(*id_); id_.reset(); + token_.reset(); } scoped_refptr @@ -538,7 +549,7 @@ void PrintPreviewUI::NotifyUIPreviewPageReady( if (g_test_delegate) { g_test_delegate->DidRenderPreviewPage(web_ui()->GetWebContents()); } - handler_->SendPagePreviewReady(base::checked_cast(page_index), *id_, + handler_->SendPagePreviewReady(base::checked_cast(page_index), *token_, request_id); } @@ -580,7 +591,7 @@ void PrintPreviewUI::NotifyUIPreviewDocumentReady( SetPrintPreviewDataForIndex(COMPLETE_PREVIEW_DOCUMENT_INDEX, std::move(data_bytes)); - handler_->OnPrintPreviewReady(*id_, request_id); + handler_->OnPrintPreviewReady(*token_, request_id); } bool PrintPreviewUI::ShouldUseCompositor() const { @@ -802,6 +813,23 @@ std::optional PrintPreviewUI::GetIDForPrintPreviewUI() const { return id_; } +std::optional PrintPreviewUI::GetPrintPreviewUIToken() + const { + return token_; +} + +// static +std::optional PrintPreviewUI::GetIDForPrintPreviewUIToken( + const base::UnguessableToken& token) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + const PrintPreviewTokenMap& map = GetPrintPreviewTokenMap(); + auto it = map.find(token); + if (it == map.end()) { + return std::nullopt; + } + return it->second; +} + void PrintPreviewUI::OnPrintPreviewDialogClosed() { WebContents* preview_dialog = web_ui()->GetWebContents(); BackgroundPrintingManager* background_printing_manager = @@ -1185,7 +1213,9 @@ void PrintPreviewUI::SetPreviewUIId() { DCHECK(!id_); id_ = g_print_preview_ui_id_map.Get().Add(this); + token_ = base::UnguessableToken::Create(); GetPrintPreviewRequestIdMap()[*id_] = -1; + GetPrintPreviewTokenMap()[*token_] = *id_; } } // namespace printing diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui.h b/chrome/browser/ui/webui/print_preview/print_preview_ui.h index e08b5197390b24..3876e9103c044f 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_ui.h +++ b/chrome/browser/ui/webui/print_preview/print_preview_ui.h @@ -20,6 +20,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/weak_ptr.h" #include "base/time/time.h" +#include "base/unguessable_token.h" #include "chrome/browser/ui/webui/constrained_web_dialog_ui.h" #include "chrome/services/printing/public/mojom/pdf_nup_converter.mojom.h" #include "components/printing/common/print.mojom.h" @@ -140,6 +141,15 @@ class PrintPreviewUI : public ConstrainedWebDialogUI, // Returns an id to uniquely identify this PrintPreviewUI. std::optional GetIDForPrintPreviewUI() const; + // Returns the unguessable token that identifies this PrintPreviewUI in + // chrome-untrusted://print/ data paths. + std::optional GetPrintPreviewUIToken() const; + + // Returns the integer id of the live PrintPreviewUI instance associated with + // `token`, or nullopt if there is no such instance. + static std::optional GetIDForPrintPreviewUIToken( + const base::UnguessableToken& token); + // Notifies the Web UI of a print preview request with |request_id|. virtual void OnPrintPreviewRequest(int request_id); @@ -288,6 +298,10 @@ class PrintPreviewUI : public ConstrainedWebDialogUI, // GetIDForPrintPreviewUI() everywhere. std::optional id_; + // The unguessable token used to look up preview data for this instance via + // chrome-untrusted://print/ data paths. + std::optional token_; + #if BUILDFLAG(ENABLE_OOP_PRINTING) // This UI's client ID with the print backend service manager. PrintBackendServiceManager::ClientId service_manager_client_id_; diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc index f242ccf6fef17a..b21f29febf8116 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc +++ b/chrome/browser/ui/webui/print_preview/print_preview_ui_browsertest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "base/test/metrics/histogram_tester.h" #include "build/build_config.h" @@ -163,8 +164,10 @@ IN_PROC_BROWSER_TEST_F(PrintPreviewBrowserTest, PreviewStartedMetric) { content::TestNavigationObserver nav_observer(nullptr); nav_observer.WatchExistingWebContents(); nav_observer.Wait(); - EXPECT_EQ(GURL("chrome-untrusted://print/1/0/print.pdf"), - nav_observer.last_navigation_url()); + GURL last_url = nav_observer.last_navigation_url(); + EXPECT_EQ("chrome-untrusted", last_url.scheme()); + EXPECT_EQ("print", last_url.host()); + EXPECT_TRUE(base::EndsWith(last_url.path(), "/0/print.pdf")); histogram_tester.ExpectBucketCount( "PrintPreview.UserAction", printing::UserActionBuckets::kPreviewStarted, /*expected_count=*/1); diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted.cc index b94f8974c561dc..62e8a9f5cc9538 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted.cc +++ b/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted.cc @@ -17,6 +17,7 @@ #include "base/threading/thread_restrictions.h" #include "chrome/browser/printing/print_preview_data_service.h" #include "chrome/browser/ui/webui/print_preview/parse_data_path.h" +#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/webui_url_constants.h" #include "content/public/browser/web_contents.h" @@ -56,11 +57,17 @@ PrintPreviewUIUntrusted::GetPrintPreviewData(const std::string& path) { std::optional parsed = ParseDataPath(path); CHECK(parsed); - scoped_refptr data = - PrintPreviewDataService::GetInstance()->GetDataEntry(parsed->ui_id, - parsed->page_index); - if (data) { - return data; + if (parsed->ui_token) { + std::optional ui_id = + PrintPreviewUI::GetIDForPrintPreviewUIToken(*parsed->ui_token); + if (ui_id) { + scoped_refptr data = + PrintPreviewDataService::GetInstance()->GetDataEntry( + *ui_id, parsed->page_index); + if (data) { + return data; + } + } } // Return empty data if this is not a valid request. diff --git a/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted_unittest.cc b/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted_unittest.cc index 89e8452743f152..62604f76e14934 100644 --- a/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted_unittest.cc +++ b/chrome/browser/ui/webui/print_preview/print_preview_ui_untrusted_unittest.cc @@ -14,11 +14,16 @@ #include "base/memory/scoped_refptr.h" #include "base/path_service.h" #include "base/threading/thread_restrictions.h" +#include "base/unguessable_token.h" #include "chrome/browser/printing/print_preview_data_service.h" +#include "chrome/browser/printing/print_preview_dialog_controller.h" #include "chrome/browser/printing/print_preview_test.h" +#include "chrome/browser/printing/print_view_manager.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_tabstrip.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" +#include "chrome/browser/ui/webui/print_preview/parse_data_path.h" +#include "chrome/browser/ui/webui/print_preview/print_preview_ui.h" #include "chrome/common/chrome_paths.h" #include "chrome/test/base/browser_with_test_window_test.h" #include "content/public/browser/web_contents.h" @@ -49,23 +54,69 @@ class PrintPreviewUIUntrustedUnitTest : public PrintPreviewTest { const PrintPreviewUIUntrustedUnitTest&) = delete; ~PrintPreviewUIUntrustedUnitTest() override = default; + + protected: + PrintPreviewUI* StartPrintPreview() { + chrome::NewTab(browser(), NewTabTypes::kNoUserAction); + WebContents* initiator = + browser()->tab_strip_model()->GetActiveWebContents(); + if (!initiator) { + ADD_FAILURE(); + return nullptr; + } + + auto* controller = PrintPreviewDialogController::GetInstance(); + if (!controller) { + ADD_FAILURE(); + return nullptr; + } + + PrintViewManager::FromWebContents(initiator) + ->PrintPreviewNow(initiator->GetPrimaryMainFrame(), + /*has_selection=*/false); + WebContents* preview_dialog = + controller->GetOrCreatePreviewDialogForTesting(initiator); + + PrintPreviewUI* preview_ui = + preview_dialog->GetWebUI()->GetController()->GetAs(); + if (!preview_ui) { + ADD_FAILURE(); + return nullptr; + } + + preview_ui->SetPreviewUIId(); + return preview_ui; + } }; TEST_F(PrintPreviewUIUntrustedUnitTest, PrintPreviewData) { - PrintPreviewDataService* data_service = - PrintPreviewDataService::GetInstance(); + PrintPreviewUI* preview_ui = StartPrintPreview(); + ASSERT_TRUE(preview_ui); + ASSERT_TRUE(preview_ui->GetPrintPreviewUIToken()); + std::string token = preview_ui->GetPrintPreviewUIToken()->ToString(); + scoped_refptr dummy_data = CreateTestData(); - data_service->SetDataEntry(0, 0, dummy_data.get()); + preview_ui->SetPrintPreviewDataForIndexForTest(FIRST_PAGE_INDEX, + dummy_data.get()); // Valid request for data in the data service. scoped_refptr data = - PrintPreviewUIUntrusted::GetPrintPreviewDataForTest("0/0/print.pdf"); + PrintPreviewUIUntrusted::GetPrintPreviewDataForTest(token + + "/0/print.pdf"); ASSERT_TRUE(data); EXPECT_EQ(dummy_data->size(), data->size()); EXPECT_EQ(dummy_data.get(), data.get()); - // Invalid request - data = PrintPreviewUIUntrusted::GetPrintPreviewDataForTest("1/0/print.pdf"); + // Invalid request: page index that has no data. + data = + PrintPreviewUIUntrusted::GetPrintPreviewDataForTest(token + + "/1/print.pdf"); + ASSERT_TRUE(data); + EXPECT_EQ(0u, data->size()); + + // Invalid request: token that does not belong to any live preview. + data = PrintPreviewUIUntrusted::GetPrintPreviewDataForTest( + base::UnguessableToken::Create().ToString() + "/0/print.pdf"); ASSERT_TRUE(data); EXPECT_EQ(0u, data->size()); @@ -84,4 +135,57 @@ TEST_F(PrintPreviewUIUntrustedUnitTest, PrintPreviewData) { EXPECT_EQ(test_data->size(), data->size()); EXPECT_TRUE(data->Equals(test_data)); } + +TEST_F(PrintPreviewUIUntrustedUnitTest, IntegerIdPathRejected) { + PrintPreviewDataService* data_service = + PrintPreviewDataService::GetInstance(); + scoped_refptr dummy_data = CreateTestData(); + data_service->SetDataEntry(7, COMPLETE_PREVIEW_DOCUMENT_INDEX, + dummy_data.get()); + + // Paths using small integers as the preview identifier must not be handled + // by the data source; only paths using a per-preview token are valid. + EXPECT_FALSE(ParseDataPath("7/-1/print.pdf").has_value()); + EXPECT_FALSE(ParseDataPath("0/0/print.pdf").has_value()); + + data_service->RemoveEntry(7); +} + +TEST_F(PrintPreviewUIUntrustedUnitTest, PerPreviewToken) { + PrintPreviewUI* preview_a = StartPrintPreview(); + ASSERT_TRUE(preview_a); + PrintPreviewUI* preview_b = StartPrintPreview(); + ASSERT_TRUE(preview_b); + + ASSERT_TRUE(preview_a->GetPrintPreviewUIToken()); + ASSERT_TRUE(preview_b->GetPrintPreviewUIToken()); + std::string token_a = preview_a->GetPrintPreviewUIToken()->ToString(); + std::string token_b = preview_b->GetPrintPreviewUIToken()->ToString(); + EXPECT_NE(token_a, token_b); + + scoped_refptr data_a = CreateTestData(); + preview_a->SetPrintPreviewDataForIndexForTest(COMPLETE_PREVIEW_DOCUMENT_INDEX, + data_a.get()); + + // Preview A's token returns preview A's data. + scoped_refptr result = + PrintPreviewUIUntrusted::GetPrintPreviewDataForTest(token_a + + "/-1/print.pdf"); + ASSERT_TRUE(result); + EXPECT_EQ(data_a.get(), result.get()); + + // Preview B's token does not return preview A's data. + result = PrintPreviewUIUntrusted::GetPrintPreviewDataForTest( + token_b + "/-1/print.pdf"); + ASSERT_TRUE(result); + EXPECT_EQ(0u, result->size()); + + // After preview A is torn down its token no longer returns data. + preview_a->ClearPreviewUIId(); + result = PrintPreviewUIUntrusted::GetPrintPreviewDataForTest( + token_a + "/-1/print.pdf"); + ASSERT_TRUE(result); + EXPECT_EQ(0u, result->size()); +} + } // namespace printing diff --git a/chrome/test/data/webui/chromeos/print_preview/native_layer_stub.ts b/chrome/test/data/webui/chromeos/print_preview/native_layer_stub.ts index fdcad55b69e4d3..1e670b244b96d3 100644 --- a/chrome/test/data/webui/chromeos/print_preview/native_layer_stub.ts +++ b/chrome/test/data/webui/chromeos/print_preview/native_layer_stub.ts @@ -134,7 +134,7 @@ export class NativeLayerStub extends TestBrowserProxy implements NativeLayer { webUIListenerCallback( 'page-count-ready', this.pageCount_, requestId, 100); for (let i = 0; i < this.pageCount_; i++) { - webUIListenerCallback('page-preview-ready', i, 0, requestId); + webUIListenerCallback('page-preview-ready', i, '0', requestId); } } else { const pages = pageRanges.reduce( @@ -148,10 +148,10 @@ export class NativeLayerStub extends TestBrowserProxy implements NativeLayer { webUIListenerCallback( 'page-count-ready', this.pageCount_, requestId, 100); pages.forEach(function(page: number) { - webUIListenerCallback('page-preview-ready', page - 1, 0, requestId); + webUIListenerCallback('page-preview-ready', page - 1, '0', requestId); }); } - return Promise.resolve(requestId); + return Promise.resolve('0'); } getPrinterCapabilities(printerId: string, type: PrinterType) { diff --git a/chrome/test/data/webui/chromeos/print_preview/test_plugin_proxy.ts b/chrome/test/data/webui/chromeos/print_preview/test_plugin_proxy.ts index fbfbd28fd400ca..c5354232596a27 100644 --- a/chrome/test/data/webui/chromeos/print_preview/test_plugin_proxy.ts +++ b/chrome/test/data/webui/chromeos/print_preview/test_plugin_proxy.ts @@ -39,7 +39,7 @@ export class TestPluginProxy extends TestBrowserProxy implements PluginProxy { return !!this.fakePlugin_; } - createPlugin(_previewUid: number, _index: number) { + createPlugin(_previewUid: string, _index: number) { this.fakePlugin_ = document.createElement('div'); this.fakePlugin_.classList.add('preview-area-plugin'); this.fakePlugin_.id = 'pdf-viewer'; @@ -47,7 +47,7 @@ export class TestPluginProxy extends TestBrowserProxy implements PluginProxy { } resetPrintPreviewMode( - _previewUid: number, _index: number, _color: boolean, _pages: number[], + _previewUid: string, _index: number, _color: boolean, _pages: number[], _modifiable: boolean) {} scrollPosition(_scrollX: number, _scrollY: number) {} @@ -58,7 +58,7 @@ export class TestPluginProxy extends TestBrowserProxy implements PluginProxy { setPointerEvents(_eventsOn: boolean) {} - loadPreviewPage(previewUid: number, pageIndex: number, index: number) { + loadPreviewPage(previewUid: string, pageIndex: number, index: number) { this.methodCalled( 'loadPreviewPage', {previewUid: previewUid, pageIndex: pageIndex, index: index}); diff --git a/chrome/test/data/webui/print_preview/native_layer_stub.ts b/chrome/test/data/webui/print_preview/native_layer_stub.ts index ec6c2cbee90170..e8847de6b73775 100644 --- a/chrome/test/data/webui/print_preview/native_layer_stub.ts +++ b/chrome/test/data/webui/print_preview/native_layer_stub.ts @@ -136,7 +136,7 @@ export class NativeLayerStub extends TestBrowserProxy implements NativeLayer { 'page-count-ready', this.pageCount_, requestId, 100); await microtasksFinished(); for (let i = 0; i < this.pageCount_; i++) { - webUIListenerCallback('page-preview-ready', i, 0, requestId); + webUIListenerCallback('page-preview-ready', i, '0', requestId); } } else { const pages = pageRanges.reduce( @@ -151,10 +151,10 @@ export class NativeLayerStub extends TestBrowserProxy implements NativeLayer { 'page-count-ready', this.pageCount_, requestId, 100); await microtasksFinished(); pages.forEach(function(page: number) { - webUIListenerCallback('page-preview-ready', page - 1, 0, requestId); + webUIListenerCallback('page-preview-ready', page - 1, '0', requestId); }); } - return requestId; + return '0'; } getPrinterCapabilities(printerId: string, type: PrinterType) { diff --git a/chrome/test/data/webui/print_preview/test_plugin_proxy.ts b/chrome/test/data/webui/print_preview/test_plugin_proxy.ts index 50c718f2471e0d..685040770cf71c 100644 --- a/chrome/test/data/webui/print_preview/test_plugin_proxy.ts +++ b/chrome/test/data/webui/print_preview/test_plugin_proxy.ts @@ -39,7 +39,7 @@ export class TestPluginProxy extends TestBrowserProxy implements PluginProxy { return !!this.fakePlugin_; } - createPlugin(_previewUid: number, _index: number) { + createPlugin(_previewUid: string, _index: number) { this.fakePlugin_ = document.createElement('div'); this.fakePlugin_.classList.add('preview-area-plugin'); this.fakePlugin_.id = 'pdf-viewer'; @@ -47,7 +47,7 @@ export class TestPluginProxy extends TestBrowserProxy implements PluginProxy { } resetPrintPreviewMode( - _previewUid: number, _index: number, _color: boolean, _pages: number[], + _previewUid: string, _index: number, _color: boolean, _pages: number[], _modifiable: boolean) {} scrollPosition(_scrollX: number, _scrollY: number) {} @@ -58,7 +58,7 @@ export class TestPluginProxy extends TestBrowserProxy implements PluginProxy { setPointerEvents(_eventsOn: boolean) {} - loadPreviewPage(previewUid: number, pageIndex: number, index: number) { + loadPreviewPage(previewUid: string, pageIndex: number, index: number) { this.methodCalled( 'loadPreviewPage', {previewUid: previewUid, pageIndex: pageIndex, index: index});