diff options
author | George Hazan <ghazan@miranda.im> | 2019-01-24 22:24:43 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2019-01-24 22:24:43 +0300 |
commit | a2fd8ed58e918c4d0edacc96990b69d42bdaa8ba (patch) | |
tree | d5fbe5e45b6441647b8aeb8845eb527546337564 | |
parent | b60702aa86920ec25f9b700181b153d5fa41cb08 (diff) |
fixes #1748 (ICQ10: add filetransfer support)
-rw-r--r-- | protocols/ICQ-WIM/src/http.cpp | 28 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/http.h | 14 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/proto.cpp | 36 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/proto.h | 45 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/server.cpp | 67 | ||||
-rw-r--r-- | protocols/ICQ-WIM/src/utils.cpp | 5 |
6 files changed, 192 insertions, 3 deletions
diff --git a/protocols/ICQ-WIM/src/http.cpp b/protocols/ICQ-WIM/src/http.cpp index 16c410c333..7e262d659d 100644 --- a/protocols/ICQ-WIM/src/http.cpp +++ b/protocols/ICQ-WIM/src/http.cpp @@ -265,6 +265,34 @@ JsonReply::~JsonReply() ///////////////////////////////////////////////////////////////////////////////////////// +FileReply::FileReply(NETLIBHTTPREQUEST *pReply) +{ + if (pReply == nullptr) { + m_errorCode = 500; + return; + } + + m_errorCode = pReply->resultCode; + if (m_errorCode != 200) + return; + + m_root = json_parse(pReply->pData); + if (m_root == nullptr) { + m_errorCode = 500; + return; + } + + m_errorCode = (*m_root)["status"].as_int(); + m_data = &(*m_root)["data"]; +} + +FileReply::~FileReply() +{ + json_delete(m_root); +} + +///////////////////////////////////////////////////////////////////////////////////////// + RobustReply::RobustReply(NETLIBHTTPREQUEST *pReply) { if (pReply == nullptr) { diff --git a/protocols/ICQ-WIM/src/http.h b/protocols/ICQ-WIM/src/http.h index bdcac34f50..0152e41ff4 100644 --- a/protocols/ICQ-WIM/src/http.h +++ b/protocols/ICQ-WIM/src/http.h @@ -36,6 +36,20 @@ public: __forceinline int detail() const { return m_detailCode; } }; +class FileReply +{ + JSONNode *m_root = nullptr; + int m_errorCode = 0; + JSONNode* m_data = nullptr; + +public: + FileReply(NETLIBHTTPREQUEST*); + ~FileReply(); + + __forceinline JSONNode& data() const { return *m_data; } + __forceinline int error() const { return m_errorCode; } +}; + class RobustReply { JSONNode *m_root = nullptr; diff --git a/protocols/ICQ-WIM/src/proto.cpp b/protocols/ICQ-WIM/src/proto.cpp index 09162fc744..0b82b7a623 100644 --- a/protocols/ICQ-WIM/src/proto.cpp +++ b/protocols/ICQ-WIM/src/proto.cpp @@ -345,9 +345,41 @@ HANDLE CIcqProto::SearchBasic(const wchar_t *pszSearch) //////////////////////////////////////////////////////////////////////////////////////// // SendFile - sends a file -HANDLE CIcqProto::SendFile(MCONTACT, const wchar_t*, wchar_t**) +HANDLE CIcqProto::SendFile(MCONTACT hContact, const wchar_t*, wchar_t **ppszFiles) { - return nullptr; // Failure + // we can't send more than one file at a time + if (ppszFiles[1] != 0) + return nullptr; + + struct _stat statbuf; + if (_wstat(ppszFiles[0], &statbuf)) { + debugLogW(L"'%s' is an invalid filename", ppszFiles[0]); + return nullptr; + } + + int iFileId = _wopen(ppszFiles[0], _O_RDONLY | _O_BINARY, _S_IREAD); + if (iFileId < 0) + return nullptr; + + auto *pTransfer = new IcqFileTransfer(hContact, ppszFiles[0]); + pTransfer->pfts.totalFiles = 1; + pTransfer->pfts.currentFileSize = pTransfer->pfts.totalBytes = statbuf.st_size; + pTransfer->m_fileId = iFileId; + + wchar_t *pwszFileName = wcsrchr(ppszFiles[0], '\\'); + if (pwszFileName != nullptr) + pwszFileName++; + else + pwszFileName = ppszFiles[0]; + + auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, "https://files.icq.com/files/init", &CIcqProto::OnFileInit); + pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("f", "json") << WCHAR_PARAM("fileName", pwszFileName) + << CHAR_PARAM("k", ICQ_APP_ID) << INT_PARAM("size", statbuf.st_size) << INT_PARAM("ts", time(0)); + CalcHash(pReq); + pReq->pUserInfo = pTransfer; + Push(pReq); + + return pTransfer; // Failure } //////////////////////////////////////////////////////////////////////////////////////// diff --git a/protocols/ICQ-WIM/src/proto.h b/protocols/ICQ-WIM/src/proto.h index 3cfd15f115..8188e75fe6 100644 --- a/protocols/ICQ-WIM/src/proto.h +++ b/protocols/ICQ-WIM/src/proto.h @@ -73,6 +73,49 @@ struct IcqConn int lastTs, timeout; }; +struct IcqFileTransfer : public MZeroedObject +{ + IcqFileTransfer(MCONTACT hContact, const wchar_t *pwszFileName) : + m_wszFileName(pwszFileName) + { + pfts.flags = PFTS_UNICODE | PFTS_SENDING; + pfts.hContact = hContact; + pfts.szCurrentFile.w = m_wszFileName.GetBuffer(); + } + + ~IcqFileTransfer() + { + if (m_fileId >= 0) + _close(m_fileId); + } + + int m_fileId = -1; + CMStringW m_wszFileName; + CMStringA m_szHost; + PROTOFILETRANSFERSTATUS pfts; + + void FillHeaders(AsyncHttpRequest *pReq) + { + pReq->AddHeader("Content-Type", "application/octet-stream"); + pReq->AddHeader("Content-Disposition", CMStringA(FORMAT, "attachment; filename=\"%s\"", T2Utf(m_wszFileName))); + + DWORD dwPortion = pfts.currentFileSize - pfts.currentFileProgress; + if (dwPortion > 1000000) + dwPortion = 1000000; + + pReq->AddHeader("Content-Range", CMStringA(FORMAT, "bytes %d-%d/%d", pfts.currentFileProgress, pfts.currentFileProgress + dwPortion - 1, pfts.currentFileSize)); + pReq->AddHeader("Content-Length", CMStringA(FORMAT, "%d", dwPortion)); + + pReq->dataLength = dwPortion; + pReq->pData = (char*)mir_alloc(dwPortion); + _lseek(m_fileId, pfts.currentFileProgress, SEEK_SET); + _read(m_fileId, pReq->pData, dwPortion); + + pfts.currentFileProgress += dwPortion; + pfts.totalProgress += dwPortion; + } +}; + class CIcqProto : public PROTO<CIcqProto> { friend struct CIcqRegistrationDlg; @@ -113,6 +156,8 @@ class CIcqProto : public PROTO<CIcqProto> void OnGetChatInfo(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnGetUserHistory(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnGetUserInfo(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnFileContinue(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnFileInit(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnLoginViaPhone(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnNormalizePhone(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*); diff --git a/protocols/ICQ-WIM/src/server.cpp b/protocols/ICQ-WIM/src/server.cpp index 9491c855e2..03c388567a 100644 --- a/protocols/ICQ-WIM/src/server.cpp +++ b/protocols/ICQ-WIM/src/server.cpp @@ -496,6 +496,73 @@ void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) StartSession(); } +void CIcqProto::OnFileContinue(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pOld) +{ + IcqFileTransfer *pTransfer = (IcqFileTransfer*)pOld->pUserInfo; + + if (pReply->resultCode != 200) { + ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, pTransfer); + delete pTransfer; + return; + } + + // file transfer succeeded? + if (pTransfer->pfts.currentFileProgress == pTransfer->pfts.currentFileSize) { + FileReply root(pReply); + if (root.error() == 200) { + ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, pTransfer); + + const JSONNode &data = root.data(); + CMStringW wszUrl(data["static_url"].as_mstring()); + SendMsg(pTransfer->pfts.hContact, 0, _T2A(wszUrl)); + } + else ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, pTransfer); + delete pTransfer; + return; + } + + // else send the next portion + auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, pTransfer->m_szHost, &CIcqProto::OnFileContinue); + pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("k", ICQ_APP_ID) << INT_PARAM("ts", time(0)); + CalcHash(pReq); + pTransfer->FillHeaders(pReq); + Push(pReq); + + pTransfer->pfts.currentFileTime = time(0); + ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, pTransfer, (LPARAM)&pTransfer->pfts); +} + +void CIcqProto::OnFileInit(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pOld) +{ + IcqFileTransfer *pTransfer = (IcqFileTransfer*)pOld->pUserInfo; + + FileReply root(pReply); + if (root.error() != 200) { + ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, pTransfer); + delete pTransfer; + return; + } + + ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, pTransfer); + + const JSONNode &data = root.data(); + CMStringW wszHost(data["host"].as_mstring()); + CMStringW wszUrl(data["url"].as_mstring()); + pTransfer->m_szHost = L"https://" + wszHost + wszUrl; + + auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_POST, pTransfer->m_szHost, &CIcqProto::OnFileContinue); + pReq << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("client", "icq") << CHAR_PARAM("k", ICQ_APP_ID) << INT_PARAM("ts", time(0)); + CalcHash(pReq); + pReq->m_szUrl.AppendChar('?'); + pReq->m_szUrl += pReq->m_szParam; pReq->m_szParam.Empty(); + pReq->pUserInfo = pTransfer; + pTransfer->FillHeaders(pReq); + Push(pReq); + + pTransfer->pfts.currentFileTime = time(0); + ProtoBroadcastAck(pTransfer->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, pTransfer, (LPARAM)&pTransfer->pfts); +} + void CIcqProto::OnGetUserHistory(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) { RobustReply root(pReply); diff --git a/protocols/ICQ-WIM/src/utils.cpp b/protocols/ICQ-WIM/src/utils.cpp index 6cf6219322..7f41347918 100644 --- a/protocols/ICQ-WIM/src/utils.cpp +++ b/protocols/ICQ-WIM/src/utils.cpp @@ -60,7 +60,10 @@ MCONTACT CIcqProto::CreateContact(DWORD dwUin, bool bTemporary) void CIcqProto::CalcHash(AsyncHttpRequest *pReq) { - CMStringA hashData(FORMAT, "POST&%s&%s", ptrA(mir_urlEncode(pReq->m_szUrl)), ptrA(mir_urlEncode(pReq->m_szParam))); + CMStringA hashData(FORMAT, "%s&%s&%s", + pReq->requestType == REQUEST_POST ? "POST" : "GET", + ptrA(mir_urlEncode(pReq->m_szUrl)), ptrA(mir_urlEncode(pReq->m_szParam))); + unsigned int len; BYTE hashOut[MIR_SHA256_HASH_SIZE]; HMAC(EVP_sha256(), m_szSessionKey, m_szSessionKey.GetLength(), (BYTE*)hashData.c_str(), hashData.GetLength(), hashOut, &len); |