commit a8a77e18fb194d02868d14ca70318b28e5d8a19d
parent 2068833093352bae027e4c607247c56ce1a792d6
Author: Lando <lando@lando.test>
Date: Mon, 17 Nov 2025 22:22:20 +0000
Merge mozilla-central to autoland
Diffstat:
5 files changed, 69 insertions(+), 4 deletions(-)
diff --git a/browser/app/desktop-launcher/file_sink.cpp b/browser/app/desktop-launcher/file_sink.cpp
@@ -9,8 +9,10 @@
// Open the download receiver
bool FileSink::open(std::wstring& filename) {
+ mFilename.assign(filename);
// Note: this only succeeds if the filename does not exist.
- fileHandle.own(CreateFileW(filename.c_str(), GENERIC_WRITE, 0, nullptr,
+ fileHandle.own(CreateFileW(filename.c_str(), GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr));
if (fileHandle.get() == INVALID_HANDLE_VALUE) {
return false;
@@ -31,3 +33,18 @@ bool FileSink::accept(char* buf, int bytesToWrite) {
}
return true;
}
+
+bool FileSink::freeze() {
+ // Exchange our write-only handle for a read-only one. This allows us to
+ // prevent renaming or deleting the file under our nose, while also being
+ // able to actually run it.
+ HANDLE readOnlyHandle = CreateFileW(
+ mFilename.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (readOnlyHandle == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ fileHandle.own(readOnlyHandle);
+ return true;
+}
diff --git a/browser/app/desktop-launcher/file_sink.h b/browser/app/desktop-launcher/file_sink.h
@@ -16,9 +16,12 @@ class FileSink : public DataSink {
bool open(std::wstring& filename);
// Send data to the download receiver
bool accept(char* buf, int bytesToWrite) override;
+ // Make our handle read-only so we can run the file
+ bool freeze();
private:
nsAutoHandle fileHandle;
+ std::wstring mFilename;
};
#endif
diff --git a/browser/app/desktop-launcher/main.cpp b/browser/app/desktop-launcher/main.cpp
@@ -62,8 +62,12 @@ int wmain() {
bool download_completed = false;
// If that doesn't work, we try to download the installer.
std::optional<std::wstring> tempfileName = get_tempfile_name();
+ // If we do create a fileSink, it needs to live until the
+ // ExecuteAndWaitForIdle calls return to ensure that other processes cannot
+ // delete or rename it.
+ std::unique_ptr<FileSink> fileSink;
if (tempfileName.has_value()) {
- std::unique_ptr<FileSink> fileSink = std::make_unique<FileSink>();
+ fileSink = std::make_unique<FileSink>();
if (fileSink->open(tempfileName.value())) {
ErrCode rc = download_firefox(fileSink.get());
if (rc == ErrCode::OK) {
@@ -74,7 +78,8 @@ int wmain() {
}
// If the installer successfully downloaded, try to launch it
if (download_completed) {
- if (ExecuteAndWaitForIdle(tempfileName.value(), STUB_INSTALLER_ARGS)) {
+ if (fileSink->freeze() &&
+ ExecuteAndWaitForIdle(tempfileName.value(), STUB_INSTALLER_ARGS)) {
std::wcout << L"Firefox installer launched" << std::endl;
return 0;
}
diff --git a/browser/app/desktop-launcher/tempfile_name.cpp b/browser/app/desktop-launcher/tempfile_name.cpp
@@ -17,7 +17,7 @@ std::optional<std::wstring> get_tempfile_name() {
wchar_t pathBuffer[BUFFER_LEN];
wchar_t filenameBuffer[BUFFER_LEN];
UUID uuid;
- DWORD pathLen = GetTempPath2W(BUFFER_LEN, pathBuffer);
+ DWORD pathLen = GetTempPathW(BUFFER_LEN, pathBuffer);
if (pathLen > BUFFER_LEN || pathLen == 0) {
// Error getting path
return std::nullopt;
diff --git a/browser/app/desktop-launcher/tests/gtest/DesktopLauncherTest.cpp b/browser/app/desktop-launcher/tests/gtest/DesktopLauncherTest.cpp
@@ -11,6 +11,7 @@
#include "tempfile_name.h"
#include "download_firefox.h"
#include "data_sink.h"
+#include "file_sink.h"
static std::optional<std::wstring> get_value_from_key(
const wchar_t* key_path, const wchar_t* value_path) {
@@ -100,3 +101,42 @@ TEST_F(DesktopLauncherTest, TestGetObjectName) {
ASSERT_NE(std::wstring::npos, objectName.value().find(L"lang="));
ASSERT_NE(std::wstring::npos, objectName.value().find(L"product="));
}
+
+TEST_F(DesktopLauncherTest, TestTempFileNamesAreDifferent) {
+ std::wstring temp1 = get_tempfile_name().value();
+ std::wstring temp2 = get_tempfile_name().value();
+ ASSERT_NE(temp1, temp2);
+}
+
+TEST_F(DesktopLauncherTest, TestFileSinkFreeze) {
+ std::wstring path = get_tempfile_name().value();
+
+ auto delete_expecting_error = [](const wchar_t* path, DWORD expected) {
+ SetLastError(ERROR_SUCCESS);
+ BOOL result = DeleteFileW(path);
+ DWORD lastError = GetLastError();
+ ASSERT_EQ(result, expected == ERROR_SUCCESS);
+ ASSERT_EQ(lastError, expected);
+ };
+
+ char data[] = "important data";
+
+ {
+ FileSink sink;
+ delete_expecting_error(path.c_str(), ERROR_FILE_NOT_FOUND);
+ ASSERT_TRUE(sink.open(path));
+ delete_expecting_error(path.c_str(), ERROR_SHARING_VIOLATION);
+ ASSERT_TRUE(sink.accept(data, sizeof(data) - 1));
+ delete_expecting_error(path.c_str(), ERROR_SHARING_VIOLATION);
+ ASSERT_TRUE(sink.freeze());
+ delete_expecting_error(path.c_str(), ERROR_SHARING_VIOLATION);
+ ASSERT_FALSE(sink.accept(data, sizeof(data) - 1));
+
+ // TODO: Automatically test that the path can be run with ShellExecuteEx
+ // or similar.
+ //
+ // To test this manually, run the launcher without Firefox installed. You
+ // should see a prompt to install Firefox; if so, then it was executable.
+ }
+ delete_expecting_error(path.c_str(), ERROR_SUCCESS);
+}