From aa95171c671becb9c8287d4229a5db9b65d7e917 Mon Sep 17 00:00:00 2001 From: George Hazan Date: Thu, 17 Oct 2019 21:54:19 +0300 Subject: fixed #1710 (Jabber: implement XEP-0363 (HTTP File Upload)) --- protocols/JabberG/src/jabber_caps.h | 1 + protocols/JabberG/src/jabber_disco.cpp | 29 ++++++-- protocols/JabberG/src/jabber_file.cpp | 6 +- protocols/JabberG/src/jabber_ft.cpp | 127 +++++++++++++++++++++++++++++---- protocols/JabberG/src/jabber_proto.cpp | 51 ++++++------- protocols/JabberG/src/jabber_proto.h | 5 +- protocols/JabberG/src/stdafx.h | 2 +- 7 files changed, 173 insertions(+), 48 deletions(-) (limited to 'protocols/JabberG') diff --git a/protocols/JabberG/src/jabber_caps.h b/protocols/JabberG/src/jabber_caps.h index 9e78c0dfb6..caa18b88f0 100755 --- a/protocols/JabberG/src/jabber_caps.h +++ b/protocols/JabberG/src/jabber_caps.h @@ -195,6 +195,7 @@ typedef unsigned __int64 JabberCapsBits; #define JABBER_FEAT_CSI "urn:xmpp:csi:0" #define JABBER_FEAT_JUD "jabber:iq:search" #define JABBER_FEAT_SERVER_AVATAR "storage:client:avatar" +#define JABBER_FEAT_UPLOAD "urn:xmpp:http:upload" #define JABBER_FEAT_PUBSUB_EVENT "http://jabber.org/protocol/pubsub#event" #define JABBER_FEAT_PUBSUB_NODE_CONFIG "http://jabber.org/protocol/pubsub#node_config" diff --git a/protocols/JabberG/src/jabber_disco.cpp b/protocols/JabberG/src/jabber_disco.cpp index 40d3c0a53f..bf2dbac1fd 100644 --- a/protocols/JabberG/src/jabber_disco.cpp +++ b/protocols/JabberG/src/jabber_disco.cpp @@ -86,12 +86,12 @@ static sttNodeIcons[] = { nullptr, "store", nullptr, IDI_NODE_STORE, 0 }, - // icons for non-standard identities + // icons for non-standard identities { nullptr, "x-service", "x-rss", IDI_NODE_RSS, 0 }, { nullptr, "application", "x-weather", IDI_NODE_WEATHER, 0 }, { nullptr, "user", nullptr, 0, SKINICON_STATUS_ONLINE }, - // icon suggestions based on supported features + // icon suggestions based on supported features { "jabber:iq:gateway", nullptr, nullptr, IDI_AGENTS, 0 }, { JABBER_FEAT_JUD, nullptr, nullptr, 0, SKINICON_OTHER_FINDUSER }, { JABBER_FEAT_COMMANDS, nullptr, nullptr, IDI_COMMAND, 0 }, @@ -1026,7 +1026,7 @@ void CJabberProto::ServiceDiscoveryShowMenu(CJabberSDNode *pNode, HTREELISTITEM SD_ACT_LOGON = 100, SD_ACT_LOGOFF, SD_ACT_UNREGISTER, SD_ACT_REGISTER = 200, SD_ACT_ADHOC, SD_ACT_ADDDIRECTORY, - SD_ACT_JOIN, SD_ACT_BOOKMARK, SD_ACT_PROXY, SD_ACT_VCARD + SD_ACT_JOIN, SD_ACT_BOOKMARK, SD_ACT_PROXY, SD_ACT_VCARD, SD_ACT_UPLOAD }; enum @@ -1063,6 +1063,7 @@ void CJabberProto::ServiceDiscoveryShowMenu(CJabberSDNode *pNode, HTREELISTITEM { JABBER_FEAT_MUC, LPGENW("Bookmark chatroom"), SD_ACT_BOOKMARK, SD_FLG_NORESOURCE | SD_FLG_HASUSER}, { JABBER_FEAT_JUD, LPGENW("Add search directory"), SD_ACT_ADDDIRECTORY}, { JABBER_FEAT_BYTESTREAMS, LPGENW("Use this proxy"), SD_ACT_PROXY}, + { JABBER_FEAT_UPLOAD, LPGENW("Use for uploads"), SD_ACT_UPLOAD}, { nullptr }, { JABBER_FEAT_REGISTER, LPGENW("Register"), SD_ACT_REGISTER}, { "jabber:iq:gateway", LPGENW("Unregister"), SD_ACT_UNREGISTER, SD_FLG_ONROSTER | SD_FLG_SUBSCRIBED}, @@ -1129,7 +1130,20 @@ void CJabberProto::ServiceDiscoveryShowMenu(CJabberSDNode *pNode, HTREELISTITEM if (bFeatureOk) { if (it.title) { - AppendMenu(hMenu, MF_STRING, it.action, TranslateW(it.title)); + UINT dwFlags = MF_STRING; + switch (it.action) { + case SD_ACT_PROXY: + if (m_bBsProxyManual) + dwFlags += MF_CHECKED; + break; + + case SD_ACT_UPLOAD: + if (m_bUseHttpUpload) + dwFlags += MF_CHECKED; + break; + } + + AppendMenu(hMenu, dwFlags, it.action, TranslateW(it.title)); lastSeparator = FALSE; } else if (!lastSeparator) { @@ -1231,10 +1245,15 @@ void CJabberProto::ServiceDiscoveryShowMenu(CJabberSDNode *pNode, HTREELISTITEM break; case SD_ACT_PROXY: - m_bBsProxyManual = true; + m_bBsProxyManual = !m_bBsProxyManual; setUString("BsProxyServer", pNode->GetJid()); break; + case SD_ACT_UPLOAD: + m_bUseHttpUpload = !m_bUseHttpUpload; + setUString("HttpUpload", pNode->GetJid()); + break; + case SD_ACT_JOIN: GroupchatJoinRoomByJid(m_pDlgServiceDiscovery->GetHwnd(), pNode->GetJid()); break; diff --git a/protocols/JabberG/src/jabber_file.cpp b/protocols/JabberG/src/jabber_file.cpp index 7d6d767e52..097eb62789 100644 --- a/protocols/JabberG/src/jabber_file.cpp +++ b/protocols/JabberG/src/jabber_file.cpp @@ -372,8 +372,6 @@ int CJabberProto::FileSendParse(HNETLIBCONN s, filetransfer *ft, char* buffer, i } else { // FT_INITIALIZING if (str[0] == '\0') { - struct _stati64 statbuf; - mir_free(str); num += 2; @@ -393,7 +391,7 @@ int CJabberProto::FileSendParse(HNETLIBCONN s, filetransfer *ft, char* buffer, i break; } debugLogW(L"Sending [%s]", ft->std.pszFiles.w[currentFile]); - _wstat64(ft->std.pszFiles.w[currentFile], &statbuf); // file size in statbuf.st_size + if ((fileId = _wopen(ft->std.pszFiles.w[currentFile], _O_BINARY | _O_RDONLY)) < 0) { debugLogA("File cannot be opened"); ft->state = FT_ERROR; @@ -402,7 +400,7 @@ int CJabberProto::FileSendParse(HNETLIBCONN s, filetransfer *ft, char* buffer, i } char fileBuffer[2048]; - int bytes = mir_snprintf(fileBuffer, "HTTP/1.1 200 OK\r\nContent-Length: %I64u\r\n\r\n", statbuf.st_size); + int bytes = mir_snprintf(fileBuffer, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n", _filelength(fileId)); WsSend(s, fileBuffer, bytes, MSG_DUMPASTEXT); ft->std.flags |= PFTS_SENDING; diff --git a/protocols/JabberG/src/jabber_ft.cpp b/protocols/JabberG/src/jabber_ft.cpp index e832925e2a..19fc9f0c44 100644 --- a/protocols/JabberG/src/jabber_ft.cpp +++ b/protocols/JabberG/src/jabber_ft.cpp @@ -75,27 +75,43 @@ void CJabberProto::FtCancel(filetransfer *ft) ///////////////// File sending using stream initiation ///////////////////////// -void CJabberProto::FtInitiate(char* jid, filetransfer *ft) +void CJabberProto::FtInitiate(const char* jid, filetransfer *ft) { - char *rs; - int i; - char sid[9]; - - if (jid == nullptr || ft == nullptr || !m_bJabberOnline || (rs = ListGetBestClientResourceNamePtr(jid)) == nullptr) { + char *rs = ListGetBestClientResourceNamePtr(jid); + if (ft == nullptr || !m_bJabberOnline || rs == nullptr) { if (ft) { ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0); delete ft; } return; } + + wchar_t *filename = ft->std.pszFiles.w[ft->std.currentFileNumber]; + if (wchar_t *p = wcsrchr(filename, '\\')) + filename = p + 1; + + // if we use XEP-0363, send a slot allocation request + if (m_bUseHttpUpload) { + ptrA szUploadService(getStringA("HttpUpload")); + if (szUploadService != nullptr) { + ft->type = FT_HTTP; + + struct _stat64 st; + _wstat64(ft->std.szCurrentFile.w, &st); + + XmlNodeIq iq(AddIQ(&CJabberProto::OnHttpSlotAllocated, JABBER_IQ_TYPE_GET, szUploadService, ft)); + iq << XCHILDNS("request", "urn:xmpp:http:upload:0") << XATTR("filename", T2Utf(filename)) << XATTRI64("size", st.st_size); + m_ThreadInfo->send(iq); + return; + } + } + ft->type = FT_SI; - for (i = 0; i < 8; i++) + char sid[9]; + for (int i = 0; i < 8; i++) sid[i] = (rand() % 10) + '0'; sid[8] = '\0'; replaceStr(ft->sid, sid); - wchar_t *filename = ft->std.pszFiles.w[ft->std.currentFileNumber]; - if (wchar_t *p = wcsrchr(filename, '\\')) - filename = p + 1; auto *pIq = AddIQ(&CJabberProto::OnFtSiResult, JABBER_IQ_TYPE_SET, MakeJid(jid, rs), ft); pIq->SetParamsToParse(JABBER_IQ_PARSE_FROM | JABBER_IQ_PARSE_TO); @@ -178,12 +194,13 @@ void CJabberProto::OnFtSiResult(const TiXmlElement *iqNode, CJabberIqInfo *pInfo BOOL CJabberProto::FtSend(HNETLIBCONN hConn, filetransfer *ft) { - struct _stati64 statbuf; int fd; char* buffer; int numRead; debugLogW(L"Sending [%s]", ft->std.pszFiles.w[ft->std.currentFileNumber]); + + struct _stat64 statbuf; _wstat64(ft->std.pszFiles.w[ft->std.currentFileNumber], &statbuf); // file size in statbuf.st_size if ((fd = _wopen(ft->std.pszFiles.w[ft->std.currentFileNumber], _O_BINARY | _O_RDONLY)) < 0) { debugLogA("File cannot be opened"); @@ -215,7 +232,7 @@ BOOL CJabberProto::FtIbbSend(int blocksize, filetransfer *ft) { debugLogW(L"Sending [%s]", ft->std.pszFiles.w[ft->std.currentFileNumber]); - struct _stati64 statbuf; + struct _stat64 statbuf; _wstat64(ft->std.pszFiles.w[ft->std.currentFileNumber], &statbuf); // file size in statbuf.st_size int fd = _wopen(ft->std.pszFiles.w[ft->std.currentFileNumber], _O_BINARY | _O_RDONLY); @@ -527,3 +544,89 @@ void CJabberProto::FtReceiveFinal(BOOL success, filetransfer *ft) delete ft; } + +void CJabberProto::OnHttpSlotAllocated(const TiXmlElement *iqNode, CJabberIqInfo *pInfo) +{ + filetransfer *ft = (filetransfer *)pInfo->GetUserData(); + if (!ft) + return; + + if (pInfo->GetIqType() != JABBER_IQ_TYPE_RESULT) { + debugLogA("HTTP upload aborted"); +LBL_Fail: + ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, pInfo->GetIqType() == JABBER_IQ_TYPE_ERROR ? ACKRESULT_DENIED : ACKRESULT_FAILED, ft, 0); + delete ft; + return; + } + + if (auto *slotNode = XmlFirstChild(iqNode, "slot")) { + if (auto *putNode = XmlFirstChild(slotNode, "put")) { + if (auto *szUrl = putNode->Attribute("url")) { + NETLIBHTTPHEADER hdr[10]; + + NETLIBHTTPREQUEST nlhr = {}; + nlhr.cbSize = sizeof(nlhr); + nlhr.requestType = REQUEST_PUT; + nlhr.flags = NLHRF_NODUMPSEND | NLHRF_SSL | NLHRF_REDIRECT; + nlhr.szUrl = (char *)szUrl; + + for (auto *it : TiXmlFilter(putNode, "header")) { + auto *szName = it->Attribute("name"); + auto *szValue = it->GetText(); + if (szName && szValue && nlhr.headersCount < _countof(hdr)) { + nlhr.headers = hdr; + hdr[nlhr.headersCount].szName = (char *)szName; + hdr[nlhr.headersCount].szValue = (char *)szValue; + nlhr.headersCount++; + } + } + + const wchar_t *pwszFileName = ft->std.pszFiles.w[ft->std.currentFileNumber]; + + int fileId = _wopen(pwszFileName, _O_BINARY | _O_RDONLY); + if (fileId < 0) { + debugLogA("error opening file %S", pwszFileName); + goto LBL_Fail; + } + + nlhr.dataLength = _filelength(fileId); + nlhr.pData = new char[nlhr.dataLength]; + _read(fileId, nlhr.pData, nlhr.dataLength); + _close(fileId); + + NETLIBHTTPREQUEST *res = Netlib_HttpTransaction(m_hNetlibUser, &nlhr); + if (res == nullptr) { + debugLogA("error uploading file %S", pwszFileName); + goto LBL_Fail; + } + + switch (res->resultCode) { + case 200: // ok + case 201: // created + break; + + default: + debugLogA("error uploading file %S: error %d", pwszFileName, res->resultCode); + Netlib_FreeHttpRequest(res); + goto LBL_Fail; + } + + Netlib_FreeHttpRequest(res); + + // this parameter is optional, if not specified we simply use upload URL + auto *szGetUrl = XmlGetAttr(XmlFirstChild(slotNode, "get"), "url"); + if (szGetUrl) + SendMsg(ft->std.hContact, 0, szGetUrl); + else + SendMsg(ft->std.hContact, 0, szUrl); + + ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0); + delete ft; + return; + } + } + } + + debugLogA("wrong or not recognizable http slot received"); + goto LBL_Fail; +} diff --git a/protocols/JabberG/src/jabber_proto.cpp b/protocols/JabberG/src/jabber_proto.cpp index 916022987e..8619ad0e2b 100755 --- a/protocols/JabberG/src/jabber_proto.cpp +++ b/protocols/JabberG/src/jabber_proto.cpp @@ -128,6 +128,7 @@ CJabberProto::CJabberProto(const char *aProtoName, const wchar_t *aUserName) : m_bProcessXMPPLinks(this, "ProcessXMPPLinks", false), m_bIgnoreRosterGroups(this, "IgnoreRosterGroups", false), m_bEnableCarbons(this, "EnableCarbons", true), + m_bUseHttpUpload(this, "UseHttpUpload", false), m_bUseOMEMO(this, "UseOMEMO", false), m_bEnableStreamMgmt(this, "UseStreamMgmt", false) { @@ -839,31 +840,31 @@ HANDLE CJabberProto::SendFile(MCONTACT hContact, const wchar_t *szDescription, w if (item->ft != nullptr) return nullptr; - JabberCapsBits jcb = GetResourceCapabilities(item->jid); - if (jcb == JABBER_RESOURCE_CAPS_IN_PROGRESS) { - Sleep(600); - jcb = GetResourceCapabilities(item->jid); - } + JabberCapsBits jcb = 0; + if (!m_bUseHttpUpload) { + GetResourceCapabilities(item->jid); + if (jcb == JABBER_RESOURCE_CAPS_IN_PROGRESS) { + Sleep(600); + jcb = GetResourceCapabilities(item->jid); + } - // fix for very smart clients, like gajim - if (!m_bBsDirect && !m_bBsProxyManual) { - // disable bytestreams - jcb &= ~JABBER_CAPS_BYTESTREAMS; - } + // fix for very smart clients, like gajim + if (!m_bBsDirect && !m_bBsProxyManual) { + // disable bytestreams + jcb &= ~JABBER_CAPS_BYTESTREAMS; + } - // if only JABBER_CAPS_SI_FT feature set (without BS or IBB), disable JABBER_CAPS_SI_FT - if ((jcb & (JABBER_CAPS_SI_FT | JABBER_CAPS_IBB | JABBER_CAPS_BYTESTREAMS)) == JABBER_CAPS_SI_FT) - jcb &= ~JABBER_CAPS_SI_FT; + // if only JABBER_CAPS_SI_FT feature set (without BS or IBB), disable JABBER_CAPS_SI_FT + if ((jcb & (JABBER_CAPS_SI_FT | JABBER_CAPS_IBB | JABBER_CAPS_BYTESTREAMS)) == JABBER_CAPS_SI_FT) + jcb &= ~JABBER_CAPS_SI_FT; - if ( - // can't get caps - (jcb & JABBER_RESOURCE_CAPS_ERROR) - // caps not already received - || (jcb == JABBER_RESOURCE_CAPS_NONE) - // XEP-0096 and OOB not supported? - || !(jcb & (JABBER_CAPS_SI_FT | JABBER_CAPS_OOB))) { - MsgPopup(hContact, TranslateT("No compatible file transfer mechanism exists"), Utf2T(item->jid)); - return nullptr; + if ((jcb & JABBER_RESOURCE_CAPS_ERROR) // can't get caps + || (jcb == JABBER_RESOURCE_CAPS_NONE) // caps not already received + || !(jcb & (JABBER_CAPS_SI_FT | JABBER_CAPS_OOB))) // XEP-0096 and OOB not supported? + { + MsgPopup(hContact, TranslateT("No compatible file transfer mechanism exists"), Utf2T(item->jid)); + return nullptr; + } } filetransfer *ft = new filetransfer(this); @@ -876,7 +877,7 @@ HANDLE CJabberProto::SendFile(MCONTACT hContact, const wchar_t *szDescription, w int i, j; for (i = j = 0; i < ft->std.totalFiles; i++) { - struct _stati64 statbuf; + struct _stat64 statbuf; if (_wstat64(ppszFiles[i], &statbuf)) debugLogW(L"'%s' is an invalid filename", ppszFiles[i]); else { @@ -895,7 +896,7 @@ HANDLE CJabberProto::SendFile(MCONTACT hContact, const wchar_t *szDescription, w ft->szDescription = mir_wstrdup(szDescription); ft->jid = mir_strdup(jid); - if (jcb & JABBER_CAPS_SI_FT) + if (m_bUseHttpUpload || (jcb & JABBER_CAPS_SI_FT)) FtInitiate(item->jid, ft); else if (jcb & JABBER_CAPS_OOB) ForkThread((MyThreadFunc)&CJabberProto::FileServerThread, ft); @@ -951,7 +952,7 @@ int CJabberProto::SendMsg(MCONTACT hContact, int unused_unknown, const char *psz } } - int isEncrypted, id = SerialNext(); + int isEncrypted, id = SerialNext(); if (!strncmp(pszSrc, PGP_PROLOG, mir_strlen(PGP_PROLOG))) { const char *szEnd = strstr(pszSrc, PGP_EPILOG); char *tempstring = (char*)alloca(mir_strlen(pszSrc) + 2); diff --git a/protocols/JabberG/src/jabber_proto.h b/protocols/JabberG/src/jabber_proto.h index 76996d45b3..b06ccb8ef3 100755 --- a/protocols/JabberG/src/jabber_proto.h +++ b/protocols/JabberG/src/jabber_proto.h @@ -186,6 +186,7 @@ struct CJabberProto : public PROTO, public IJabberInterface CMOption m_bProcessXMPPLinks; CMOption m_bIgnoreRosterGroups; CMOption m_bEnableCarbons; + CMOption m_bUseHttpUpload; CMOption m_bUseOMEMO; CMOption m_bEnableStreamMgmt; @@ -457,7 +458,7 @@ struct CJabberProto : public PROTO, public IJabberInterface void __cdecl FileServerThread(filetransfer *ft); void FtCancel(filetransfer *ft); - void FtInitiate(char* jid, filetransfer *ft); + void FtInitiate(const char* jid, filetransfer *ft); void FtHandleSiRequest(const TiXmlElement *iqNode); void FtAcceptSiRequest(filetransfer *ft); void FtAcceptIbbRequest(filetransfer *ft); @@ -550,6 +551,8 @@ struct CJabberProto : public PROTO, public IJabberInterface BOOL OnFtHandleIbbIq(const TiXmlElement *iqNode, CJabberIqInfo *pInfo); BOOL OnIbbRecvdData(const char *data, const char *sid, const char *seq); + void OnHttpSlotAllocated(const TiXmlElement *iqNode, CJabberIqInfo *pInfo); + void OnFtSiResult(const TiXmlElement *iqNode, CJabberIqInfo *pInfo); BOOL FtIbbSend(int blocksize, filetransfer *ft); BOOL FtSend(HNETLIBCONN hConn, filetransfer *ft); diff --git a/protocols/JabberG/src/stdafx.h b/protocols/JabberG/src/stdafx.h index 5651ca7f9b..cb2c171274 100755 --- a/protocols/JabberG/src/stdafx.h +++ b/protocols/JabberG/src/stdafx.h @@ -405,7 +405,7 @@ struct JABBER_MODEMSGS char *szFreechat; }; -typedef enum { FT_SI, FT_OOB, FT_BYTESTREAM, FT_IBB } JABBER_FT_TYPE; +typedef enum { FT_SI, FT_OOB, FT_BYTESTREAM, FT_IBB, FT_HTTP } JABBER_FT_TYPE; typedef enum { FT_CONNECTING, FT_INITIALIZING, FT_RECEIVING, FT_DONE, FT_ERROR, FT_DENIED } JABBER_FILE_STATE; struct filetransfer -- cgit v1.2.3