// ---------------------------------------------------------------------------80
// ICQ plugin for Miranda Instant Messenger
// ________________________________________
//
// Copyright © 2000-2001 Richard Hughes, Roland Rabien, Tristan Van de Vreede
// Copyright © 2001-2002 Jon Keating, Richard Hughes
// Copyright © 2002-2004 Martin Öberg, Sam Kothari, Robert Rainwater
// Copyright © 2004-2010 Joe Kucera
// Copyright © 2012-2018 Miranda NG team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// -----------------------------------------------------------------------------
// DESCRIPTION:
//
// OSCAR File-Transfers implementation
// -----------------------------------------------------------------------------
#include "stdafx.h"
struct oscarthreadstartinfo
{
int type;
int incoming;
MCONTACT hContact;
HNETLIBCONN hConnection;
DWORD dwRemoteIP;
oscar_filetransfer *ft;
oscar_listener *listener;
};
// small utility function
extern void NormalizeBackslash(char* path);
//
// Common functions
/////////////////////////////
char* FindFilePathContainer(const char **files, int iFile, char *szContainer)
{
const char *szThisFile = files[iFile];
char *szFileName = (char*)ExtractFileName(szThisFile);
szContainer[0] = '\0';
if (szThisFile != szFileName) { // find an earlier subdirectory to be used as a container
for (int i = iFile - 1; i >= 0; i--) {
size_t len = mir_strlen(files[i]);
if (!_strnicmp(files[i], szThisFile, len) && (szThisFile[len] == '\\' || szThisFile[len] == '/')) {
const char *pszLastBackslash;
if (((pszLastBackslash = strrchr(files[i], '\\')) == nullptr) &&
((pszLastBackslash = strrchr(files[i], '/')) == nullptr)) {
mir_strcpy(szContainer, files[i]);
}
else {
len = pszLastBackslash - files[i] + 1;
null_strcpy(szContainer, szThisFile + len, szFileName - szThisFile - len);
}
}
}
}
return szFileName;
}
//
// Utility functions
/////////////////////////////
oscar_filetransfer* CIcqProto::CreateOscarTransfer()
{
oscar_filetransfer *ft = (oscar_filetransfer*)SAFE_MALLOC(sizeof(oscar_filetransfer));
ft->ft_magic = FT_MAGIC_OSCAR; // Setup signature
// Init members
ft->fileId = -1;
{
mir_cslock l(oftMutex);
m_arFileTransfers.insert(ft);
}
NetLog_Direct("OFT: FT struct %p created", ft);
return ft;
}
filetransfer* CIcqProto::CreateIcqFileTransfer()
{
filetransfer *ft = (filetransfer*)SAFE_MALLOC(sizeof(filetransfer));
ft->ft_magic = FT_MAGIC_ICQ;
{
mir_cslock l(oftMutex);
m_arFileTransfers.insert(ft);
}
NetLog_Direct("FT struct %p created", ft);
return ft;
}
int CIcqProto::IsValidFileTransfer(basic_filetransfer *ft)
{
mir_cslock l(oftMutex);
return m_arFileTransfers.indexOf(ft) != -1;
}
int CIcqProto::IsValidOscarTransfer(basic_filetransfer *ft)
{
mir_cslock l(oftMutex);
return m_arFileTransfers.indexOf(ft) != -1 && ft->ft_magic == FT_MAGIC_OSCAR;
}
oscar_filetransfer* CIcqProto::FindOscarTransfer(MCONTACT hContact, DWORD dwID1, DWORD dwID2)
{
mir_cslock l(oftMutex);
for (auto &it : m_arFileTransfers) {
if (it->ft_magic == FT_MAGIC_OSCAR) {
oscar_filetransfer *oft = (oscar_filetransfer*)it;
if (oft->hContact == hContact && oft->pMessage.dwMsgID1 == dwID1 && oft->pMessage.dwMsgID2 == dwID2)
return oft;
}
}
return nullptr;
}
// Release file transfer structure
void CIcqProto::SafeReleaseFileTransfer(basic_filetransfer **bft)
{
mir_cslock l(oftMutex);
// Check for filetransfer validity
int idx = m_arFileTransfers.indexOf(*bft);
if (idx == -1)
return;
if (*bft) {
if ((*bft)->ft_magic == FT_MAGIC_ICQ) { // release ICQ filetransfer structure and its contents
filetransfer *ift = (filetransfer*)(*bft);
SAFE_FREE(&ift->szFilename);
SAFE_FREE(&ift->szDescription);
SAFE_FREE(&ift->szSavePath);
SAFE_FREE(&ift->szThisSubdir);
if (ift->pszFiles) {
for (int i = 0; i < (int)ift->dwFileCount; i++) {
// szThisFile can be a duplicate of pszFiles[i]
if (ift->szThisFile == ift->pszFiles[i])
ift->szThisFile = nullptr;
SAFE_FREE(&ift->pszFiles[i]);
}
SAFE_FREE((void**)&ift->pszFiles);
}
SAFE_FREE(&ift->szThisFile);
// Invalidate transfer
m_arFileTransfers.remove(idx);
NetLog_Direct("FT struct %p released", bft);
// Release memory
SAFE_FREE((void**)bft);
}
else if ((*bft)->ft_magic == FT_MAGIC_OSCAR) { // release oscar filetransfer structure and its contents
oscar_filetransfer *oft = (oscar_filetransfer*)(*bft);
// If connected, close connection
if (oft->connection)
CloseOscarConnection(oft->connection);
// Release oscar listener
if (oft->listener)
ReleaseOscarListener((oscar_listener**)&oft->listener);
// Release cookie
if (oft->dwCookie)
FreeCookie(oft->dwCookie);
// Release all dynamic members
SAFE_FREE(&oft->rawFileName);
SAFE_FREE(&oft->szSavePath);
SAFE_FREE(&oft->szThisFile);
SAFE_FREE(&oft->szThisPath);
SAFE_FREE(&oft->szDescription);
if (oft->files) {
for (int i = 0; i < oft->wFilesCount; i++)
SAFE_FREE(&oft->files[i].szFile);
SAFE_FREE((void**)&oft->files);
}
if (oft->files_list)
SAFE_FREE((void**)&oft->files_list);
if (oft->file_containers) {
for (int i = 0; i < oft->containerCount; i++)
SAFE_FREE(&oft->file_containers[i]);
SAFE_FREE((void**)&oft->file_containers);
}
if (oft->fileId != -1)
_close(oft->fileId);
// Invalidate transfer
m_arFileTransfers.remove(idx);
NetLog_Direct("OFT: FT struct %p released", bft);
// Release memory
SAFE_FREE((void**)bft);
}
}
}
// Calculate oft checksum of buffer
// --------------------------------
// Information was gathered from Gaim's sources, thanks
//
DWORD oft_calc_checksum(int offset, const BYTE *buffer, int len, DWORD dwChecksum)
{
DWORD checksum = (dwChecksum >> 16) & 0xffff;
for (int i = 0; i < len; i++) {
WORD val = buffer[i];
if (((i + offset) & 1) == 0)
val = val << 8;
if (checksum < val)
checksum -= val + 1;
else // simulate carry
checksum -= val;
}
checksum = ((checksum & 0x0000ffff) + (checksum >> 16));
checksum = ((checksum & 0x0000ffff) + (checksum >> 16));
return checksum << 16;
}
DWORD oft_calc_file_checksum(int hFile, __int64 maxSize)
{
BYTE buf[OFT_BUFFER_SIZE];
int bytesRead;
__int64 offset = 0;
DWORD dwCheck = 0xFFFF0000;
_lseek(hFile, 0, SEEK_SET);
bytesRead = _read(hFile, buf, (maxSize < sizeof(buf)) ? maxSize : (unsigned)sizeof(buf));
if (bytesRead == -1)
return dwCheck;
while (bytesRead) {
dwCheck = oft_calc_checksum((int)offset, buf, bytesRead, dwCheck);
offset += bytesRead;
bytesRead = _read(hFile, buf, sizeof(buf));
if (bytesRead + offset > maxSize) bytesRead = (int)(maxSize - offset);
}
_lseek(hFile, 0, SEEK_SET); // back to beginning
return dwCheck;
}
oscar_listener* CIcqProto::CreateOscarListener(oscar_filetransfer *ft, NETLIBNEWCONNECTIONPROC_V2 handler)
{
oscar_listener *listener = (oscar_listener*)SAFE_MALLOC(sizeof(oscar_listener));
if (listener) {
listener->ppro = this;
listener->ft = ft;
if (listener->hBoundPort = NetLib_BindPort(handler, listener, &listener->wPort, nullptr))
return listener; // Success
SAFE_FREE((void**)&listener);
}
return nullptr; // Failure
}
void CIcqProto::ReleaseOscarListener(oscar_listener **pListener)
{
oscar_listener *listener = *pListener;
if (listener) { // Close listening port
if (listener->hBoundPort) {
Netlib_CloseHandle(listener->hBoundPort);
listener->hBoundPort = nullptr;
}
NetLog_Direct("Oscar listener on port %d released.", listener->wPort);
}
SAFE_FREE((void**)pListener);
}
//
// Miranda FT interface handlers & services
/////////////////////////////
void CIcqProto::handleRecvServMsgOFT(BYTE *buf, size_t wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand)
{
MCONTACT hContact = HContactFromUID(dwUin, szUID, nullptr);
if (wCommand == 0) { // this is OFT request
oscar_tlv_chain* chain = readIntoTLVChain(&buf, wLen, 0);
if (chain) {
WORD wAckType = chain->getWord(0x0A, 1);
if (wAckType == 1) { // This is first request in this OFT
oscar_filetransfer *ft = CreateOscarTransfer();
char *pszFileName = nullptr;
char *pszDescription = nullptr;
debugLogA("This is a file request");
// This TLV chain may contain the following TLVs:
// TLV(A): Acktype 0x0001 - file request / abort request
// 0x0002 - file ack
// TLV(F): Unknown
// TLV(E): Language ?
// TLV(2): Proxy IP
// TLV(16): Proxy IP Check
// TLV(3): External IP
// TLV(4): Internal IP
// TLV(5): Port
// TLV(17): Port Check
// TLV(10): Proxy Flag
// TLV(D): Charset of User Message
// TLV(C): User Message (ICQ_COOL_FT)
// TLV(2711): FT info
// TLV(2712): Charset of file name
// init filetransfer structure
ft->pMessage.dwMsgID1 = dwID1;
ft->pMessage.dwMsgID2 = dwID2;
ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0;
ft->dwProxyIP = chain->getDWord(0x02, 1);
ft->dwRemoteInternalIP = chain->getDWord(0x03, 1);
ft->dwRemoteExternalIP = chain->getDWord(0x04, 1);
ft->wRemotePort = chain->getWord(0x05, 1);
ft->wReqNum = wAckType;
{ // User Message
oscar_tlv* tlv = chain->getTLV(0x0C, 1);
if (tlv) { // parse User Message
BYTE* tBuf = tlv->pData;
pszDescription = (char*)_alloca(tlv->wLen + 2);
unpackString(&tBuf, (char*)pszDescription, tlv->wLen);
pszDescription[tlv->wLen] = '\0';
pszDescription[tlv->wLen + 1] = '\0';
{ // apply User Message encoding
oscar_tlv *charset = chain->getTLV(0x0D, 1);
char *str = pszDescription;
char *bTag, *eTag;
if (charset) { // decode charset
char *szEnc = (char*)_alloca(charset->wLen + 1);
null_strcpy(szEnc, (char*)charset->pData, charset->wLen);
str = ApplyEncoding((char*)pszDescription, szEnc);
}
else str = null_strdup(str);
// eliminate HTML tags
pszDescription = EliminateHtml(str, mir_strlen(str));
bTag = strstrnull(pszDescription, "");
if (bTag) { // take special Description - ICQJ's extension
eTag = strstrnull(bTag, "");
if (eTag) {
*eTag = '\0';
str = null_strdup(bTag + 6);
SAFE_FREE(&pszDescription);
pszDescription = str;
}
}
else {
bTag = strstrnull(pszDescription, "");
if (bTag) { // take only - Description tag if present
eTag = strstrnull(bTag, "");
if (eTag) {
*eTag = '\0';
str = null_strdup(bTag + 4);
SAFE_FREE(&pszDescription);
pszDescription = str;
}
}
}
}
}
if (!mir_strlen(pszDescription)) {
SAFE_FREE(&pszDescription);
pszDescription = ICQTranslateUtf(LPGEN("No description given"));
}
}
{ // parse File Transfer Info block
oscar_tlv* tlv = chain->getTLV(0x2711, 1);
// sanity check
if (!tlv || tlv->wLen < 8) {
debugLogA("Error: Malformed file request");
// release structures
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
SAFE_FREE(&pszDescription);
return;
}
BYTE *tBuf = tlv->pData;
size_t tLen = tlv->wLen;
WORD wFlag;
unpackWord(&tBuf, &wFlag); // FT flag
unpackWord(&tBuf, &ft->wFilesCount);
unpackDWord(&tBuf, (DWORD*)&ft->qwTotalSize);
tLen -= 8;
// Filename / Directory Name
if (tLen) { // some filename specified, unpack
size_t wFilenameLength = tLen - 1;
pszFileName = (char*)_alloca(tLen);
unpackString(&tBuf, (char*)pszFileName, wFilenameLength);
pszFileName[wFilenameLength] = '\0';
}
else if (ft->wFilesCount == 1) // give some generic file name
pszFileName = "unnamed_file";
else // or empty directory name
pszFileName = "";
// apply Filename / Directory Name encoding
oscar_tlv* charset = chain->getTLV(0x2712, 1);
if (charset) {
char* szEnc = (char*)_alloca(charset->wLen + 1);
null_strcpy(szEnc, (char*)charset->pData, charset->wLen);
pszFileName = ApplyEncoding(pszFileName, szEnc);
}
else pszFileName = ansi_to_utf8(pszFileName);
if (ft->wFilesCount == 1) { // Filename - use for DB event
char *szFileName = (char*)_alloca(mir_strlen(pszFileName) + 1);
mir_strcpy(szFileName, pszFileName);
SAFE_FREE(&pszFileName);
pszFileName = szFileName;
}
else { // Save Directory name for future use
ft->szThisPath = pszFileName;
// for multi-file transfer we do not display "folder" name, but create only a simple notice
pszFileName = (char*)_alloca(64);
char tmp[64];
mir_snprintf(pszFileName, 64, ICQTranslateUtfStatic(LPGEN("%d Files"), tmp, _countof(tmp)), ft->wFilesCount);
}
}
// Total Size TLV (ICQ 6 and AIM 6)
{
oscar_tlv *tlv = chain->getTLV(0x2713, 1);
if (tlv && tlv->wLen >= 8) {
BYTE *tBuf = tlv->pData;
unpackQWord(&tBuf, &ft->qwTotalSize);
}
}
int bAdded;
ft->hContact = HContactFromUID(dwUin, szUID, &bAdded);
ft->fileId = -1;
// Send chain event
char *szBlob = (char*)_alloca(sizeof(DWORD) + mir_strlen(pszFileName) + mir_strlen(pszDescription) + 2);
*(PDWORD)szBlob = 0;
mir_strcpy(szBlob + sizeof(DWORD), pszFileName);
mir_strcpy(szBlob + sizeof(DWORD) + mir_strlen(pszFileName) + 1, pszDescription);
wchar_t* ptszFileName = mir_utf8decodeW(pszFileName);
PROTORECVFILE pre = { 0 };
pre.dwFlags = PRFF_UNICODE;
pre.fileCount = 1;
pre.timestamp = time(nullptr);
pre.descr.w = mir_utf8decodeW(pszDescription);
pre.files.w = &ptszFileName;
pre.lParam = (LPARAM)ft;
ProtoChainRecvFile(ft->hContact, &pre);
mir_free(pre.descr.w);
mir_free(ptszFileName);
}
else if (wAckType == 2) { // First attempt failed, reverse requested
oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2);
if (ft) {
NetLog_Direct("OFT: Redirect received (%d)", wAckType);
ft->wReqNum = wAckType;
if (ft->flags & OFTF_SENDING) {
ReleaseOscarListener((oscar_listener**)&ft->listener);
ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0;
ft->dwProxyIP = chain->getDWord(0x02, 1);
ft->dwRemoteInternalIP = chain->getDWord(0x03, 1);
ft->dwRemoteExternalIP = chain->getDWord(0x04, 1);
ft->wRemotePort = chain->getWord(0x05, 1);
OpenOscarConnection(hContact, ft, ft->bUseProxy ? OCT_PROXY_RECV : OCT_REVERSE);
}
else { // Just sanity
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
}
}
else debugLogA("Error: Invalid request, no such transfer");
}
else if (wAckType == 3) { // Transfering thru proxy, join tunnel
oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2);
if (ft) { // release possible previous listener
NetLog_Direct("OFT: Redirect received (%d)", wAckType);
ft->wReqNum = wAckType;
ReleaseOscarListener((oscar_listener**)&ft->listener);
ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0;
ft->dwProxyIP = chain->getDWord(0x02, 1);
ft->wRemotePort = chain->getWord(0x05, 1);
if (ft->bUseProxy && ft->dwProxyIP) // Init proxy connection
OpenOscarConnection(hContact, ft, OCT_PROXY_RECV);
else // try Stage 4
OpenOscarConnection(hContact, ft, OCT_PROXY);
}
else debugLogA("Error: Invalid request, no such transfer");
}
else if (wAckType == 4) {
oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2);
if (ft) {
NetLog_Direct("OFT: Redirect received (%d)", wAckType);
ft->wReqNum = wAckType;
ft->bUseProxy = chain->getTLV(0x10, 1) ? 1 : 0;
ft->dwProxyIP = chain->getDWord(0x02, 1);
ft->wRemotePort = chain->getWord(0x05, 1);
if (ft->bUseProxy && ft->dwProxyIP) // Init proxy connection
OpenOscarConnection(hContact, ft, OCT_PROXY_RECV);
else
debugLogA("Error: Invalid request, IP missing.");
}
else debugLogA("Error: Invalid request, no such transfer");
}
else debugLogA("Error: Uknown Stage %d request", wAckType);
disposeChain(&chain);
}
else debugLogA("Error: Missing TLV chain in OFT request");
}
else if (wCommand == 1) { // transfer cancelled/aborted
oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2);
if (ft) {
debugLogA("OFT: File transfer cancelled by %s", strUID(dwUin, szUID));
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0);
// Notify user, that the FT was cancelled // TODO: new ACKRESULT_?
icq_LogMessage(LOG_ERROR, LPGEN("The file transfer was aborted by the other user."));
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
}
else debugLogA("Error: Invalid request, no such transfer");
}
else if (wCommand == 2) { // transfer accepted - connection established
oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2);
if (ft) {
NetLog_Direct("OFT: Session established.");
// Init connection
if (ft->flags & OFTF_SENDING) {
if (ft->connection && ft->connection->status == OCS_CONNECTED) {
if (!(ft->flags & OFTF_FILE_REQUEST_SENT)) {
ft->flags |= OFTF_FILE_REQUEST_SENT;
// proceed with first file
oft_sendPeerInit(ft->connection);
}
}
ft->flags |= OFTF_INITIALIZED; // accept was received
}
else debugLogA("Warning: Received invalid rendezvous accept");
}
else debugLogA("Error: Invalid request, no such transfer");
}
else debugLogA("Error: Unknown wCommand=0x%x in OFT request", wCommand);
}
void CIcqProto::handleRecvServResponseOFT(BYTE *buf, size_t wLen, DWORD dwUin, char *szUID, void* ft)
{
if (wLen < 2)
return;
size_t wDataLen;
unpackWord(&buf, &wDataLen);
if (wDataLen == 2) {
oscar_filetransfer *oft = (oscar_filetransfer*)ft;
WORD wStatus;
unpackWord(&buf, &wStatus);
switch (wStatus) {
case 1:
// FT denied (icq5)
debugLogA("OFT: File transfer denied by %s", strUID(dwUin, szUID));
ProtoBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_DENIED, (HANDLE)oft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oft);
break;
case 4: // Proxy error
icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Proxy error"));
ProtoBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oft);
break;
case 5: // Invalid request
icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Invalid request"));
ProtoBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oft);
break;
case 6: // Proxy Failed (IP = 0)
icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Proxy unavailable"));
ProtoBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oft);
break;
default:
debugLogA("OFT: Uknown request response code 0x%x", wStatus);
ProtoBroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oft);
}
}
}
// This function is called from the Netlib when someone is connecting to our oscar_listener
static void oft_newConnectionReceived(HNETLIBCONN hNewConnection, DWORD dwRemoteIP, void *pExtra)
{
oscarthreadstartinfo *otsi = (oscarthreadstartinfo*)SAFE_MALLOC(sizeof(oscarthreadstartinfo));
oscar_listener *listener = (oscar_listener*)pExtra;
otsi->type = listener->ft->flags & OFTF_SENDING ? OCT_NORMAL : OCT_REVERSE;
otsi->incoming = 1;
otsi->hConnection = hNewConnection;
otsi->dwRemoteIP = dwRemoteIP;
otsi->listener = listener;
// Start a new thread for the incomming connection
listener->ppro->ForkThread((CIcqProto::MyThreadFunc)&CIcqProto::oft_connectionThread, otsi);
}
static char* oftGetFileContainer(oscar_filetransfer* oft, const char** files, int iFile)
{
char szPath[MAX_PATH];
FindFilePathContainer(files, iFile, szPath);
char *szPathUtf = ansi_to_utf8(szPath);
int i;
// try to find existing container
for (i = 0; i < oft->containerCount; i++)
if (!mir_strcmp(szPathUtf, oft->file_containers[i])) {
SAFE_FREE((void**)&szPathUtf);
return oft->file_containers[i];
}
// create new container
i = oft->containerCount++;
oft->file_containers = (char**)SAFE_REALLOC(oft->file_containers, (sizeof(char*) * oft->containerCount));
oft->file_containers[i] = szPathUtf;
return oft->file_containers[i];
}
HANDLE CIcqProto::oftInitTransfer(MCONTACT hContact, DWORD dwUin, char* szUid, const wchar_t** files, const wchar_t* pszDesc)
{
int i, filesCount;
struct _stati64 statbuf;
// Initialize filetransfer struct
debugLogA("Init file send");
oscar_filetransfer *ft = CreateOscarTransfer();
ft->hContact = hContact;
ft->pMessage.bMessageType = MTYPE_FILEREQ;
InitMessageCookie(&ft->pMessage);
for (filesCount = 0; files[filesCount]; filesCount++);
ft->files = (oft_file_record *)SAFE_MALLOC(sizeof(oft_file_record) * filesCount);
ft->files_list = (char**)SAFE_MALLOC(sizeof(wchar_t *) * filesCount);
ft->qwTotalSize = 0;
char **filesUtf = (char**)SAFE_MALLOC(sizeof(char *) * filesCount);
for (i = 0; i < filesCount; i++) filesUtf[i] = FileNameToUtf(files[i]);
// Prepare files arrays
for (i = 0; i < filesCount; i++) {
if (_wstat64(files[i], &statbuf))
debugLogA("IcqSendFile() was passed invalid filename \"%s\"", files[i]);
else {
if (!(statbuf.st_mode & _S_IFDIR)) { // take only files
ft->files[ft->wFilesCount].szFile = ft->files_list[ft->wFilesCount] = null_strdup(filesUtf[i]);
ft->files[ft->wFilesCount].szContainer = oftGetFileContainer(ft, (LPCSTR*)filesUtf, i);
ft->wFilesCount++;
ft->qwTotalSize += statbuf.st_size;
}
}
}
for (i = 0; i < filesCount; i++)
SAFE_FREE(&filesUtf[i]);
SAFE_FREE((void**)&filesUtf);
if (!ft->wFilesCount) { // found no valid files to send
icq_LogMessage(LOG_ERROR, LPGEN("Failed to Initialize File Transfer. No valid files were specified."));
// Notify UI
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
return nullptr; // Failure
}
#ifdef __GNUC__
#define OSCAR_MAX_SIZE 0x100000000ULL
#else
#define OSCAR_MAX_SIZE 0x100000000
#endif
if (ft->qwTotalSize >= OSCAR_MAX_SIZE && ft->wFilesCount > 1) { // file larger than 4GB can be send only as single
icq_LogMessage(LOG_ERROR, LPGEN("The files are too big to be sent at once. Files bigger than 4 GB can be sent only separately."));
// Notify UI
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
return nullptr; // Failure
}
debugLogA("OFT: Found %d files.", ft->wFilesCount);
ft->szDescription = make_utf8_string(pszDesc);
ft->flags = OFTF_SENDING;
ft->fileId = -1;
ft->iCurrentFile = 0;
ft->dwCookie = AllocateCookie(CKT_FILE, ICQ_MSG_SRV_SEND, hContact, ft);
// Init oscar fields
{
ft->wEncrypt = 0;
ft->wCompress = 0;
ft->wPartsCount = 1;
ft->wPartsLeft = 1;
mir_strcpy(ft->rawIDString, "Cool FileXfer");
ft->bHeaderFlags = 0x20;
ft->bNameOff = 0x1C;
ft->bSizeOff = 0x11;
ft->dwRecvForkCheck = 0xFFFF0000;
ft->dwThisForkCheck = 0xFFFF0000;
ft->dwRecvFileCheck = 0xFFFF0000;
}
// Send file transfer request
{
char *pszFiles;
if (ft->wFilesCount == 1) { // transfering single file, give filename
pszFiles = (char*)ExtractFileName(ft->files[0].szFile);
}
else { // check if transfering one directory
char *szFirstDiv, *szFirstDir = ft->file_containers[0];
size_t nFirstDirLen;
// default is no root dir
pszFiles = "";
if ((szFirstDiv = strstrnull(szFirstDir, "\\")) || (szFirstDiv = strstrnull(szFirstDir, "/")))
nFirstDirLen = szFirstDiv - szFirstDir;
else
nFirstDirLen = mir_strlen(szFirstDir);
if (nFirstDirLen) { // got root dir from first container, check if others are only sub-dirs
for (i = 0; i < ft->containerCount; i++) {
if (_strnicmp((char*)ft->file_containers[i], (char*)szFirstDir, nFirstDirLen)) {
szFirstDir = nullptr;
break;
}
}
if (szFirstDir) { // fine, we are sending only one directory
pszFiles = szFirstDir;
if (szFirstDiv) szFirstDiv[0] = '\0';
nFirstDirLen++; // include backslash
// cut all files container by root dir - it is transferred as root separately
for (i = 0; i < ft->wFilesCount; i++)
ft->files[i].szContainer += nFirstDirLen;
}
}
}
// Create listener
ft->listener = CreateOscarListener(ft, oft_newConnectionReceived);
// Send packet
if (ft->listener) {
oft_sendFileRequest(dwUin, szUid, ft, pszFiles, getDword("RealIP", 0));
}
else { // try stage 1 proxy
ft->szThisFile = null_strdup(pszFiles);
OpenOscarConnection(hContact, ft, OCT_PROXY_INIT);
}
}
return ft; // Success
}
HANDLE CIcqProto::oftFileAllow(MCONTACT hContact, HANDLE hTransfer, const wchar_t *szPath)
{
oscar_filetransfer *ft = (oscar_filetransfer*)hTransfer;
DWORD dwUin;
uid_str szUid;
if (getContactUid(hContact, &dwUin, &szUid))
return nullptr; // Invalid contact
if (!IsValidOscarTransfer(ft))
return nullptr; // Invalid transfer
ft->szSavePath = make_utf8_string(szPath);
if (ft->szThisPath) { // Append Directory name to the save path, when transfering a directory
ft->szSavePath = (char*)SAFE_REALLOC(ft->szSavePath, mir_strlen(ft->szSavePath) + mir_strlen(ft->szThisPath) + 4);
NormalizeBackslash(ft->szSavePath);
mir_strcat(ft->szSavePath, ft->szThisPath);
NormalizeBackslash(ft->szSavePath);
}
NetLog_Direct("OFT: Request accepted, saving to '%s'.", ft->szSavePath);
// Create cookie
ft->dwCookie = AllocateCookie(CKT_FILE, ICQ_MSG_SRV_SEND, hContact, ft);
OpenOscarConnection(hContact, ft, ft->bUseProxy ? OCT_PROXY_RECV : OCT_NORMAL);
return hTransfer; // Success
}
DWORD CIcqProto::oftFileDeny(MCONTACT hContact, HANDLE hTransfer, const wchar_t*)
{
oscar_filetransfer *ft = (oscar_filetransfer*)hTransfer;
DWORD dwUin;
uid_str szUid;
if (getContactUid(hContact, &dwUin, &szUid))
return 1; // Invalid contact
if (!IsValidOscarTransfer(ft))
return 1; // Invalid transfer
if (ft->hContact != hContact)
return 1; // Bad contact or hTransfer
NetLog_Direct("OFT: Request denied.");
oft_sendFileDeny(dwUin, szUid, ft);
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
return 0; // Success
}
DWORD CIcqProto::oftFileCancel(MCONTACT hContact, HANDLE hTransfer)
{
oscar_filetransfer* ft = (oscar_filetransfer*)hTransfer;
DWORD dwUin;
uid_str szUid;
if (getContactUid(hContact, &dwUin, &szUid))
return 1; // Invalid contact
if (!IsValidOscarTransfer(ft))
return 1; // Invalid transfer
if (ft->hContact != hContact)
return 1; // Bad contact or hTransfer
NetLog_Direct("OFT: Transfer cancelled.");
oft_sendFileCancel(dwUin, szUid, ft);
ProtoBroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
return 0; // Success
}
void CIcqProto::oftFileResume(oscar_filetransfer *ft, int action, const wchar_t *szFilename)
{
int openFlags;
if (ft->connection == nullptr)
return;
oscar_connection *oc = ft->connection;
NetLog_Direct("OFT: Resume Transfer, Action: %d, FileName: '%s'", action, szFilename);
switch (action) {
case FILERESUME_RESUME:
openFlags = _O_BINARY | _O_RDWR;
break;
case FILERESUME_OVERWRITE:
openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY;
ft->qwFileBytesDone = 0;
break;
case FILERESUME_SKIP:
openFlags = _O_BINARY | _O_WRONLY;
ft->qwFileBytesDone = ft->qwThisFileSize;
break;
case FILERESUME_RENAME:
openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY;
SAFE_FREE(&ft->szThisFile);
ft->szThisFile = make_utf8_string(szFilename);
ft->qwFileBytesDone = 0;
break;
default: // workaround for bug in Miranda Core
if (ft->resumeAction == FILERESUME_RESUME)
openFlags = _O_BINARY | _O_RDWR;
else { // default to overwrite
openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY;
ft->qwFileBytesDone = 0;
}
}
ft->resumeAction = action;
ft->fileId = OpenFileUtf(ft->szThisFile, openFlags, _S_IREAD | _S_IWRITE);
if (ft->fileId == -1) {
NetLog_Direct("OFT: errno=%d", errno);
icq_LogMessage(LOG_ERROR, LPGEN("Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder."));
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oc->ft);
return;
}
if (action == FILERESUME_RESUME)
ft->qwFileBytesDone = _lseeki64(ft->fileId, 0, SEEK_END);
else
_lseeki64(ft->fileId, ft->qwFileBytesDone, SEEK_SET);
ft->qwBytesDone += ft->qwFileBytesDone;
if (action == FILERESUME_RESUME) { // use smart-resume
oc->status = OCS_RESUME;
ft->dwRecvFileCheck = oft_calc_file_checksum(ft->fileId, ft->qwFileBytesDone);
_lseek(ft->fileId, 0, SEEK_END);
NetLog_Direct("OFT: Starting Smart-Resume");
sendOFT2FramePacket(oc, OFT_TYPE_RESUMEREQUEST);
return;
}
if (action == FILERESUME_SKIP) // we are skipping the file, send "we are done"
oc->status = OCS_NEGOTIATION;
else { // Send "we are ready"
oc->status = OCS_DATA;
ft->flags |= OFTF_FILE_RECEIVING;
sendOFT2FramePacket(oc, OFT_TYPE_READY);
}
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0);
if (!ft->qwThisFileSize || action == FILERESUME_SKIP) { // if the file is empty we will not receive any data
BYTE buf;
oft_handleFileData(oc, &buf, 0);
}
}
static void oft_buildProtoFileTransferStatus(oscar_filetransfer* ft, PROTOFILETRANSFERSTATUS* pfts)
{
memset(pfts, 0, sizeof(PROTOFILETRANSFERSTATUS));
pfts->hContact = ft->hContact;
pfts->flags = PFTS_UTF + ((ft->flags & OFTF_SENDING) ? PFTS_SENDING : PFTS_RECEIVING);
if (ft->flags & OFTF_SENDING)
pfts->pszFiles.a = ft->files_list;
else
pfts->pszFiles.a = nullptr; /* FIXME */
pfts->totalFiles = ft->wFilesCount;
pfts->currentFileNumber = ft->iCurrentFile;
pfts->totalBytes = ft->qwTotalSize;
pfts->totalProgress = ft->qwBytesDone;
pfts->szWorkingDir.a = ft->szThisPath;
pfts->szCurrentFile.a = ft->szThisFile;
pfts->currentFileSize = ft->qwThisFileSize;
pfts->currentFileTime = ft->dwThisFileDate;
pfts->currentFileProgress = ft->qwFileBytesDone;
}
void CIcqProto::CloseOscarConnection(oscar_connection *oc)
{
mir_cslock l(oftMutex);
if (oc) {
oc->type = OCT_CLOSING;
if (oc->hConnection) // we need this for Netlib handle consistency
NetLib_CloseConnection(&oc->hConnection, FALSE);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
void CIcqProto::OpenOscarConnection(MCONTACT hContact, oscar_filetransfer *ft, int type)
{
oscarthreadstartinfo *otsi = (oscarthreadstartinfo*)SAFE_MALLOC(sizeof(oscarthreadstartinfo));
otsi->hContact = hContact;
otsi->type = type;
otsi->ft = ft;
ForkThread((MyThreadFunc)&CIcqProto::oft_connectionThread, otsi);
}
int CIcqProto::CreateOscarProxyConnection(oscar_connection *oc)
{
// inform UI
ProtoBroadcastAck(oc->ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTPROXY, oc->ft, 0);
NETLIBOPENCONNECTION nloc = { 0 };
nloc.szHost = OSCAR_PROXY_HOST;
nloc.wPort = getWord("OscarPort", m_bSecureConnection ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT);
if (nloc.wPort == 0)
nloc.wPort = RandRange(1024, 65535);
if (m_bGatewayMode)
nloc.flags |= NLOCF_HTTPGATEWAY;
oc->hConnection = NetLib_OpenConnection(m_hNetlibUser, "Proxy ", &nloc);
if (!oc->hConnection) // proxy connection failed
return 0;
oc->type = OCT_PROXY;
oc->status = OCS_PROXY;
oc->ft->connection = oc;
// init proxy
proxy_sendInitTunnel(oc);
return 1; // Success
}
void __cdecl CIcqProto::oft_connectionThread(oscarthreadstartinfo *otsi)
{
oscar_connection oc = { 0 };
oscar_listener *source;
Thread_SetName("ICQ: oft_connectionThread");
oc.hContact = otsi->hContact;
oc.hConnection = otsi->hConnection;
oc.type = otsi->type;
oc.incoming = otsi->incoming;
oc.ft = otsi->ft;
source = otsi->listener;
if (oc.incoming) {
if (IsValidOscarTransfer(source->ft)) {
oc.ft = source->ft;
oc.ft->dwRemoteExternalIP = otsi->dwRemoteIP;
oc.hContact = oc.ft->hContact;
oc.ft->connection = &oc;
oc.status = OCS_CONNECTED;
}
else { // FT is already over, kill listener
NetLog_Direct("Received unexpected connection, closing.");
CloseOscarConnection(&oc);
ReleaseOscarListener(&source);
SAFE_FREE((void**)&otsi);
return;
}
}
SAFE_FREE((void**)&otsi);
if (oc.hContact) // Load contact information
getContactUid(oc.hContact, &oc.dwUin, &oc.szUid);
// Load local IP information
oc.dwLocalExternalIP = getDword("IP", 0);
oc.dwLocalInternalIP = getDword("RealIP", 0);
if (!oc.incoming) { // create outgoing connection
if (oc.type == OCT_NORMAL || oc.type == OCT_REVERSE) { // create outgoing connection to peer
NETLIBOPENCONNECTION nloc = { 0 };
IN_ADDR addr = { 0 }, addr2 = { 0 };
if (oc.ft->dwRemoteExternalIP == oc.dwLocalExternalIP && oc.ft->dwRemoteInternalIP)
addr.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP);
else if (oc.ft->dwRemoteExternalIP) {
addr.S_un.S_addr = htonl(oc.ft->dwRemoteExternalIP);
// for different internal, try it also (for LANs with multiple external IP, VPNs, etc.)
if (oc.ft->dwRemoteInternalIP != oc.ft->dwRemoteExternalIP)
addr2.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP);
}
else // try LAN
addr.S_un.S_addr = htonl(oc.ft->dwRemoteInternalIP);
// Inform UI that we will attempt to connect
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, oc.ft, 0);
if (!addr.S_un.S_addr && oc.type == OCT_NORMAL) { // IP to connect to is empty, request reverse
oscar_listener* listener = CreateOscarListener(oc.ft, oft_newConnectionReceived);
if (listener) { // we got listening port, fine send request
oc.ft->listener = listener;
// notify UI
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_LISTENING, oc.ft, 0);
oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, oc.dwLocalInternalIP, listener->wPort, FALSE);
return;
}
if (!CreateOscarProxyConnection(&oc)) { // normal connection failed, notify peer, wait for error or stage 3 proxy
oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE);
// stage 3 can follow
return;
}
}
else if (addr.S_un.S_addr && oc.ft->wRemotePort) {
nloc.szHost = inet_ntoa(addr);
nloc.wPort = oc.ft->wRemotePort;
nloc.timeout = 8; // 8 secs to connect
oc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, oc.type == OCT_REVERSE ? "Reverse " : nullptr, &nloc);
if (!oc.hConnection && addr2.S_un.S_addr) { // first address failed, try second one if available
nloc.szHost = inet_ntoa(addr2);
oc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, oc.type == OCT_REVERSE ? "Reverse " : nullptr, &nloc);
}
if (!oc.hConnection) {
if (oc.type == OCT_NORMAL) { // connection failed, try reverse
oscar_listener* listener = CreateOscarListener(oc.ft, oft_newConnectionReceived);
if (listener) { // we got listening port, fine send request
oc.ft->listener = listener;
// notify UI that we await connection
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_LISTENING, oc.ft, 0);
oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, oc.dwLocalInternalIP, listener->wPort, FALSE);
return;
}
}
if (!CreateOscarProxyConnection(&oc)) { // proxy connection failed, notify peer, wait for error or stage 4 proxy
oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE);
// stage 3 or stage 4 can follow
return;
}
}
else {
oc.status = OCS_CONNECTED;
// ack normal connection
oc.ft->connection = &oc;
// acknowledge OFT - connection is ready
oft_sendFileAccept(oc.dwUin, oc.szUid, oc.ft);
// signal UI
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, oc.ft, 0);
}
}
else { // try proxy, stage 3 (sending)
if (!CreateOscarProxyConnection(&oc)) { // proxy connection failed, notify peer, wait for error or stage 4 proxy
oft_sendFileRedirect(oc.dwUin, oc.szUid, oc.ft, 0, 0, FALSE);
// stage 4 can follow
return;
}
}
}
else if (oc.type == OCT_PROXY_RECV) { // stage 2 & stage 4
if (oc.ft->dwProxyIP && oc.ft->wRemotePort) { // create proxy connection, join tunnel
NETLIBOPENCONNECTION nloc = { 0 };
IN_ADDR addr = { 0 };
// inform UI that we will connect to file proxy
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTPROXY, oc.ft, 0);
addr.S_un.S_addr = htonl(oc.ft->dwProxyIP);
nloc.szHost = inet_ntoa(addr);
nloc.wPort = getWord("OscarPort", m_bSecureConnection ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT);
if (nloc.wPort == 0)
nloc.wPort = RandRange(1024, 65535);
if (m_bGatewayMode)
nloc.flags |= NLOCF_HTTPGATEWAY;
oc.hConnection = NetLib_OpenConnection(m_hNetlibUser, "Proxy ", &nloc);
if (!oc.hConnection) { // proxy connection failed, we are out of possibilities
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
// notify the other side, that we failed
oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x04);
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&oc.ft);
return;
}
oc.status = OCS_PROXY;
oc.ft->connection = &oc;
// Join proxy tunnel
proxy_sendJoinTunnel(&oc, oc.ft->wRemotePort);
}
else // stage 2 failed (empty IP)
{ // try stage 3, or send response error 0x06
if (!CreateOscarProxyConnection(&oc)) {
oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x06);
// notify UI
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&oc.ft);
return;
}
}
}
else if (oc.type == OCT_PROXY) { // stage 4
if (!CreateOscarProxyConnection(&oc)) { // proxy connection failed, we are out of possibilities
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
// notify the other side, that we failed
oft_sendFileResponse(oc.dwUin, oc.szUid, oc.ft, 0x06);
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&oc.ft);
return;
}
}
else if (oc.type == OCT_PROXY_INIT) { // stage 1
if (!CreateOscarProxyConnection(&oc)) { // We failed to init transfer, notify UI
icq_LogMessage(LOG_ERROR, LPGEN("Failed to Initialize File Transfer. Unable to bind local port and File proxy unavailable."));
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oc.ft);
return;
}
else
oc.type = OCT_PROXY_INIT;
}
}
if (!oc.hConnection) { // one more sanity check
NetLog_Direct("Error: No OFT connection.");
return;
}
if (oc.status != OCS_PROXY) { // Connected, notify FT UI
ProtoBroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, oc.ft, 0);
// send init OFT frame - just for different order of packets (just like Trillian)
if (oc.status == OCS_CONNECTED && (oc.ft->flags & OFTF_SENDING) && ((oc.ft->flags & OFTF_INITIALIZED) || oc.type == OCT_REVERSE) && !(oc.ft->flags & OFTF_FILE_REQUEST_SENT)) {
oc.ft->flags |= OFTF_FILE_REQUEST_SENT;
// proceed with first file
oft_sendPeerInit(&oc);
}
}
HANDLE hPacketRecver = Netlib_CreatePacketReceiver(oc.hConnection, 8192);
NETLIBPACKETRECVER packetRecv = {};
// Packet receiving loop
while (oc.hConnection) {
packetRecv.dwTimeout = oc.wantIdleTime ? 0 : 120000;
int recvResult = Netlib_GetMorePackets(hPacketRecver, &packetRecv);
if (!recvResult) {
NetLog_Direct("Clean closure of oscar socket (%p)", oc.hConnection);
break;
}
if (recvResult == SOCKET_ERROR) {
if (GetLastError() == ERROR_TIMEOUT) { // TODO: this will not work on some systems
if (oc.wantIdleTime) { // here we want to send file data packets
oft_sendFileData(&oc);
}
else if (oc.status != OCS_WAITING) {
NetLog_Direct("Connection timeouted, closing.");
break;
}
}
else if (oc.type != OCT_CLOSING || GetLastError() != 87) { // log only significant errors, not "connection killed by us"
NetLog_Direct("Abortive closure of oscar socket (%p) (%d)", oc.hConnection, GetLastError());
break;
}
}
if (oc.type == OCT_CLOSING)
packetRecv.bytesUsed = packetRecv.bytesAvailable;
else
packetRecv.bytesUsed = oft_handlePackets(&oc, packetRecv.buffer, packetRecv.bytesAvailable);
}
// End of packet receiving loop
NetLib_SafeCloseHandle(&hPacketRecver);
CloseOscarConnection(&oc);
// Clean up
{
mir_cslock l(oftMutex);
if (m_arFileTransfers.indexOf(oc.ft) != -1)
oc.ft->connection = nullptr; // release link
}
// Give server some time for abort/cancel to arrive
SleepEx(1000, TRUE);
// Error handling
if (IsValidOscarTransfer(oc.ft)) {
if (oc.status == OCS_DATA) {
ProtoBroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
icq_LogMessage(LOG_ERROR, LPGEN("Connection lost during file transfer."));
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&oc.ft);
}
else if (oc.status == OCS_NEGOTIATION) {
ProtoBroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
icq_LogMessage(LOG_ERROR, LPGEN("File transfer negotiation failed for unknown reason."));
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&oc.ft);
}
}
}
void CIcqProto::sendOscarPacket(oscar_connection *oc, icq_packet *packet)
{
if (oc->hConnection) {
int nResult = Netlib_Send(oc->hConnection, (const char*)packet->pData, packet->wLen, 0);
if (nResult == SOCKET_ERROR) {
NetLog_Direct("Oscar %p socket error: %d, closing", oc->hConnection, GetLastError());
CloseOscarConnection(oc);
}
}
SAFE_FREE((void**)&packet->pData);
}
int CIcqProto::oft_handlePackets(oscar_connection *oc, BYTE *buf, size_t len)
{
size_t bytesUsed = 0;
while (len > 0) {
if (oc->status == OCS_DATA && (oc->ft->flags & OFTF_FILE_RECEIVING))
return oft_handleFileData(oc, buf, len);
if (oc->status == OCS_PROXY)
return oft_handleProxyData(oc, buf, len);
if (len < 6)
break;
BYTE *pBuf = buf;
DWORD dwHead;
unpackDWord(&pBuf, &dwHead);
if (dwHead != 0x4F465432) { // bad packet
NetLog_Direct("OFT: Received invalid packet (dwHead = 0x%x).", dwHead);
CloseOscarConnection(oc);
break;
}
size_t datalen;
unpackWord(&pBuf, &datalen);
if (len < datalen) // wait for whole packet
break;
WORD datatype;
unpackWord(&pBuf, &datatype);
#ifdef _DEBUG
NetLog_Direct("OFT2: Type %u, Length %u bytes", datatype, datalen);
#endif
handleOFT2FramePacket(oc, datatype, pBuf, datalen-8);
/* Increase pointers so we can check for more data */
buf += datalen;
len -= datalen;
bytesUsed += datalen;
}
return (int)bytesUsed;
}
int CIcqProto::oft_handleProxyData(oscar_connection *oc, BYTE *buf, size_t len)
{
oscar_filetransfer *ft = oc->ft;
size_t datalen, bytesUsed = 0;
while (len > 2) {
BYTE *pBuf = buf;
unpackWord(&pBuf, &datalen);
datalen += 2;
if (len < datalen)
break; // packet is not complete
if (datalen < 12) { // malformed packet
CloseOscarConnection(oc);
break;
}
pBuf += 2; // packet version
WORD wCommand;
unpackWord(&pBuf, &wCommand);
pBuf += 6;
// handle packet
switch (wCommand) {
case 0x01: // Error
WORD wError;
char* szError;
unpackWord(&pBuf, &wError);
switch (wError) {
case 0x0D:
szError = "Bad request";
break;
case 0x0E:
szError = "Malformed packet";
break;
case 0x10:
szError = "Initial request timeout";
break;
case 0x1A:
szError = "Accept period timeout";
break;
case 0x1C:
szError = "Invalid data";
break;
default:
szError = "Unknown";
}
// Notify peer
oft_sendFileResponse(oc->dwUin, oc->szUid, oc->ft, 0x06);
debugLogA("Proxy Error: %s (0x%x)", szError, wError);
// Notify UI
ProtoBroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc->ft, 0);
// Release structure
SafeReleaseFileTransfer((basic_filetransfer**)&oc->ft);
break;
case 0x03: // Tunnel created
WORD wCode;
DWORD dwIP;
unpackWord(&pBuf, &wCode);
unpackDWord(&pBuf, &dwIP);
if (oc->type == OCT_PROXY_INIT) { // Proxy ready, send Stage 1 Request
ft->bUseProxy = 1;
ft->wRemotePort = wCode;
ft->dwProxyIP = dwIP;
oft_sendFileRequest(oc->dwUin, oc->szUid, ft, ft->szThisFile, 0);
SAFE_FREE(&ft->szThisFile);
// Notify UI
ProtoBroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, oc->ft, 0);
}
else {
debugLogA("Proxy Tunnel ready, notify peer.");
oft_sendFileRedirect(oc->dwUin, oc->szUid, ft, dwIP, wCode, TRUE);
}
break;
case 0x05: // Connection ready
oc->status = OCS_CONNECTED; // connection ready to send packets
// Notify UI
ProtoBroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTED, oc->ft, 0);
// signal we are ready
if (oc->type == OCT_PROXY_RECV) {
oft_sendFileAccept(oc->dwUin, oc->szUid, ft);
if (ft->flags & OFTF_SENDING) // connection is ready for transfer (sending only)
ft->flags |= OFTF_INITIALIZED;
}
debugLogA("Proxy Tunnel established");
if ((ft->flags & OFTF_INITIALIZED) && (ft->flags & OFTF_SENDING) && !(ft->flags & OFTF_FILE_REQUEST_SENT)) {
ft->flags |= OFTF_FILE_REQUEST_SENT;
// proceed with first file
oft_sendPeerInit(ft->connection);
}
break;
default:
debugLogA("Unknown proxy command 0x%x", wCommand);
}
buf += datalen;
len -= datalen;
bytesUsed += datalen;
}
return (int)bytesUsed;
}
int CIcqProto::oft_handleFileData(oscar_connection *oc, BYTE *buf, size_t len)
{
oscar_filetransfer *ft = oc->ft;
size_t dwLen = len, bytesUsed = 0;
// do not accept more data than expected
if (ft->qwThisFileSize - ft->qwFileBytesDone < dwLen)
dwLen = (int)(ft->qwThisFileSize - ft->qwFileBytesDone);
if (ft->fileId == -1) { // something went terribly bad
CloseOscarConnection(oc);
return 0;
}
_write(ft->fileId, buf, (unsigned)dwLen);
// update checksum
ft->dwRecvFileCheck = oft_calc_checksum((int)ft->qwFileBytesDone, buf, (unsigned)dwLen, ft->dwRecvFileCheck);
bytesUsed += dwLen;
ft->qwBytesDone += dwLen;
ft->qwFileBytesDone += dwLen;
// notify FT UI of our progress, at most every 700ms - do not be faster than Miranda
if (GetTickCount() > ft->dwLastNotify + 700 || ft->qwFileBytesDone == ft->qwThisFileSize) {
PROTOFILETRANSFERSTATUS pfts;
oft_buildProtoFileTransferStatus(ft, &pfts);
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts);
ft->dwLastNotify = GetTickCount();
}
if (ft->qwFileBytesDone == ft->qwThisFileSize) {
/* EOF */
ft->flags &= ~OFTF_FILE_RECEIVING;
_close(ft->fileId);
ft->fileId = -1;
if (ft->resumeAction != FILERESUME_SKIP && ft->dwRecvFileCheck != ft->dwThisFileCheck) {
NetLog_Direct("Error: File checksums does not match!");
// Notify UI
char *pszMsg = ICQTranslateUtf(LPGEN("The checksum of file \"%s\" does not match, the file is probably damaged."));
char szBuf[MAX_PATH];
mir_snprintf(szBuf, pszMsg, ExtractFileName(ft->szThisFile));
icq_LogMessage(LOG_ERROR, szBuf);
SAFE_FREE(&pszMsg);
}
// keep transfer going (icq6 ignores checksums completely)
else if (ft->resumeAction == FILERESUME_SKIP)
NetLog_Direct("OFT: File receive skipped.");
else
NetLog_Direct("OFT: File received successfully.");
if ((DWORD)(ft->iCurrentFile + 1) == ft->wFilesCount) {
ft->bHeaderFlags = 0x01; // the whole process is over
// ack received file
sendOFT2FramePacket(oc, OFT_TYPE_DONE);
oc->type = OCT_CLOSING;
NetLog_Direct("File Transfer completed successfully.");
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&ft);
}
else { // ack received file
sendOFT2FramePacket(oc, OFT_TYPE_DONE);
oc->status = OCS_NEGOTIATION;
}
}
return (int)bytesUsed;
}
void CIcqProto::handleOFT2FramePacket(oscar_connection *oc, WORD datatype, BYTE *pBuffer, size_t wLen)
{
oscar_filetransfer *ft = oc->ft;
DWORD dwID1;
DWORD dwID2;
if (wLen < 232) { // allow shorter packets, but at least with filename
NetLog_Direct("Error: Malformed OFT2 Frame, ignoring.");
return;
}
unpackLEDWord(&pBuffer, &dwID1);
wLen -= 4;
unpackLEDWord(&pBuffer, &dwID2);
wLen -= 4;
if (datatype == OFT_TYPE_REQUEST && !(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) { // first request does not contain MsgIDs we need to send them in ready packet
dwID1 = ft->pMessage.dwMsgID1;
dwID2 = ft->pMessage.dwMsgID2;
}
if (ft->pMessage.dwMsgID1 != dwID1 || ft->pMessage.dwMsgID2 != dwID2) { // this is not the right packet - bad Message IDs
NetLog_Direct("Error: Invalid Packet Cookie, closing.");
CloseOscarConnection(oc);
return;
}
switch (datatype) {
case OFT_TYPE_REQUEST:
{ // Sender ready
if (ft->flags & OFTF_SENDING) { // just sanity check - this is only for receiving client
NetLog_Direct("Error: Invalid Packet, closing.");
CloseOscarConnection(oc);
return;
}
// Read Frame data
if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) {
unpackWord(&pBuffer, &ft->wEncrypt);
unpackWord(&pBuffer, &ft->wCompress);
unpackWord(&pBuffer, &ft->wFilesCount);
}
else
pBuffer += 6;
unpackWord(&pBuffer, &ft->wFilesLeft);
ft->iCurrentFile = ft->wFilesCount - ft->wFilesLeft;
if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED))
unpackWord(&pBuffer, &ft->wPartsCount);
else
pBuffer += 2;
unpackWord(&pBuffer, &ft->wPartsLeft);
if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) { // just check it
DWORD dwSize;
unpackDWord(&pBuffer, &dwSize);
if (dwSize != (DWORD)ft->qwTotalSize) { // the 32bits does not match, use them as full size
ft->qwTotalSize = dwSize;
debugLogA("Warning: Invalid total size.");
}
}
else pBuffer += 4;
// this allows us to receive single >4GB file correctly
DWORD dwSize;
unpackDWord(&pBuffer, &dwSize);
if (dwSize == (DWORD)ft->qwTotalSize && ft->wFilesCount == 1)
ft->qwThisFileSize = ft->qwTotalSize;
else
ft->qwThisFileSize = dwSize;
unpackDWord(&pBuffer, &ft->dwThisFileDate);
unpackDWord(&pBuffer, &ft->dwThisFileCheck);
unpackDWord(&pBuffer, &ft->dwRecvForkCheck);
unpackDWord(&pBuffer, &ft->dwThisForkSize);
unpackDWord(&pBuffer, &ft->dwThisFileCreation);
unpackDWord(&pBuffer, &ft->dwThisForkCheck);
pBuffer += 4; // File Bytes Done
unpackDWord(&pBuffer, &ft->dwRecvFileCheck);
if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED))
unpackString(&pBuffer, ft->rawIDString, 32);
else
pBuffer += 32;
unpackByte(&pBuffer, &ft->bHeaderFlags);
unpackByte(&pBuffer, &ft->bNameOff);
unpackByte(&pBuffer, &ft->bSizeOff);
if (!(ft->flags & OFTF_FILE_REQUEST_RECEIVED)) {
unpackString(&pBuffer, (char*)ft->rawDummy, 69);
unpackString(&pBuffer, (char*)ft->rawMacInfo, 16);
}
else pBuffer += 85;
unpackWord(&pBuffer, &ft->wEncoding);
unpackWord(&pBuffer, &ft->wSubEncoding);
ft->cbRawFileName = wLen - 176;
SAFE_FREE((void**)&ft->rawFileName); // release previous buffers
SAFE_FREE(&ft->szThisFile);
ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName + 2);
unpackString(&pBuffer, ft->rawFileName, ft->cbRawFileName);
// Prepare file
if (ft->wEncoding == 2) // UCS-2 encoding
ft->szThisFile = ApplyEncoding(ft->rawFileName, "unicode-2-0");
else
ft->szThisFile = ansi_to_utf8(ft->rawFileName);
// convert dir markings to normal backslashes
for (size_t i = 0; i < mir_strlen(ft->szThisFile); i++)
if (ft->szThisFile[i] == 0x01)
ft->szThisFile[i] = '\\';
ft->flags |= OFTF_FILE_REQUEST_RECEIVED; // First Frame Processed
NetLog_Direct("File '%s', %I64u Bytes", ft->szThisFile, ft->qwThisFileSize);
{ // Prepare Path Information
char *szFile = strrchr(ft->szThisFile, '\\');
SAFE_FREE(&ft->szThisPath); // release previous path
if (szFile) {
ft->szThisPath = ft->szThisFile;
szFile[0] = '\0'; // split that strings
ft->szThisFile = null_strdup(szFile + 1);
// no cheating with paths
if (!IsValidRelativePath(ft->szThisPath)) {
NetLog_Direct("Invalid path information");
break;
}
}
else ft->szThisPath = null_strdup("");
}
/* no cheating with paths */
if (!IsValidRelativePath(ft->szThisFile)) {
NetLog_Direct("Invalid path information");
break;
}
char *szFullPath = (char*)SAFE_MALLOC(mir_strlen(ft->szSavePath) + mir_strlen(ft->szThisPath) + mir_strlen(ft->szThisFile) + 3);
mir_strcpy(szFullPath, ft->szSavePath);
NormalizeBackslash(szFullPath);
mir_strcat(szFullPath, ft->szThisPath);
NormalizeBackslash(szFullPath);
// make sure the dest dir exists
if (MakeDirUtf(szFullPath))
NetLog_Direct("Failed to create destination directory!");
mir_strcat(szFullPath, ft->szThisFile);
// we joined the full path to dest file
SAFE_FREE(&ft->szThisFile);
ft->szThisFile = szFullPath;
ft->qwFileBytesDone = 0;
/* file resume */
PROTOFILETRANSFERSTATUS pfts;
oft_buildProtoFileTransferStatus(ft, &pfts);
if (ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&pfts)) {
oc->status = OCS_WAITING;
break; /* UI supports resume: it will call PS_FILERESUME */
}
ft->fileId = OpenFileUtf(ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE);
#ifdef _DEBUG
NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, ft->fileId);
#endif
if (ft->fileId == -1) {
#ifdef _DEBUG
NetLog_Direct("OFT: errno=%d", errno);
#endif
icq_LogMessage(LOG_ERROR, LPGEN("Your file receive has been aborted because Miranda could not open the destination file in order to write to it. You may be trying to save to a read-only folder."));
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oc->ft);
return;
}
// Send "we are ready"
oc->status = OCS_DATA;
ft->flags |= OFTF_FILE_RECEIVING;
sendOFT2FramePacket(oc, OFT_TYPE_READY);
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0);
if (!ft->qwThisFileSize) { // if the file is empty we will not receive any data
BYTE buf;
oft_handleFileData(oc, &buf, 0);
}
return;
}
case OFT_TYPE_READY:
case OFT_TYPE_RESUMEACK:
// Receiver is ready
oc->status = OCS_DATA;
oc->wantIdleTime = 1;
ft->flags |= OFTF_FILE_SENDING;
NetLog_Direct("OFT: Receiver ready.");
break;
case OFT_TYPE_RESUMEREQUEST:
// Receiver wants to resume file transfer from point
if (!(ft->flags & OFTF_SENDING)) { // just sanity check - this is only for sending client
NetLog_Direct("Error: Invalid Packet, closing.");
CloseOscarConnection(oc);
return;
}
{
DWORD dwResumeCheck, dwResumeOffset, dwFileCheck;
// Read Resume Frame data
pBuffer += 44;
unpackDWord(&pBuffer, &dwResumeOffset);
unpackDWord(&pBuffer, &dwResumeCheck);
dwFileCheck = oft_calc_file_checksum(ft->fileId, dwResumeOffset);
if (dwFileCheck == dwResumeCheck && dwResumeOffset <= ft->qwThisFileSize) { // resume seems ok
ft->qwFileBytesDone = dwResumeOffset;
ft->qwBytesDone += dwResumeOffset;
_lseek(ft->fileId, dwResumeOffset, SEEK_SET);
NetLog_Direct("OFT: Resume request, ready.");
}
else
NetLog_Direct("OFT: Resume request, restarting.");
// Ready for resume
sendOFT2FramePacket(oc, OFT_TYPE_RESUMEREADY);
}
break;
case OFT_TYPE_RESUMEREADY:
// Process Smart-resume reply
if (ft->flags & OFTF_SENDING) { // just sanity check - this is only for receiving client
NetLog_Direct("Error: Invalid Packet, closing.");
CloseOscarConnection(oc);
return;
}
{
DWORD dwResumeOffset, dwResumeCheck;
// Read Resume Reply data
pBuffer += 44;
unpackDWord(&pBuffer, &dwResumeOffset);
unpackDWord(&pBuffer, &dwResumeCheck);
if (ft->qwFileBytesDone != dwResumeOffset) {
ft->qwBytesDone -= (ft->qwFileBytesDone - dwResumeOffset);
ft->qwFileBytesDone = dwResumeOffset;
if (dwResumeOffset)
ft->dwRecvFileCheck = dwResumeCheck;
else // Restarted resume (data mismatch)
ft->dwRecvFileCheck = 0xFFFF0000;
}
_lseek(ft->fileId, dwResumeOffset, SEEK_SET);
if (ft->qwThisFileSize != ft->qwFileBytesDone)
NetLog_Direct("OFT: Resuming from offset %u.", dwResumeOffset);
// Prepare to receive data
oc->status = OCS_DATA;
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0);
// Ready for receive
sendOFT2FramePacket(oc, OFT_TYPE_RESUMEACK);
if (ft->qwThisFileSize == ft->qwFileBytesDone) { // all data already processed
BYTE buf;
oft_handleFileData(oc, &buf, 0);
}
}
break;
case OFT_TYPE_DONE:
// File done
oc->status = OCS_NEGOTIATION;
oc->wantIdleTime = 0;
ft->flags &= ~OFTF_FILE_SENDING;
NetLog_Direct("OFT: File sent successfully.");
#ifdef _DEBUG
NetLog_Direct("OFT: _close(%u)", ft->fileId);
#endif
_close(ft->fileId); // FIXME: this needs fix for "skip file" feature
ft->fileId = -1;
ft->iCurrentFile++;
// continue with next file
oft_sendPeerInit(oc);
break;
default:
NetLog_Direct("Error: Uknown OFT frame type 0x%x", datatype);
}
}
//
// Proxy packets
/////////////////////////////
void CIcqProto::proxy_sendInitTunnel(oscar_connection *oc)
{
icq_packet packet;
size_t wLen = 39 + getUINLen(m_dwLocalUIN);
packet.wLen = WORD(wLen);
init_generic_packet(&packet, 2);
packWord(&packet, WORD(wLen));
packWord(&packet, OSCAR_PROXY_VERSION);
packWord(&packet, 0x02); // wCommand
packDWord(&packet, 0); // Unknown
packWord(&packet, 0); // Flags?
packUIN(&packet, m_dwLocalUIN);
packLEDWord(&packet, oc->ft->pMessage.dwMsgID1);
packLEDWord(&packet, oc->ft->pMessage.dwMsgID2);
packDWord(&packet, 0x00010010); // TLV(1)
packGUID(&packet, MCAP_FILE_TRANSFER);
sendOscarPacket(oc, &packet);
}
void CIcqProto::proxy_sendJoinTunnel(oscar_connection *oc, WORD wPort)
{
icq_packet packet;
size_t wLen = 41 + getUINLen(m_dwLocalUIN);
packet.wLen = WORD(wLen);
init_generic_packet(&packet, 2);
packWord(&packet, WORD(wLen));
packWord(&packet, OSCAR_PROXY_VERSION);
packWord(&packet, 0x04); // wCommand
packDWord(&packet, 0); // Unknown
packWord(&packet, 0); // Flags?
packUIN(&packet, m_dwLocalUIN);
packWord(&packet, wPort);
packLEDWord(&packet, oc->ft->pMessage.dwMsgID1);
packLEDWord(&packet, oc->ft->pMessage.dwMsgID2);
packDWord(&packet, 0x00010010); // TLV(1)
packGUID(&packet, MCAP_FILE_TRANSFER);
sendOscarPacket(oc, &packet);
}
//
// Direct packets
/////////////////////////////
void CIcqProto::oft_sendFileData(oscar_connection *oc)
{
oscar_filetransfer *ft = oc->ft;
BYTE buf[OFT_BUFFER_SIZE];
if (ft->fileId == -1)
return;
int bytesRead = _read(ft->fileId, buf, sizeof(buf));
if (bytesRead == -1)
return;
if (!bytesRead) { //
oc->wantIdleTime = 0;
return;
}
if ((DWORD)bytesRead > (ft->qwThisFileSize - ft->qwFileBytesDone)) { // do not send more than expected, limit to known size
bytesRead = (DWORD)(ft->qwThisFileSize - ft->qwFileBytesDone);
oc->wantIdleTime = 0;
}
if (bytesRead) {
icq_packet packet;
packet.wLen = bytesRead;
init_generic_packet(&packet, 0);
packBuffer(&packet, buf, bytesRead); // we are sending raw data
sendOscarPacket(oc, &packet);
ft->qwBytesDone += bytesRead;
ft->qwFileBytesDone += bytesRead;
}
if (GetTickCount() > ft->dwLastNotify + 700 || oc->wantIdleTime == 0 || ft->qwFileBytesDone == ft->qwThisFileSize) { // notify only once a while or after last data packet sent
PROTOFILETRANSFERSTATUS pfts;
oft_buildProtoFileTransferStatus(ft, &pfts);
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts);
ft->dwLastNotify = GetTickCount();
}
}
void CIcqProto::oft_sendPeerInit(oscar_connection *oc)
{
mir_cslock l(oftMutex);
// prepare init frame
oscar_filetransfer *ft = oc->ft;
if (ft->iCurrentFile >= (int)ft->wFilesCount) { // All files done, great!
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oc->ft);
return;
}
SAFE_FREE(&ft->szThisFile);
ft->szThisFile = null_strdup(ft->files[ft->iCurrentFile].szFile);
struct _stati64 statbuf;
if (FileStatUtf(ft->szThisFile, &statbuf)) {
icq_LogMessage(LOG_ERROR, LPGEN("Your file transfer has been aborted because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it."));
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oc->ft);
return;
}
// create full relative filename
char* szThisContainer = ft->files[ft->iCurrentFile].szContainer;
char *pszThisFileName = (char*)SAFE_MALLOC(mir_strlen(ft->szThisFile) + mir_strlen(szThisContainer) + 4);
mir_strcpy(pszThisFileName, szThisContainer);
NormalizeBackslash(pszThisFileName);
mir_strcat(pszThisFileName, ExtractFileName(ft->szThisFile));
// convert backslashes to dir markings
for (size_t i = 0; i < mir_strlen(pszThisFileName); i++)
if (pszThisFileName[i] == '\\' || pszThisFileName[i] == '/')
pszThisFileName[i] = 0x01;
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0);
ft->fileId = OpenFileUtf(ft->szThisFile, _O_BINARY | _O_RDONLY, 0);
if (ft->fileId == -1) {
NetLog_Direct("OFT: error opening file %s, errno=%d", ft->szThisFile, errno);
SAFE_FREE((void**)&pszThisFileName);
icq_LogMessage(LOG_ERROR, LPGEN("Your file transfer has been aborted (error=%d), because one of the files that you selected to send is no longer readable from the disk. You may have deleted or moved it."));
ProtoBroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
// Release transfer
SafeReleaseFileTransfer((basic_filetransfer**)&oc->ft);
return;
}
ft->qwThisFileSize = statbuf.st_size;
ft->dwThisFileDate = statbuf.st_mtime;
ft->dwThisFileCreation = statbuf.st_ctime;
ft->dwThisFileCheck = oft_calc_file_checksum(ft->fileId, ft->qwThisFileSize);
ft->qwFileBytesDone = 0;
ft->dwRecvFileCheck = 0xFFFF0000;
SAFE_FREE((void**)&ft->rawFileName);
if (IsUSASCII(pszThisFileName, mir_strlen(pszThisFileName))) {
ft->wEncoding = 0; // ascii
ft->cbRawFileName = WORD(mir_strlen(pszThisFileName) + 1);
if (ft->cbRawFileName < 64)
ft->cbRawFileName = 64;
ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName);
mir_strcpy(ft->rawFileName, (char*)pszThisFileName);
SAFE_FREE((void**)&pszThisFileName);
}
else {
ft->wEncoding = 2; // ucs-2
WCHAR *pwsThisFile = make_unicode_string(pszThisFileName);
SAFE_FREE((void**)&pszThisFileName);
ft->cbRawFileName = WORD(mir_wstrlen(pwsThisFile) * sizeof(WCHAR)) + 2;
if (ft->cbRawFileName < 64)
ft->cbRawFileName = 64;
ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName);
// convert to LE ordered string
BYTE *pwsThisFileBuf = (BYTE*)pwsThisFile; // need this - unpackWideString moves the address!
unpackWideString(&pwsThisFileBuf, (WCHAR*)ft->rawFileName, mir_wstrlen(pwsThisFile) * sizeof(WCHAR));
SAFE_FREE((void**)&pwsThisFile);
}
ft->wFilesLeft = WORD(ft->wFilesCount - ft->iCurrentFile);
sendOFT2FramePacket(oc, OFT_TYPE_REQUEST);
}
void CIcqProto::sendOFT2FramePacket(oscar_connection *oc, WORD datatype)
{
oscar_filetransfer *ft = oc->ft;
icq_packet packet;
packet.wLen = WORD(192 + ft->cbRawFileName);
init_generic_packet(&packet, 0);
// Basic Oscar Frame
packDWord(&packet, 0x4F465432); // Magic
packWord(&packet, packet.wLen);
packWord(&packet, datatype);
// Cookie
packLEDWord(&packet, ft->pMessage.dwMsgID1);
packLEDWord(&packet, ft->pMessage.dwMsgID2);
packWord(&packet, ft->wEncrypt);
packWord(&packet, ft->wCompress);
packWord(&packet, ft->wFilesCount);
packWord(&packet, ft->wFilesLeft);
packWord(&packet, ft->wPartsCount);
packWord(&packet, ft->wPartsLeft);
packDWord(&packet, (DWORD)ft->qwTotalSize);
packDWord(&packet, (DWORD)ft->qwThisFileSize);
packDWord(&packet, ft->dwThisFileDate);
packDWord(&packet, ft->dwThisFileCheck);
packDWord(&packet, ft->dwRecvForkCheck);
packDWord(&packet, ft->dwThisForkSize);
packDWord(&packet, ft->dwThisFileCreation);
packDWord(&packet, ft->dwThisForkCheck);
packDWord(&packet, (DWORD)ft->qwFileBytesDone);
packDWord(&packet, ft->dwRecvFileCheck);
packBuffer(&packet, (LPBYTE)ft->rawIDString, 32);
packByte(&packet, ft->bHeaderFlags);
packByte(&packet, ft->bNameOff);
packByte(&packet, ft->bSizeOff);
packBuffer(&packet, ft->rawDummy, 69);
packBuffer(&packet, ft->rawMacInfo, 16);
packWord(&packet, ft->wEncoding);
packWord(&packet, ft->wSubEncoding);
packBuffer(&packet, (LPBYTE)ft->rawFileName, ft->cbRawFileName);
sendOscarPacket(oc, &packet);
}