UltraVNC: C++ source code analysis remote file transfer interface displays remote file system process
The UltraVNC remote file transfer client is mainly processed in FileTransfer.cpp, and the server is mainly processed in vncclient.cpp.
In addition, here is a brief mention of the file attribute structure under Windows:
The first 44 bytes of WIN32_FIND_DATA represent file attributes.
The FILETIME structure holds a 64-bit unsigned file date and time value. This value represents the time in units of 100 nanoseconds since January 1, 1601.
typedef struct _WIN32_FIND_DATA {
DWORD dwFileAttributes; //文件类型,4个字节
FILETIME ftCreationTime; // 文件创建时间,8个字节
FILETIME ftLastAccessTime; // 文件最后一次访问时间,8个字节
FILETIME ftLastWriteTime; // 文件最后一次修改时间,8个字节
DWORD nFileSizeHigh; // 文件长度高32位,指向内存地址的指针,一般是0,4个字节
DWORD nFileSizeLow; // 文件长度低32位,内存大小,4个字节
DWORD dwReserved0; // 系统保留,4个字节
DWORD dwReserved1; // 系统保留,4个字节
TCHAR cFileName[ MAX_PATH ]; // 长文件名
TCHAR cAlternateFileName[ 14 ]; // 8.3格式文件名
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;
The first part of the client file transfer interface displays remote disk information
Client requests remote disk information
//
// request the list of remote drives
//
void FileTransfer::RequestRemoteDrives()
{
// vnclog.Print(0, _T("RequestRemoteDrives\n"));
if (!m_fFTAllowed) return;
// TODO : hook error !
rfbFileTransferMsg ft;
ft.type = rfbFileTransfer;
ft.contentType = rfbDirContentRequest;
ft.contentParam = rfbRDrivesList; // List of Remote Drives please
ft.length = 0;
m_pCC->WriteExact((char *)&ft, sz_rfbFileTransferMsg, rfbFileTransfer);
return;
}
The server receives the request and then obtains the system disk information.
The interface for obtaining disk information is dwLen = GetLogicalDriveStrings(256, szDrivesList);
// The client requests the content of a directory or Drives List
case rfbDirContentRequest:
switch (msg.ft.contentParam)
{
// Client requests the List of Local Drives
case rfbRDrivesList:
{
...
}
break;
// Client requests the content of a directory
case rfbRDirContent:
{
...
}
break;
}
break;
The server sends system disk information to the client
rfbFileTransferMsg ft;
ft.type = rfbFileTransfer;
ft.contentType = rfbDirPacket;
ft.contentParam = rfbADrivesList;
ft.length = Swap32IfLE((int)dwLen);
//adzm 2010-09 - minimize packets. SendExact flushes the queue.
m_socket->SendExactQueue((char *)&ft, sz_rfbFileTransferMsg, rfbFileTransfer);
m_socket->SendExact((char *)szDrivesList, (int)dwLen);
The client receives disk information and processes ListRemoteDrives(hWnd, Swap32IfLE(ft.length));
case rfbADrivesList:
ListRemoteDrives(hWnd, Swap32IfLE(ft.length));
m_fFileCommandPending = false;
break;
The second part of the client file transfer interface displays specific information on a disk on the remote end.
The client requests the remote end to send specific folder information under a certain disk
//
// Request the contents of a remote directory
//
void FileTransfer::RequestRemoteDirectoryContent(HWND hWnd, LPSTR szPath)
{
// vnclog.Print(0, _T("RequestRemoteDirectoryContent\n"));
if (!m_fFTAllowed)
{
m_fFileCommandPending = false;
return;
}
char ofDir[MAX_PATH];
char ofDirT[MAX_PATH];
int nSelected = -1;
int nCount = 0;
HWND hWndRemoteList = GetDlgItem(hWnd, IDC_REMOTE_FILELIST);
ofDir[0] = '\0';
ofDirT[0] = '\0';
if (lstrlen(szPath) == 0)
{
nCount = ListView_GetItemCount(hWndRemoteList);
for (nSelected = 0; nSelected < nCount; nSelected++)
{
if(ListView_GetItemState(hWndRemoteList, nSelected, LVIS_SELECTED) & LVIS_SELECTED)
{
LVITEM Item;
Item.mask = LVIF_TEXT;
Item.iItem = nSelected;
Item.iSubItem = 0;
Item.pszText = ofDirT;
Item.cchTextMax = MAX_PATH;
ListView_GetItem(hWndRemoteList, &Item);
break;
}
}
}
else
{
if (!IsShortcutFolder(szPath))
szPath[6] = '\0';
// szPath always contains a drive letter (X:) or (..)
strcpy(ofDirT, szPath);
// In the case of (..) we keep the current path intact
char szUpDirMask[16];
sprintf(szUpDirMask, "%s..%s", rfbDirPrefix, rfbDirSuffix);
if (strcmp(ofDirT, szUpDirMask))
SetDlgItemText(hWnd, IDC_CURR_REMOTE, "");
}
if (nSelected == nCount || lstrlen(ofDirT) == 0)
{
GetDlgItemText(hWnd, IDC_CURR_REMOTE, ofDirT, sizeof(ofDirT));
if (strlen(ofDirT) == 0)
{
m_fFileCommandPending = false;
return;
}
}
else
{
if (ofDirT[0] == rfbDirPrefix[0] && ofDirT[1] == rfbDirPrefix[1])
{
strncpy(ofDir, ofDirT + 2, strlen(ofDirT) - 3);
ofDir[strlen(ofDirT) - 4] = '\0';
}
else
{
m_fFileCommandPending = false;
return;
}
GetDlgItemText(hWnd, IDC_CURR_REMOTE, ofDirT, sizeof(ofDirT));
if (!_stricmp(ofDir, ".."))
{
char* p;
ofDirT[strlen(ofDirT) - 1] = '\0';
p = strrchr(ofDirT, '\\');
if (p == NULL)
{
m_fFileCommandPending = false;
return;
}
*p = '\0';
}
else
strcat(ofDirT, ofDir);
strcat(ofDirT, "\\");
SetDlgItemText(hWnd, IDC_CURR_REMOTE, ofDirT);
}
strcpy(ofDir, ofDirT);
// Todo: In case of shortcuts dir, do a translation here !
// Select the good drive in the drives combo box (the first time only)
int nIndex = SendDlgItemMessage(hWnd, IDC_REMOTE_DRIVECB, CB_GETCURSEL, 0, 0L);
if (nIndex == LB_ERR)
{
char szDrive[5];
strcpy(szDrive, rfbDirPrefix);
strncat(szDrive, ofDir, 2);
nIndex = SendDlgItemMessage(hWnd, IDC_REMOTE_DRIVECB, CB_FINDSTRING, -1, (LPARAM)(LPSTR)szDrive);
SendDlgItemMessage(hWnd, IDC_REMOTE_DRIVECB, CB_SETCURSEL, nIndex, 0L);
}
ListView_DeleteAllItems(hWndRemoteList);
rfbFileTransferMsg ft;
ft.type = rfbFileTransfer;
ft.contentType = rfbDirContentRequest;
ft.contentParam = rfbRDirContent; // Directory content please
ft.length = Swap32IfLE(strlen(ofDir));
//adzm 2010-09
m_pCC->WriteExactQueue((char *)&ft, sz_rfbFileTransferMsg, rfbFileTransfer);
m_pCC->WriteExact((char *)ofDir, strlen(ofDir));
return;
}
The server receives the request and then obtains the folder information
// The client requests the content of a directory or Drives List
case rfbDirContentRequest:
switch (msg.ft.contentParam)
{
// Client requests the List of Local Drives
case rfbRDrivesList:
{
...
}
break;
// Client requests the content of a directory
case rfbRDirContent:
{
...
}
break;
}
break;
Main methods to obtain folder contents
WIN32_FIND_DATA fd;
HANDLE ff;
BOOL fRet = TRUE;
ff = FindFirstFile(szDir, &fd);
while ( fRet )
{
...
/*这里依次把文件夹下每个文件或者目录信息发送到客户端*/
....
fRet = FindNextFile(ff, &fd);
}
FindClose(ff);
Finally, the server sends the end flag
// End of the transfer
ft.contentParam = 0;
ft.length = Swap32IfLE(0);
m_socket->SendExact((char *)&ft, sz_rfbFileTransferMsg, rfbFileTransfer);
The client receives the server folder information. There are two functions
PopulateRemoteListBox(hWnd, Swap32IfLE(ft.length)); update the current directory displayed on the remote side of the file transfer interface
ReceiveDirectoryItem(hWnd, Swap32IfLE(ft.length)); update the file The remote end of the transmission interface displays specific directory information
case rfbDirPacket:
switch (ft.contentParam)
{
// Response to a rfbRDrivesList request
case rfbADrivesList:
ListRemoteDrives(hWnd, Swap32IfLE(ft.length));
m_fFileCommandPending = false;
break;
// Response to a rfbRDirContent request
case rfbADirectory:
case rfbAFile:
if (!m_fDirectoryReceptionRunning)
PopulateRemoteListBox(hWnd, Swap32IfLE(ft.length));
else
ReceiveDirectoryItem(hWnd, Swap32IfLE(ft.length));
break;
default: // This is bad. Add rfbADirectoryEnd instead...
if (m_fDirectoryReceptionRunning)
{
FinishDirectoryReception();
m_fFileCommandPending = false;
}
break;
}
break;