/* Jabber Protocol Plugin for Miranda IM Copyright ( C ) 2002-04 Santithorn Bunchua Copyright ( C ) 2005-12 George Hazan 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "jabber.h" #include #include #include #include #include "jabber_caps.h" #define JABBER_NETWORK_BUFFER_SIZE 2048 void __cdecl CJabberProto::FileReceiveThread( filetransfer* ft ) { char* buffer; int datalen; ThreadData info( this, JABBER_SESSION_NORMAL ); Log( "Thread started: type=file_receive server='%s' port='%d'", ft->httpHostName, ft->httpPort ); ft->type = FT_OOB; if (( buffer=( char* )mir_alloc( JABBER_NETWORK_BUFFER_SIZE )) == NULL ) { Log( "Cannot allocate network buffer, thread ended" ); JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0 ); delete ft; return; } NETLIBOPENCONNECTION nloc = { 0 }; nloc.cbSize = sizeof( nloc ); nloc.cbSize = sizeof( NETLIBOPENCONNECTION ); nloc.szHost = ft->httpHostName; nloc.wPort = ft->httpPort; info.s = ( HANDLE ) CallService( MS_NETLIB_OPENCONNECTION, ( WPARAM ) m_hNetlibUser, ( LPARAM )&nloc ); if ( info.s == NULL ) { Log( "Connection failed ( %d ), thread ended", WSAGetLastError()); JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0 ); mir_free( buffer ); delete ft; return; } ft->s = info.s; info.send( "GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n", ft->httpPath, ft->httpHostName ); ft->state = FT_CONNECTING; Log( "Entering file_receive recv loop" ); datalen = 0; while ( ft->state != FT_DONE && ft->state != FT_ERROR ) { int recvResult, bytesParsed; Log( "Waiting for data..." ); recvResult = info.recv( buffer+datalen, JABBER_NETWORK_BUFFER_SIZE-datalen ); if ( recvResult <= 0 ) break; datalen += recvResult; bytesParsed = FileReceiveParse( ft, buffer, datalen ); if ( bytesParsed < datalen ) memmove( buffer, buffer+bytesParsed, datalen-bytesParsed ); datalen -= bytesParsed; } ft->s = NULL; if ( ft->state==FT_DONE || ( ft->state==FT_RECEIVING && ft->std.currentFileSize < 0 )) ft->complete(); Log( "Thread ended: type=file_receive server='%s'", ft->httpHostName ); mir_free( buffer ); delete ft; } int CJabberProto::FileReceiveParse( filetransfer* ft, char* buffer, int datalen ) { char* p, *q, *s, *eob; char* str; int num, code; eob = buffer + datalen; p = buffer; num = 0; while ( true ) { if ( ft->state==FT_CONNECTING || ft->state==FT_INITIALIZING ) { for ( q=p; q+1state == FT_CONNECTING ) { // looking for "HTTP/1.1 200 OK" if ( sscanf( str, "HTTP/%*d.%*d %d %*s", &code )==1 && code==200 ) { ft->state = FT_INITIALIZING; ft->std.currentFileSize = -1; Log( "Change to FT_INITIALIZING" ); JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, ft, 0 ); } } else { // FT_INITIALIZING if ( str[0] == '\0' ) { TCHAR* s; if (( s = _tcsrchr( ft->httpPath, '/' )) != NULL ) s++; else s = ft->httpPath; ft->std.tszCurrentFile = mir_tstrdup( s ); JabberHttpUrlDecode( ft->std.tszCurrentFile ); if ( ft->create() == -1 ) { ft->state = FT_ERROR; break; } ft->state = FT_RECEIVING; ft->std.currentFileProgress = 0; Log( "Change to FT_RECEIVING" ); } else if (( s=strchr( str, ':' )) != NULL ) { *s = '\0'; if ( !strcmp( str, "Content-Length" )) ft->std.totalBytes = ft->std.currentFileSize = _atoi64( s+1 ); } } mir_free( str ); q += 2; num += ( q-p ); p = q; } else { ft->state = FT_ERROR; break; } } else { break; } } else if ( ft->state == FT_RECEIVING ) { int bufferSize, writeSize; __int64 remainingBytes; if ( ft->std.currentFileSize < 0 || ft->std.currentFileProgress < ft->std.currentFileSize ) { bufferSize = eob - p; remainingBytes = ft->std.currentFileSize - ft->std.currentFileProgress; if ( remainingBytes < bufferSize ) writeSize = remainingBytes; else writeSize = bufferSize; if ( _write( ft->fileId, p, writeSize ) != writeSize ) { Log( "_write() error" ); ft->state = FT_ERROR; } else { ft->std.currentFileProgress += writeSize; ft->std.totalProgress += writeSize; JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, ( LPARAM )&ft->std ); if ( ft->std.currentFileProgress == ft->std.currentFileSize ) ft->state = FT_DONE; } } num = datalen; break; } else break; } return num; } void JabberFileServerConnection( JABBER_SOCKET hConnection, DWORD /*dwRemoteIP*/, void* extra ) { CJabberProto* ppro = ( CJabberProto* )extra; NETLIBCONNINFO connInfo = { sizeof(connInfo) }; CallService(MS_NETLIB_GETCONNECTIONINFO, (WPARAM)hConnection, (LPARAM)&connInfo); TCHAR szPort[10]; mir_sntprintf( szPort, SIZEOF( szPort ), _T("%d"), connInfo.wPort ); ppro->Log( "File server incoming connection accepted: %s", connInfo.szIpPort ); JABBER_LIST_ITEM *item = ppro->ListGetItemPtr( LIST_FILE, szPort ); if ( item == NULL ) { ppro->Log( "No file is currently served, file server connection closed." ); Netlib_CloseHandle( hConnection ); return; } filetransfer* ft = item->ft; JABBER_SOCKET slisten = ft->s; ft->s = hConnection; ppro->Log( "Set ft->s to %d ( saving %d )", hConnection, slisten ); char* buffer = ( char* )mir_alloc( JABBER_NETWORK_BUFFER_SIZE+1 ); if ( buffer == NULL ) { ppro->Log( "Cannot allocate network buffer, file server connection closed." ); Netlib_CloseHandle( hConnection ); ft->state = FT_ERROR; if ( ft->hFileEvent != NULL ) SetEvent( ft->hFileEvent ); return; } ppro->Log( "Entering recv loop for this file connection... ( ft->s is hConnection )" ); int datalen = 0; while ( ft->state!=FT_DONE && ft->state!=FT_ERROR ) { int recvResult, bytesParsed; recvResult = Netlib_Recv( hConnection, buffer+datalen, JABBER_NETWORK_BUFFER_SIZE-datalen, 0 ); if ( recvResult <= 0 ) break; datalen += recvResult; buffer[datalen] = '\0'; ppro->Log( "RECV:%s", buffer ); bytesParsed = ppro->FileSendParse( hConnection, ft, buffer, datalen ); if ( bytesParsed < datalen ) memmove( buffer, buffer+bytesParsed, datalen-bytesParsed ); datalen -= bytesParsed; } ppro->Log( "Closing connection for this file transfer... ( ft->s is now hBind )" ); Netlib_CloseHandle( hConnection ); ft->s = slisten; ppro->Log( "ft->s is restored to %d", ft->s ); if ( ft->hFileEvent != NULL ) SetEvent( ft->hFileEvent ); mir_free( buffer ); } void __cdecl CJabberProto::FileServerThread( filetransfer* ft ) { Log( "Thread started: type=file_send" ); ThreadData info( this, JABBER_SESSION_NORMAL ); ft->type = FT_OOB; NETLIBBIND nlb = {0}; nlb.cbSize = sizeof( NETLIBBIND ); nlb.pfnNewConnectionV2 = JabberFileServerConnection; nlb.pExtra = this; nlb.wPort = 0; // Use user-specified incoming port ranges, if available info.s = ( HANDLE ) CallService( MS_NETLIB_BINDPORT, ( WPARAM ) m_hNetlibUser, ( LPARAM )&nlb ); if ( info.s == NULL ) { Log( "Cannot allocate port to bind for file server thread, thread ended." ); JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0 ); delete ft; return; } ft->s = info.s; Log( "ft->s = %d", info.s ); HANDLE hEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); ft->hFileEvent = hEvent; TCHAR szPort[20]; mir_sntprintf( szPort, SIZEOF( szPort ), _T("%d"), nlb.wPort ); JABBER_LIST_ITEM *item = ListAdd( LIST_FILE, szPort ); item->ft = ft; TCHAR* ptszResource = ListGetBestClientResourceNamePtr( ft->jid ); if ( ptszResource != NULL ) { ft->state = FT_CONNECTING; for ( int i=0; i < ft->std.totalFiles && ft->state != FT_ERROR && ft->state != FT_DENIED; i++ ) { ft->std.currentFileNumber = i; ft->state = FT_CONNECTING; if ( ft->httpPath ) mir_free( ft->httpPath ); ft->httpPath = NULL; TCHAR* p; if (( p = _tcschr( ft->std.ptszFiles[i], '\\' )) != NULL ) p++; else p = ft->std.ptszFiles[i]; TCHAR* pFileName = JabberHttpUrlEncode( p ); if ( pFileName != NULL ) { int id = SerialNext(); if ( ft->iqId ) mir_free( ft->iqId ); ft->iqId = ( TCHAR* )mir_alloc( sizeof(TCHAR)*( strlen( JABBER_IQID )+20 )); wsprintf( ft->iqId, _T(JABBER_IQID)_T("%d"), id ); char *myAddr = NULL; DBVARIANT dbv; if (m_options.BsDirect && m_options.BsDirectManual) { if ( !DBGetContactSettingString( NULL, m_szModuleName, "BsDirectAddr", &dbv )) myAddr = dbv.pszVal; } if ( myAddr == NULL ) myAddr = (char*)CallService( MS_NETLIB_ADDRESSTOSTRING, 1, nlb.dwExternalIP ); char szAddr[ 256 ]; mir_snprintf( szAddr, sizeof(szAddr), "http://%s:%d/%s", myAddr, nlb.wPort, pFileName ); mir_free( pFileName ); mir_free( myAddr ); int len = lstrlen(ptszResource) + lstrlen(ft->jid) + 2; TCHAR* fulljid = ( TCHAR* )alloca( sizeof( TCHAR )*len ); wsprintf( fulljid, _T("%s/%s"), ft->jid, ptszResource ); XmlNodeIq iq( _T("set"), id, fulljid ); HXML query = iq << XQUERY( _T(JABBER_FEAT_OOB)); query << XCHILD( _T("url"), _A2T(szAddr)); query << XCHILD( _T("desc"), ft->szDescription); m_ThreadInfo->send( iq ); Log( "Waiting for the file to be sent..." ); WaitForSingleObject( hEvent, INFINITE ); } Log( "File sent, advancing to the next file..." ); JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0 ); } CloseHandle( hEvent ); ft->hFileEvent = NULL; Log( "Finish all files" ); } ft->s = NULL; Log( "ft->s is NULL" ); ListRemove( LIST_FILE, szPort ); switch ( ft->state ) { case FT_DONE: Log( "Finish successfully" ); JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0 ); break; case FT_DENIED: JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DENIED, ft, 0 ); break; default: // FT_ERROR: Log( "Finish with errors" ); JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0 ); break; } Log( "Thread ended: type=file_send" ); delete ft; } int CJabberProto::FileSendParse( JABBER_SOCKET s, filetransfer* ft, char* buffer, int datalen ) { char* p, *q, *t, *eob; char* str; int num; int currentFile; int fileId; int numRead; eob = buffer + datalen; p = buffer; num = 0; while ( ft->state==FT_CONNECTING || ft->state==FT_INITIALIZING ) { for ( q=p; q+1= eob ) break; if (( str=( char* )mir_alloc( q-p+1 )) == NULL ) { ft->state = FT_ERROR; break; } strncpy( str, p, q-p ); str[q-p] = '\0'; Log( "FT Got: %s", str ); if ( ft->state == FT_CONNECTING ) { // looking for "GET filename.ext HTTP/1.1" if ( !strncmp( str, "GET ", 4 )) { for ( t=str+4; *t!='\0' && *t!=' '; t++ ); *t = '\0'; for ( t=str+4; *t!='\0' && *t=='/'; t++ ); ft->httpPath = mir_a2t( t ); JabberHttpUrlDecode( ft->httpPath ); ft->state = FT_INITIALIZING; Log( "Change to FT_INITIALIZING" ); } } else { // FT_INITIALIZING if ( str[0] == '\0' ) { struct _stati64 statbuf; mir_free( str ); num += 2; currentFile = ft->std.currentFileNumber; TCHAR* t = _tcsrchr( ft->std.ptszFiles[ currentFile ], '\\' ); if ( t != NULL ) t++; else t = ft->std.ptszFiles[currentFile]; if ( ft->httpPath==NULL || lstrcmp( ft->httpPath, t )) { if ( ft->httpPath == NULL ) Log( "Requested file name does not matched ( httpPath==NULL )" ); else Log( "Requested file name does not matched ( '%s' vs. '%s' )", ft->httpPath, t ); ft->state = FT_ERROR; break; } Log( "Sending [%s]", ft->std.ptszFiles[ currentFile ] ); _tstati64( ft->std.ptszFiles[ currentFile ], &statbuf ); // file size in statbuf.st_size if (( fileId = _topen( ft->std.ptszFiles[currentFile], _O_BINARY|_O_RDONLY )) < 0 ) { Log( "File cannot be opened" ); ft->state = FT_ERROR; mir_free( ft->httpPath ); ft->httpPath = NULL; break; } char fileBuffer[ 2048 ]; int bytes = mir_snprintf( fileBuffer, sizeof(fileBuffer), "HTTP/1.1 200 OK\r\nContent-Length: %I64u\r\n\r\n", statbuf.st_size ); WsSend( s, fileBuffer, bytes, MSG_DUMPASTEXT ); ft->std.flags |= PFTS_SENDING; ft->std.currentFileProgress = 0; Log( "Sending file data..." ); while (( numRead = _read( fileId, fileBuffer, 2048 )) > 0 ) { if ( Netlib_Send( s, fileBuffer, numRead, 0 ) != numRead ) { ft->state = FT_ERROR; break; } ft->std.currentFileProgress += numRead; ft->std.totalProgress += numRead; JSendBroadcast( ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, ( LPARAM )&ft->std ); } _close( fileId ); if ( ft->state != FT_ERROR ) ft->state = FT_DONE; Log( "Finishing this file..." ); mir_free( ft->httpPath ); ft->httpPath = NULL; break; } } mir_free( str ); q += 2; num += ( q-p ); p = q; } return num; } ///////////////////////////////////////////////////////////////////////////////////////// // filetransfer class members filetransfer::filetransfer( CJabberProto* proto ) { memset( this, 0, sizeof( filetransfer )); ppro = proto; fileId = -1; std.cbSize = sizeof( std ); std.flags = PFTS_TCHAR; } filetransfer::~filetransfer() { ppro->Log( "Destroying file transfer session %08p", this ); if ( !bCompleted ) ppro->JSendBroadcast( std.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, this, 0 ); close(); if ( hWaitEvent != INVALID_HANDLE_VALUE ) CloseHandle( hWaitEvent ); if ( jid ) mir_free( jid ); if ( sid ) mir_free( sid ); if ( iqId ) mir_free( iqId ); if ( fileSize ) mir_free( fileSize ); if ( httpHostName ) mir_free( httpHostName ); if ( httpPath ) mir_free( httpPath ); if ( szDescription ) mir_free( szDescription ); if ( std.tszWorkingDir ) mir_free( std.tszWorkingDir ); if ( std.tszCurrentFile ) mir_free( std.tszCurrentFile ); if ( std.ptszFiles ) { for ( int i=0; i < std.totalFiles; i++ ) if ( std.ptszFiles[i] ) mir_free( std.ptszFiles[i] ); mir_free( std.ptszFiles ); } } void filetransfer::close() { if ( fileId != -1 ) { _close( fileId ); fileId = -1; } } void filetransfer::complete() { close(); bCompleted = true; ppro->JSendBroadcast( std.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, this, 0); } int filetransfer::create() { if ( fileId != -1 ) return fileId; TCHAR filefull[ MAX_PATH ]; mir_sntprintf( filefull, SIZEOF(filefull), _T("%s\\%s"), std.tszWorkingDir, std.tszCurrentFile ); replaceStrT( std.tszCurrentFile, filefull ); if ( hWaitEvent != INVALID_HANDLE_VALUE ) CloseHandle( hWaitEvent ); hWaitEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( ppro->JSendBroadcast( std.hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, this, ( LPARAM )&std )) WaitForSingleObject( hWaitEvent, INFINITE ); if ( fileId == -1 ) { ppro->Log( "Saving to [%s]", std.tszCurrentFile ); fileId = _topen( std.tszCurrentFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE ); } if ( fileId == -1 ) ppro->Log( "Cannot create file '%s' during a file transfer", filefull ); else if ( std.currentFileSize != 0 ) _chsize( fileId, std.currentFileSize ); return fileId; }