/****************************************************************************** * ROM Properties Page shell extension. (rp-download) * * SetFileOriginInfo_win32.cpp: setFileOriginInfo() function. (Win32 version) * * * * Copyright (c) 2016-2025 by David Korth. * * SPDX-License-Identifier: GPL-2.0-or-later * ******************************************************************************/ #include "stdafx.h" #include "SetFileOriginInfo.hpp" #ifndef _WIN32 # error SetFileOriginInfo_posix.cpp is for Windows systems, not POSIX. #endif /* !_WIN32 */ // libwin32common #include "libwin32common/userdirs.hpp" #include "libwin32common/w32err.hpp" #include "libwin32common/w32time.h" // librptext #include "librptext/conversion.hpp" #include "librptext/wchar.hpp" // C includes #include // C++ STL classes using std::string; using std::tstring; #ifdef UNICODE // NTDLL function calls #include #include // FIXME: RTL_CONSTANT_STRING macro is in ntdef.h, // but #include'ing it breaks things. // This version is from MinGW-w64 12.0.0. #define RTL_CONSTANT_STRING(s) { sizeof(s)-sizeof((s)[0]), sizeof(s), const_cast(s) } // NTDLL functions not declared in winternl.h. extern "C" { __kernel_entry NTSYSCALLAPI NTSTATUS NtWriteFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key ); typedef const OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES; __kernel_entry NTSYSCALLAPI NTSTATUS NtDeleteFile( PCOBJECT_ATTRIBUTES ObjectAttributes ); typedef __typeof__(NtCreateFile) *pfn_NtCreateFile_t; typedef __typeof__(NtWriteFile) *pfn_NtWriteFile_t; typedef __typeof__(NtClose) *pfn_NtClose_t; typedef __typeof__(NtDeleteFile) *pfn_NtDeleteFile_t; } #endif /* UNICODE */ namespace RpDownload { /** * Get the storeFileOriginInfo setting from rom-properties.conf. * * Default value is true. * * @return storeFileOriginInfo setting. */ static bool getStoreFileOriginInfo(void) { static constexpr bool default_value = true; DWORD dwRet; TCHAR szValue[64]; // Get the config filename. // NOTE: Not cached, since rp-download downloads one file per run. // NOTE: This is sitll readable even when running as Low integrity. tstring conf_filename = U82T_s(LibWin32Common::getConfigDirectory()); if (conf_filename.empty()) { // Empty filename... return default_value; } // Add a trailing slash if necessary. if (conf_filename.at(conf_filename.size()-1) != DIR_SEP_CHR) { conf_filename += DIR_SEP_CHR; } conf_filename += _T("rom-properties\\rom-properties.conf"); dwRet = GetPrivateProfileString(_T("Downloads"), _T("StoreFileOriginInfo"), NULL, szValue, _countof(szValue), conf_filename.c_str()); if ((dwRet == 5 && !_tcsicmp(szValue, _T("false"))) || (dwRet == 1 && szValue[0] == _T('0'))) { // Disabled. return false; } // Other value. Assume enabled. return true; } /** * Set the file origin info. * This uses xattrs on Linux and ADS on Windows. * @param file Open file (Must be writable) * @param url Origin URL * @param mtime If >= 0, this value is set as the mtime. * @return 0 on success; negative POSIX error code on error. */ int setFileOriginInfo(FILE *file, const TCHAR *url, time_t mtime) { // NOTE: Even if one of the xattr functions fails, we'll // continue with others and setting mtime. The first error // will be returned at the end of the function. int err = 0; // TODO: Add a static_warning() macro? // - http://stackoverflow.com/questions/8936063/does-there-exist-a-static-warning #if _USE_32BIT_TIME_T # error 32-bit time_t is not supported. Get a newer compiler. #endif #ifdef UNICODE // Check if storeFileOriginInfo is enabled. const bool storeFileOriginInfo = getStoreFileOriginInfo(); if (storeFileOriginInfo) do { // Create an ADS named "Zone.Identifier". // References: // - https://cqureacademy.com/blog/alternate-data-streams // - https://stackoverflow.com/questions/46141321/open-alternate-data-stream-ads-from-file-handle-or-file-id // - https://stackoverflow.com/a/46141949 // NOTE: ntdll.lib isn't present in all build environments. // Use GetModuleHandle() and GetProcAddress() instead. HMODULE hNtDll = GetModuleHandle(_T("ntdll.dll")); if (!hNtDll) { // No NTDLL.DLL? Maybe this is Win9x... break; } #define NTDLL_LOAD(fn) pfn_##fn##_t pfn_##fn = reinterpret_cast(GetProcAddress(hNtDll, #fn)) NTDLL_LOAD(NtCreateFile); NTDLL_LOAD(NtWriteFile); NTDLL_LOAD(NtClose); NTDLL_LOAD(NtDeleteFile); if (!pfn_NtCreateFile || !pfn_NtWriteFile || !pfn_NtClose || !pfn_NtDeleteFile) { // At least one function pointer is missing. break; } HANDLE hFile = reinterpret_cast(_get_osfhandle(fileno(file))); IO_STATUS_BLOCK iosb; UNICODE_STRING ObjectName = RTL_CONSTANT_STRING(L":Zone.Identifier"); OBJECT_ATTRIBUTES oa; InitializeObjectAttributes(&oa, &ObjectName, 0, hFile, nullptr); HANDLE hAds = nullptr; NTSTATUS status = pfn_NtCreateFile( &hAds, // FileHandle FILE_GENERIC_WRITE, // DesiredAccess &oa, // ObjectAttributes &iosb, // IoStatusBlock nullptr, // AllocationSize FILE_ATTRIBUTE_NORMAL, // FileAttributes FILE_SHARE_READ, // ShareAccess FILE_OVERWRITE_IF, // CreateDisposition FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, // CreateOptions nullptr, // EaBuffer 0); // EaLength if (status < 0) { // Error opening the ADS. // TODO: Convert NTSTATUS to POSIX? /*err = w32err_to_posix(GetLastError()); if (err == 0) { err = EIO; }*/ err = EIO; break; } // Write a zone identifier. // NOTE: Assuming UTF-8 encoding. // FIXME: Chromium has some shenanigans for Windows 10. // Reference: https://github.com/chromium/chromium/blob/55f44515cd0b9e7739b434d1c62f4b7e321cd530/components/services/quarantine/quarantine_win.cc static constexpr char zoneID_hdr[] = "[ZoneTransfer]\r\nZoneID=3\r\nHostUrl="; string s_zoneID; s_zoneID.reserve(sizeof(zoneID_hdr) + _tcslen(url) + 2 + 16); s_zoneID = zoneID_hdr; s_zoneID += T2U8(url); s_zoneID += "\r\n"; status = pfn_NtWriteFile( hAds, // FileHandle nullptr, // Event nullptr, // ApcRoutine nullptr, // ApcContext &iosb, // IoStatusBlock s_zoneID.data(), // Buffer static_cast(s_zoneID.size()), // Length nullptr, // ByteOffset nullptr); // Key pfn_NtClose(hAds); if (status < 0) { // Error writing the stream data. // Delete the stream. pfn_NtDeleteFile(&oa); // TODO: Convert NTSTATUS to Win32? err = -EIO; } } while (0); #endif /* UNICODE */ if (mtime >= 0) { // TODO: 100ns timestamp precision for access time? struct _utimbuf utimbuf; utimbuf.actime = time(nullptr); utimbuf.modtime = mtime; // Flush the file before setting the times to ensure // that MSVCRT doesn't write anything afterwards. ::fflush(file); // Set the mtime. int ret = _futime(fileno(file), &utimbuf); if (ret != 0 && err == 0) { err = errno; if (err == 0) { err = EIO; } } } return -err; } } //namespace RpDownload