summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2019-01-24 22:24:43 +0300
committerGeorge Hazan <ghazan@miranda.im>2019-01-24 22:24:43 +0300
commita2fd8ed58e918c4d0edacc96990b69d42bdaa8ba (patch)
treed5fbe5e45b6441647b8aeb8845eb527546337564
parentb60702aa86920ec25f9b700181b153d5fa41cb08 (diff)
fixes #1748 (ICQ10: add filetransfer support)
-rw-r--r--protocols/ICQ-WIM/src/http.cpp28
-rw-r--r--protocols/ICQ-WIM/src/http.h14
-rw-r--r--protocols/ICQ-WIM/src/proto.cpp36
-rw-r--r--protocols/ICQ-WIM/src/proto.h45
-rw-r--r--protocols/ICQ-WIM/src/server.cpp67
-rw-r--r--protocols/ICQ-WIM/src/utils.cpp5
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);