//--------------------------------------------------------------------------- #pragma hdrstop //--------------------------------------------------------------------------- #ifndef STRICT #define STRICT #endif //--------------------------------------------------------------------------- #define LENOF(x) ( (sizeof((x))) / (sizeof(*(x)))) //--------------------------------------------------------------------------- #ifdef _MSC_VER #include #define snwprintf _snwprintf #endif //--------------------------------------------------------------------------- #include #include #include #pragma warn -inl #include #pragma warn .inl #include #include #include "DragExt.h" //--------------------------------------------------------------------------- #ifdef __BORLANDC__ #undef STDAPI #define STDAPI EXTERN_C __declspec(dllexport) HRESULT STDAPICALLTYPE #endif //--------------------------------------------------------------------------- #define DEBUG(MSG) \ if (GLogOn) \ { \ Debug(MSG); \ } //--------------------------------------------------------------------------- #define DRAG_EXT_REG_KEY L"Software\\Martin Prikryl\\WinSCP 2\\DragExt" #define DRAG_EXT_NAME L"WinSCP Shell Extension" #define THREADING_MODEL L"Apartment" #define CLSID_SIZE 39 //--------------------------------------------------------------------------- class CShellExtClassFactory : public IClassFactory { public: CShellExtClassFactory(); virtual ~CShellExtClassFactory(); // IUnknown members STDMETHODIMP QueryInterface(REFIID, LPVOID FAR*); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IClassFactory members STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID FAR*); STDMETHODIMP LockServer(BOOL); protected: unsigned long FReferenceCounter; }; //--------------------------------------------------------------------------- class CShellExt : public IShellExtInit, ICopyHook { public: CShellExt(); virtual ~CShellExt(); // IUnknown members STDMETHODIMP QueryInterface(REFIID, LPVOID FAR*); STDMETHODIMP_(ULONG) AddRef(); STDMETHODIMP_(ULONG) Release(); // IShellExtInit methods STDMETHODIMP Initialize(LPCITEMIDLIST pIDFolder, LPDATAOBJECT pDataObj, HKEY hKeyID); // ICopyHook method STDMETHODIMP_(UINT) CopyCallback(HWND Hwnd, UINT Func, UINT Flags, LPCWSTR SrcFile, DWORD SrcAttribs, LPCWSTR DestFile, DWORD DestAttribs); protected: unsigned long FReferenceCounter; LPDATAOBJECT FDataObj; HANDLE FMutex; unsigned long FLastTicks; }; //--------------------------------------------------------------------------- unsigned int GRefThisDll = 0; bool GEnabled = false; wchar_t GLogFile[MAX_PATH] = L""; bool GLogOn = false; FILE* GLogHandle = NULL; HANDLE GLogMutex; HINSTANCE GInstance; //--------------------------------------------------------------------------- void Debug(const wchar_t* Message) { if (GLogOn) { unsigned long WaitResult = WaitForSingleObject(GLogMutex, 1000); if (WaitResult != WAIT_TIMEOUT) { try { if (GLogHandle == NULL) { if (wcslen(GLogFile) == 0) { GLogOn = false; } else { GLogHandle = _wfopen(GLogFile, L"at"); if (GLogHandle == NULL) { GLogOn = false; } else { setbuf(GLogHandle, NULL); fwprintf(GLogHandle, L"----------------------------\n"); } } } if (GLogOn) { SYSTEMTIME Time; GetSystemTime(&Time); fwprintf(GLogHandle, L"[%2d/%2d/%4d %2d:%02d:%02d.%03d][%04x] %s\n", Time.wDay, Time.wMonth, Time.wYear, Time.wHour, Time.wMinute, Time.wSecond, Time.wMilliseconds, GetCurrentThreadId(), Message); } } catch(...) { } ReleaseMutex(GLogMutex); } } } //--------------------------------------------------------------------------- void LogVersion(HINSTANCE HInstance) { if (GLogOn) { wchar_t FileName[MAX_PATH]; if (GetModuleFileName(HInstance, FileName, LENOF(FileName)) > 0) { Debug(FileName); unsigned long InfoHandle, Size; Size = GetFileVersionInfoSize(FileName, &InfoHandle); if (Size > 0) { void* Info; Info = new wchar_t[Size]; if (GetFileVersionInfo(FileName, InfoHandle, Size, Info) != 0) { VS_FIXEDFILEINFO* VersionInfo; unsigned int VersionInfoSize; if (VerQueryValue(Info, L"\\", reinterpret_cast(&VersionInfo), &VersionInfoSize) != 0) { wchar_t VersionStr[100]; snwprintf(VersionStr, LENOF(VersionStr), L"LogVersion %d.%d.%d.%d", HIWORD(VersionInfo->dwFileVersionMS), LOWORD(VersionInfo->dwFileVersionMS), HIWORD(VersionInfo->dwFileVersionLS), LOWORD(VersionInfo->dwFileVersionLS)); Debug(VersionStr); } else { Debug(L"LogVersion no fixed version info"); } } else { Debug(L"LogVersion cannot read version info"); } } else { Debug(L"LogVersion no version info"); } } } } //--------------------------------------------------------------------------- extern "C" int APIENTRY DllMain(HINSTANCE HInstance, DWORD Reason, LPVOID /*Reserved*/) { if (Reason == DLL_PROCESS_ATTACH) { GInstance = HInstance; if (GRefThisDll == 0) { GLogMutex = CreateMutex(NULL, false, L"WinSCPDragExtLogMutex"); for (int Root = 0; Root <= 1; Root++) { HKEY Key; if (RegOpenKeyEx(Root == 0 ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, DRAG_EXT_REG_KEY, 0, STANDARD_RIGHTS_READ | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &Key) == ERROR_SUCCESS) { unsigned long Type; unsigned long Value; unsigned long Size; wchar_t Buf[MAX_PATH]; Size = sizeof(Value); if ((RegQueryValueEx(Key, L"Enable", NULL, &Type, reinterpret_cast(&Value), &Size) == ERROR_SUCCESS) && (Type == REG_DWORD)) { GEnabled = (Value != 0); } Size = sizeof(Buf); if ((RegQueryValueEx(Key, L"LogFile", NULL, &Type, reinterpret_cast(&Buf), &Size) == ERROR_SUCCESS) && (Type == REG_SZ)) { wcsncpy(GLogFile, Buf, LENOF(GLogFile)); GLogFile[LENOF(GLogFile) - 1] = L'\0'; GLogOn = true; } RegCloseKey(Key); } } DEBUG(L"DllMain loaded settings"); DEBUG(GEnabled ? L"DllMain enabled" : L"DllMain disabled"); #ifdef UNICODE DEBUG(L"Unicode"); #else DEBUG(L"Ansi"); #endif LogVersion(HInstance); } else { DEBUG(L"DllMain settings already loaded"); } DEBUG(L"DllMain attach leave"); } else if (Reason == DLL_PROCESS_DETACH) { DEBUG(L"DllMain detach enter"); CloseHandle(GLogMutex); } return 1; // ok } //--------------------------------------------------------------------------- STDAPI DllCanUnloadNow(void) { bool CanUnload = (GRefThisDll == 0); DEBUG(CanUnload ? L"DllCanUnloadNow can" : L"DllCanUnloadNow cannot"); return (CanUnload ? S_OK : S_FALSE); } //--------------------------------------------------------------------------- STDAPI DllGetClassObject(REFCLSID Rclsid, REFIID Riid, LPVOID* PpvOut) { DEBUG(L"DllGetClassObject"); *PpvOut = NULL; if (IsEqualIID(Rclsid, CLSID_ShellExtension)) { DEBUG(L"DllGetClassObject is ShellExtension"); CShellExtClassFactory* Pcf = new CShellExtClassFactory; return Pcf->QueryInterface(Riid, PpvOut); } return CLASS_E_CLASSNOTAVAILABLE; } //--------------------------------------------------------------------------- bool RegisterServer(bool AllUsers) { DEBUG(L"RegisterServer enter"); DEBUG(AllUsers ? L"RegisterServer all users" : L"RegisterServer current users"); bool Result = false; HKEY RootKey = AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; HKEY HKey; DWORD Unused; wchar_t ClassID[CLSID_SIZE]; StringFromGUID2(CLSID_ShellExtension, ClassID, LENOF(ClassID)); if ((RegOpenKeyEx(RootKey, L"Software\\Classes", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS) && (RegCreateKeyEx(HKey, L"CLSID", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &HKey, &Unused) == ERROR_SUCCESS)) { DEBUG(L"RegisterServer CLSID created"); if (RegCreateKey(HKey, ClassID, &HKey) == ERROR_SUCCESS) { DEBUG(L"RegisterServer class ID created"); RegSetValueEx(HKey, NULL, 0, REG_SZ, reinterpret_cast(DRAG_EXT_NAME), sizeof(DRAG_EXT_NAME)); if (RegCreateKey(HKey, L"InProcServer32", &HKey) == ERROR_SUCCESS) { DEBUG(L"RegisterServer InProcServer32 created"); wchar_t Filename[MAX_PATH]; GetModuleFileName(GInstance, Filename, LENOF(Filename)); RegSetValueEx(HKey, NULL, 0, REG_SZ, reinterpret_cast(Filename), (wcslen(Filename) + 1) * sizeof(wchar_t)); RegSetValueEx(HKey, L"ThreadingModel", 0, REG_SZ, reinterpret_cast(THREADING_MODEL), sizeof(THREADING_MODEL)); } } RegCloseKey(HKey); if ((RegOpenKeyEx(RootKey, L"Software\\Classes", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS) && (RegCreateKeyEx(HKey, L"directory\\shellex\\CopyHookHandlers\\WinSCPCopyHook", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &HKey, &Unused) == ERROR_SUCCESS)) { DEBUG(L"RegisterServer WinSCPCopyHook created"); RegSetValueEx(HKey, NULL, 0, REG_SZ, reinterpret_cast(ClassID), (wcslen(ClassID) + 1) * sizeof(wchar_t)); RegCloseKey(HKey); if ((RegCreateKeyEx(RootKey, DRAG_EXT_REG_KEY, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &HKey, &Unused) == ERROR_SUCCESS)) { DEBUG(L"RegisterServer drag ext key created"); unsigned long Value = 1; RegSetValueEx(HKey, L"Enable", 0, REG_DWORD, reinterpret_cast(&Value), sizeof(Value)); RegCloseKey(HKey); Result = true; } SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0); } } DEBUG(L"RegisterServer leave"); return Result; } //--------------------------------------------------------------------------- STDAPI DllRegisterServer() { DEBUG(L"DllRegisterServer enter"); HRESULT Result; if (RegisterServer(true) || RegisterServer(false)) { Result = S_OK; } else { Result = SELFREG_E_CLASS; } DEBUG(L"DllRegisterServer leave"); return Result; } //--------------------------------------------------------------------------- bool UnregisterServer(bool AllUsers) { DEBUG(L"UnregisterServer enter"); DEBUG(AllUsers ? L"UnregisterServer all users" : L"UnregisterServer current users"); bool Result = false; wchar_t ClassID[CLSID_SIZE]; StringFromGUID2(CLSID_ShellExtension, ClassID, LENOF(ClassID)); HKEY RootKey = AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER; HKEY HKey; if ((RegOpenKeyEx(RootKey, L"Software\\Classes", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS) && (RegOpenKeyEx(HKey, L"directory\\shellex\\CopyHookHandlers", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS)) { RegDeleteKey(HKey, L"WinSCPCopyHook"); RegCloseKey(HKey); } if ((RegOpenKeyEx(RootKey, L"Software\\Classes", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS) && (RegOpenKeyEx(HKey, L"CLSID", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS)) { if (RegOpenKeyEx(HKey, ClassID, 0, KEY_WRITE, &HKey) == ERROR_SUCCESS) { RegDeleteKey(HKey, L"InProcServer32"); RegCloseKey(HKey); if ((RegOpenKeyEx(RootKey, L"Software\\Classes", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS) && (RegOpenKeyEx(HKey, L"CLSID", 0, KEY_WRITE, &HKey) == ERROR_SUCCESS)) { RegDeleteKey(HKey, ClassID); RegCloseKey(HKey); Result = true; } } } if (RegOpenKeyEx(RootKey, DRAG_EXT_REG_KEY, 0, KEY_WRITE, &HKey) == ERROR_SUCCESS) { // Previously we were setting the value explicitly to 0, // but doing that for both HKLM and HKCU. // While on register, we set it to 1 for HKLM only, // what makes the extension disabled effectivelly, // as KHCU value 0, is kept even after re-registration RegDeleteValue(HKey, L"Enable"); RegCloseKey(HKey); Result = true; } SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, 0, 0); DEBUG(L"UnregisterServer leave"); return Result; } //--------------------------------------------------------------------------- STDAPI DllUnregisterServer() { DEBUG(L"DllUnregisterServer enter"); HRESULT Result = SELFREG_E_CLASS; if (UnregisterServer(true)) { Result = S_OK; } if (UnregisterServer(false)) { Result = S_OK; } DEBUG(L"DllUnregisterServer leave"); return Result; } //--------------------------------------------------------------------------- CShellExtClassFactory::CShellExtClassFactory() { DEBUG(L"CShellExtClassFactory"); FReferenceCounter = 0; GRefThisDll++; } //--------------------------------------------------------------------------- CShellExtClassFactory::~CShellExtClassFactory() { DEBUG(L"~CShellExtClassFactory"); GRefThisDll--; } //--------------------------------------------------------------------------- STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID Riid, LPVOID FAR* Ppv) { DEBUG(L"QueryInterface"); *Ppv = NULL; // Any interface on this object is the object pointer if (IsEqualIID(Riid, IID_IUnknown) || IsEqualIID(Riid, IID_IClassFactory)) { DEBUG(L"QueryInterface is IUnknown or IClassFactory"); *Ppv = (LPCLASSFACTORY)this; AddRef(); return NOERROR; } return E_NOINTERFACE; } //--------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef() { DEBUG(L"AddRef"); return ++FReferenceCounter; } //--------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CShellExtClassFactory::Release() { DEBUG(L"Release"); if (--FReferenceCounter) { return FReferenceCounter; } delete this; return 0; } //--------------------------------------------------------------------------- STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN UnkOuter, REFIID Riid, LPVOID* PpvObj) { DEBUG(L"CreateInstance"); *PpvObj = NULL; // Shell extensions typically don't support aggregation (inheritance) if (UnkOuter) { return CLASS_E_NOAGGREGATION; } // Create the main shell extension object. The shell will then call // QueryInterface with IID_IShellExtInit--this is how shell extensions are // initialized. CShellExt* ShellExt = new CShellExt(); //Create the CShellExt object return ShellExt->QueryInterface(Riid, PpvObj); } //--------------------------------------------------------------------------- STDMETHODIMP CShellExtClassFactory::LockServer(BOOL /*Lock*/) { DEBUG(L"LockServer"); return NOERROR; } //--------------------------------------------------------------------------- // CShellExt CShellExt::CShellExt() { DEBUG(L"CShellExt enter"); FReferenceCounter = 0L; FDataObj = NULL; FMutex = CreateMutex(NULL, false, DRAG_EXT_MUTEX); FLastTicks = 0; GRefThisDll++; DEBUG(L"CShellExt leave"); } //--------------------------------------------------------------------------- CShellExt::~CShellExt() { DEBUG(L"~CShellExt enter"); if (FDataObj) { FDataObj->Release(); } CloseHandle(FMutex); GRefThisDll--; DEBUG(L"~CShellExt leave"); } //--------------------------------------------------------------------------- STDMETHODIMP CShellExt::QueryInterface(REFIID Riid, LPVOID FAR* Ppv) { DEBUG(L"CShellExt::QueryInterface enter"); HRESULT Result = E_NOINTERFACE; *Ppv = NULL; if (!GEnabled) { DEBUG(L"CShellExt::QueryInterface shellext disabled"); } else { if (IsEqualIID(Riid, IID_IShellExtInit) || IsEqualIID(Riid, IID_IUnknown)) { DEBUG(L"CShellExt::QueryInterface is IShellExtInit or IUnknown"); *Ppv = (LPSHELLEXTINIT)this; } else if (IsEqualIID(Riid, IID_IShellCopyHook)) { DEBUG(L"CShellExt::QueryInterface is IShellCopyHook"); *Ppv = (LPCOPYHOOK)this; } if (*Ppv) { AddRef(); Result = NOERROR; } } DEBUG(L"CShellExt::QueryInterface leave"); return Result; } //--------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CShellExt::AddRef() { DEBUG(L"CShellExt::AddRef"); return ++FReferenceCounter; } //--------------------------------------------------------------------------- STDMETHODIMP_(ULONG) CShellExt::Release() { DEBUG(L"CShellExt::Release"); if (--FReferenceCounter) { return FReferenceCounter; } delete this; return 0; } //--------------------------------------------------------------------------- STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST /*IDFolder*/, LPDATAOBJECT DataObj, HKEY /*RegKey*/) { DEBUG(L"CShellExt::Initialize enter"); if (FDataObj != NULL) { FDataObj->Release(); FDataObj = NULL; } // duplicate the object pointer and registry handle if (DataObj != NULL) { FDataObj = DataObj; DataObj->AddRef(); } DEBUG(L"CShellExt::Initialize leave"); return NOERROR; } //--------------------------------------------------------------------------- STDMETHODIMP_(UINT) CShellExt::CopyCallback(HWND /*Hwnd*/, UINT Func, UINT /*Flags*/, LPCWSTR SrcFile, DWORD /*SrcAttribs*/, LPCWSTR DestFile, DWORD /*DestAttribs*/) { DEBUG(L"CShellExt::CopyCallback enter"); UINT Result = IDYES; if (GEnabled && ((Func == FO_COPY) || (Func == FO_MOVE))) { DEBUG(L"CShellExt::CopyCallback copy or move"); unsigned long Ticks = GetTickCount(); if (((Ticks - FLastTicks) >= 100) || (FLastTicks > Ticks)) { DEBUG(L"CShellExt::CopyCallback interval elapsed"); DEBUG(L"CShellExt::CopyCallback source / dest:"); DEBUG(SrcFile); DEBUG(DestFile); FLastTicks = Ticks; const wchar_t* BackPtr = wcsrchr(SrcFile, L'\\'); if ((BackPtr != NULL) && (wcsncmp(BackPtr + 1, DRAG_EXT_DUMMY_DIR_PREFIX, DRAG_EXT_DUMMY_DIR_PREFIX_LEN) == 0)) { DEBUG(L"CShellExt::CopyCallback filename has prefix"); HANDLE MapFile = OpenFileMapping(FILE_MAP_ALL_ACCESS, false, DRAG_EXT_MAPPING); if (MapFile != NULL) { DEBUG(L"CShellExt::CopyCallback mapfile found"); TDragExtCommStruct* CommStruct; CommStruct = static_cast(MapViewOfFile(MapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0)); if (CommStruct != NULL) { DEBUG(L"CShellExt::CopyCallback mapview created"); unsigned long WaitResult = WaitForSingleObject(FMutex, 1000); if (WaitResult != WAIT_TIMEOUT) { DEBUG(L"CShellExt::CopyCallback mutex got"); if (CommStruct->Version >= TDragExtCommStruct::MinVersion) { DEBUG(L"CShellExt::CopyCallback supported structure version"); if (CommStruct->Dragging) { DEBUG(L"CShellExt::CopyCallback dragging"); DEBUG(CommStruct->DropDest); bool IsDropDest; if (_wcsicmp(CommStruct->DropDest, SrcFile) == 0) { IsDropDest = true; DEBUG(L"CShellExt::CopyCallback dragged file match as is"); } else { wchar_t DropDestShort[MAX_PATH]; wchar_t SrcFileShort[MAX_PATH]; size_t DropDestSize = GetShortPathName(CommStruct->DropDest, DropDestShort, LENOF(DropDestShort)); size_t SrcFileSize = GetShortPathName(SrcFile, SrcFileShort, LENOF(SrcFileShort)); if ((DropDestSize == 0) || (SrcFileSize == 0)) { DEBUG(L"CShellExt::CopyCallback cannot convert paths to short form"); IsDropDest = false; } else if ((DropDestSize >= LENOF(DropDestShort)) || (SrcFileSize >= LENOF(SrcFileShort))) { DEBUG(L"CShellExt::CopyCallback short paths too long"); IsDropDest = false; } else { DEBUG(DropDestShort); DEBUG(SrcFileShort); if (_wcsicmp(DropDestShort, SrcFileShort) == 0) { DEBUG(L"CShellExt::CopyCallback dragged file match after converted to short form"); IsDropDest = true; } else { DEBUG(L"CShellExt::CopyCallback dragged file does NOT match"); IsDropDest = false; } } } if (IsDropDest) { CommStruct->Dragging = false; wcsncpy(CommStruct->DropDest, DestFile, LENOF(CommStruct->DropDest)); CommStruct->DropDest[LENOF(CommStruct->DropDest)-1] = L'\0'; Result = IDNO; DEBUG(L"CShellExt::CopyCallback dragging refused"); } } else { DEBUG(L"CShellExt::CopyCallback NOT dragging"); } } else { DEBUG(L"CShellExt::CopyCallback unsupported structure version"); } ReleaseMutex(FMutex); DEBUG(L"CShellExt::CopyCallback mutex released"); } else { DEBUG(L"CShellExt::CopyCallback mutex timeout"); } UnmapViewOfFile(CommStruct); } else { DEBUG(L"CShellExt::CopyCallback mapview NOT created"); } CloseHandle(MapFile); } else { DEBUG(L"CShellExt::CopyCallback mapfile NOT found"); } } else { DEBUG(L"CShellExt::CopyCallback filename has NOT prefix"); } } else { DEBUG(L"CShellExt::CopyCallback interval NOT elapsed"); } } else { if (!GEnabled) { DEBUG(L"CShellExt::CopyCallback Disabled"); } else { wchar_t Buf[1024]; wcscpy(Buf, L"CShellExt::CopyCallback NOT copy nor move - X"); // This is to avoid using swprintf, what's in a lib, // we do not link yet, in 64-bit build (maybe this changes, once // we switch to BCB build) Buf[wcslen(Buf) - 1] = L'0' + Func; DEBUG(Buf); } } DEBUG(L"CShellExt::CopyCallback leave"); return Result; }