rom-properties/src/rp-download/SetFileOriginInfo_win32.cpp
David Korth e5c512ca92 [rp-download] SetFileOriginInfo_win32.cpp: Load NTDLL.DLL function pointers using GetProcAddress().
AppVeyor's MSVC 2015 image doesn't have ntdll.lib for some reason.
2025-06-13 18:14:49 -04:00

254 lines
7.3 KiB
C++

/******************************************************************************
* 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 <sys/utime.h>
// C++ STL classes
using std::string;
using std::tstring;
#ifdef UNICODE
// NTDLL function calls
#include <io.h>
#include <winternl.h>
// 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<PWSTR>(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<pfn_##fn##_t>(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<HANDLE>(_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<DWORD>(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