summaryrefslogtreecommitdiff
path: root/protocols/IcqOscarJ/src
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/IcqOscarJ/src')
-rw-r--r--protocols/IcqOscarJ/src/UI/askauthentication.cpp96
-rw-r--r--protocols/IcqOscarJ/src/UI/icqoscar.h2
-rw-r--r--protocols/IcqOscarJ/src/UI/loginpassword.cpp97
-rw-r--r--protocols/IcqOscarJ/src/UI/userinfotab.cpp315
-rw-r--r--protocols/IcqOscarJ/src/capabilities.cpp271
-rw-r--r--protocols/IcqOscarJ/src/capabilities.h45
-rw-r--r--protocols/IcqOscarJ/src/chan_01login.cpp105
-rw-r--r--protocols/IcqOscarJ/src/chan_02data.cpp222
-rw-r--r--protocols/IcqOscarJ/src/chan_03error.cpp35
-rw-r--r--protocols/IcqOscarJ/src/chan_04close.cpp314
-rw-r--r--protocols/IcqOscarJ/src/chan_05ping.cpp89
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/changeinfo.h122
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/constants.cpp198
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/db.cpp224
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp540
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/editlist.cpp201
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/editstring.cpp367
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/icqoscar.h2
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/main.cpp28
-rw-r--r--protocols/IcqOscarJ/src/changeinfo/upload.cpp91
-rw-r--r--protocols/IcqOscarJ/src/channels.h47
-rw-r--r--protocols/IcqOscarJ/src/cookies.cpp301
-rw-r--r--protocols/IcqOscarJ/src/cookies.h148
-rw-r--r--protocols/IcqOscarJ/src/directpackets.cpp293
-rw-r--r--protocols/IcqOscarJ/src/fam_01service.cpp983
-rw-r--r--protocols/IcqOscarJ/src/fam_02location.cpp312
-rw-r--r--protocols/IcqOscarJ/src/fam_03buddy.cpp787
-rw-r--r--protocols/IcqOscarJ/src/fam_04message.cpp3043
-rw-r--r--protocols/IcqOscarJ/src/fam_09bos.cpp106
-rw-r--r--protocols/IcqOscarJ/src/fam_0alookup.cpp130
-rw-r--r--protocols/IcqOscarJ/src/fam_0bstatus.cpp62
-rw-r--r--protocols/IcqOscarJ/src/fam_13servclist.cpp2068
-rw-r--r--protocols/IcqOscarJ/src/fam_15icqserver.cpp1201
-rw-r--r--protocols/IcqOscarJ/src/fam_17signon.cpp175
-rw-r--r--protocols/IcqOscarJ/src/families.h74
-rw-r--r--protocols/IcqOscarJ/src/globals.h57
-rw-r--r--protocols/IcqOscarJ/src/guids.h81
-rw-r--r--protocols/IcqOscarJ/src/i18n.cpp541
-rw-r--r--protocols/IcqOscarJ/src/i18n.h70
-rw-r--r--protocols/IcqOscarJ/src/iconlib.cpp92
-rw-r--r--protocols/IcqOscarJ/src/iconlib.h52
-rw-r--r--protocols/IcqOscarJ/src/icq_advsearch.cpp185
-rw-r--r--protocols/IcqOscarJ/src/icq_advsearch.h32
-rw-r--r--protocols/IcqOscarJ/src/icq_avatar.cpp1814
-rw-r--r--protocols/IcqOscarJ/src/icq_avatar.h127
-rw-r--r--protocols/IcqOscarJ/src/icq_clients.cpp1155
-rw-r--r--protocols/IcqOscarJ/src/icq_constants.h652
-rw-r--r--protocols/IcqOscarJ/src/icq_db.cpp399
-rw-r--r--protocols/IcqOscarJ/src/icq_db.h44
-rw-r--r--protocols/IcqOscarJ/src/icq_direct.cpp1171
-rw-r--r--protocols/IcqOscarJ/src/icq_direct.h94
-rw-r--r--protocols/IcqOscarJ/src/icq_directmsg.cpp361
-rw-r--r--protocols/IcqOscarJ/src/icq_fieldnames.cpp595
-rw-r--r--protocols/IcqOscarJ/src/icq_fieldnames.h49
-rw-r--r--protocols/IcqOscarJ/src/icq_filerequests.cpp218
-rw-r--r--protocols/IcqOscarJ/src/icq_filetransfer.cpp515
-rw-r--r--protocols/IcqOscarJ/src/icq_firstrun.cpp124
-rw-r--r--protocols/IcqOscarJ/src/icq_http.cpp212
-rw-r--r--protocols/IcqOscarJ/src/icq_http.h48
-rw-r--r--protocols/IcqOscarJ/src/icq_infoupdate.cpp401
-rw-r--r--protocols/IcqOscarJ/src/icq_menu.cpp263
-rw-r--r--protocols/IcqOscarJ/src/icq_opts.cpp617
-rw-r--r--protocols/IcqOscarJ/src/icq_packet.cpp898
-rw-r--r--protocols/IcqOscarJ/src/icq_packet.h120
-rw-r--r--protocols/IcqOscarJ/src/icq_popups.cpp323
-rw-r--r--protocols/IcqOscarJ/src/icq_popups.h41
-rw-r--r--protocols/IcqOscarJ/src/icq_proto.cpp2375
-rw-r--r--protocols/IcqOscarJ/src/icq_proto.h984
-rw-r--r--protocols/IcqOscarJ/src/icq_rates.cpp529
-rw-r--r--protocols/IcqOscarJ/src/icq_rates.h146
-rw-r--r--protocols/IcqOscarJ/src/icq_server.cpp444
-rw-r--r--protocols/IcqOscarJ/src/icq_server.h71
-rw-r--r--protocols/IcqOscarJ/src/icq_servlist.cpp2829
-rw-r--r--protocols/IcqOscarJ/src/icq_servlist.h172
-rw-r--r--protocols/IcqOscarJ/src/icq_uploadui.cpp1019
-rw-r--r--protocols/IcqOscarJ/src/icq_xstatus.cpp1381
-rw-r--r--protocols/IcqOscarJ/src/icq_xtraz.cpp466
-rw-r--r--protocols/IcqOscarJ/src/icqosc_svcs.cpp816
-rw-r--r--protocols/IcqOscarJ/src/icqosc_svcs.h39
-rw-r--r--protocols/IcqOscarJ/src/icqoscar.cpp35
-rw-r--r--protocols/IcqOscarJ/src/icqoscar.h134
-rw-r--r--protocols/IcqOscarJ/src/init.cpp245
-rw-r--r--protocols/IcqOscarJ/src/init.h40
-rw-r--r--protocols/IcqOscarJ/src/log.cpp161
-rw-r--r--protocols/IcqOscarJ/src/log.h38
-rw-r--r--protocols/IcqOscarJ/src/oscar_filetransfer.cpp2447
-rw-r--r--protocols/IcqOscarJ/src/oscar_filetransfer.h164
-rw-r--r--protocols/IcqOscarJ/src/resource.h176
-rw-r--r--protocols/IcqOscarJ/src/stdpackets.cpp1895
-rw-r--r--protocols/IcqOscarJ/src/stdpackets.h43
-rw-r--r--protocols/IcqOscarJ/src/tlv.cpp391
-rw-r--r--protocols/IcqOscarJ/src/tlv.h81
-rw-r--r--protocols/IcqOscarJ/src/utilities.cpp2183
-rw-r--r--protocols/IcqOscarJ/src/utilities.h188
-rw-r--r--protocols/IcqOscarJ/src/version.h3
95 files changed, 43940 insertions, 0 deletions
diff --git a/protocols/IcqOscarJ/src/UI/askauthentication.cpp b/protocols/IcqOscarJ/src/UI/askauthentication.cpp
new file mode 100644
index 0000000000..818d5ce6de
--- /dev/null
+++ b/protocols/IcqOscarJ/src/UI/askauthentication.cpp
@@ -0,0 +1,96 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+struct AskAuthParam
+{
+ CIcqProto* ppro;
+ HANDLE hContact;
+};
+
+static INT_PTR CALLBACK AskAuthProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ AskAuthParam* dat = (AskAuthParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ dat = (AskAuthParam*)lParam;
+ if (!dat->hContact || !dat->ppro->icqOnline())
+ EndDialog(hwndDlg, 0);
+
+ TranslateDialogDefault(hwndDlg);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ SendDlgItemMessage(hwndDlg, IDC_EDITAUTH, EM_LIMITTEXT, (WPARAM)255, 0);
+ SetDlgItemText(hwndDlg, IDC_EDITAUTH, TranslateT("Please authorize me to add you to my contact list."));
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ if (dat->ppro->icqOnline())
+ {
+ DWORD dwUin;
+ uid_str szUid;
+ if ( dat->ppro->getContactUid(dat->hContact, &dwUin, &szUid))
+ return TRUE; // Invalid contact
+
+ char* szReason = GetDlgItemTextUtf(hwndDlg, IDC_EDITAUTH);
+ dat->ppro->icq_sendAuthReqServ(dwUin, szUid, szReason);
+ SAFE_FREE((void**)&szReason);
+
+ // auth bug fix (thx Bio)
+ if (dat->ppro->m_bSsiEnabled && dwUin)
+ dat->ppro->resetServContactAuthState(dat->hContact, dwUin);
+
+ EndDialog(hwndDlg, 0);
+ }
+ return TRUE;
+
+ case IDCANCEL:
+ EndDialog(hwndDlg, 0);
+ return TRUE;
+ }
+
+ break;
+
+ case WM_CLOSE:
+ EndDialog(hwndDlg,0);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+INT_PTR CIcqProto::RequestAuthorization(WPARAM wParam, LPARAM lParam)
+{
+ AskAuthParam param = { this, (HANDLE)wParam };
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_ASKAUTH), NULL, AskAuthProc, (LPARAM)&param);
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/UI/icqoscar.h b/protocols/IcqOscarJ/src/UI/icqoscar.h
new file mode 100644
index 0000000000..77283f6f7f
--- /dev/null
+++ b/protocols/IcqOscarJ/src/UI/icqoscar.h
@@ -0,0 +1,2 @@
+/* For MinGW sake */
+#include "../icqoscar.h"
diff --git a/protocols/IcqOscarJ/src/UI/loginpassword.cpp b/protocols/IcqOscarJ/src/UI/loginpassword.cpp
new file mode 100644
index 0000000000..ab65212331
--- /dev/null
+++ b/protocols/IcqOscarJ/src/UI/loginpassword.cpp
@@ -0,0 +1,97 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+INT_PTR CALLBACK LoginPasswdDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ ppro = (CIcqProto*)lParam;
+ SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam );
+ {
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)ppro->m_hIconProtocol->GetIcon(true));
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)ppro->m_hIconProtocol->GetIcon());
+
+ DWORD dwUin = ppro->getContactUin(NULL);
+
+ char pszUIN[MAX_PATH], str[MAX_PATH];
+ null_snprintf(pszUIN, 128, ICQTranslateUtfStatic(LPGEN("Enter a password for UIN %u:"), str, MAX_PATH), dwUin);
+ SetDlgItemTextUtf(hwndDlg, IDC_INSTRUCTION, pszUIN);
+
+ SendDlgItemMessage(hwndDlg, IDC_LOGINPW, EM_LIMITTEXT, PASSWORDMAXLEN - 1, 0);
+
+ CheckDlgButton(hwndDlg, IDC_SAVEPASS, ppro->getSettingByte(NULL, "RememberPass", 0));
+ }
+ break;
+
+ case WM_DESTROY:
+ ppro->m_hIconProtocol->ReleaseIcon(true);
+ ppro->m_hIconProtocol->ReleaseIcon();
+ break;
+
+ case WM_CLOSE:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ case WM_COMMAND:
+ {
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ ppro->m_bRememberPwd = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SAVEPASS);
+ ppro->setSettingByte(NULL, "RememberPass", ppro->m_bRememberPwd);
+
+ GetDlgItemTextA(hwndDlg, IDC_LOGINPW, ppro->m_szPassword, sizeof(ppro->m_szPassword));
+
+ ppro->icq_login(ppro->m_szPassword);
+
+ EndDialog(hwndDlg, IDOK);
+ break;
+
+ case IDCANCEL:
+ ppro->SetCurrentStatus(ID_STATUS_OFFLINE);
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+ }
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+void CIcqProto::RequestPassword()
+{
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_LOGINPW), NULL, LoginPasswdDlgProc, LPARAM(this));
+}
diff --git a/protocols/IcqOscarJ/src/UI/userinfotab.cpp b/protocols/IcqOscarJ/src/UI/userinfotab.cpp
new file mode 100644
index 0000000000..91d28c0e04
--- /dev/null
+++ b/protocols/IcqOscarJ/src/UI/userinfotab.cpp
@@ -0,0 +1,315 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Code for User details ICQ specific pages
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+#define SVS_NORMAL 0
+#define SVS_ZEROISUNSPEC 2
+#define SVS_IP 3
+#define SVS_SIGNED 6
+#define SVS_ICQVERSION 8
+#define SVS_TIMESTAMP 9
+#define SVS_STATUSID 10
+
+char* MirandaVersionToString(char* szStr, int bUnicode, int v, int m);
+
+extern const char *nameXStatus[];
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static void SetValue(CIcqProto* ppro, HWND hwndDlg, int idCtrl, HANDLE hContact, char* szModule, char* szSetting, int special)
+{
+ DBVARIANT dbv = {0};
+ char str[MAX_PATH];
+ char* pstr = NULL;
+ int unspecified = 0;
+ int bUtf = 0, bDbv = 0, bAlloc = 0;
+
+ dbv.type = DBVT_DELETED;
+
+ if ((hContact == NULL) && ((int)szModule<0x100))
+ {
+ dbv.type = (BYTE)szModule;
+
+ switch((int)szModule) {
+ case DBVT_BYTE:
+ dbv.cVal = (BYTE)szSetting;
+ break;
+ case DBVT_WORD:
+ dbv.wVal = (WORD)szSetting;
+ break;
+ case DBVT_DWORD:
+ dbv.dVal = (DWORD)szSetting;
+ break;
+ case DBVT_ASCIIZ:
+ dbv.pszVal = pstr = szSetting;
+ break;
+ default:
+ unspecified = 1;
+ dbv.type = DBVT_DELETED;
+ }
+ }
+ else
+ {
+ if (szModule == NULL)
+ unspecified = 1;
+ else
+ {
+ unspecified = DBGetContactSetting(hContact, szModule, szSetting, &dbv);
+ bDbv = 1;
+ }
+ }
+
+ if (!unspecified)
+ {
+ switch (dbv.type) {
+ case DBVT_BYTE:
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.bVal == 0);
+ pstr = _itoa(special == SVS_SIGNED ? dbv.cVal:dbv.bVal, str, 10);
+ break;
+
+ case DBVT_WORD:
+ if (special == SVS_ICQVERSION)
+ {
+ if (dbv.wVal != 0)
+ {
+ char szExtra[80];
+
+ null_snprintf(str, 250, "%d", dbv.wVal);
+ pstr = str;
+
+ if (hContact && ppro->IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 1))
+ {
+ ICQTranslateUtfStatic(LPGEN(" (DC Established)"), szExtra, 80);
+ strcat(str, (char*)szExtra);
+ bUtf = 1;
+ }
+ }
+ else
+ unspecified = 1;
+ }
+ else if (special == SVS_STATUSID)
+ {
+ char *pXName;
+ char *pszStatus = MirandaStatusToStringUtf(dbv.wVal);
+ BYTE bXStatus = ppro->getContactXStatus(hContact);
+
+ if (bXStatus)
+ {
+ pXName = ppro->getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, NULL);
+ if (!strlennull(pXName))
+ { // give default name
+ pXName = ICQTranslateUtf(nameXStatus[bXStatus-1]);
+ }
+ null_snprintf(str, sizeof(str), "%s (%s)", pszStatus, pXName);
+ SAFE_FREE((void**)&pXName);
+ }
+ else
+ null_snprintf(str, sizeof(str), pszStatus);
+
+ bUtf = 1;
+ SAFE_FREE(&pszStatus);
+ pstr = str;
+ unspecified = 0;
+ }
+ else
+ {
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.wVal == 0);
+ pstr = _itoa(special == SVS_SIGNED ? dbv.sVal:dbv.wVal, str, 10);
+ }
+ break;
+
+ case DBVT_DWORD:
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.dVal == 0);
+ if (special == SVS_IP)
+ {
+ struct in_addr ia;
+ ia.S_un.S_addr = htonl(dbv.dVal);
+ pstr = inet_ntoa(ia);
+ if (dbv.dVal == 0)
+ unspecified=1;
+ }
+ else if (special == SVS_TIMESTAMP)
+ {
+ if (dbv.dVal == 0)
+ unspecified = 1;
+ else
+ pstr = time2text(dbv.dVal);
+ }
+ else
+ pstr = _itoa(special == SVS_SIGNED ? dbv.lVal:dbv.dVal, str, 10);
+ break;
+
+ case DBVT_ASCIIZ:
+ case DBVT_WCHAR:
+ unspecified = (special == SVS_ZEROISUNSPEC && dbv.pszVal[0] == '\0');
+ if (!unspecified && pstr != szSetting)
+ {
+ pstr = ppro->getSettingStringUtf(hContact, szModule, szSetting, NULL);
+ bUtf = 1;
+ bAlloc = 1;
+ }
+ if (idCtrl == IDC_UIN)
+ SetDlgItemTextUtf(hwndDlg, IDC_UINSTATIC, ICQTranslateUtfStatic(LPGEN("ScreenName:"), str, MAX_PATH));
+ break;
+
+ default:
+ pstr = str;
+ strcpy(str,"???");
+ break;
+ }
+ }
+
+ EnableDlgItem(hwndDlg, idCtrl, !unspecified);
+ if (unspecified)
+ SetDlgItemTextUtf(hwndDlg, idCtrl, ICQTranslateUtfStatic(LPGEN("<not specified>"), str, MAX_PATH));
+ else if (bUtf)
+ SetDlgItemTextUtf(hwndDlg, idCtrl, pstr);
+ else
+ SetDlgItemTextA(hwndDlg, idCtrl, pstr);
+
+ if (bDbv)
+ ICQFreeVariant(&dbv);
+
+ if (bAlloc)
+ SAFE_FREE(&pstr);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static INT_PTR CALLBACK IcqDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ break;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_PARAMCHANGED:
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (( PSHNOTIFY* )lParam )->lParam );
+ break;
+
+ case PSN_INFOCHANGED:
+ {
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (!ppro)
+ break;
+
+ char* szProto;
+ HANDLE hContact = (HANDLE)((LPPSHNOTIFY)lParam)->lParam;
+
+ if (hContact == NULL)
+ szProto = ppro->m_szModuleName;
+ else
+ szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
+
+ if (!szProto)
+ break;
+
+ SetValue(ppro, hwndDlg, IDC_UIN, hContact, szProto, UNIQUEIDSETTING, SVS_NORMAL);
+ SetValue(ppro, hwndDlg, IDC_ONLINESINCE, hContact, szProto, "LogonTS", SVS_TIMESTAMP);
+ SetValue(ppro, hwndDlg, IDC_IDLETIME, hContact, szProto, "IdleTS", SVS_TIMESTAMP);
+ SetValue(ppro, hwndDlg, IDC_IP, hContact, szProto, "IP", SVS_IP);
+ SetValue(ppro, hwndDlg, IDC_REALIP, hContact, szProto, "RealIP", SVS_IP);
+
+ if (hContact)
+ {
+ SetValue(ppro, hwndDlg, IDC_PORT, hContact, szProto, "UserPort", SVS_ZEROISUNSPEC);
+ SetValue(ppro, hwndDlg, IDC_VERSION, hContact, szProto, "Version", SVS_ICQVERSION);
+ SetValue(ppro, hwndDlg, IDC_MIRVER, hContact, szProto, "MirVer", SVS_ZEROISUNSPEC);
+ if (ppro->getSettingByte(hContact, "ClientID", 0))
+ ppro->setSettingDword(hContact, "TickTS", 0);
+ SetValue(ppro, hwndDlg, IDC_SYSTEMUPTIME, hContact, szProto, "TickTS", SVS_TIMESTAMP);
+ SetValue(ppro, hwndDlg, IDC_STATUS, hContact, szProto, "Status", SVS_STATUSID);
+ }
+ else
+ {
+ char str[MAX_PATH];
+
+ SetValue(ppro, hwndDlg, IDC_PORT, hContact, (char*)DBVT_WORD, (char*)ppro->wListenPort, SVS_ZEROISUNSPEC);
+ SetValue(ppro, hwndDlg, IDC_VERSION, hContact, (char*)DBVT_WORD, (char*)ICQ_VERSION, SVS_ICQVERSION);
+ SetValue(ppro, hwndDlg, IDC_MIRVER, hContact, (char*)DBVT_ASCIIZ, MirandaVersionToString(str, TRUE, ICQ_PLUG_VERSION, CallService(MS_SYSTEM_GETVERSION,0,0)), SVS_ZEROISUNSPEC);
+ SetDlgItemTextUtf(hwndDlg, IDC_SUPTIME, ICQTranslateUtfStatic(LPGEN("Member since:"), str, MAX_PATH));
+ SetValue(ppro, hwndDlg, IDC_SYSTEMUPTIME, hContact, szProto, "MemberTS", SVS_TIMESTAMP);
+ SetValue(ppro, hwndDlg, IDC_STATUS, hContact, (char*)DBVT_WORD, (char*)ppro->m_iStatus, SVS_STATUSID);
+ }
+ }
+ break;
+ }
+ break;
+ }
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDCANCEL:
+ SendMessage(GetParent(hwndDlg),msg,wParam,lParam);
+ break;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CIcqProto::OnUserInfoInit(WPARAM wParam, LPARAM lParam)
+{
+ if ((!IsICQContact((HANDLE)lParam)) && lParam)
+ return 0;
+
+ OPTIONSDIALOGPAGE odp = {0};
+ odp.cbSize = sizeof(odp);
+ odp.flags = ODPF_TCHAR | ODPF_DONTTRANSLATE;
+ odp.hInstance = hInst;
+ odp.dwInitParam = LPARAM(this);
+ odp.pfnDlgProc = IcqDlgProc;
+ odp.position = -1900000000;
+ odp.ptszTitle = m_tszUserName;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_ICQ);
+ UserInfo_AddPage(wParam, &odp);
+
+ if (!lParam)
+ {
+ TCHAR buf[200];
+ null_snprintf(buf, SIZEOF(buf), TranslateT("%s Details"), m_tszUserName);
+ odp.ptszTitle = buf;
+
+ odp.position = -1899999999;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_INFO_CHANGEINFO);
+ odp.pfnDlgProc = ChangeInfoDlgProc;
+ UserInfo_AddPage(wParam, &odp);
+ }
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/capabilities.cpp b/protocols/IcqOscarJ/src/capabilities.cpp
new file mode 100644
index 0000000000..f994f861af
--- /dev/null
+++ b/protocols/IcqOscarJ/src/capabilities.cpp
@@ -0,0 +1,271 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Contains helper functions to handle oscar user capabilities. Scanning and
+// adding capabilities are assumed to be more timecritical than looking up
+// capabilites. During the login sequence there could possibly be many hundred
+// scans but only a few lookups. So when you add or change something in this
+// code you must have this in mind, dont do anything that will slow down the
+// adding process too much.
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+struct icq_capability
+{
+ DWORD capID; // A bitmask, we use it in order to save database space
+ capstr capCLSID; // A binary representation of a oscar capability
+};
+
+static const icq_capability CapabilityRecord[] =
+{
+ {CAPF_SRV_RELAY, {CAP_SRV_RELAY }},
+ {CAPF_UTF, {CAP_UTF }},
+ {CAPF_RTF, {CAP_RTF }},
+ {CAPF_CONTACTS, {CAP_CONTACTS }},
+ {CAPF_TYPING, {CAP_TYPING }},
+ {CAPF_ICQDIRECT, {CAP_ICQDIRECT }},
+ {CAPF_XTRAZ, {CAP_XTRAZ }},
+ {CAPF_OSCAR_FILE,{CAP_OSCAR_FILE}}
+};
+
+// Mask of all handled capabilities' flags
+#define CapabilityFlagsMask (CAPF_SRV_RELAY | CAPF_UTF | CAPF_RTF | CAPF_CONTACTS | CAPF_TYPING | CAPF_ICQDIRECT | CAPF_XTRAZ | CAPF_OSCAR_FILE)
+
+
+#ifdef _DEBUG
+struct icq_capability_name
+{
+ DWORD capID;
+ const char* capName;
+};
+
+static const icq_capability_name CapabilityNames[] =
+{
+ {CAPF_SRV_RELAY, "ServerRelay"},
+ {CAPF_UTF, "UTF8 Messages"},
+ {CAPF_RTF, "RTF Messages"},
+ {CAPF_CONTACTS, "Contact Transfer"},
+ {CAPF_TYPING, "Typing Notifications"},
+ {CAPF_ICQDIRECT, "Direct Connections"},
+ {CAPF_XTRAZ, "Xtraz"},
+ {CAPF_OSCAR_FILE, "File Transfers"},
+ {CAPF_STATUS_MESSAGES,"Individual Status Messages"},
+ {CAPF_STATUS_MOOD, "Mood"},
+ {CAPF_XSTATUS, "Custom Status"}
+};
+
+void NetLog_CapabilityChange(CIcqProto *ppro, const char *szChange, DWORD fdwCapabilities)
+{
+ char szBuffer[MAX_PATH] = {0};
+
+ if (!fdwCapabilities) return;
+
+ for (int nIndex = 0; nIndex < SIZEOF(CapabilityNames); nIndex++)
+ {
+ // Check if the current capability is present
+ if ((fdwCapabilities & CapabilityNames[nIndex].capID) == CapabilityNames[nIndex].capID)
+ {
+ if (strlennull(szBuffer))
+ strcat(szBuffer, ", ");
+ strcat(szBuffer, CapabilityNames[nIndex].capName);
+ }
+ }
+ // Log the change
+ ppro->NetLog_Server("Capabilities: %s %s", szChange, szBuffer);
+}
+#endif
+
+
+// Deletes all oscar capabilities for a given contact
+void CIcqProto::ClearAllContactCapabilities(HANDLE hContact)
+{
+ setSettingDword(hContact, DBSETTING_CAPABILITIES, 0);
+}
+
+
+// Deletes one or many oscar capabilities for a given contact
+void CIcqProto::ClearContactCapabilities(HANDLE hContact, DWORD fdwCapabilities)
+{
+ // Get current capability flags
+ DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0);
+
+ if (fdwContactCaps != (fdwContactCaps & ~fdwCapabilities))
+ {
+#ifdef _DEBUG
+ NetLog_CapabilityChange(this, "Removed", fdwCapabilities & fdwContactCaps);
+#endif
+ // Clear unwanted capabilities
+ fdwContactCaps &= ~fdwCapabilities;
+
+ // And write it back to disk
+ setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps);
+ }
+}
+
+
+// Sets one or many oscar capabilities for a given contact
+void CIcqProto::SetContactCapabilities(HANDLE hContact, DWORD fdwCapabilities)
+{
+ // Get current capability flags
+ DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0);
+
+ if (fdwContactCaps != (fdwContactCaps | fdwCapabilities))
+ {
+#ifdef _DEBUG
+ NetLog_CapabilityChange(this, "Added", fdwCapabilities & ~fdwContactCaps);
+#endif
+ // Update them
+ fdwContactCaps |= fdwCapabilities;
+
+ // And write it back to disk
+ setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps);
+ }
+}
+
+
+// Returns true if the given contact supports the requested capabilites
+BOOL CIcqProto::CheckContactCapabilities(HANDLE hContact, DWORD fdwCapabilities)
+{
+ // Get current capability flags
+ DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0);
+
+ // Check if all requested capabilities are supported
+ if ((fdwContactCaps & fdwCapabilities) == fdwCapabilities)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+// Scan capability against the capability buffer
+capstr* MatchCapability(BYTE *buf, int bufsize, const capstr *cap, int capsize)
+{
+ while (bufsize >= BINARY_CAP_SIZE) // search the buffer for a capability
+ {
+ if (!memcmp(buf, cap, capsize))
+ {
+ return (capstr*)buf; // give found capability for version info
+ }
+ else
+ {
+ buf += BINARY_CAP_SIZE;
+ bufsize -= BINARY_CAP_SIZE;
+ }
+ }
+ return 0;
+}
+
+
+// Scan short capability against the capability buffer
+capstr* MatchShortCapability(BYTE *buf, int bufsize, const shortcapstr *cap)
+{
+ capstr fullCap;
+
+ memcpy(fullCap, capShortCaps, BINARY_CAP_SIZE);
+ fullCap[2] = (*cap)[0];
+ fullCap[3] = (*cap)[1];
+
+ return MatchCapability(buf, bufsize, &fullCap, BINARY_CAP_SIZE);
+}
+
+
+// Scans a binary buffer for OSCAR capabilities.
+DWORD GetCapabilitiesFromBuffer(BYTE *pBuffer, int nLength)
+{
+ DWORD fdwCaps = 0;
+
+ // Calculate the number of records
+ int nRecordSize = SIZEOF(CapabilityRecord);
+
+ // Loop over all capabilities in the buffer and
+ // compare them to our own record of capabilities
+ for (int nIndex = 0; nIndex < nRecordSize; nIndex++)
+ {
+ if (MatchCapability(pBuffer, nLength, &CapabilityRecord[nIndex].capCLSID, BINARY_CAP_SIZE))
+ { // Match, add capability flag
+ fdwCaps |= CapabilityRecord[nIndex].capID;
+ }
+ }
+
+ return fdwCaps;
+}
+
+
+// Scans a binary buffer for oscar capabilities and adds them to the contact.
+// You probably want to call ClearAllContactCapabilities() first.
+void CIcqProto::AddCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength)
+{
+ // Get current capability flags
+ DWORD fdwContactCaps = getSettingDword(hContact, DBSETTING_CAPABILITIES, 0);
+ // Get capability flags from buffer
+ DWORD fdwCapabilities = GetCapabilitiesFromBuffer(pBuffer, nLength);
+
+ if (fdwContactCaps != (fdwContactCaps | fdwCapabilities))
+ {
+#ifdef _DEBUG
+ NetLog_CapabilityChange(this, "Added", fdwCapabilities & ~fdwContactCaps);
+#endif
+ // Add capability flags from buffer
+ fdwContactCaps |= fdwCapabilities;
+
+ // And write them back to database
+ setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps);
+ }
+}
+
+
+// Scans a binary buffer for oscar capabilities and adds them to the contact.
+// You probably want to call ClearAllContactCapabilities() first.
+void CIcqProto::SetCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength, BOOL bReset)
+{
+ // Get current capability flags
+ DWORD fdwContactCaps = bReset ? 0 : getSettingDword(hContact, DBSETTING_CAPABILITIES, 0);
+ // Get capability flags from buffer
+ DWORD fdwCapabilities = GetCapabilitiesFromBuffer(pBuffer, nLength);
+
+#ifdef _DEBUG
+ if (bReset)
+ NetLog_CapabilityChange(this, "Set", fdwCapabilities);
+ else
+ {
+ NetLog_CapabilityChange(this, "Removed", fdwContactCaps & ~fdwCapabilities & CapabilityFlagsMask);
+ NetLog_CapabilityChange(this, "Added", fdwCapabilities & ~fdwContactCaps);
+ }
+#endif
+
+ if (fdwCapabilities != (fdwContactCaps & ~CapabilityFlagsMask))
+ { // Get current unmanaged capability flags
+ fdwContactCaps &= ~CapabilityFlagsMask;
+
+ // Add capability flags from buffer
+ fdwContactCaps |= fdwCapabilities;
+
+ // And write them back to database
+ setSettingDword(hContact, DBSETTING_CAPABILITIES, fdwContactCaps);
+ }
+}
diff --git a/protocols/IcqOscarJ/src/capabilities.h b/protocols/IcqOscarJ/src/capabilities.h
new file mode 100644
index 0000000000..3b6590dd09
--- /dev/null
+++ b/protocols/IcqOscarJ/src/capabilities.h
@@ -0,0 +1,45 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Contains helper functions to handle OSCAR user capabilities.
+//
+// -----------------------------------------------------------------------------
+
+#ifndef __CAPABILITIES_H
+#define __CAPABILITIES_H
+
+
+// capabilities
+typedef BYTE capstr[BINARY_CAP_SIZE];
+typedef BYTE shortcapstr[BINARY_SHORT_CAP_SIZE];
+
+extern const capstr capShortCaps;
+
+capstr* MatchCapability(BYTE *buf, int bufsize, const capstr *cap, int capsize = BINARY_CAP_SIZE);
+capstr* MatchShortCapability(BYTE *buf, int bufsize, const shortcapstr *cap);
+
+
+#endif /* __CAPABILITIES_H */
diff --git a/protocols/IcqOscarJ/src/chan_01login.cpp b/protocols/IcqOscarJ/src/chan_01login.cpp
new file mode 100644
index 0000000000..3c4c4febc6
--- /dev/null
+++ b/protocols/IcqOscarJ/src/chan_01login.cpp
@@ -0,0 +1,105 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::handleLoginChannel(BYTE *buf, WORD datalen, serverthread_info *info)
+{
+ icq_packet packet;
+
+#ifdef _DEBUG
+ NetLog_Server("Received SRV_HELLO from %s", info->isLoginServer ? "login server" : "communication server");
+#endif
+
+ // isLoginServer is "1" if we just received SRV_HELLO
+ if (info->isLoginServer)
+ {
+ if (m_bSecureLogin)
+ {
+ char szUin[UINMAXLEN];
+ WORD wUinLen;
+
+#ifdef _DEBUG
+ NetLog_Server("Sending %s to %s", "CLI_HELLO", "login server");
+#endif
+ packet.wLen = 12;
+ write_flap(&packet, ICQ_LOGIN_CHAN);
+ packDWord(&packet, 0x00000001);
+ packTLVDWord(&packet, 0x8003, 0x00100000); // unknown
+ sendServPacket(&packet); // greet login server
+
+ wUinLen = strlennull(strUID(m_dwLocalUIN, szUin));
+#ifdef _DEBUG
+ NetLog_Server("Sending %s to %s", "ICQ_SIGNON_AUTH_REQUEST", "login server");
+#endif
+
+ serverPacketInit(&packet, (WORD)(14 + wUinLen));
+ packFNACHeader(&packet, ICQ_AUTHORIZATION_FAMILY, ICQ_SIGNON_AUTH_REQUEST, 0, 0);
+ packTLV(&packet, 0x0001, wUinLen, (LPBYTE)szUin);
+ sendServPacket(&packet); // request login digest
+ }
+ else
+ {
+ sendClientAuth((char*)info->szAuthKey, info->wAuthKeyLen, FALSE);
+#ifdef _DEBUG
+ NetLog_Server("Sent CLI_IDENT to %s", "login server");
+#endif
+ }
+
+ info->isLoginServer = 0;
+ if (info->cookieDataLen)
+ {
+ SAFE_FREE((void**)&info->cookieData);
+ info->cookieDataLen = 0;
+ }
+ }
+ else
+ {
+ if (info->cookieDataLen)
+ {
+ wLocalSequence = generate_flap_sequence();
+
+ serverCookieInit(&packet, info->cookieData, (WORD)info->cookieDataLen);
+ sendServPacket(&packet);
+
+#ifdef _DEBUG
+ NetLog_Server("Sent CLI_IDENT to %s", "communication server");
+#endif
+
+ SAFE_FREE((void**)&info->cookieData);
+ info->cookieDataLen = 0;
+ }
+ else
+ {
+ // We need a cookie to identify us to the communication server
+ NetLog_Server("Error: Connected to %s without a cookie!", "communication server");
+ }
+ }
+}
diff --git a/protocols/IcqOscarJ/src/chan_02data.cpp b/protocols/IcqOscarJ/src/chan_02data.cpp
new file mode 100644
index 0000000000..d55e0262e9
--- /dev/null
+++ b/protocols/IcqOscarJ/src/chan_02data.cpp
@@ -0,0 +1,222 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Handle channel 2 (Data) packets
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::handleDataChannel(BYTE *pBuffer, WORD wBufferLength, serverthread_info *info)
+{
+ snac_header snacHeader = {0};
+
+ if (!unpackSnacHeader(&snacHeader, &pBuffer, &wBufferLength) || !snacHeader.bValid)
+ {
+ NetLog_Server("Error: Failed to parse SNAC header");
+ }
+ else
+ {
+#ifdef _DEBUG
+ if (snacHeader.wFlags & 0x8000)
+ NetLog_Server(" Received SNAC(x%02X,x%02X), version %u", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wVersion);
+ else
+ NetLog_Server(" Received SNAC(x%02X,x%02X)", snacHeader.wFamily, snacHeader.wSubtype);
+#endif
+
+ switch (snacHeader.wFamily) {
+
+ case ICQ_SERVICE_FAMILY:
+ handleServiceFam(pBuffer, wBufferLength, &snacHeader, info);
+ break;
+
+ case ICQ_LOCATION_FAMILY:
+ handleLocationFam(pBuffer, wBufferLength, &snacHeader);
+ break;
+
+ case ICQ_BUDDY_FAMILY:
+ handleBuddyFam(pBuffer, wBufferLength, &snacHeader, info);
+ break;
+
+ case ICQ_MSG_FAMILY:
+ handleMsgFam(pBuffer, wBufferLength, &snacHeader);
+ break;
+
+ case ICQ_BOS_FAMILY:
+ handleBosFam(pBuffer, wBufferLength, &snacHeader);
+ break;
+
+ case ICQ_LOOKUP_FAMILY:
+ handleLookupFam(pBuffer, wBufferLength, &snacHeader);
+ break;
+
+ case ICQ_STATS_FAMILY:
+ handleStatusFam(pBuffer, wBufferLength, &snacHeader);
+ break;
+
+ case ICQ_LISTS_FAMILY:
+ handleServCListFam(pBuffer, wBufferLength, &snacHeader, info);
+ break;
+
+ case ICQ_EXTENSIONS_FAMILY:
+ handleIcqExtensionsFam(pBuffer, wBufferLength, &snacHeader);
+ break;
+
+ case ICQ_AUTHORIZATION_FAMILY:
+ handleAuthorizationFam(pBuffer, wBufferLength, &snacHeader, info);
+ break;
+
+ default:
+ NetLog_Server("Ignoring SNAC(x%02X,x%02X) - FAMILYx%02X not implemented", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wFamily);
+ break;
+
+ }
+ }
+}
+
+
+int unpackSnacHeader(snac_header *pSnacHeader, BYTE **pBuffer, WORD *pwBufferLength)
+{
+ WORD wRef1, wRef2;
+
+ // Check header
+ if (!pSnacHeader) return 0;
+
+ // 10 bytes is the minimum size of a header
+ if (*pwBufferLength < 10)
+ {
+ // Buffer overflow
+ pSnacHeader->bValid = FALSE;
+ return 1;
+ }
+
+ // Unpack all the standard data
+ unpackWord(pBuffer, &(pSnacHeader->wFamily));
+ unpackWord(pBuffer, &(pSnacHeader->wSubtype));
+ unpackWord(pBuffer, &(pSnacHeader->wFlags));
+ unpackWord(pBuffer, &wRef1); // unpack reference id (sequence)
+ unpackWord(pBuffer, &wRef2); // command
+ pSnacHeader->dwRef = wRef1 | (wRef2<<0x10);
+
+ *pwBufferLength -= 10;
+
+ // If flag bit 15 is set, we also have a version tag
+ // (...at least that is what I think it is)
+ if (pSnacHeader->wFlags & 0x8000)
+ {
+ if (*pwBufferLength >= 2)
+ {
+ WORD wExtraBytes = 0;
+
+ unpackWord(pBuffer, &wExtraBytes);
+ *pwBufferLength -= 2;
+
+ if (*pwBufferLength >= wExtraBytes)
+ {
+ if (wExtraBytes == 6)
+ {
+ *pBuffer += 4; // TLV type and length?
+ unpackWord(pBuffer, &(pSnacHeader->wVersion));
+ *pwBufferLength -= wExtraBytes;
+ pSnacHeader->bValid = TRUE;
+ }
+ else if (wExtraBytes == 0x0E)
+ {
+ *pBuffer += 8; // TLV(2) - unknown
+ *pBuffer += 4;
+ unpackWord(pBuffer, &(pSnacHeader->wVersion));
+ *pwBufferLength -= wExtraBytes;
+ pSnacHeader->bValid = TRUE;
+ }
+ else
+ {
+ *pBuffer += wExtraBytes;
+ *pwBufferLength -= wExtraBytes;
+ pSnacHeader->bValid = TRUE;
+ }
+ }
+ else
+ {
+ // Buffer overflow
+ pSnacHeader->bValid = FALSE;
+ }
+ }
+ else
+ {
+ // Buffer overflow
+ pSnacHeader->bValid = FALSE;
+ }
+ }
+ else
+ {
+ pSnacHeader->bValid = TRUE;
+ }
+
+ return 1;
+}
+
+
+void CIcqProto::LogFamilyError(WORD wFamily, WORD wError)
+{
+ char *msg;
+
+ switch(wError) {
+ case 0x01: msg = "Invalid SNAC header"; break;
+ case 0x02: msg = "Server rate limit exceeded"; break;
+ case 0x03: msg = "Client rate limit exceeded"; break;
+ case 0x04: msg = "Recipient is not logged in"; break;
+ case 0x05: msg = "Requested service unavailable"; break;
+ case 0x06: msg = "Requested service not defined"; break;
+ case 0x07: msg = "You sent obsolete SNAC"; break;
+ case 0x08: msg = "Not supported by server"; break;
+ case 0x09: msg = "Not supported by client"; break;
+ case 0x0A: msg = "Refused by client"; break;
+ case 0x0B: msg = "Reply too big"; break;
+ case 0x0C: msg = "Responses lost"; break;
+ case 0x0D: msg = "Request denied"; break;
+ case 0x0E: msg = "Incorrect SNAC format"; break;
+ case 0x0F: msg = "Insufficient rights"; break;
+ case 0x10: msg = "In local permit/deny (recipient blocked)"; break;
+ case 0x11: msg = "Sender is too evil"; break;
+ case 0x12: msg = "Receiver is too evil"; break;
+ case 0x13: msg = "User temporarily unavailable"; break;
+ case 0x14: msg = "No match"; break;
+ case 0x15: msg = "List overflow"; break;
+ case 0x16: msg = "Request ambiguous"; break;
+ case 0x17: msg = "Server queue full"; break;
+ case 0x18: msg = "Not while on AOL"; break;
+ case 0x19: msg = "Query failed"; break;
+ case 0x1A: msg = "Timeout"; break;
+ case 0x1C: msg = "General failure"; break;
+ case 0x1D: msg = "Progress"; break;
+ case 0x1E: msg = "In free area"; break;
+ case 0x1F: msg = "Restricted by parental controls"; break;
+ case 0x20: msg = "Remote restricted by parental controls"; break;
+ default: msg = ""; break;
+ }
+
+ NetLog_Server("SNAC(x%02X,x01) - Error(%u): %s", wFamily, wError, msg);
+}
diff --git a/protocols/IcqOscarJ/src/chan_03error.cpp b/protocols/IcqOscarJ/src/chan_03error.cpp
new file mode 100644
index 0000000000..71c6e2ea7c
--- /dev/null
+++ b/protocols/IcqOscarJ/src/chan_03error.cpp
@@ -0,0 +1,35 @@
+// ---------------------------------------------------------------------------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,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater
+// Copyright © 2004,2005 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void CIcqProto::handleErrorChannel(unsigned char* buf, WORD datalen)
+{
+ NetLog_Server("Ignoring server packet on ERROR channel");
+}
diff --git a/protocols/IcqOscarJ/src/chan_04close.cpp b/protocols/IcqOscarJ/src/chan_04close.cpp
new file mode 100644
index 0000000000..3c9ee46350
--- /dev/null
+++ b/protocols/IcqOscarJ/src/chan_04close.cpp
@@ -0,0 +1,314 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::handleCloseChannel(BYTE *buf, WORD datalen, serverthread_info *info)
+{
+ oscar_tlv_chain *chain = NULL;
+
+ // Parse server reply, prepare reconnection
+ if (!info->bLoggedIn && datalen && !info->newServerReady)
+ handleLoginReply(buf, datalen, info);
+
+ if (info->isMigrating)
+ handleMigration(info);
+
+ if ((!info->bLoggedIn || info->isMigrating) && info->newServerReady)
+ {
+ if (!connectNewServer(info))
+ { // Connecting failed
+ if (info->isMigrating)
+ icq_LogUsingErrorCode(LOG_ERROR, GetLastError(), LPGEN("Unable to connect to migrated ICQ communication server"));
+ else
+ icq_LogUsingErrorCode(LOG_ERROR, GetLastError(), LPGEN("Unable to connect to ICQ communication server"));
+
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+
+ info->isMigrating = 0;
+ }
+ info->newServerReady = 0;
+
+ return;
+ }
+
+ if (chain = readIntoTLVChain(&buf, datalen, 0))
+ {
+ // TLV 9 errors (runtime errors?)
+ WORD wError = chain->getWord(0x09, 1);
+ if (wError)
+ {
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+
+ handleRuntimeError(wError);
+ }
+
+ disposeChain(&chain);
+ }
+ // Server closed connection on error, or sign off
+ NetLib_CloseConnection(&hServerConn, TRUE);
+}
+
+
+void CIcqProto::handleLoginReply(BYTE *buf, WORD datalen, serverthread_info *info)
+{
+ oscar_tlv_chain *chain = NULL;
+
+ icq_sendCloseConnection(); // imitate icq5 behaviour
+
+ if (!(chain = readIntoTLVChain(&buf, datalen, 0)))
+ {
+ NetLog_Server("Error: Missing chain on close channel");
+ NetLib_CloseConnection(&hServerConn, TRUE);
+ return; // Invalid data
+ }
+
+ // TLV 8 errors (signon errors?)
+ WORD wError = chain->getWord(0x08, 1);
+ if (wError)
+ {
+ handleSignonError(wError);
+
+ // we return only if the server did not gave us cookie (possible to connect with soft error)
+ if (!chain->getLength(0x06, 1))
+ {
+ disposeChain(&chain);
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ icq_serverDisconnect(FALSE);
+ return; // Failure
+ }
+ }
+
+ // We are in the login phase and no errors were reported.
+ // Extract communication server info.
+ info->newServer = chain->getString(0x05, 1);
+ info->newServerSSL = chain->getNumber(0x8E, 1);
+ info->cookieData = (BYTE*)chain->getString(0x06, 1);
+ info->cookieDataLen = chain->getLength(0x06, 1);
+
+ // We dont need this anymore
+ disposeChain(&chain);
+
+ if (!info->newServer || !info->cookieData)
+ {
+ icq_LogMessage(LOG_FATAL, LPGEN("You could not sign on because the server returned invalid data. Try again."));
+
+ SAFE_FREE(&info->newServer);
+ SAFE_FREE((void**)&info->cookieData);
+ info->cookieDataLen = 0;
+
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ NetLib_CloseConnection(&hServerConn, TRUE);
+ return; // Failure
+ }
+
+ NetLog_Server("Authenticated.");
+ info->newServerReady = 1;
+
+ return;
+}
+
+
+int CIcqProto::connectNewServer(serverthread_info *info)
+{
+ int res = 0;
+
+ /* Get the ip and port */
+ WORD wServerPort = info->wServerPort; // prepare default port
+ parseServerAddress(info->newServer, &wServerPort);
+
+ NETLIBOPENCONNECTION nloc = {0};
+ nloc.flags = 0;
+ nloc.szHost = info->newServer;
+ nloc.wPort = wServerPort;
+
+ if (!m_bGatewayMode)
+ {
+ NetLib_SafeCloseHandle(&info->hPacketRecver);
+ NetLib_CloseConnection(&hServerConn, TRUE);
+
+ NetLog_Server("Closed connection to login server");
+
+ hServerConn = NetLib_OpenConnection(m_hServerNetlibUser, NULL, &nloc);
+ if (hServerConn && info->newServerSSL)
+ { /* Start SSL session if requested */
+ if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hServerConn, 0))
+ NetLib_CloseConnection(&hServerConn, FALSE);
+ }
+
+ if (hServerConn)
+ {
+ /* Time to recreate the packet receiver */
+ info->hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)hServerConn, 0x2400);
+ if (!info->hPacketRecver)
+ {
+ NetLog_Server("Error: Failed to create packet receiver.");
+ }
+ else // we need to reset receiving structs
+ {
+ info->bReinitRecver = 1;
+ res = 1;
+ }
+ }
+ }
+ else
+ { // TODO: We should really do some checks here
+ NetLog_Server("Walking in Gateway to %s", info->newServer);
+ // TODO: This REQUIRES more work (most probably some kind of mid-netlib module)
+ icq_httpGatewayWalkTo(hServerConn, &nloc);
+ res = 1;
+ }
+ if (!res) SAFE_FREE((void**)&info->cookieData);
+
+ // Free allocated memory
+ // NOTE: "cookie" will get freed when we have connected to the communication server.
+ SAFE_FREE(&info->newServer);
+
+ return res;
+}
+
+
+void CIcqProto::handleMigration(serverthread_info *info)
+{
+ // Check the data that was saved when the migration was announced
+ NetLog_Server("Migrating to %s", info->newServer);
+ if (!info->newServer || !info->cookieData)
+ {
+ icq_LogMessage(LOG_FATAL, LPGEN("You have been disconnected from the ICQ network because the current server shut down."));
+
+ SAFE_FREE(&info->newServer);
+ SAFE_FREE((void**)&info->cookieData);
+ info->newServerReady = 0;
+ info->isMigrating = 0;
+ }
+}
+
+void CIcqProto::handleSignonError(WORD wError)
+{
+ switch (wError) {
+
+ case 0x01: // Unregistered uin
+ case 0x04: // Incorrect uin or password
+ case 0x05: // Mismatch uin or password
+ case 0x06: // Internal Client error (bad input to authorizer)
+ case 0x07: // Invalid account
+ BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD);
+ ZeroMemory(m_szPassword, sizeof(m_szPassword));
+ icq_LogFatalParam(LPGEN("Connection failed.\nYour ICQ number or password was rejected (%d)."), wError);
+ break;
+
+ case 0x02: // Service temporarily unavailable
+ case 0x0D: // Bad database status
+ case 0x10: // Service temporarily offline
+ case 0x12: // Database send error
+ case 0x14: // Reservation map error
+ case 0x15: // Reservation link error
+ case 0x1A: // Reservation timeout
+ BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER);
+ icq_LogFatalParam(LPGEN("Connection failed.\nThe server is temporarily unavailable (%d)."), wError);
+ break;
+
+ case 0x16: // The users num connected from this IP has reached the maximum
+ case 0x17: // The users num connected from this IP has reached the maximum (reserved)
+ icq_LogFatalParam(LPGEN("Connection failed.\nServer has too many connections from your IP (%d)."), wError);
+ break;
+
+ case 0x18: // Reservation rate limit exceeded
+ case 0x1D: // Rate limit exceeded
+ BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER);
+ icq_LogFatalParam(LPGEN("Connection failed.\nYou have connected too quickly,\nplease wait and retry 10 to 20 minutes later (%d)."), wError);
+ break;
+
+ case 0x1B: // You are using an older version of ICQ. Upgrade required
+ BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPROTOCOL);
+ icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nThe server did not accept this client version."));
+ break;
+
+ case 0x1C: // You are using an older version of ICQ. Upgrade recommended
+ icq_LogMessage(LOG_WARNING, LPGEN("The server sent warning, this version is getting old.\nTry to look for a new one."));
+ break;
+
+ case 0x1E: // Can't register on the ICQ network
+ icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nYou were rejected by the server for an unknown reason.\nThis can happen if the UIN is already connected."));
+ break;
+
+ case 0x0C: // Invalid database fields, MD5 login not supported
+ icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nSecure (MD5) login is not supported on this account."));
+ break;
+
+ case 0: // No error
+ break;
+
+ case 0x08: // Deleted account
+ case 0x09: // Expired account
+ case 0x0A: // No access to database
+ case 0x0B: // No access to resolver
+ case 0x0E: // Bad resolver status
+ case 0x0F: // Internal error
+ case 0x11: // Suspended account
+ case 0x13: // Database link error
+ case 0x19: // User too heavily warned
+ case 0x1F: // Token server timeout
+ case 0x20: // Invalid SecureID number
+ case 0x21: // MC error
+ case 0x22: // Age restriction
+ case 0x23: // RequireRevalidation
+ case 0x24: // Link rule rejected
+ case 0x25: // Missing information or bad SNAC format
+ case 0x26: // Link broken
+ case 0x27: // Invalid client IP
+ case 0x28: // Partner rejected
+ case 0x29: // SecureID missing
+ case 0x2A: // Blocked account | Bump user
+
+ default:
+ icq_LogFatalParam(LPGEN("Connection failed.\nUnknown error during sign on: 0x%02x"), wError);
+ break;
+ }
+}
+
+
+void CIcqProto::handleRuntimeError(WORD wError)
+{
+ switch (wError)
+ {
+
+ case 0x01:
+ {
+ BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_OTHERLOCATION);
+ icq_LogMessage(LOG_FATAL, LPGEN("You have been disconnected from the ICQ network because you logged on from another location using the same ICQ number."));
+ break;
+ }
+
+ default:
+ icq_LogFatalParam(LPGEN("Unknown runtime error: 0x%02x"), wError);
+ break;
+ }
+}
diff --git a/protocols/IcqOscarJ/src/chan_05ping.cpp b/protocols/IcqOscarJ/src/chan_05ping.cpp
new file mode 100644
index 0000000000..2a2b843509
--- /dev/null
+++ b/protocols/IcqOscarJ/src/chan_05ping.cpp
@@ -0,0 +1,89 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::handlePingChannel(BYTE *buf, WORD datalen)
+{
+ NetLog_Server("Warning: Ignoring server packet on PING channel");
+}
+
+
+void __cdecl CIcqProto::KeepAliveThread(void *arg)
+{
+ serverthread_info *info = (serverthread_info*)arg;
+ icq_packet packet;
+ DWORD dwInterval = getSettingDword(NULL, "KeepAliveInterval", KEEPALIVE_INTERVAL);
+
+ NetLog_Server("Keep alive thread starting.");
+
+ info->hKeepAliveEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ for (;;)
+ {
+ DWORD dwWait = ICQWaitForSingleObject(info->hKeepAliveEvent, dwInterval);
+ if (serverThreadHandle == NULL) // connection lost, end
+ break;
+ if (dwWait == WAIT_TIMEOUT)
+ {
+ // Send a keep alive packet to server
+ packet.wLen = 0;
+ write_flap(&packet, ICQ_PING_CHAN);
+ sendServPacket(&packet);
+ }
+ else if (dwWait == WAIT_IO_COMPLETION)
+ // Possible shutdown in progress
+ if (Miranda_Terminated()) break;
+ else
+ break;
+ }
+
+ NetLog_Server("Keep alive thread ended.");
+
+ CloseHandle(info->hKeepAliveEvent);
+ info->hKeepAliveEvent = NULL;
+}
+
+
+void CIcqProto::StartKeepAlive(serverthread_info *info)
+{
+ if (info->hKeepAliveEvent) // start only once
+ return;
+
+ if (getSettingByte(NULL, "KeepAlive", DEFAULT_KEEPALIVE_ENABLED))
+ CloseHandle( ForkThreadEx(&CIcqProto::KeepAliveThread, info));
+}
+
+
+void CIcqProto::StopKeepAlive(serverthread_info *info)
+{ // finish keep alive thread
+ if (info->hKeepAliveEvent)
+ SetEvent(info->hKeepAliveEvent);
+}
diff --git a/protocols/IcqOscarJ/src/changeinfo/changeinfo.h b/protocols/IcqOscarJ/src/changeinfo/changeinfo.h
new file mode 100644
index 0000000000..ecc4fc3106
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/changeinfo.h
@@ -0,0 +1,122 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2010 Joe Kucera, Bio
+//
+// 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:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+
+#ifndef __CHANGEINFO_H
+#define __CHANGEINFO_H
+
+
+#ifndef AW_SLIDE
+#define SPI_GETCOMBOBOXANIMATION 0x1004
+#define AW_SLIDE 0x40000
+#define AW_ACTIVATE 0x20000
+#define AW_VER_POSITIVE 0x4
+#define UDM_SETPOS32 (WM_USER+113)
+#define UDM_GETPOS32 (WM_USER+114)
+#endif
+
+
+#define LI_DIVIDER 0
+#define LI_STRING 1
+#define LI_LIST 2
+#define LI_LONGSTRING 3
+#define LI_NUMBER 4
+#define LIM_TYPE 0x0000FFFF
+#define LIF_ZEROISVALID 0x80000000
+#define LIF_SIGNED 0x40000000
+#define LIF_PASSWORD 0x20000000
+#define LIF_CHANGEONLY 0x10000000
+
+struct SettingItem
+{
+ const char *szDescription;
+ unsigned displayType; //LI_ constant
+ int dbType; //DBVT_ constant
+ const char *szDbSetting;
+ const void *pList;
+};
+
+struct SettingItemData
+{
+ LPARAM value;
+ int changed;
+};
+
+// contants.c
+extern const SettingItem setting[];
+extern const int settingCount;
+
+//dlgproc.c
+struct ChangeInfoData : public MZeroedObject
+{
+ HWND hwndDlg;
+ CIcqProto *ppro;
+ HFONT hListFont;
+ HWND hwndList;
+ int editTopIndex;
+ int iEditItem;
+ char Password[PASSWORDMAXLEN];
+
+ SettingItemData *settingData;
+
+ HANDLE hAckHook;
+ HANDLE hUpload[2];
+
+ ChangeInfoData() { settingData = (SettingItemData*)SAFE_MALLOC(sizeof(SettingItemData) * settingCount); hAckHook = NULL; hUpload[0] = NULL; hUpload[1] = NULL;}
+ ~ChangeInfoData() { SAFE_FREE((void**)&settingData); }
+
+ char* GetItemSettingText(int i, char *buf, size_t buf_size);
+ void PaintItemSetting(HDC hdc, RECT *rc, int i, UINT itemState);
+
+ //db.cpp
+ void LoadSettingsFromDb(int keepChanged);
+ void FreeStoredDbSettings(void);
+ int ChangesMade(void);
+ void ClearChangeFlags(void);
+ int SaveSettingsToDb(HWND hwndDlg);
+
+ //upload.cpp
+ int UploadSettings(void);
+
+ //editstring.cpp
+ void BeginStringEdit(int iItem,RECT *rc,int i,WORD wVKey);
+ void EndStringEdit(int save);
+ //editlist.cpp
+ void BeginListEdit(int iItem, RECT *rc, int iSetting, WORD wVKey);
+ void EndListEdit(int save);
+};
+
+INT_PTR CALLBACK ChangeInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+
+//editstring.c
+int IsStringEditWindow(HWND hwnd);
+char* BinaryToEscapes(char *str);
+
+//editlist.c
+int IsListEditWindow(HWND hwnd);
+
+#endif /* __CHANGEINFO_H */
diff --git a/protocols/IcqOscarJ/src/changeinfo/constants.cpp b/protocols/IcqOscarJ/src/changeinfo/constants.cpp
new file mode 100644
index 0000000000..92403a74f0
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/constants.cpp
@@ -0,0 +1,198 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2009 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+static FieldNamesItem timezones[]={
+ {24 ,LPGEN("GMT-12:00 Eniwetok; Kwajalein")},
+ {23 ,LPGEN("GMT-11:30")},
+ {22 ,LPGEN("GMT-11:00 Midway Island; Samoa")},
+ {21 ,LPGEN("GMT-10:30")},
+ {20 ,LPGEN("GMT-10:00 Hawaii")},
+ {19 ,LPGEN("GMT-9:30")},
+ {18 ,LPGEN("GMT-9:00 Alaska")},
+ {17 ,LPGEN("GMT-8:30")},
+ {16 ,LPGEN("GMT-8:00 Pacific Time; Tijuana")},
+ {15 ,LPGEN("GMT-7:30")},
+ {14 ,LPGEN("GMT-7:00 Arizona; Mountain Time")},
+ {13 ,LPGEN("GMT-6:30")},
+ {12 ,LPGEN("GMT-6:00 Central Time; Central America; Saskatchewan")},
+ {11 ,LPGEN("GMT-5:30")},
+ {10 ,LPGEN("GMT-5:00 Eastern Time; Bogota; Lima; Quito")},
+ {9 ,LPGEN("GMT-4:30")},
+ {8 ,LPGEN("GMT-4:00 Atlantic Time; Santiago; Caracas; La Paz")},
+ {7 ,LPGEN("GMT-3:30 Newfoundland")},
+ {6 ,LPGEN("GMT-3:00 Greenland; Buenos Aires; Georgetown")},
+ {5 ,LPGEN("GMT-2:30")},
+ {4 ,LPGEN("GMT-2:00 Mid-Atlantic")},
+ {3 ,LPGEN("GMT-1:30")},
+ {2 ,LPGEN("GMT-1:00 Cape Verde Islands; Azores")},
+ {1 ,LPGEN("GMT-0:30")},
+ {0 ,LPGEN("GMT+0:00 London; Dublin; Edinburgh; Lisbon; Casablanca")},
+ {-1 ,LPGEN("GMT+0:30")},
+ {-2 ,LPGEN("GMT+1:00 Central European Time; West Central Africa; Warsaw")},
+ {-3 ,LPGEN("GMT+1:30")},
+ {-4 ,LPGEN("GMT+2:00 Jerusalem; Helsinki; Harare; Cairo; Bucharest; Athens")},
+ {-5 ,LPGEN("GMT+2:30")},
+ {-6 ,LPGEN("GMT+3:00 Moscow; St. Petersburg; Nairobi; Kuwait; Baghdad")},
+ {-7 ,LPGEN("GMT+3:30 Tehran")},
+ {-8 ,LPGEN("GMT+4:00 Baku; Tbilisi; Yerevan; Abu Dhabi; Muscat")},
+ {-9 ,LPGEN("GMT+4:30 Kabul")},
+ {-10 ,LPGEN("GMT+5:00 Calcutta; Chennai; Mumbai; New Delhi; Ekaterinburg")},
+ {-11 ,LPGEN("GMT+5:30")},
+ {-12 ,LPGEN("GMT+6:00 Astana; Dhaka; Almaty; Novosibirsk; Sri Jayawardenepura")},
+ {-13 ,LPGEN("GMT+6:30 Rangoon")},
+ {-14 ,LPGEN("GMT+7:00 Bankok; Hanoi; Jakarta; Krasnoyarsk")},
+ {-15 ,LPGEN("GMT+7:30")},
+ {-16 ,LPGEN("GMT+8:00 Perth; Taipei; Singapore; Hong Kong; Beijing")},
+ {-17 ,LPGEN("GMT+8:30")},
+ {-18 ,LPGEN("GMT+9:00 Tokyo; Osaka; Seoul; Sapporo; Yakutsk")},
+ {-19 ,LPGEN("GMT+9:30 Darwin; Adelaide")},
+ {-20 ,LPGEN("GMT+10:00 East Australia; Guam; Vladivostok")},
+ {-21 ,LPGEN("GMT+10:30")},
+ {-22 ,LPGEN("GMT+11:00 Magadan; Solomon Is.; New Caledonia")},
+ {-23 ,LPGEN("GMT+11:30")},
+ {-24 ,LPGEN("GMT+12:00 Auckland; Wellington; Fiji; Kamchatka; Marshall Is.")},
+ {-100,NULL}
+};
+
+
+static FieldNamesItem months[]={
+ {1, LPGEN("January")},
+ {2, LPGEN("February")},
+ {3, LPGEN("March")},
+ {4, LPGEN("April")},
+ {5, LPGEN("May")},
+ {6, LPGEN("June")},
+ {7, LPGEN("July")},
+ {8, LPGEN("August")},
+ {9, LPGEN("September")},
+ {10,LPGEN("October")},
+ {11,LPGEN("November")},
+ {12,LPGEN("December")},
+ {0, NULL}
+};
+
+
+const int ageRange[]={13,0x7FFF}; // 14, 130
+const int yearRange[]={1753,0x7FFF}; // 1880, 2000
+const int dayRange[]={1,31};
+
+
+const SettingItem setting[]={
+ //personal
+ {LPGEN("Personal"), LI_DIVIDER},
+ {LPGEN("Nickname"), LI_STRING, DBVT_UTF8, "Nick"},
+ {LPGEN("First name"), LI_STRING, DBVT_UTF8, "FirstName"},
+ {LPGEN("Last name"), LI_STRING, DBVT_UTF8, "LastName"},
+// {LPGEN("Age"), LI_NUMBER, DBVT_WORD, "Age", ageRange},
+ {LPGEN("Gender"), LI_LIST, DBVT_BYTE, "Gender", genderField},
+ {LPGEN("About"), LI_LONGSTRING, DBVT_UTF8, "About"},
+ //password
+ {LPGEN("Password"), LI_DIVIDER},
+ {LPGEN("Password"), LI_STRING|LIF_PASSWORD,DBVT_ASCIIZ, "Password"},
+ //contact
+ {LPGEN("Contact"), LI_DIVIDER},
+ {LPGEN("Primary e-mail"), LI_STRING, DBVT_ASCIIZ, "e-mail0"},
+ {LPGEN("Secondary e-mail"), LI_STRING, DBVT_ASCIIZ, "e-mail1"},
+ {LPGEN("Tertiary e-mail"), LI_STRING, DBVT_ASCIIZ, "e-mail2"},
+ {LPGEN("Homepage"), LI_STRING, DBVT_ASCIIZ, "Homepage"},
+ {LPGEN("Street"), LI_STRING, DBVT_UTF8, "Street"},
+ {LPGEN("City"), LI_STRING, DBVT_UTF8, "City"},
+ {LPGEN("State"), LI_STRING, DBVT_UTF8, "State"},
+ {LPGEN("ZIP/postcode"), LI_STRING, DBVT_UTF8, "ZIP"},
+ {LPGEN("Country"), LI_LIST, DBVT_WORD, "Country", countryField},
+ {LPGEN("Phone number"), LI_STRING, DBVT_ASCIIZ, "Phone"},
+ {LPGEN("Fax number"), LI_STRING, DBVT_ASCIIZ, "Fax"},
+ {LPGEN("Cellular number"),LI_STRING, DBVT_ASCIIZ, "Cellular"},
+ //more
+ {LPGEN("Personal Detail"),LI_DIVIDER},
+ {LPGEN("Timezone"), LI_LIST|LIF_ZEROISVALID|LIF_SIGNED,DBVT_BYTE, "Timezone", timezones},
+ {LPGEN("Year of birth"), LI_NUMBER, DBVT_WORD, "BirthYear", yearRange},
+ {LPGEN("Month of birth"), LI_LIST, DBVT_BYTE, "BirthMonth", months},
+ {LPGEN("Day of birth"), LI_NUMBER, DBVT_BYTE, "BirthDay", dayRange},
+ {LPGEN("Marital Status"), LI_LIST, DBVT_BYTE, "MaritalStatus", maritalField},
+ {LPGEN("Spoken language 1"), LI_LIST, DBVT_BYTE, "Language1", languageField},
+ {LPGEN("Spoken language 2"), LI_LIST, DBVT_BYTE, "Language2", languageField},
+ {LPGEN("Spoken language 3"), LI_LIST, DBVT_BYTE, "Language3", languageField},
+ //more
+ {LPGEN("Originally from"),LI_DIVIDER},
+ {LPGEN("Street"), LI_STRING, DBVT_UTF8, "OriginStreet"},
+ {LPGEN("City"), LI_STRING, DBVT_UTF8, "OriginCity"},
+ {LPGEN("State"), LI_STRING, DBVT_UTF8, "OriginState"},
+ {LPGEN("Country"), LI_LIST, DBVT_WORD, "OriginCountry", countryField},
+ //study
+ {LPGEN("Education"), LI_DIVIDER},
+ {LPGEN("Level"), LI_LIST, DBVT_WORD, "StudyLevel", studyLevelField},
+ {LPGEN("Institute"), LI_STRING, DBVT_UTF8, "StudyInstitute"},
+ {LPGEN("Degree"), LI_STRING, DBVT_UTF8, "StudyDegree"},
+ {LPGEN("Graduation Year"),LI_NUMBER, DBVT_WORD, "StudyYear", yearRange},
+ //work
+ {LPGEN("Work"), LI_DIVIDER},
+ {LPGEN("Company name"), LI_STRING, DBVT_UTF8, "Company"},
+ {LPGEN("Company homepage"),LI_STRING, DBVT_ASCIIZ, "CompanyHomepage"},
+ {LPGEN("Company street"), LI_STRING, DBVT_UTF8, "CompanyStreet"},
+ {LPGEN("Company city"), LI_STRING, DBVT_UTF8, "CompanyCity"},
+ {LPGEN("Company state"), LI_STRING, DBVT_UTF8, "CompanyState"},
+ {LPGEN("Company phone"), LI_STRING, DBVT_ASCIIZ, "CompanyPhone"},
+ {LPGEN("Company fax"), LI_STRING, DBVT_ASCIIZ, "CompanyFax"},
+ {LPGEN("Company ZIP/postcode"),LI_STRING,DBVT_UTF8, "CompanyZIP"},
+ {LPGEN("Company country"),LI_LIST, DBVT_WORD, "CompanyCountry", countryField},
+ {LPGEN("Company department"),LI_STRING, DBVT_UTF8, "CompanyDepartment"},
+ {LPGEN("Company position"),LI_STRING, DBVT_UTF8, "CompanyPosition"},
+ {LPGEN("Company industry"),LI_LIST, DBVT_WORD, "CompanyIndustry", industryField},
+// {LPGEN("Company occupation"),LI_LIST, DBVT_WORD, "CompanyOccupation", occupationField},
+ //interests
+ {LPGEN("Personal Interests"), LI_DIVIDER},
+ {LPGEN("Interest category 1"),LI_LIST, DBVT_WORD, "Interest0Cat", interestsField},
+ {LPGEN("Interest areas 1"),LI_STRING, DBVT_ASCIIZ, "Interest0Text"},
+ {LPGEN("Interest category 2"),LI_LIST, DBVT_WORD, "Interest1Cat", interestsField},
+ {LPGEN("Interest areas 2"),LI_STRING, DBVT_ASCIIZ, "Interest1Text"},
+ {LPGEN("Interest category 3"),LI_LIST, DBVT_WORD, "Interest2Cat", interestsField},
+ {LPGEN("Interest areas 3"),LI_STRING, DBVT_ASCIIZ, "Interest2Text"},
+ {LPGEN("Interest category 4"),LI_LIST, DBVT_WORD, "Interest3Cat", interestsField},
+ {LPGEN("Interest areas 4"),LI_STRING, DBVT_ASCIIZ, "Interest3Text"},
+ //pastbackground
+// {LPGEN("Past Background"), LI_DIVIDER},
+// {LPGEN("Category 1"), LI_LIST, DBVT_ASCIIZ, "Past0", pastField},
+// {LPGEN("Past Background 1"),LI_STRING, DBVT_ASCIIZ, "Past0Text"},
+// {LPGEN("Category 2"), LI_LIST, DBVT_ASCIIZ, "Past1", pastField},
+// {LPGEN("Past Background 2"),LI_STRING, DBVT_ASCIIZ, "Past1Text"},
+// {LPGEN("Category 3"), LI_LIST, DBVT_ASCIIZ, "Past2", pastField},
+// {LPGEN("Past Background 3"),LI_STRING, DBVT_ASCIIZ, "Past2Text"},
+ //affiliation
+// {LPGEN("Affiliations"), LI_DIVIDER},
+// {LPGEN("Affiliation category 1"),LI_LIST,DBVT_ASCIIZ, "Affiliation0", affiliationField},
+// {LPGEN("Affiliation 1"), LI_STRING, DBVT_ASCIIZ, "Affiliation0Text"},
+// {LPGEN("Affiliation category 2"),LI_LIST,DBVT_ASCIIZ, "Affiliation1", affiliationField},
+// {LPGEN("Affiliation 2"), LI_STRING, DBVT_ASCIIZ, "Affiliation1Text"},
+// {LPGEN("Affiliation category 3"),LI_LIST,DBVT_ASCIIZ, "Affiliation2", affiliationField},
+// {LPGEN("Affiliation 3"), LI_STRING, DBVT_ASCIIZ, "Affiliation2Text"}
+};
+
+const int settingCount = SIZEOF(setting);
diff --git a/protocols/IcqOscarJ/src/changeinfo/db.cpp b/protocols/IcqOscarJ/src/changeinfo/db.cpp
new file mode 100644
index 0000000000..643dcaa034
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/db.cpp
@@ -0,0 +1,224 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2009 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void ChangeInfoData::LoadSettingsFromDb(int keepChanged)
+{
+ for (int i=0; i < settingCount; i++)
+ {
+ if (setting[i].displayType == LI_DIVIDER) continue;
+ if (keepChanged && settingData[i].changed) continue;
+ if (setting[i].dbType == DBVT_ASCIIZ || setting[i].dbType == DBVT_UTF8)
+ SAFE_FREE((void**)(char**)&settingData[i].value);
+ else if (!keepChanged)
+ settingData[i].value = 0;
+
+ settingData[i].changed = 0;
+
+ if (setting[i].displayType & LIF_PASSWORD) continue;
+
+ DBVARIANT dbv = {DBVT_DELETED};
+ if (!ppro->getSetting(NULL, setting[i].szDbSetting, &dbv))
+ {
+ switch(dbv.type) {
+ case DBVT_ASCIIZ:
+ settingData[i].value = (LPARAM)ppro->getSettingStringUtf(NULL, setting[i].szDbSetting, NULL);
+ break;
+
+ case DBVT_UTF8:
+ settingData[i].value = (LPARAM)null_strdup(dbv.pszVal);
+ break;
+
+ case DBVT_WORD:
+ if (setting[i].displayType & LIF_SIGNED)
+ settingData[i].value = dbv.sVal;
+ else
+ settingData[i].value = dbv.wVal;
+ break;
+
+ case DBVT_BYTE:
+ if (setting[i].displayType & LIF_SIGNED)
+ settingData[i].value = dbv.cVal;
+ else
+ settingData[i].value = dbv.bVal;
+ break;
+
+#ifdef _DEBUG
+ default:
+ MessageBoxA(NULL, "That's not supposed to happen either", "Huh?", MB_OK);
+ break;
+#endif
+ }
+ ICQFreeVariant(&dbv);
+ }
+
+ char buf[MAX_PATH];
+ TCHAR tbuf[MAX_PATH];
+
+ if (utf8_to_tchar_static(GetItemSettingText(i, buf, SIZEOF(buf)), tbuf, SIZEOF(tbuf)))
+ ListView_SetItemText(hwndList, i, 1, tbuf);
+ }
+}
+
+
+void ChangeInfoData::FreeStoredDbSettings(void)
+{
+ for (int i=0; i < settingCount; i++ )
+ if (setting[i].dbType == DBVT_ASCIIZ || setting[i].dbType == DBVT_UTF8)
+ SAFE_FREE((void**)&settingData[i].value);
+}
+
+
+int ChangeInfoData::ChangesMade(void)
+{
+ for (int i=0; i < settingCount; i++ )
+ if (settingData[i].changed)
+ return 1;
+ return 0;
+}
+
+
+void ChangeInfoData::ClearChangeFlags(void)
+{
+ for (int i=0; i < settingCount; i++)
+ settingData[i].changed = 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct PwConfirmDlgParam
+{
+ CIcqProto* ppro;
+ char* Pass;
+};
+
+static INT_PTR CALLBACK PwConfirmDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ PwConfirmDlgParam* dat = (PwConfirmDlgParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ SendDlgItemMessage(hwndDlg,IDC_PASSWORD,EM_LIMITTEXT,15,0);
+ return TRUE;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDOK:
+ {
+ char szTest[16];
+
+ GetDlgItemTextA(hwndDlg,IDC_OLDPASS,szTest,sizeof(szTest));
+
+ if (strcmpnull(szTest, dat->ppro->GetUserPassword(TRUE)))
+ {
+ MessageBoxUtf(hwndDlg, LPGEN("The password does not match your current password. Check Caps Lock and try again."), LPGEN("Change ICQ Details"), MB_OK);
+ SendDlgItemMessage(hwndDlg,IDC_OLDPASS,EM_SETSEL,0,(LPARAM)-1);
+ SetFocus(GetDlgItem(hwndDlg,IDC_OLDPASS));
+ break;
+ }
+
+ GetDlgItemTextA(hwndDlg,IDC_PASSWORD,szTest,sizeof(szTest));
+ if(strcmpnull(szTest, dat->Pass))
+ {
+ MessageBoxUtf(hwndDlg, LPGEN("The password does not match the password you originally entered. Check Caps Lock and try again."), LPGEN("Change ICQ Details"), MB_OK);
+ SendDlgItemMessage(hwndDlg,IDC_PASSWORD,EM_SETSEL,0,(LPARAM)-1);
+ SetFocus(GetDlgItem(hwndDlg,IDC_PASSWORD));
+ break;
+ }
+ }
+ case IDCANCEL:
+ EndDialog(hwndDlg,wParam);
+ break;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+
+int ChangeInfoData::SaveSettingsToDb(HWND hwndDlg)
+{
+ int ret = 1;
+
+ for (int i = 0; i < settingCount; i++)
+ {
+ if (!settingData[i].changed) continue;
+ if (!(setting[i].displayType & LIF_ZEROISVALID) && settingData[i].value==0)
+ {
+ ppro->deleteSetting(NULL, setting[i].szDbSetting);
+ continue;
+ }
+ switch(setting[i].dbType) {
+ case DBVT_ASCIIZ:
+ if (setting[i].displayType & LIF_PASSWORD)
+ {
+ int nSettingLen = strlennull((char*)settingData[i].value);
+
+ if (nSettingLen > 8 || nSettingLen < 1)
+ {
+ MessageBoxUtf(hwndDlg, LPGEN("The ICQ server does not support passwords longer than 8 characters. Please use a shorter password."), LPGEN("Change ICQ Details"), MB_OK);
+ ret=0;
+ break;
+ }
+ PwConfirmDlgParam param = { ppro, (char*)settingData[i].value };
+ if (IDOK != DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_PWCONFIRM), hwndDlg, PwConfirmDlgProc, (LPARAM)&param))
+ {
+ ret = 0;
+ break;
+ }
+ strcpy(ppro->m_szPassword, (char*)settingData[i].value);
+ }
+ else {
+ if (*(char*)settingData[i].value)
+ ppro->setSettingStringUtf(NULL, setting[i].szDbSetting, (char*)settingData[i].value);
+ else
+ ppro->deleteSetting(NULL, setting[i].szDbSetting);
+ }
+ break;
+
+ case DBVT_UTF8:
+ if (*(char*)settingData[i].value)
+ ppro->setSettingStringUtf(NULL, setting[i].szDbSetting, (char*)settingData[i].value);
+ else
+ ppro->deleteSetting(NULL, setting[i].szDbSetting);
+ break;
+
+ case DBVT_WORD:
+ ppro->setSettingWord(NULL, setting[i].szDbSetting, (WORD)settingData[i].value);
+ break;
+
+ case DBVT_BYTE:
+ ppro->setSettingByte(NULL, setting[i].szDbSetting, (BYTE)settingData[i].value);
+ break;
+ }
+ }
+ return ret;
+}
diff --git a/protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp b/protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp
new file mode 100644
index 0000000000..404777aa97
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/dlgproc.cpp
@@ -0,0 +1,540 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2010 Joe Kucera, Bio
+//
+// 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:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+#define DM_PROTOACK (WM_USER+10)
+
+static int DrawTextUtf(HDC hDC, char *text, LPRECT lpRect, UINT uFormat, LPSIZE lpSize)
+{
+ int res;
+
+ WCHAR *tmp = make_unicode_string(text);
+ res = DrawTextW(hDC, tmp, -1, lpRect, uFormat);
+ if (lpSize)
+ GetTextExtentPoint32W(hDC, tmp, strlennull(tmp), lpSize);
+ SAFE_FREE((void**)&tmp);
+
+ return res;
+}
+
+
+char* ChangeInfoData::GetItemSettingText(int i, char *buf, size_t bufsize)
+{
+ char *text = buf;
+ int alloced = 0;
+
+ buf[0] = '\0';
+
+ if (settingData[i].value == 0 && !(setting[i].displayType & LIF_ZEROISVALID))
+ {
+ if (setting[i].displayType & LIF_CHANGEONLY)
+ text = ICQTranslateUtfStatic(LPGEN("<unremovable once applied>"), buf, bufsize);
+ else
+ text = ICQTranslateUtfStatic(LPGEN("<empty>"), buf, bufsize);
+ }
+ else
+ {
+ switch (setting[i].displayType & LIM_TYPE) {
+ case LI_STRING:
+ case LI_LONGSTRING:
+ text = BinaryToEscapes((char*)settingData[i].value);
+ alloced = 1;
+ break;
+
+ case LI_NUMBER:
+ _itoa(settingData[i].value, text, 10);
+ break;
+
+ case LI_LIST:
+ if (setting[i].dbType == DBVT_ASCIIZ)
+ text = ICQTranslateUtfStatic((char*)settingData[i].value, buf, bufsize);
+ else
+ {
+ text = ICQTranslateUtfStatic(LPGEN("Unknown value"), buf, bufsize);
+
+ FieldNamesItem *list = (FieldNamesItem*)setting[i].pList;
+ for (int j=0; TRUE; j++)
+ if (list[j].code == settingData[i].value)
+ {
+ text = ICQTranslateUtfStatic(list[j].text, buf, bufsize);
+ break;
+ }
+ else if (!list[j].text)
+ {
+ if (list[j].code == settingData[i].value)
+ text = ICQTranslateUtfStatic("Unspecified", buf, bufsize);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ if (setting[i].displayType & LIF_PASSWORD)
+ {
+ if (settingData[i].changed)
+ for (int j=0; text[j]; j++) text[j] = '*';
+ else
+ {
+ if (alloced)
+ {
+ SAFE_FREE(&text);
+ alloced = 0;
+ }
+ text = "********";
+ }
+ }
+ if (text != buf)
+ {
+ char *tmp = text;
+
+ text = null_strcpy(buf, text, bufsize - 1);
+ if (alloced)
+ SAFE_FREE(&tmp);
+ }
+
+ return text;
+}
+
+
+void ChangeInfoData::PaintItemSetting(HDC hdc, RECT *rc, int i, UINT itemState)
+{
+ char str[MAX_PATH];
+ char *text = GetItemSettingText(i, str, SIZEOF(str));
+
+ if (settingData[i].value == 0 && !(setting[i].displayType & LIF_ZEROISVALID))
+ SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
+
+ if ((setting[i].displayType & LIM_TYPE) == LI_LIST && (itemState & CDIS_SELECTED || iEditItem == i))
+ {
+ RECT rcBtn;
+
+ rcBtn = *rc;
+ rcBtn.left = rcBtn.right - rc->bottom + rc->top;
+ InflateRect(&rcBtn,-2,-2);
+ rc->right = rcBtn.left;
+ DrawFrameControl(hdc, &rcBtn, DFC_SCROLL, iEditItem == i ? DFCS_SCROLLDOWN|DFCS_PUSHED : DFCS_SCROLLDOWN);
+ }
+ DrawTextUtf(hdc, text, rc, DT_END_ELLIPSIS|DT_LEFT|DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER, NULL);
+}
+
+
+static int ChangeInfoDlg_Resize(HWND hwndDlg, LPARAM lParam, UTILRESIZECONTROL *urc)
+{
+ switch (urc->wId) {
+ case IDC_LIST:
+ return RD_ANCHORX_WIDTH | RD_ANCHORY_HEIGHT;
+
+ case IDC_SAVE:
+ return RD_ANCHORX_RIGHT | RD_ANCHORY_BOTTOM;
+
+ case IDC_UPLOADING:
+ return RD_ANCHORX_WIDTH | RD_ANCHORY_BOTTOM;
+ }
+
+ return RD_ANCHORX_LEFT | RD_ANCHORY_TOP; // default
+}
+
+
+INT_PTR CALLBACK ChangeInfoDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ ChangeInfoData* dat = (ChangeInfoData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ dat = new ChangeInfoData();
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)dat);
+
+ dat->hwndDlg = hwndDlg;
+ dat->ppro = (CIcqProto*)lParam;
+ dat->hwndList = GetDlgItem(hwndDlg, IDC_LIST);
+
+ ListView_SetExtendedListViewStyle(dat->hwndList, LVS_EX_FULLROWSELECT);
+ dat->iEditItem = -1;
+ {
+ HFONT hFont;
+ LOGFONT lf;
+
+ dat->hListFont = (HFONT)SendMessage(dat->hwndList, WM_GETFONT, 0, 0);
+ GetObject(dat->hListFont, sizeof(lf), &lf);
+ lf.lfHeight -= 5;
+ hFont = CreateFontIndirect(&lf);
+ SendMessage(dat->hwndList, WM_SETFONT, (WPARAM)hFont, 0);
+ }
+ { // Prepare ListView Columns
+ LV_COLUMN lvc = {0};
+ RECT rc;
+
+ GetClientRect(dat->hwndList, &rc);
+ rc.right -= GetSystemMetrics(SM_CXVSCROLL);
+ lvc.mask = LVCF_WIDTH;
+ lvc.cx = rc.right / 3;
+ ListView_InsertColumn(dat->hwndList, 0, &lvc);
+ lvc.cx = rc.right - lvc.cx;
+ ListView_InsertColumn(dat->hwndList, 1, &lvc);
+ }
+ { // Prepare Setting Items
+ LV_ITEM lvi = {0};
+ lvi.mask = LVIF_PARAM | LVIF_TEXT;
+
+ for (lvi.iItem = 0; lvi.iItem < settingCount; lvi.iItem++)
+ {
+ TCHAR text[MAX_PATH];
+
+ lvi.lParam = lvi.iItem;
+ lvi.pszText = text;
+ utf8_to_tchar_static(setting[lvi.iItem].szDescription, text, SIZEOF(text));
+ ListView_InsertItem(dat->hwndList, &lvi);
+ }
+ }
+
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ return TRUE;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_PARAMCHANGED:
+ dat->ppro = (CIcqProto*)((PSHNOTIFY*)lParam)->lParam;
+ dat->LoadSettingsFromDb(0);
+ {
+ char *pwd = dat->ppro->GetUserPassword(TRUE);
+ strcpy(dat->Password, (pwd) ? pwd : "" ); /// FIXME
+ }
+ break;
+
+ case PSN_INFOCHANGED:
+ dat->LoadSettingsFromDb(1);
+ break;
+
+ case PSN_KILLACTIVE:
+ dat->EndStringEdit(1);
+ dat->EndListEdit(1);
+ break;
+
+ case PSN_APPLY:
+ if (dat->ChangesMade())
+ {
+ if (IDYES!=MessageBoxUtf(hwndDlg, LPGEN("You've made some changes to your ICQ details but it has not been saved to the server. Are you sure you want to close this dialog?"), LPGEN("Change ICQ Details"), MB_YESNOCANCEL))
+ {
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, PSNRET_INVALID_NOCHANGEPAGE);
+ return TRUE;
+ }
+ }
+ break;
+ }
+ break;
+
+ case IDC_LIST:
+ switch (((LPNMHDR)lParam)->code) {
+ case LVN_GETDISPINFO:
+ if (dat->iEditItem != -1)
+ {
+ if (dat->editTopIndex != ListView_GetTopIndex(dat->hwndList))
+ {
+ dat->EndStringEdit(1);
+ dat->EndListEdit(1);
+ }
+ }
+ break;
+
+ case NM_CUSTOMDRAW:
+ {
+ LPNMLVCUSTOMDRAW cd=(LPNMLVCUSTOMDRAW)lParam;
+
+ switch(cd->nmcd.dwDrawStage) {
+ case CDDS_PREPAINT:
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_NOTIFYITEMDRAW);
+ return TRUE;
+
+ case CDDS_ITEMPREPAINT:
+ {
+ RECT rcItem;
+
+ if (dat->iEditItem != -1)
+ {
+ if (dat->editTopIndex != ListView_GetTopIndex(dat->hwndList))
+ {
+ dat->EndStringEdit(1);
+ dat->EndListEdit(1);
+ }
+ }
+
+ ListView_GetItemRect(dat->hwndList, cd->nmcd.dwItemSpec, &rcItem, LVIR_BOUNDS);
+
+ if (GetWindowLongPtr(dat->hwndList, GWL_STYLE) & WS_DISABLED)
+ { // Disabled List
+ SetTextColor(cd->nmcd.hdc, cd->clrText);
+ FillRect(cd->nmcd.hdc, &rcItem, GetSysColorBrush(COLOR_3DFACE));
+ }
+ else if ((cd->nmcd.uItemState & CDIS_SELECTED || dat->iEditItem == (int)cd->nmcd.dwItemSpec)
+ && setting[cd->nmcd.lItemlParam].displayType != LI_DIVIDER)
+ { // Selected item
+ SetTextColor(cd->nmcd.hdc, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ FillRect(cd->nmcd.hdc, &rcItem, GetSysColorBrush(COLOR_HIGHLIGHT));
+ }
+ else
+ { // Unselected item
+ SetTextColor(cd->nmcd.hdc, GetSysColor(COLOR_WINDOWTEXT));
+ FillRect(cd->nmcd.hdc, &rcItem, GetSysColorBrush(COLOR_WINDOW));
+ }
+
+ HFONT hoFont = (HFONT)SelectObject(cd->nmcd.hdc, dat->hListFont);
+
+ if (setting[cd->nmcd.lItemlParam].displayType == LI_DIVIDER)
+ {
+ RECT rcLine;
+ SIZE textSize;
+ char str[MAX_PATH];
+ char *szText = ICQTranslateUtfStatic(setting[cd->nmcd.lItemlParam].szDescription, str, MAX_PATH);
+
+ SetTextColor(cd->nmcd.hdc, GetSysColor(COLOR_3DSHADOW));
+ DrawTextUtf(cd->nmcd.hdc, szText, &rcItem, DT_CENTER|DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER, &textSize);
+ rcLine.top = (rcItem.top + rcItem.bottom) / 2 - 1;
+ rcLine.bottom = rcLine.top + 2;
+ rcLine.left = rcItem.left + 3;
+ rcLine.right = (rcItem.left + rcItem.right - textSize.cx) / 2 - 3;
+ DrawEdge(cd->nmcd.hdc, &rcLine, BDR_SUNKENOUTER, BF_RECT);
+ rcLine.left = (rcItem.left + rcItem.right + textSize.cx) / 2 + 3;
+ rcLine.right = rcItem.right - 3;
+ DrawEdge(cd->nmcd.hdc, &rcLine, BDR_SUNKENOUTER, BF_RECT);
+ }
+ else
+ {
+ RECT rcItemDescr, rcItemValue;
+ char str[MAX_PATH];
+
+ ListView_GetSubItemRect(dat->hwndList, cd->nmcd.dwItemSpec, 0, LVIR_BOUNDS, &rcItemDescr);
+ ListView_GetSubItemRect(dat->hwndList, cd->nmcd.dwItemSpec, 1, LVIR_BOUNDS, &rcItemValue);
+
+ rcItemDescr.right = rcItemValue.left;
+ rcItemDescr.left += 2;
+ DrawTextUtf(cd->nmcd.hdc, ICQTranslateUtfStatic(setting[cd->nmcd.lItemlParam].szDescription, str, MAX_PATH), &rcItemDescr, DT_END_ELLIPSIS|DT_LEFT|DT_NOCLIP|DT_NOPREFIX|DT_SINGLELINE|DT_VCENTER, NULL);
+
+ dat->PaintItemSetting(cd->nmcd.hdc, &rcItemValue, cd->nmcd.lItemlParam, cd->nmcd.uItemState);
+ }
+ SelectObject(cd->nmcd.hdc, hoFont);
+
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT);
+
+ return TRUE;
+ }
+ }
+ break;
+ }
+ case NM_CLICK:
+ {
+ LPNMLISTVIEW nm=(LPNMLISTVIEW)lParam;
+ LV_ITEM lvi;
+ RECT rc;
+
+ dat->EndStringEdit(1);
+ dat->EndListEdit(1);
+ if (nm->iSubItem != 1) break;
+ lvi.mask = LVIF_PARAM|LVIF_STATE;
+ lvi.stateMask = 0xFFFFFFFF;
+ lvi.iItem = nm->iItem; lvi.iSubItem = nm->iSubItem;
+ ListView_GetItem(dat->hwndList, &lvi);
+ if (!(lvi.state & LVIS_SELECTED)) break;
+ ListView_EnsureVisible(dat->hwndList, lvi.iItem, FALSE);
+ ListView_GetSubItemRect(dat->hwndList, lvi.iItem, lvi.iSubItem, LVIR_BOUNDS, &rc);
+ dat->editTopIndex = ListView_GetTopIndex(dat->hwndList);
+ switch (setting[lvi.lParam].displayType & LIM_TYPE) {
+ case LI_STRING:
+ case LI_LONGSTRING:
+ case LI_NUMBER:
+ dat->BeginStringEdit(nm->iItem, &rc, lvi. lParam, 0);
+ break;
+ case LI_LIST:
+ dat->BeginListEdit(nm->iItem, &rc, lvi. lParam, 0);
+ break;
+ }
+ break;
+ }
+ case LVN_KEYDOWN:
+ {
+ LPNMLVKEYDOWN nm=(LPNMLVKEYDOWN)lParam;
+ LV_ITEM lvi;
+ RECT rc;
+
+ dat->EndStringEdit(1);
+ dat->EndListEdit(1);
+ if(nm->wVKey==VK_SPACE || nm->wVKey==VK_RETURN || nm->wVKey==VK_F2) nm->wVKey=0;
+ if(nm->wVKey && (nm->wVKey<'0' || (nm->wVKey>'9' && nm->wVKey<'A') || (nm->wVKey>'Z' && nm->wVKey<VK_NUMPAD0) || nm->wVKey>=VK_F1))
+ break;
+ lvi.mask=LVIF_PARAM|LVIF_STATE;
+ lvi.stateMask=0xFFFFFFFF;
+ lvi.iItem = ListView_GetNextItem(dat->hwndList, -1, LVNI_ALL|LVNI_SELECTED);
+ if (lvi.iItem==-1) break;
+ lvi.iSubItem=1;
+ ListView_GetItem(dat->hwndList,&lvi);
+ ListView_EnsureVisible(dat->hwndList,lvi.iItem,FALSE);
+ ListView_GetSubItemRect(dat->hwndList,lvi.iItem,lvi.iSubItem,LVIR_BOUNDS,&rc);
+ dat->editTopIndex = ListView_GetTopIndex(dat->hwndList);
+ switch(setting[lvi.lParam].displayType & LIM_TYPE) {
+ case LI_STRING:
+ case LI_LONGSTRING:
+ case LI_NUMBER:
+ dat->BeginStringEdit(lvi.iItem,&rc,lvi.lParam,nm->wVKey);
+ break;
+ case LI_LIST:
+ dat->BeginListEdit(lvi.iItem,&rc,lvi.lParam,nm->wVKey);
+ break;
+ }
+ SetWindowLongPtr(hwndDlg, DWLP_MSGRESULT, TRUE);
+ return TRUE;
+ }
+ case NM_KILLFOCUS:
+ if (!IsStringEditWindow(GetFocus())) dat->EndStringEdit(1);
+ if (!IsListEditWindow(GetFocus())) dat->EndListEdit(1);
+ break;
+ }
+ break;
+ }
+ break;
+ case WM_KILLFOCUS:
+ dat->EndStringEdit(1);
+ dat->EndListEdit(1);
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDCANCEL:
+ SendMessage(GetParent(hwndDlg), msg, wParam, lParam);
+ break;
+
+ case IDC_SAVE:
+ if (!dat->SaveSettingsToDb(hwndDlg))
+ break;
+
+ EnableDlgItem(hwndDlg, IDC_SAVE, FALSE);
+ EnableDlgItem(hwndDlg, IDC_LIST, FALSE);
+ {
+ char str[MAX_PATH];
+ SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, ICQTranslateUtfStatic(LPGEN("Upload in progress..."), str, MAX_PATH));
+ }
+ EnableDlgItem(hwndDlg, IDC_UPLOADING, TRUE);
+ ShowDlgItem(hwndDlg, IDC_UPLOADING, SW_SHOW);
+ dat->hAckHook = HookEventMessage(ME_PROTO_ACK, hwndDlg, DM_PROTOACK);
+
+ if (!dat->UploadSettings())
+ {
+ EnableDlgItem(hwndDlg, IDC_SAVE, TRUE);
+ EnableDlgItem(hwndDlg, IDC_LIST, TRUE);
+ ShowDlgItem(hwndDlg, IDC_UPLOADING, SW_HIDE);
+ UnhookEvent(dat->hAckHook);
+ dat->hAckHook = NULL;
+ }
+ break;
+ }
+ break;
+
+ case WM_SIZE:
+ { // make the dlg resizeable
+ UTILRESIZEDIALOG urd = {0};
+
+ if (IsIconic(hwndDlg)) break;
+ urd.cbSize = sizeof(urd);
+ urd.hInstance = hInst;
+ urd.hwndDlg = hwndDlg;
+ urd.lParam = 0; // user-defined
+ urd.lpTemplate = MAKEINTRESOURCEA(IDD_INFO_CHANGEINFO);
+ urd.pfnResizer = ChangeInfoDlg_Resize;
+ CallService(MS_UTILS_RESIZEDIALOG, 0, (LPARAM) &urd);
+
+ { // update listview column widths
+ RECT rc;
+
+ GetClientRect(dat->hwndList, &rc);
+ rc.right -= GetSystemMetrics(SM_CXVSCROLL);
+ ListView_SetColumnWidth(dat->hwndList, 0, rc.right / 3);
+ ListView_SetColumnWidth(dat->hwndList, 1, rc.right - rc.right / 3);
+ }
+ break;
+ }
+
+ case DM_PROTOACK:
+ {
+ ACKDATA *ack=(ACKDATA*)lParam;
+ int i,done;
+ char str[MAX_PATH];
+ char buf[MAX_PATH];
+
+ if (ack->type != ACKTYPE_SETINFO) break;
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ for (i=0; i < SIZEOF(dat->hUpload); i++)
+ if (dat->hUpload[i] && ack->hProcess == dat->hUpload[i]) break;
+
+ if (i == SIZEOF(dat->hUpload)) break;
+ dat->hUpload[i] = NULL;
+ for (done = 0, i = 0; i < SIZEOF(dat->hUpload); i++)
+ done += dat->hUpload[i] == NULL;
+ null_snprintf(buf, sizeof(buf), "%s%d%%", ICQTranslateUtfStatic(LPGEN("Upload in progress..."), str, MAX_PATH), 100*done/(SIZEOF(dat->hUpload)));
+ SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, buf);
+ if (done < SIZEOF(dat->hUpload)) break;
+
+ dat->ClearChangeFlags();
+ UnhookEvent(dat->hAckHook);
+ dat->hAckHook = NULL;
+ EnableDlgItem(hwndDlg, IDC_LIST, TRUE);
+ EnableDlgItem(hwndDlg, IDC_UPLOADING, FALSE);
+ SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, ICQTranslateUtfStatic(LPGEN("Upload complete"), str, MAX_PATH));
+ SendMessage(GetParent(hwndDlg), PSM_FORCECHANGED, 0, 0);
+ }
+ else if (ack->result==ACKRESULT_FAILED)
+ {
+ UnhookEvent(dat->hAckHook);
+ dat->hAckHook = NULL;
+ EnableDlgItem(hwndDlg, IDC_LIST, TRUE);
+ EnableDlgItem(hwndDlg, IDC_UPLOADING, FALSE);
+ SetDlgItemTextUtf(hwndDlg, IDC_UPLOADING, ICQTranslateUtfStatic(LPGEN("Upload FAILED"), str, MAX_PATH));
+ SendMessage(GetParent(hwndDlg), PSM_FORCECHANGED, 0, 0);
+ EnableDlgItem(hwndDlg, IDC_SAVE, TRUE);
+ }
+ break;
+ }
+ case WM_DESTROY:
+ if (dat->hAckHook)
+ {
+ UnhookEvent(dat->hAckHook);
+ dat->hAckHook = NULL;
+ }
+ {
+ HFONT hFont = (HFONT)SendMessage(dat->hwndList, WM_GETFONT, 0, 0);
+ DeleteObject(hFont);
+ }
+ dat->FreeStoredDbSettings();
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, 0);
+ delete dat;
+ break;
+ }
+ return FALSE;
+}
diff --git a/protocols/IcqOscarJ/src/changeinfo/editlist.cpp b/protocols/IcqOscarJ/src/changeinfo/editlist.cpp
new file mode 100644
index 0000000000..f2a1470fd3
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/editlist.cpp
@@ -0,0 +1,201 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2009 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+static ChangeInfoData *dataListEdit = NULL;
+static HWND hwndListEdit = NULL;
+static BOOL (WINAPI *MyAnimateWindow)(HWND,DWORD,DWORD);
+static WNDPROC OldListEditProc;
+
+static LRESULT CALLBACK ListEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_LBUTTONUP:
+ CallWindowProc(OldListEditProc, hwnd, msg, wParam, lParam);
+ {
+ POINT pt;
+
+ pt.x = (short)LOWORD(lParam);
+ pt.y = (short)HIWORD(lParam);
+ ClientToScreen(hwnd, &pt);
+ if (SendMessage(hwnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y)) == HTVSCROLL) break;
+ }
+ {
+ int i = SendMessage(hwnd, LB_GETCURSEL, 0, 0);
+
+ if (dataListEdit)
+ dataListEdit->EndListEdit(i != LB_ERR);
+ }
+ return 0;
+
+ case WM_CHAR:
+ if (wParam != '\r') break;
+ {
+ int i = SendMessage(hwnd, LB_GETCURSEL, 0, 0);
+
+ if (dataListEdit)
+ dataListEdit->EndListEdit(i != LB_ERR);
+ }
+ return 0;
+ case WM_KILLFOCUS:
+ if (dataListEdit)
+ dataListEdit->EndListEdit(1);
+ return 0;
+ }
+ return CallWindowProc(OldListEditProc, hwnd, msg, wParam, lParam);
+}
+
+
+void ChangeInfoData::BeginListEdit(int iItem, RECT *rc, int iSetting, WORD wVKey)
+{
+ int j,n;
+ POINT pt;
+ int itemHeight;
+ char str[MAX_PATH];
+
+ if (dataListEdit)
+ dataListEdit->EndListEdit(0);
+
+ pt.x=pt.y=0;
+ ClientToScreen(hwndList,&pt);
+ OffsetRect(rc,pt.x,pt.y);
+ InflateRect(rc,-2,-2);
+ rc->left-=2;
+ iEditItem = iItem;
+ ListView_RedrawItems(hwndList, iEditItem, iEditItem);
+ UpdateWindow(hwndList);
+
+ dataListEdit = this;
+ hwndListEdit = CreateWindowEx(WS_EX_TOOLWINDOW|WS_EX_TOPMOST, _T("LISTBOX"), _T(""), WS_POPUP|WS_BORDER|WS_VSCROLL, rc->left, rc->bottom, rc->right - rc->left, 150, NULL, NULL, hInst, NULL);
+ SendMessage(hwndListEdit, WM_SETFONT, (WPARAM)hListFont, 0);
+ itemHeight = SendMessage(hwndListEdit, LB_GETITEMHEIGHT, 0, 0);
+
+ FieldNamesItem *list = (FieldNamesItem*)setting[iSetting].pList;
+
+ if (list == countryField)
+ { // some country codes were changed leaving old details uknown, convert it for the user
+ if (settingData[iSetting].value == 420) settingData[iSetting].value = 42; // conversion of obsolete codes (OMG!)
+ else if (settingData[iSetting].value == 421) settingData[iSetting].value = 4201;
+ else if (settingData[iSetting].value == 102) settingData[iSetting].value = 1201;
+ }
+
+ n = ListBoxAddStringUtf(hwndListEdit, "Unspecified");
+ for (j=0; ; j++)
+ if (!list[j].text)
+ {
+ SendMessage(hwndListEdit, LB_SETITEMDATA, n, j);
+ if ((settingData[iSetting].value == 0 && list[j].code == 0)
+ || (setting[iSetting].dbType != DBVT_ASCIIZ && settingData[iSetting].value == list[j].code))
+ SendMessage(hwndListEdit, LB_SETCURSEL, n, 0);
+ break;
+ }
+
+ for (j=0; list[j].text; j++)
+ {
+ n = ListBoxAddStringUtf(hwndListEdit, list[j].text);
+ SendMessage(hwndListEdit, LB_SETITEMDATA, n, j);
+ if ((setting[iSetting].dbType == DBVT_ASCIIZ && (!strcmpnull((char*)settingData[iSetting].value, list[j].text))
+ || (setting[iSetting].dbType == DBVT_ASCIIZ && (!strcmpnull((char*)settingData[iSetting].value, ICQTranslateUtfStatic(list[j].text, str, MAX_PATH))))
+ || ((char*)settingData[iSetting].value == NULL && list[j].code == 0))
+ || (setting[iSetting].dbType != DBVT_ASCIIZ && settingData[iSetting].value == list[j].code))
+ SendMessage(hwndListEdit, LB_SETCURSEL, n, 0);
+ }
+ SendMessage(hwndListEdit, LB_SETTOPINDEX, SendMessage(hwndListEdit, LB_GETCURSEL, 0, 0) - 3, 0);
+ int listCount = SendMessage(hwndListEdit, LB_GETCOUNT, 0, 0);
+ if (itemHeight * listCount < 150)
+ SetWindowPos(hwndListEdit, 0, 0, 0, rc->right - rc->left, itemHeight * listCount + GetSystemMetrics(SM_CYBORDER) * 2, SWP_NOZORDER|SWP_NOMOVE);
+ OldListEditProc = (WNDPROC)SetWindowLongPtr(hwndListEdit, GWLP_WNDPROC, (LONG_PTR)ListEditSubclassProc);
+ if (MyAnimateWindow = (BOOL (WINAPI*)(HWND,DWORD,DWORD))GetProcAddress(GetModuleHandleA("user32"), "AnimateWindow"))
+ {
+ BOOL enabled;
+
+ SystemParametersInfo(SPI_GETCOMBOBOXANIMATION, 0, &enabled, FALSE);
+ if (enabled) MyAnimateWindow(hwndListEdit, 200, AW_SLIDE|AW_ACTIVATE|AW_VER_POSITIVE);
+ }
+ ShowWindow(hwndListEdit, SW_SHOW);
+ SetFocus(hwndListEdit);
+ if (wVKey)
+ PostMessage(hwndListEdit, WM_KEYDOWN, wVKey, 0);
+}
+
+
+void ChangeInfoData::EndListEdit(int save)
+{
+ if (hwndListEdit == NULL || iEditItem == -1 || this != dataListEdit) return;
+ if (save)
+ {
+ int iItem = SendMessage(hwndListEdit, LB_GETCURSEL, 0, 0);
+ int i = SendMessage(hwndListEdit, LB_GETITEMDATA, iItem, 0);
+
+ if (setting[iEditItem].dbType == DBVT_ASCIIZ)
+ {
+ char *szNewValue = (((FieldNamesItem*)setting[iEditItem].pList)[i].text);
+ if (((FieldNamesItem*)setting[iEditItem].pList)[i].code || setting[iEditItem].displayType & LIF_ZEROISVALID)
+ {
+ settingData[iEditItem].changed = strcmpnull(szNewValue, (char*)settingData[iEditItem].value);
+ SAFE_FREE((void**)&settingData[iEditItem].value);
+ settingData[iEditItem].value = (LPARAM)null_strdup(szNewValue);
+ }
+ else
+ {
+ settingData[iEditItem].changed = (char*)settingData[iEditItem].value!=NULL;
+ SAFE_FREE((void**)&settingData[iEditItem].value);
+ }
+ }
+ else
+ {
+ settingData[iEditItem].changed = ((FieldNamesItem*)setting[iEditItem].pList)[i].code != settingData[iEditItem].value;
+ settingData[iEditItem].value = ((FieldNamesItem*)setting[iEditItem].pList)[i].code;
+ }
+ if (settingData[iEditItem].changed)
+ {
+ char buf[MAX_PATH];
+ TCHAR tbuf[MAX_PATH];
+
+ if (utf8_to_tchar_static(ICQTranslateUtfStatic(((FieldNamesItem*)setting[iEditItem].pList)[i].text, buf, SIZEOF(buf)), tbuf, SIZEOF(buf)))
+ ListView_SetItemText(hwndList, iEditItem, 1, tbuf);
+
+ EnableDlgItem(GetParent(hwndList), IDC_SAVE, TRUE);
+
+ }
+ }
+ ListView_RedrawItems(hwndList, iEditItem, iEditItem);
+ iEditItem = -1;
+ dataListEdit = NULL;
+ DestroyWindow(hwndListEdit);
+ hwndListEdit = NULL;
+}
+
+
+int IsListEditWindow(HWND hwnd)
+{
+ if (hwnd == hwndListEdit) return 1;
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/changeinfo/editstring.cpp b/protocols/IcqOscarJ/src/changeinfo/editstring.cpp
new file mode 100644
index 0000000000..288351e8d3
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/editstring.cpp
@@ -0,0 +1,367 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2009 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+static ChangeInfoData *dataStringEdit = NULL;
+static WNDPROC OldStringEditProc, OldExpandButtonProc;
+static HWND hwndEdit = NULL, hwndExpandButton = NULL, hwndUpDown = NULL;
+
+static const char escapes[]={'a','\a',
+'b','\b',
+'e',27,
+'f','\f',
+'r','\r',
+'t','\t',
+'v','\v',
+'\\','\\'};
+
+static void EscapesToMultiline(WCHAR *str,PDWORD selStart,PDWORD selEnd)
+{ //converts "\\n" and "\\t" to "\r\n" and "\t" because a multi-line edit box can show them properly
+ DWORD i;
+
+ for(i=0; *str; str++, i++)
+ {
+ if (*str != '\\') continue;
+ if (str[1] == 'n')
+ {
+ *str++ = '\r';
+ i++;
+ *str = '\n';
+ }
+ else if (str[1] == 't')
+ {
+ *str = '\t';
+ memmove(str+1, str+2, sizeof(WCHAR)*(strlennull(str)-1));
+
+ if (*selStart>i) --*selStart;
+ if (*selEnd>i) --*selEnd;
+ }
+ }
+}
+
+
+
+static void EscapesToBinary(char *str)
+{
+ for (;*str;str++)
+ {
+ if (*str!='\\') continue;
+ if(str[1]=='n') {*str++='\r'; *str='\n'; continue;}
+ if(str[1]=='0')
+ {
+ char *codeend;
+ *str=(char)strtol(str+1,&codeend,8);
+ if (*str==0) {*str='\\'; continue;}
+ memmove(str+1,codeend,strlennull(codeend)+1);
+ continue;
+ }
+ for(int i=0;i<SIZEOF(escapes);i+=2)
+ if(str[1]==escapes[i])
+ {
+ *str=escapes[i+1];
+ memmove(str+1,str+2,strlennull(str)-1);
+ break;
+ }
+ }
+}
+
+
+
+char *BinaryToEscapes(char *str)
+{
+ int extra=10,len=strlennull(str)+11,i;
+ char *out,*pout;
+
+ out=pout=(char*)SAFE_MALLOC(len);
+ for (;*str;str++)
+ {
+ if ((unsigned char)*str>=' ')
+ {
+ *pout++=*str;
+ continue;
+ }
+ if(str[0]=='\r' && str[1]=='\n')
+ {
+ *pout++='\\';
+ *pout++='n';
+ str++;
+ continue;
+ }
+ if(extra<3)
+ {
+ extra+=8; len+=8;
+ pout=out=(char*)SAFE_REALLOC(out,len);
+ }
+ *pout++='\\';
+ for(i = 0; i < SIZEOF(escapes); i += 2)
+ if (*str==escapes[i+1])
+ {
+ *pout++=escapes[i];
+ extra--;
+ break;
+ }
+ if(i < SIZEOF(escapes)) continue;
+ *pout++='0'; extra--;
+ if (*str>=8)
+ {
+ *pout++=(*str>>3)+'0';
+ extra--;
+ }
+ *pout++=(*str&7)+'0'; extra--;
+ }
+ *pout='\0';
+ return out;
+}
+
+
+
+static LRESULT CALLBACK StringEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_KEYDOWN:
+ if (wParam==VK_ESCAPE)
+ {
+ if (dataStringEdit)
+ dataStringEdit->EndStringEdit(0);
+ return 0;
+ }
+ if (wParam==VK_RETURN)
+ {
+ if (GetWindowLongPtr(hwnd, GWL_STYLE) & ES_MULTILINE && !(GetKeyState(VK_CONTROL) & 0x8000)) break;
+ if (dataStringEdit)
+ dataStringEdit->EndStringEdit(1);
+ return 0;
+ }
+ break;
+
+ case WM_GETDLGCODE:
+ return DLGC_WANTALLKEYS|CallWindowProc(OldStringEditProc, hwnd, msg, wParam, lParam);
+
+ case WM_KILLFOCUS:
+ if ((HWND)wParam == hwndExpandButton) break;
+ if (dataStringEdit)
+ dataStringEdit->EndStringEdit(1);
+ return 0;
+ }
+ return CallWindowProc(OldStringEditProc, hwnd, msg, wParam, lParam);
+}
+
+
+
+static LRESULT CALLBACK ExpandButtonSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_LBUTTONUP:
+ if(GetCapture()==hwnd)
+ {
+ //do expand
+ RECT rcStart,rcEnd;
+ DWORD selStart,selEnd;
+ WCHAR *text;
+ BOOL animEnabled=TRUE;
+
+ GetWindowRect(hwndEdit,&rcStart);
+ InflateRect(&rcStart,2,2);
+
+ text = GetWindowTextUcs(hwndEdit);
+ SendMessage(hwndEdit,EM_GETSEL,(WPARAM)&selStart,(LPARAM)&selEnd);
+ DestroyWindow(hwndEdit);
+ EscapesToMultiline(text,&selStart,&selEnd);
+ hwndEdit=CreateWindowExA(WS_EX_TOOLWINDOW,"EDIT","",WS_POPUP|WS_BORDER|WS_VISIBLE|ES_WANTRETURN|ES_AUTOVSCROLL|WS_VSCROLL|ES_MULTILINE,rcStart.left,rcStart.top,rcStart.right-rcStart.left,rcStart.bottom-rcStart.top,NULL,NULL,hInst,NULL);
+ SetWindowTextUcs(hwndEdit, text);
+ OldStringEditProc=(WNDPROC)SetWindowLongPtr(hwndEdit,GWLP_WNDPROC,(LONG_PTR)StringEditSubclassProc);
+ SendMessage(hwndEdit,WM_SETFONT,(WPARAM)dataStringEdit->hListFont,0);
+ SendMessage(hwndEdit,EM_SETSEL,selStart,selEnd);
+ SetFocus(hwndEdit);
+
+ rcEnd.left=rcStart.left; rcEnd.top=rcStart.top;
+ rcEnd.right=rcEnd.left+350;
+ rcEnd.bottom=rcEnd.top+150;
+ if (LOBYTE(LOWORD(GetVersion()))>4 || HIBYTE(LOWORD(GetVersion()))>0)
+ SystemParametersInfo(SPI_GETCOMBOBOXANIMATION,0,&animEnabled,FALSE);
+ if(animEnabled)
+ {
+ DWORD startTime,timeNow;
+ startTime=GetTickCount();
+ for (;;)
+ {
+ UpdateWindow(hwndEdit);
+ timeNow=GetTickCount();
+ if(timeNow>startTime+200) break;
+ SetWindowPos(hwndEdit,0,rcEnd.left,rcEnd.top,(rcEnd.right-rcStart.right)*(timeNow-startTime)/200+rcStart.right-rcEnd.left,(rcEnd.bottom-rcStart.bottom)*(timeNow-startTime)/200+rcStart.bottom-rcEnd.top,SWP_NOZORDER);
+ }
+ }
+ SetWindowPos(hwndEdit,0,rcEnd.left,rcEnd.top,rcEnd.right-rcEnd.left,rcEnd.bottom-rcEnd.top,SWP_NOZORDER);
+
+ DestroyWindow(hwnd);
+ hwndExpandButton=NULL;
+
+ SAFE_FREE((void**)&text);
+ }
+ break;
+ }
+ return CallWindowProc(OldExpandButtonProc,hwnd,msg,wParam,lParam);
+}
+
+
+void ChangeInfoData::BeginStringEdit(int iItem, RECT *rc, int i, WORD wVKey)
+{
+ char *szValue;
+ char str[80];
+ int alloced=0;
+
+ EndStringEdit(0);
+ InflateRect(rc,-2,-2);
+ rc->left-=2;
+ if (setting[i].displayType & LIF_PASSWORD && !settingData[i].changed)
+ szValue = " ";
+ else if ((setting[i].displayType & LIM_TYPE) == LI_NUMBER)
+ {
+ szValue = str;
+ null_snprintf(str, sizeof(str), "%d", settingData[i].value );
+ }
+ else if (settingData[i].value)
+ {
+ szValue = BinaryToEscapes((char*)settingData[i].value);
+ alloced = 1;
+ }
+ else szValue = "";
+
+ iEditItem = iItem;
+
+ if ((setting[i].displayType & LIM_TYPE)==LI_LONGSTRING)
+ {
+ rc->right-=rc->bottom-rc->top;
+ hwndExpandButton=CreateWindowA("BUTTON","",WS_VISIBLE|WS_CHILD|BS_PUSHBUTTON|BS_ICON,rc->right,rc->top,rc->bottom-rc->top,rc->bottom-rc->top,hwndList,NULL,hInst,NULL);
+ SendMessage(hwndExpandButton,BM_SETIMAGE,IMAGE_ICON,(LPARAM)LoadImage(hInst,MAKEINTRESOURCE(IDI_EXPANDSTRINGEDIT),IMAGE_ICON,0,0,LR_SHARED));
+ OldExpandButtonProc=(WNDPROC)SetWindowLongPtr(hwndExpandButton,GWLP_WNDPROC,(LONG_PTR)ExpandButtonSubclassProc);
+ }
+
+ dataStringEdit = this;
+ hwndEdit = CreateWindow(_T("EDIT"),_T(""),WS_VISIBLE|WS_CHILD|ES_AUTOHSCROLL|((setting[i].displayType&LIM_TYPE)==LI_NUMBER?ES_NUMBER:0)|(setting[i].displayType&LIF_PASSWORD?ES_PASSWORD:0),rc->left,rc->top,rc->right-rc->left,rc->bottom-rc->top,hwndList,NULL,hInst,NULL);
+ SetWindowTextUtf(hwndEdit, szValue);
+ if (alloced) SAFE_FREE(&szValue);
+ OldStringEditProc=(WNDPROC)SetWindowLongPtr(hwndEdit,GWLP_WNDPROC,(LONG_PTR)StringEditSubclassProc);
+ SendMessage(hwndEdit,WM_SETFONT,(WPARAM)hListFont,0);
+ if ((setting[i].displayType & LIM_TYPE) == LI_NUMBER)
+ {
+ int *range= (int*)setting[i].pList;
+ RECT rcUpDown;
+ hwndUpDown=CreateWindow(UPDOWN_CLASS,_T(""),WS_VISIBLE|WS_CHILD|UDS_AUTOBUDDY|UDS_ALIGNRIGHT|UDS_HOTTRACK|UDS_NOTHOUSANDS|UDS_SETBUDDYINT,0,0,0,0,hwndList,NULL,hInst,NULL);
+ SendMessage(hwndUpDown, UDM_SETRANGE32, range[0], range[1]);
+ SendMessage(hwndUpDown, UDM_SETPOS32, 0, settingData[i].value);
+ if (!(setting[i].displayType & LIF_ZEROISVALID) && settingData[i].value==0)
+ SetWindowTextA(hwndEdit, "");
+ GetClientRect(hwndUpDown, &rcUpDown);
+ rc->right -= rcUpDown.right;
+ SetWindowPos(hwndEdit,0,0,0,rc->right-rc->left,rc->bottom-rc->top,SWP_NOZORDER|SWP_NOMOVE);
+ }
+ SendMessage(hwndEdit,EM_SETSEL,0,(LPARAM)-1);
+ SetFocus(hwndEdit);
+ PostMessage(hwndEdit,WM_KEYDOWN,wVKey,0);
+}
+
+
+void ChangeInfoData::EndStringEdit(int save)
+{
+ if (hwndEdit == NULL || iEditItem == -1 || this != dataStringEdit) return;
+ if (save)
+ {
+ char *text = (char*)SAFE_MALLOC(GetWindowTextLength(hwndEdit)+1);
+
+ GetWindowTextA(hwndEdit,(char*)text,GetWindowTextLength(hwndEdit)+1);
+ EscapesToBinary(text);
+ if ((setting[iEditItem].displayType&LIM_TYPE)==LI_NUMBER)
+ {
+ LPARAM newValue;
+ int *range=(int*)setting[iEditItem].pList;
+ newValue = atoi(text);
+ if (newValue)
+ {
+ if (newValue<range[0]) newValue=range[0];
+ if (newValue>range[1]) newValue=range[1];
+ }
+ settingData[iEditItem].changed = settingData[iEditItem].value != newValue;
+ settingData[iEditItem].value = newValue;
+ SAFE_FREE(&text);
+ }
+ else
+ {
+ if (!(setting[iEditItem].displayType & LIF_PASSWORD))
+ {
+ SAFE_FREE(&text);
+ text = GetWindowTextUtf(hwndEdit);
+ EscapesToBinary(text);
+ }
+ if ((setting[iEditItem].displayType & LIF_PASSWORD && strcmpnull(text," ")) ||
+ (!(setting[iEditItem].displayType & LIF_PASSWORD) && strcmpnull(text, (char*)settingData[iEditItem].value) && (strlennull(text) + strlennull((char*)settingData[iEditItem].value))))
+ {
+ SAFE_FREE((void**)&settingData[iEditItem].value);
+ if (strlennull(text))
+ settingData[iEditItem].value = (LPARAM)text;
+ else
+ {
+ settingData[iEditItem].value = 0;
+ SAFE_FREE(&text);
+ }
+ settingData[iEditItem].changed = 1;
+ }
+ }
+ if (settingData[iEditItem].changed)
+ {
+ TCHAR tbuf[MAX_PATH];
+
+ GetWindowText(hwndEdit, tbuf, SIZEOF(tbuf));
+ ListView_SetItemText(hwndList, iEditItem, 1, tbuf);
+
+ EnableDlgItem(hwndDlg, IDC_SAVE, TRUE);
+ }
+ }
+ ListView_RedrawItems(hwndList, iEditItem, iEditItem);
+ iEditItem = -1;
+ dataStringEdit = NULL;
+ DestroyWindow(hwndEdit);
+ hwndEdit = NULL;
+ if (hwndExpandButton) DestroyWindow(hwndExpandButton);
+ hwndExpandButton = NULL;
+ if (hwndUpDown) DestroyWindow(hwndUpDown);
+ hwndUpDown = NULL;
+}
+
+
+
+int IsStringEditWindow(HWND hwnd)
+{
+ if (hwnd == hwndEdit) return 1;
+ if (hwnd == hwndExpandButton) return 1;
+ if (hwnd == hwndUpDown) return 1;
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/changeinfo/icqoscar.h b/protocols/IcqOscarJ/src/changeinfo/icqoscar.h
new file mode 100644
index 0000000000..77283f6f7f
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/icqoscar.h
@@ -0,0 +1,2 @@
+/* For MinGW sake */
+#include "../icqoscar.h"
diff --git a/protocols/IcqOscarJ/src/changeinfo/main.cpp b/protocols/IcqOscarJ/src/changeinfo/main.cpp
new file mode 100644
index 0000000000..22669be3aa
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/main.cpp
@@ -0,0 +1,28 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2008 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
diff --git a/protocols/IcqOscarJ/src/changeinfo/upload.cpp b/protocols/IcqOscarJ/src/changeinfo/upload.cpp
new file mode 100644
index 0000000000..a468dc9f2b
--- /dev/null
+++ b/protocols/IcqOscarJ/src/changeinfo/upload.cpp
@@ -0,0 +1,91 @@
+// ---------------------------------------------------------------------------80
+// ICQ plugin for Miranda Instant Messenger
+// ________________________________________
+//
+// Copyright © 2001-2004 Richard Hughes, Martin Öberg
+// Copyright © 2004-2009 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// ChangeInfo Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+int CIcqProto::StringToListItemId(const char *szSetting,int def)
+{
+ int i;
+
+ for(i=0;i<settingCount;i++)
+ if (!strcmpnull(szSetting,setting[i].szDbSetting))
+ break;
+
+ if (i==settingCount) return def;
+
+ FieldNamesItem *list = (FieldNamesItem*)setting[i].pList;
+
+ char *szValue = getSettingStringUtf(NULL, szSetting, NULL);
+ if (!szValue)
+ return def;
+
+ for (i=0; list[i].text; i++)
+ if (!strcmpnull(list[i].text, szValue))
+ break;
+
+ SAFE_FREE(&szValue);
+ if (!list[i].text) return def;
+
+ return list[i].code;
+}
+
+
+int ChangeInfoData::UploadSettings(void)
+{
+ if (!ppro->icqOnline())
+ {
+ MessageBoxUtf(hwndDlg, LPGEN("You are not currently connected to the ICQ network. You must be online in order to update your information on the server."), LPGEN("Change ICQ Details"), MB_OK);
+ return 0;
+ }
+
+ hUpload[0] = (HANDLE)ppro->ChangeInfoEx(CIXT_FULL, 0);
+
+ //password
+ char* tmp = ppro->GetUserPassword(TRUE);
+ if (tmp)
+ {
+ if (strlennull(Password) > 0 && strcmpnull(Password, tmp))
+ {
+ hUpload[1] = (HANDLE)ppro->icq_changeUserPasswordServ(tmp);
+ char szPwd[PASSWORDMAXLEN] = {0};
+
+ if (ppro->GetUserStoredPassword(szPwd, sizeof(szPwd)))
+ { // password is stored in DB, update
+ char ptmp[PASSWORDMAXLEN];
+
+ strcpy(ptmp, tmp);
+
+ CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(ptmp), (LPARAM)ptmp);
+
+ ppro->setSettingString(NULL, "Password", ptmp);
+ }
+ }
+ }
+
+ return 1;
+}
diff --git a/protocols/IcqOscarJ/src/channels.h b/protocols/IcqOscarJ/src/channels.h
new file mode 100644
index 0000000000..ba10483b56
--- /dev/null
+++ b/protocols/IcqOscarJ/src/channels.h
@@ -0,0 +1,47 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Header for FLAP Channel packet handlers
+//
+// -----------------------------------------------------------------------------
+#ifndef __CHANNELS_H
+#define __CHANNELS_H
+
+
+struct snac_header
+{
+ BOOL bValid;
+ WORD wFamily;
+ WORD wSubtype;
+ WORD wFlags;
+ DWORD dwRef;
+ WORD wVersion;
+};
+
+int unpackSnacHeader(snac_header *pSnacHeader, BYTE **pBuffer, WORD *pwBufferLength);
+
+
+#endif /* __CHANNELS_H */
diff --git a/protocols/IcqOscarJ/src/cookies.cpp b/protocols/IcqOscarJ/src/cookies.cpp
new file mode 100644
index 0000000000..87ffb4bc07
--- /dev/null
+++ b/protocols/IcqOscarJ/src/cookies.cpp
@@ -0,0 +1,301 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Handles packet & message cookies
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+#define INVALID_COOKIE_INDEX -1
+
+void CIcqProto::RemoveExpiredCookies()
+{
+ time_t tNow = time(NULL);
+
+ for (int i = cookies.getCount()-1; i >= 0; i--)
+ {
+ icq_cookie_info *cookie = cookies[i];
+
+ if ((cookie->dwTime + COOKIE_TIMEOUT) < tNow)
+ {
+ cookies.remove(i);
+ SAFE_FREE((void**)&cookie);
+ }
+ }
+}
+
+
+// Generate and allocate cookie
+DWORD CIcqProto::AllocateCookie(BYTE bType, WORD wIdent, HANDLE hContact, void *pvExtra)
+{
+ icq_lock l(cookieMutex);
+
+ DWORD dwThisSeq = wCookieSeq++;
+ dwThisSeq &= 0x7FFF;
+ dwThisSeq |= wIdent<<0x10;
+
+ icq_cookie_info* p = (icq_cookie_info*)SAFE_MALLOC(sizeof(icq_cookie_info));
+ if (p)
+ {
+ p->bType = bType;
+ p->dwCookie = dwThisSeq;
+ p->hContact = hContact;
+ p->pvExtra = pvExtra;
+ p->dwTime = time(NULL);
+ cookies.insert(p);
+ }
+ return dwThisSeq;
+}
+
+
+DWORD CIcqProto::GenerateCookie(WORD wIdent)
+{
+ icq_lock l(cookieMutex);
+
+ DWORD dwThisSeq = wCookieSeq++;
+ dwThisSeq &= 0x7FFF;
+ dwThisSeq |= wIdent<<0x10;
+
+ return dwThisSeq;
+}
+
+
+int CIcqProto::GetCookieType(DWORD dwCookie)
+{
+ icq_lock l(cookieMutex);
+
+ int i = cookies.getIndex(( icq_cookie_info* )&dwCookie );
+ if ( i != INVALID_COOKIE_INDEX )
+ i = cookies[i]->bType;
+
+ return i;
+}
+
+
+int CIcqProto::FindCookie(DWORD dwCookie, HANDLE *phContact, void **ppvExtra)
+{
+ icq_lock l(cookieMutex);
+
+ int i = cookies.getIndex(( icq_cookie_info* )&dwCookie );
+ if (i != INVALID_COOKIE_INDEX)
+ {
+ if (phContact)
+ *phContact = cookies[i]->hContact;
+ if (ppvExtra)
+ *ppvExtra = cookies[i]->pvExtra;
+
+ // Cookie found
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int CIcqProto::FindCookieByData(void *pvExtra, DWORD *pdwCookie, HANDLE *phContact)
+{
+ icq_lock l(cookieMutex);
+
+ for (int i = 0; i < cookies.getCount(); i++)
+ {
+ if (pvExtra == cookies[i]->pvExtra)
+ {
+ if (phContact)
+ *phContact = cookies[i]->hContact;
+ if (pdwCookie)
+ *pdwCookie = cookies[i]->dwCookie;
+
+ // Cookie found
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+int CIcqProto::FindCookieByType(BYTE bType, DWORD *pdwCookie, HANDLE *phContact, void** ppvExtra)
+{
+ icq_lock l(cookieMutex);
+
+ for (int i = 0; i < cookies.getCount(); i++)
+ {
+ if (bType == cookies[i]->bType)
+ {
+ if (pdwCookie)
+ *pdwCookie = cookies[i]->dwCookie;
+ if (phContact)
+ *phContact = cookies[i]->hContact;
+ if (ppvExtra)
+ *ppvExtra = cookies[i]->pvExtra;
+
+ // Cookie found
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+int CIcqProto::FindMessageCookie(DWORD dwMsgID1, DWORD dwMsgID2, DWORD *pdwCookie, HANDLE *phContact, cookie_message_data **ppvExtra)
+{
+ icq_lock l(cookieMutex);
+
+ for (int i = 0; i < cookies.getCount(); i++)
+ {
+ if (cookies[i]->bType == CKT_MESSAGE || cookies[i]->bType == CKT_FILE || cookies[i]->bType == CKT_REVERSEDIRECT)
+ { // message cookie found
+ cookie_message_data *pCookie = (cookie_message_data*)cookies[i]->pvExtra;
+
+ if (pCookie->dwMsgID1 == dwMsgID1 && pCookie->dwMsgID2 == dwMsgID2)
+ {
+ if (phContact)
+ *phContact = cookies[i]->hContact;
+ if (pdwCookie)
+ *pdwCookie = cookies[i]->dwCookie;
+ if (ppvExtra)
+ *ppvExtra = pCookie;
+
+ // Cookie found
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+void CIcqProto::FreeCookie(DWORD dwCookie)
+{
+ icq_lock l(cookieMutex);
+
+ int i = cookies.getIndex((icq_cookie_info*)&dwCookie);
+ if (i != INVALID_COOKIE_INDEX)
+ { // Cookie found, remove from list
+ icq_cookie_info *cookie = cookies[i];
+
+ cookies.remove(i);
+ SAFE_FREE((void**)&cookie);
+ }
+
+ RemoveExpiredCookies();
+}
+
+
+void CIcqProto::FreeCookieByData(BYTE bType, void *pvExtra)
+{
+ icq_lock l(cookieMutex);
+
+ for (int i = 0; i < cookies.getCount(); i++)
+ {
+ icq_cookie_info *cookie = cookies[i];
+
+ if (bType == cookie->bType && pvExtra == cookie->pvExtra)
+ { // Cookie found, remove from list
+ cookies.remove(i);
+ SAFE_FREE((void**)&cookie);
+ break;
+ }
+ }
+
+ RemoveExpiredCookies();
+}
+
+
+void CIcqProto::ReleaseCookie(DWORD dwCookie)
+{
+ icq_lock l(cookieMutex);
+
+ int i = cookies.getIndex(( icq_cookie_info* )&dwCookie );
+ if (i != INVALID_COOKIE_INDEX)
+ { // Cookie found, remove from list
+ icq_cookie_info *cookie = cookies[i];
+
+ cookies.remove(i);
+ SAFE_FREE((void**)&cookie->pvExtra);
+ SAFE_FREE((void**)&cookie);
+ }
+ RemoveExpiredCookies();
+}
+
+
+void CIcqProto::InitMessageCookie(cookie_message_data *pCookie)
+{
+ DWORD dwMsgID1;
+ DWORD dwMsgID2;
+
+ do
+ { // ensure that message ids are unique
+ dwMsgID1 = time(NULL);
+ dwMsgID2 = RandRange(0, 0x0FFFF);
+ } while (FindMessageCookie(dwMsgID1, dwMsgID2, NULL, NULL, NULL));
+
+ if (pCookie)
+ {
+ pCookie->dwMsgID1 = dwMsgID1;
+ pCookie->dwMsgID2 = dwMsgID2;
+ }
+}
+
+
+cookie_message_data* CIcqProto::CreateMessageCookie(WORD bMsgType, BYTE bAckType)
+{
+ cookie_message_data *pCookie = (cookie_message_data*)SAFE_MALLOC(sizeof(cookie_message_data));
+ if (pCookie)
+ {
+ pCookie->bMessageType = bMsgType;
+ pCookie->nAckType = bAckType;
+
+ InitMessageCookie(pCookie);
+ }
+ return pCookie;
+}
+
+
+cookie_message_data* CIcqProto::CreateMessageCookieData(BYTE bMsgType, HANDLE hContact, DWORD dwUin, int bUseSrvRelay)
+{
+ BYTE bAckType;
+ WORD wStatus = getContactStatus(hContact);
+
+ if (!getSettingByte(hContact, "SlowSend", getSettingByte(NULL, "SlowSend", DEFAULT_SLOWSEND)) ||
+ (!dwUin && wStatus == ID_STATUS_OFFLINE))
+ bAckType = ACKTYPE_NONE;
+ else if (bUseSrvRelay)
+ bAckType = ACKTYPE_CLIENT;
+ else
+ bAckType = ACKTYPE_SERVER;
+
+ cookie_message_data* pCookieData = CreateMessageCookie(bMsgType, bAckType);
+
+ // set flag for offline messages - to allow proper error handling
+ if (wStatus == ID_STATUS_OFFLINE || wStatus == ID_STATUS_INVISIBLE)
+ pCookieData->isOffline = TRUE;
+
+ return pCookieData;
+}
diff --git a/protocols/IcqOscarJ/src/cookies.h b/protocols/IcqOscarJ/src/cookies.h
new file mode 100644
index 0000000000..05e0d170eb
--- /dev/null
+++ b/protocols/IcqOscarJ/src/cookies.h
@@ -0,0 +1,148 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __COOKIES_H
+#define __COOKIES_H
+
+
+#define CKT_MESSAGE 0x01
+#define CKT_FILE 0x02
+#define CKT_SEARCH 0x04
+#define CKT_SERVERLIST 0x08
+#define CKT_SERVICEREQUEST 0x0A
+#define CKT_REVERSEDIRECT 0x0C
+#define CKT_FAMILYSPECIAL 0x10
+#define CKT_OFFLINEMESSAGE 0x12
+#define CKT_DIRECTORY_QUERY 0x18
+#define CKT_DIRECTORY_UPDATE 0x19
+#define CKT_AVATAR 0x20
+
+struct CIcqProto;
+
+/* Basic structure used to hold operation cookies list */
+struct icq_cookie_info
+{
+ DWORD dwCookie;
+ HANDLE hContact;
+ void *pvExtra;
+ time_t dwTime;
+ BYTE bType;
+};
+
+
+/* Specific structures to hold request specific data - pvExtra */
+
+struct cookie_family_request
+{
+ WORD wFamily;
+ void (CIcqProto::*familyHandler)(HANDLE hConn, char* cookie, WORD cookieLen);
+};
+
+
+struct cookie_offline_messages
+{
+ int nMessages;
+ int nMissed;
+};
+
+
+#define ACKTYPE_NONE 0
+#define ACKTYPE_SERVER 1
+#define ACKTYPE_CLIENT 2
+
+struct cookie_message_data
+{
+ DWORD dwMsgID1;
+ DWORD dwMsgID2;
+ WORD bMessageType;
+ BYTE nAckType;
+ BYTE isOffline;
+};
+
+#define REQUESTTYPE_OWNER 0
+#define REQUESTTYPE_USERAUTO 1
+#define REQUESTTYPE_USERMINIMAL 2
+#define REQUESTTYPE_USERDETAILED 3
+#define REQUESTTYPE_PROFILE 4
+
+struct cookie_fam15_data
+{
+ BYTE bRequestType;
+};
+
+
+#define SEARCHTYPE_UID 0
+#define SEARCHTYPE_EMAIL 1
+#define SEARCHTYPE_NAMES 2
+#define SEARCHTYPE_DETAILS 4
+
+struct cookie_search
+{
+ BYTE bSearchType;
+ char* szObject;
+ DWORD dwMainId;
+ DWORD dwStatus;
+};
+
+
+struct cookie_avatar
+{
+ DWORD dwUin;
+ HANDLE hContact;
+ unsigned int hashlen;
+ BYTE *hash;
+ unsigned int cbData;
+ TCHAR *szFile;
+};
+
+
+struct cookie_reverse_connect: public cookie_message_data
+{
+ HANDLE hContact;
+ DWORD dwUin;
+ int type;
+ void *ft;
+};
+
+
+#define DIRECTORYREQUEST_INFOUSER 0x01
+#define DIRECTORYREQUEST_INFOOWNER 0x02
+#define DIRECTORYREQUEST_INFOMULTI 0x03
+#define DIRECTORYREQUEST_SEARCH 0x08
+#define DIRECTORYREQUEST_UPDATEOWNER 0x10
+#define DIRECTORYREQUEST_UPDATENOTE 0x11
+#define DIRECTORYREQUEST_UPDATEPRIVACY 0x12
+
+struct cookie_directory_data
+{
+ BYTE bRequestType;
+};
+
+
+#endif /* __COOKIES_H */
diff --git a/protocols/IcqOscarJ/src/directpackets.cpp b/protocols/IcqOscarJ/src/directpackets.cpp
new file mode 100644
index 0000000000..594f005675
--- /dev/null
+++ b/protocols/IcqOscarJ/src/directpackets.cpp
@@ -0,0 +1,293 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void EncryptDirectPacket(directconnect* dc, icq_packet* p);
+
+void packEmptyMsg(icq_packet *packet);
+
+static void packDirectMsgHeader(icq_packet* packet, WORD wDataLen, WORD wCommand, DWORD dwCookie, BYTE bMsgType, BYTE bMsgFlags, WORD wX1, WORD wX2)
+{
+ directPacketInit(packet, 29 + wDataLen);
+ packByte(packet, 2); /* channel */
+ packLEDWord(packet, 0); /* space for crypto */
+ packLEWord(packet, wCommand);
+ packLEWord(packet, 14); /* unknown */
+ packLEWord(packet, (WORD)dwCookie);
+ packLEDWord(packet, 0); /* unknown */
+ packLEDWord(packet, 0); /* unknown */
+ packLEDWord(packet, 0); /* unknown */
+ packByte(packet, bMsgType);
+ packByte(packet, bMsgFlags);
+ packLEWord(packet, wX1); /* unknown. Is 1 for getawaymsg, 0 otherwise */
+ packLEWord(packet, wX2); // this is probably priority
+}
+
+
+void CIcqProto::icq_sendDirectMsgAck(directconnect* dc, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, char* szCap)
+{
+ icq_packet packet;
+
+ packDirectMsgHeader(&packet, (WORD)(bMsgType==MTYPE_PLAIN ? (szCap ? 53 : 11) : 3), DIRECT_ACK, wCookie, bMsgType, bMsgFlags, 0, 0);
+ packEmptyMsg(&packet); /* empty message */
+
+ if (bMsgType == MTYPE_PLAIN)
+ {
+ packMsgColorInfo(&packet);
+
+ if (szCap)
+ {
+ packLEDWord(&packet, 0x26); /* CLSID length */
+ packBuffer(&packet, (LPBYTE)szCap, 0x26); /* GUID */
+ }
+ }
+ EncryptDirectPacket(dc, &packet);
+ sendDirectPacket(dc, &packet);
+
+ NetLog_Direct("Sent acknowledgement thru direct connection");
+}
+
+
+DWORD CIcqProto::icq_sendGetAwayMsgDirect(HANDLE hContact, int type)
+{
+ icq_packet packet;
+ DWORD dwCookie;
+ cookie_message_data *pCookieData;
+
+ if (getSettingWord(hContact, "Version", 0) == 9)
+ return 0; // v9 DC protocol does not support this message
+
+ pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type);
+ dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ packDirectMsgHeader(&packet, 3, DIRECT_MESSAGE, dwCookie, (BYTE)type, 3, 1, 0);
+ packEmptyMsg(&packet); // message
+
+ return (SendDirectMessage(hContact, &packet)) ? dwCookie : 0;
+}
+
+
+void CIcqProto::icq_sendAwayMsgReplyDirect(directconnect* dc, WORD wCookie, BYTE msgType, const char** szMsg)
+{
+ icq_packet packet;
+
+ if (validateStatusMessageRequest(dc->hContact, msgType))
+ {
+ NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dc->dwRemoteUin);
+
+ icq_lock l(m_modeMsgsMutex);
+
+ if (szMsg && *szMsg)
+ {
+ // prepare Ansi message - only Ansi supported
+ WORD wMsgLen = strlennull(*szMsg) + 1;
+ char *szAnsiMsg = (char*)_alloca(wMsgLen);
+
+ utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen);
+ wMsgLen = strlennull(szAnsiMsg);
+ packDirectMsgHeader(&packet, (WORD)(3 + wMsgLen), DIRECT_ACK, wCookie, msgType, 3, 0, 0);
+ packLEWord(&packet, (WORD)(wMsgLen + 1));
+ packBuffer(&packet, (LPBYTE)szAnsiMsg, (WORD)(wMsgLen + 1));
+ EncryptDirectPacket(dc, &packet);
+
+ sendDirectPacket(dc, &packet);
+ }
+ }
+}
+
+
+void CIcqProto::icq_sendFileAcceptDirect(HANDLE hContact, filetransfer* ft)
+{
+ // v7 packet
+ icq_packet packet;
+
+ packDirectMsgHeader(&packet, 18, DIRECT_ACK, ft->dwCookie, MTYPE_FILEREQ, 0, 0, 0);
+ packLEWord(&packet, 1); // description
+ packByte(&packet, 0);
+ packWord(&packet, wListenPort);
+ packLEWord(&packet, 0);
+ packLEWord(&packet, 1); // filename
+ packByte(&packet, 0); // TODO: really send filename
+ packLEDWord(&packet, ft->dwTotalSize); // file size
+ packLEDWord(&packet, wListenPort); // FIXME: ideally we want to open a new port for this
+
+ SendDirectMessage(hContact, &packet);
+
+ NetLog_Direct("Sent file accept direct, port %u", wListenPort);
+}
+
+
+void CIcqProto::icq_sendFileDenyDirect(HANDLE hContact, filetransfer *ft, const char *szReason)
+{
+ // v7 packet
+ icq_packet packet;
+ char *szReasonAnsi = NULL;
+ int cbReasonAnsi = 0;
+
+ if (!utf8_decode(szReason, &szReasonAnsi))
+ szReasonAnsi = NULL;
+ else
+ cbReasonAnsi = strlennull(szReasonAnsi);
+
+ packDirectMsgHeader(&packet, (WORD)(18 + cbReasonAnsi), DIRECT_ACK, ft->dwCookie, MTYPE_FILEREQ, 0, 1, 0);
+ packLEWord(&packet, (WORD)(1 + cbReasonAnsi)); // description
+ if (szReasonAnsi) packBuffer(&packet, (LPBYTE)szReasonAnsi, (WORD)cbReasonAnsi);
+ packByte(&packet, 0);
+ packWord(&packet, 0);
+ packLEWord(&packet, 0);
+ packLEWord(&packet, 1); // filename
+ packByte(&packet, 0); // TODO: really send filename
+ packLEDWord(&packet, 0); // file size
+ packLEDWord(&packet, 0);
+
+ SAFE_FREE(&szReasonAnsi);
+
+ SendDirectMessage(hContact, &packet);
+
+ NetLog_Direct("Sent file deny direct.");
+}
+
+
+int CIcqProto::icq_sendFileSendDirectv7(filetransfer *ft, const char *pszFiles)
+{
+ icq_packet packet;
+ char *szFilesAnsi = NULL;
+ WORD wDescrLen = strlennull(ft->szDescription), wFilesLen = 0;
+
+ if (!utf8_decode(pszFiles, &szFilesAnsi))
+ szFilesAnsi = NULL;
+ else
+ wFilesLen = strlennull(szFilesAnsi);
+
+ packDirectMsgHeader(&packet, (WORD)(18 + wDescrLen + wFilesLen), DIRECT_MESSAGE, (WORD)ft->dwCookie, MTYPE_FILEREQ, 0, 0, 0);
+ packLEWord(&packet, (WORD)(wDescrLen + 1));
+ packBuffer(&packet, (LPBYTE)ft->szDescription, (WORD)(wDescrLen + 1));
+ packLEDWord(&packet, 0); // listen port
+ packLEWord(&packet, (WORD)(wFilesLen + 1));
+ packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
+ packLEDWord(&packet, ft->dwTotalSize);
+ packLEDWord(&packet, 0); // listen port (again)
+
+ SAFE_FREE(&szFilesAnsi);
+
+ NetLog_Direct("Sending v%u file transfer request direct", 7);
+
+ return SendDirectMessage(ft->hContact, &packet);
+}
+
+
+int CIcqProto::icq_sendFileSendDirectv8(filetransfer *ft, const char *pszFiles)
+{
+ icq_packet packet;
+ char *szFilesAnsi = NULL;
+ WORD wDescrLen = strlennull(ft->szDescription), wFilesLen = 0;
+
+ if (!utf8_decode(pszFiles, &szFilesAnsi))
+ szFilesAnsi = NULL;
+ else
+ wFilesLen = strlennull(szFilesAnsi);
+
+ packDirectMsgHeader(&packet, (WORD)(0x2E + 22 + wDescrLen + wFilesLen + 1), DIRECT_MESSAGE, (WORD)ft->dwCookie, MTYPE_PLUGIN, 0, 0, 0);
+ packEmptyMsg(&packet); // message
+ packPluginTypeId(&packet, MTYPE_FILEREQ);
+
+ packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length
+ packLEDWord(&packet, wDescrLen); // Description
+ packBuffer(&packet, (LPBYTE)ft->szDescription, wDescrLen);
+ packWord(&packet, 0x8c82); // Unknown (port?), seen 0x80F6
+ packWord(&packet, 0x0222); // Unknown, seen 0x2e01
+ packLEWord(&packet, (WORD)(wFilesLen + 1));
+ packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
+ packLEDWord(&packet, ft->dwTotalSize);
+ packLEDWord(&packet, 0x0008c82); // Unknown, (seen 0xf680 ~33000)
+
+ SAFE_FREE(&szFilesAnsi);
+
+ NetLog_Direct("Sending v%u file transfer request direct", 8);
+
+ return SendDirectMessage(ft->hContact, &packet);
+}
+
+
+DWORD CIcqProto::icq_SendDirectMessage(HANDLE hContact, const char *szMessage, int nBodyLength, WORD wPriority, cookie_message_data *pCookieData, char *szCap)
+{
+ icq_packet packet;
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ // Pack the standard header
+ packDirectMsgHeader(&packet, (WORD)(nBodyLength + (szCap ? 53:11)), DIRECT_MESSAGE, dwCookie, (BYTE)pCookieData->bMessageType, 0, 0, 0);
+
+ packLEWord(&packet, (WORD)(nBodyLength+1)); // Length of message
+ packBuffer(&packet, (LPBYTE)szMessage, (WORD)(nBodyLength+1)); // Message
+ packMsgColorInfo(&packet);
+ if (szCap)
+ {
+ packLEDWord(&packet, 0x00000026); // length of GUID
+ packBuffer(&packet, (LPBYTE)szCap, 0x26); // UTF-8 GUID
+ }
+
+ if (SendDirectMessage(hContact, &packet))
+ return dwCookie; // Success
+
+ FreeCookie(dwCookie); // release cookie
+ return 0; // Failure
+}
+
+void CIcqProto::icq_sendXtrazRequestDirect(HANDLE hContact, DWORD dwCookie, char* szBody, int nBodyLen, WORD wType)
+{
+ icq_packet packet;
+
+ packDirectMsgHeader(&packet, (WORD)(11 + getPluginTypeIdLen(wType) + nBodyLen), DIRECT_MESSAGE, dwCookie, MTYPE_PLUGIN, 0, 0, 1);
+ packEmptyMsg(&packet); // message (unused)
+ packPluginTypeId(&packet, wType);
+
+ packLEDWord(&packet, nBodyLen + 4);
+ packLEDWord(&packet, nBodyLen);
+ packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen);
+
+ SendDirectMessage(hContact, &packet);
+}
+
+void CIcqProto::icq_sendXtrazResponseDirect(HANDLE hContact, WORD wCookie, char* szBody, int nBodyLen, WORD wType)
+{
+ icq_packet packet;
+
+ packDirectMsgHeader(&packet, (WORD)(getPluginTypeIdLen(wType) + 11 + nBodyLen), DIRECT_ACK, wCookie, MTYPE_PLUGIN, 0, 0, 0);
+ //
+ packEmptyMsg(&packet); // Message (unused)
+
+ packPluginTypeId(&packet, wType);
+
+ packLEDWord(&packet, nBodyLen + 4);
+ packLEDWord(&packet, nBodyLen);
+ packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen);
+
+ SendDirectMessage(hContact, &packet);
+}
diff --git a/protocols/IcqOscarJ/src/fam_01service.cpp b/protocols/IcqOscarJ/src/fam_01service.cpp
new file mode 100644
index 0000000000..9bc06f19ab
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_01service.cpp
@@ -0,0 +1,983 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Handles packets from Service family
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+#include <m_version.h>
+
+extern capstr capXStatus[];
+
+void CIcqProto::handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info)
+{
+ icq_packet packet;
+
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_SERVER_READY:
+#ifdef _DEBUG
+ NetLog_Server("Server is ready and is requesting my Family versions");
+ NetLog_Server("Sending my Families");
+#endif
+
+ // This packet is a response to SRV_FAMILIES SNAC(1,3).
+ // This tells the server which SNAC families and their corresponding
+ // versions which the client understands. This also seems to identify
+ // the client as an ICQ vice AIM client to the server.
+ // Miranda mimics the behaviour of ICQ 6
+ serverPacketInit(&packet, 54);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_FAMILIES);
+ packDWord(&packet, 0x00220001);
+ packDWord(&packet, 0x00010004);
+ packDWord(&packet, 0x00130004);
+ packDWord(&packet, 0x00020001);
+ packDWord(&packet, 0x00030001);
+ packDWord(&packet, 0x00150001);
+ packDWord(&packet, 0x00040001);
+ packDWord(&packet, 0x00060001);
+ packDWord(&packet, 0x00090001);
+ packDWord(&packet, 0x000a0001);
+ packDWord(&packet, 0x000b0001);
+ sendServPacket(&packet);
+ break;
+
+ case ICQ_SERVER_FAMILIES2:
+ /* This is a reply to CLI_FAMILIES and it tells the client which families and their versions that this server understands.
+ * We send a rate request packet */
+#ifdef _DEBUG
+ NetLog_Server("Server told me his Family versions");
+ NetLog_Server("Requesting Rate Information");
+#endif
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQ_RATE_INFO);
+ sendServPacket(&packet);
+ break;
+
+ case ICQ_SERVER_RATE_INFO:
+#ifdef _DEBUG
+ NetLog_Server("Server sent Rate Info");
+#endif
+ /* init rates management */
+ m_rates = new rates(this, pBuffer, wBufferLength);
+ /* ack rate levels */
+#ifdef _DEBUG
+ NetLog_Server("Sending Rate Info Ack");
+#endif
+ m_rates->initAckPacket(&packet);
+ sendServPacket(&packet);
+
+ /* CLI_REQINFO - This command requests from the server certain information about the client that is stored on the server. */
+#ifdef _DEBUG
+ NetLog_Server("Sending CLI_REQINFO");
+#endif
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQINFO);
+ sendServPacket(&packet);
+
+ if (m_bSsiEnabled)
+ {
+ cookie_servlist_action* ack;
+ DWORD dwCookie;
+
+ DWORD dwLastUpdate = getSettingDword(NULL, "SrvLastUpdate", 0);
+ WORD wRecordCount = getSettingWord(NULL, "SrvRecordCount", 0);
+
+ // CLI_REQLISTS - we want to use SSI
+#ifdef _DEBUG
+ NetLog_Server("Requesting roster rights");
+#endif
+ serverPacketInit(&packet, 16);
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_REQLISTS);
+ packTLVWord(&packet, 0x0B, 0x000F); // mimic ICQ 6
+ sendServPacket(&packet);
+
+ if (!wRecordCount) // CLI_REQROSTER
+ { // we do not have any data - request full list
+#ifdef _DEBUG
+ NetLog_Server("Requesting full roster");
+#endif
+ serverPacketInit(&packet, 10);
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (ack)
+ { // we try to use standalone cookie if available
+ ack->dwAction = SSA_CHECK_ROSTER; // loading list
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_CLI_REQUEST, 0, ack);
+ }
+ else // if not use that old fake
+ dwCookie = ICQ_LISTS_CLI_REQUEST<<0x10;
+
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_REQUEST, 0, dwCookie);
+ sendServPacket(&packet);
+ }
+ else // CLI_CHECKROSTER
+ {
+#ifdef _DEBUG
+ NetLog_Server("Requesting roster check");
+#endif
+ serverPacketInit(&packet, 16);
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (ack) // TODO: rewrite - use get list service for empty list
+ { // we try to use standalone cookie if available
+ ack->dwAction = SSA_CHECK_ROSTER; // loading list
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_CLI_CHECK, 0, ack);
+ }
+ else // if not use that old fake
+ dwCookie = ICQ_LISTS_CLI_CHECK<<0x10;
+
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_CHECK, 0, dwCookie);
+ // check if it was not changed elsewhere (force reload, set that setting to zero)
+ if (IsServerGroupsDefined())
+ {
+ packDWord(&packet, dwLastUpdate); // last saved time
+ packWord(&packet, wRecordCount); // number of records saved
+ }
+ else
+ { // we need to get groups info into DB, force receive list
+ packDWord(&packet, 0); // last saved time
+ packWord(&packet, 0); // number of records saved
+ }
+ sendServPacket(&packet);
+ }
+ }
+
+ // CLI_REQLOCATION
+#ifdef _DEBUG
+ NetLog_Server("Requesting Location rights");
+#endif
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_CLI_REQ_RIGHTS);
+ sendServPacket(&packet);
+
+ // CLI_REQBUDDY
+#ifdef _DEBUG
+ NetLog_Server("Requesting Client-side contactlist rights");
+#endif
+ serverPacketInit(&packet, 16);
+ packFNACHeader(&packet, ICQ_BUDDY_FAMILY, ICQ_USER_CLI_REQBUDDY);
+ // Query flags: 1 = Enable Avatars
+ // 2 = Enable offline status message notification
+ // 4 = Enable Avatars for offline contacts
+ // 8 = Use reject for not authorized contacts
+ packTLVWord(&packet, 0x05, 0x0007);
+ sendServPacket(&packet);
+
+ // CLI_REQICBM
+#ifdef _DEBUG
+ NetLog_Server("Sending CLI_REQICBM");
+#endif
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_CLI_REQICBM);
+ sendServPacket(&packet);
+
+ // CLI_REQBOS
+#ifdef _DEBUG
+ NetLog_Server("Sending CLI_REQBOS");
+#endif
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_BOS_FAMILY, ICQ_PRIVACY_REQ_RIGHTS);
+ sendServPacket(&packet);
+ break;
+
+ case ICQ_SERVER_PAUSE:
+ NetLog_Server("Server is going down in a few seconds... (Flags: %u)", pSnacHeader->wFlags);
+ // This is the list of groups that we want to have on the next server
+ serverPacketInit(&packet, 30);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_PAUSE_ACK);
+ packWord(&packet,ICQ_SERVICE_FAMILY);
+ packWord(&packet,ICQ_LISTS_FAMILY);
+ packWord(&packet,ICQ_LOCATION_FAMILY);
+ packWord(&packet,ICQ_BUDDY_FAMILY);
+ packWord(&packet,ICQ_EXTENSIONS_FAMILY);
+ packWord(&packet,ICQ_MSG_FAMILY);
+ packWord(&packet,0x06);
+ packWord(&packet,ICQ_BOS_FAMILY);
+ packWord(&packet,ICQ_LOOKUP_FAMILY);
+ packWord(&packet,ICQ_STATS_FAMILY);
+ sendServPacket(&packet);
+#ifdef _DEBUG
+ NetLog_Server("Sent server pause ack");
+#endif
+ break;
+
+ case ICQ_SERVER_MIGRATIONREQ:
+ {
+#ifdef _DEBUG
+ NetLog_Server("Server migration requested (Flags: %u)", pSnacHeader->wFlags);
+#endif
+ pBuffer += 2; // Unknown, seen: 0
+ wBufferLength -= 2;
+
+ oscar_tlv_chain *chain = readIntoTLVChain(&pBuffer, wBufferLength, 0);
+
+ if (info->cookieDataLen > 0)
+ SAFE_FREE((void**)&info->cookieData);
+
+ info->newServer = chain->getString(0x05, 1);
+ info->newServerSSL = chain->getNumber(0x8E, 1);
+ info->cookieData = (BYTE*)chain->getString(0x06, 1);
+ info->cookieDataLen = chain->getLength(0x06, 1);
+
+ disposeChain(&chain);
+
+ if (!info->newServer || !info->cookieData)
+ {
+ icq_LogMessage(LOG_FATAL, LPGEN("A server migration has failed because the server returned invalid data. You must reconnect manually."));
+ SAFE_FREE(&info->newServer);
+ SAFE_FREE((void**)&info->cookieData);
+ info->cookieDataLen = 0;
+ info->newServerReady = 0;
+ return;
+ }
+
+ NetLog_Server("Migration has started. New server will be %s", info->newServer);
+
+ m_iDesiredStatus = m_iStatus;
+ SetCurrentStatus(ID_STATUS_CONNECTING); // revert to connecting state
+
+ info->newServerReady = 1;
+ info->isMigrating = 1;
+ }
+ break;
+
+ case ICQ_SERVER_NAME_INFO: // This is the reply to CLI_REQINFO
+ {
+ BYTE bUinLen;
+ oscar_tlv_chain *chain;
+
+#ifdef _DEBUG
+ NetLog_Server("Received self info");
+#endif
+ unpackByte(&pBuffer, &bUinLen);
+ pBuffer += bUinLen;
+ pBuffer += 4; /* warning level & user class */
+ wBufferLength -= 5 + bUinLen;
+
+ if (pSnacHeader->dwRef == ICQ_CLIENT_REQINFO<<0x10)
+ { // This is during the login sequence
+ DWORD dwValue;
+
+ // TLV(x01) User type?
+ // TLV(x0C) Empty CLI2CLI Direct connection info
+ // TLV(x0A) External IP
+ // TLV(x0F) Number of seconds that user has been online
+ // TLV(x03) The online since time.
+ // TLV(x0A) External IP again
+ // TLV(x22) Unknown
+ // TLV(x1E) Unknown: empty.
+ // TLV(x05) Member of ICQ since.
+ // TLV(x14) Unknown
+ chain = readIntoTLVChain(&pBuffer, wBufferLength, 0);
+
+ // Save external IP
+ dwValue = chain->getDWord(0x0A, 1);
+ setSettingDword(NULL, "IP", dwValue);
+
+ // Save member since timestamp
+ dwValue = chain->getDWord(0x05, 1);
+ if (dwValue) setSettingDword(NULL, "MemberTS", dwValue);
+
+ dwValue = chain->getDWord(0x03, 1);
+ setSettingDword(NULL, "LogonTS", dwValue ? dwValue : time(NULL));
+
+ disposeChain(&chain);
+
+ // If we are in SSI mode, this is sent after the list is acked instead
+ // to make sure that we don't set status before seing the visibility code
+ if (!m_bSsiEnabled || info->isMigrating)
+ handleServUINSettings(wListenPort, info);
+ }
+ else if (m_hNotifyNameInfoEvent)
+ // Just notify that the set status note & mood process is finished
+ SetEvent(m_hNotifyNameInfoEvent);
+ }
+ break;
+
+ case ICQ_SERVER_RATE_CHANGE:
+
+ if (wBufferLength >= 2)
+ {
+ WORD wStatus;
+ WORD wClass;
+ DWORD dwLevel;
+ // We now have global rate management, although controlled are only some
+ // areas. This should not arrive in most cases. If it does, update our
+ // local rate levels & issue broadcast.
+ unpackWord(&pBuffer, &wStatus);
+ unpackWord(&pBuffer, &wClass);
+ pBuffer += 20;
+ unpackDWord(&pBuffer, &dwLevel);
+
+ m_ratesMutex->Enter();
+ m_rates->updateLevel(wClass, dwLevel);
+ m_ratesMutex->Leave();
+
+ if (wStatus == 2 || wStatus == 3)
+ { // this is only the simplest solution, needs rate management to every section
+ BroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus);
+ if (wStatus == 2)
+ NetLog_Server("Rates #%u: Alert", wClass);
+ else
+ NetLog_Server("Rates #%u: Limit", wClass);
+ }
+ else if (wStatus == 4)
+ {
+ BroadcastAck(NULL, ICQACKTYPE_RATEWARNING, ACKRESULT_STATUS, (HANDLE)wClass, wStatus);
+ NetLog_Server("Rates #%u: Clear", wClass);
+ }
+ }
+
+ break;
+
+ case ICQ_SERVER_REDIRECT_SERVICE: // reply to family request, got new connection point
+ {
+ oscar_tlv_chain *pChain = NULL;
+ cookie_family_request *pCookieData;
+
+ if (!(pChain = readIntoTLVChain(&pBuffer, wBufferLength, 0)))
+ {
+ NetLog_Server("Received Broken Redirect Service SNAC(1,5).");
+ break;
+ }
+ WORD wFamily = pChain->getWord(0x0D, 1);
+
+ // pick request data
+ if ((!FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData)) || (pCookieData->wFamily != wFamily))
+ {
+ disposeChain(&pChain);
+ NetLog_Server("Received unexpected SNAC(1,5), skipping.");
+ break;
+ }
+
+ FreeCookie(pSnacHeader->dwRef);
+
+ { // new family entry point received
+ char *pServer = pChain->getString(0x05, 1);
+ BYTE bServerSSL = pChain->getNumber(0x8E, 1);
+ char *pCookie = pChain->getString(0x06, 1);
+ WORD wCookieLen = pChain->getLength(0x06, 1);
+
+ if (!pServer || !pCookie)
+ {
+ NetLog_Server("Server returned invalid data, family unavailable.");
+
+ SAFE_FREE(&pServer);
+ SAFE_FREE(&pCookie);
+ SAFE_FREE((void**)&pCookieData);
+ disposeChain(&pChain);
+ break;
+ }
+
+ // Get new family server ip and port
+ WORD wPort = info->wServerPort; // get default port
+ parseServerAddress(pServer, &wPort);
+
+ // establish connection
+ NETLIBOPENCONNECTION nloc = {0};
+ if (m_bGatewayMode)
+ nloc.flags |= NLOCF_HTTPGATEWAY;
+ nloc.szHost = pServer;
+ nloc.wPort = wPort;
+
+ HANDLE hConnection = NetLib_OpenConnection(m_hServerNetlibUser, wFamily == ICQ_AVATAR_FAMILY ? "Avatar " : NULL, &nloc);
+
+ if (hConnection == NULL)
+ {
+ NetLog_Server("Unable to connect to ICQ new family server.");
+ } // we want the handler to be called even if the connecting failed
+ else if (bServerSSL)
+ { /* Start SSL session if requested */
+#ifdef _DEBUG
+ NetLog_Server("(%d) Starting SSL negotiation", CallService(MS_NETLIB_GETSOCKET, (WPARAM)hConnection, 0));
+#endif
+ if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hConnection, 0))
+ {
+ NetLog_Server("Unable to connect to ICQ new family server, SSL could not be negotiated");
+ NetLib_CloseConnection(&hConnection, FALSE);
+ }
+ }
+
+ (this->*pCookieData->familyHandler)(hConnection, pCookie, wCookieLen);
+
+ // Free allocated memory
+ // NOTE: "cookie" will get freed when we have connected to the avatar server.
+ disposeChain(&pChain);
+ SAFE_FREE(&pServer);
+ SAFE_FREE((void**)&pCookieData);
+ }
+
+ break;
+ }
+
+ case ICQ_SERVER_EXTSTATUS: // our session data
+ {
+#ifdef _DEBUG
+ NetLog_Server("Received owner session data.");
+#endif
+ while (wBufferLength > 4)
+ { // loop thru all items
+ WORD itemType = pBuffer[0] * 0x10 | pBuffer[1];
+ BYTE itemFlags = pBuffer[2];
+ BYTE itemLen = pBuffer[3];
+
+ if (itemType == AVATAR_HASH_PHOTO) /// TODO: handle photo item
+ { // skip photo item
+#ifdef _DEBUG
+ NetLog_Server("Photo item recognized");
+#endif
+ }
+ else if ((itemType == AVATAR_HASH_STATIC || itemType == AVATAR_HASH_FLASH) && (itemLen >= 0x10))
+ {
+#ifdef _DEBUG
+ NetLog_Server("Avatar item recognized");
+#endif
+ if (m_bAvatarsEnabled && !info->bMyAvatarInited) // signal the server after login
+ { // this refreshes avatar state - it used to work automatically, but now it does not
+ if (getSettingByte(NULL, "ForceOurAvatar", 0))
+ { // keep our avatar
+ TCHAR *file = GetOwnAvatarFileName();
+ SetMyAvatar(0, (LPARAM)file);
+ SAFE_FREE(&file);
+ }
+ else // only change avatar hash to the same one
+ {
+ BYTE hash[0x14];
+
+ memcpy(hash, pBuffer, 0x14);
+ hash[2] = 1; // update image status
+ updateServAvatarHash(hash, 0x14);
+ }
+ info->bMyAvatarInited = TRUE;
+ break;
+ }
+ // process owner avatar hash changed notification
+ handleAvatarOwnerHash(itemType, itemFlags, pBuffer, itemLen + 4);
+ }
+ else if (itemType == 0x02)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Status message item recognized");
+#endif
+ }
+ else if (itemType == 0x0E)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Status mood item recognized");
+#endif
+ }
+
+ // move to next item
+ if (wBufferLength >= itemLen + 4)
+ {
+ wBufferLength -= itemLen + 4;
+ pBuffer += itemLen + 4;
+ }
+ else
+ {
+ pBuffer += wBufferLength;
+ wBufferLength = 0;
+ }
+ }
+ break;
+ }
+
+ case ICQ_ERROR:
+ { // Something went wrong, probably the request for avatar family failed
+ WORD wError;
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ LogFamilyError(ICQ_SERVICE_FAMILY, wError);
+ break;
+ }
+
+ // Stuff we don't care about
+ case ICQ_SERVER_MOTD:
+#ifdef _DEBUG
+ NetLog_Server("Server message of the day");
+#endif
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_SERVICE_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ }
+}
+
+
+char* CIcqProto::buildUinList(int subtype, WORD wMaxLen, HANDLE* hContactResume)
+{
+ char* szList;
+ HANDLE hContact;
+ WORD wCurrentLen = 0;
+ DWORD dwUIN;
+ uid_str szUID;
+ char szLen[2];
+ int add;
+
+ szList = (char*)SAFE_MALLOC(CallService(MS_DB_CONTACT_GETCOUNT, 0, 0) * UINMAXLEN);
+ szLen[1] = '\0';
+
+ if (*hContactResume)
+ hContact = *hContactResume;
+ else
+ hContact = FindFirstContact();
+
+ while (hContact != NULL)
+ {
+ if (!getContactUid(hContact, &dwUIN, &szUID))
+ {
+ szLen[0] = strlennull(strUID(dwUIN, szUID));
+
+ switch (subtype)
+ {
+
+ case BUL_VISIBLE:
+ add = ID_STATUS_ONLINE == getSettingWord(hContact, "ApparentMode", 0);
+ break;
+
+ case BUL_INVISIBLE:
+ add = ID_STATUS_OFFLINE == getSettingWord(hContact, "ApparentMode", 0);
+ break;
+
+ case BUL_TEMPVISIBLE:
+ add = getSettingByte(hContact, "TemporaryVisible", 0);
+ // clear temporary flag
+ // Here we assume that all temporary contacts will be in one packet
+ setSettingByte(hContact, "TemporaryVisible", 0);
+ break;
+
+ default:
+ add = 1;
+
+ // If we are in SS mode, we only add those contacts that are
+ // not in our SS list, or are awaiting authorization, to our
+ // client side list
+ if (m_bSsiEnabled && getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) &&
+ !getSettingByte(hContact, "Auth", 0))
+ add = 0;
+
+ // Never add hidden contacts to CS list
+ if (DBGetContactSettingByte(hContact, "CList", "Hidden", 0))
+ add = 0;
+
+ break;
+ }
+
+ if (add)
+ {
+ wCurrentLen += szLen[0] + 1;
+ if (wCurrentLen > wMaxLen)
+ {
+ *hContactResume = hContact;
+ return szList;
+ }
+
+ strcat(szList, szLen);
+ strcat(szList, szUID);
+ }
+ }
+
+ hContact = FindNextContact(hContact);
+ }
+ *hContactResume = NULL;
+
+ return szList;
+}
+
+
+void CIcqProto::sendEntireListServ(WORD wFamily, WORD wSubtype, int listType)
+{
+ HANDLE hResumeContact = NULL;
+
+ do
+ { // server doesn't seem to be able to cope with packets larger than 8k
+ // send only about 100contacts per packet
+ char *szList = buildUinList(listType, 0x3E8, &hResumeContact);
+ int nListLen = strlennull(szList);
+
+ if (nListLen)
+ {
+ icq_packet packet;
+
+ serverPacketInit(&packet, (WORD)(nListLen + 10));
+ packFNACHeader(&packet, wFamily, wSubtype);
+ packBuffer(&packet, (LPBYTE)szList, (WORD)nListLen);
+ sendServPacket(&packet);
+ }
+
+ SAFE_FREE((void**)&szList);
+ }
+ while (hResumeContact);
+}
+
+
+static void packShortCapability(icq_packet *packet, WORD wCapability)
+{ // pack standard capability
+ DWORD dwQ1 = 0x09460000 | wCapability;
+
+ packDWord(packet, dwQ1);
+ packDWord(packet, 0x4c7f11d1);
+ packDWord(packet, 0x82224445);
+ packDWord(packet, 0x53540000);
+}
+
+
+// CLI_SETUSERINFO
+void CIcqProto::setUserInfo()
+{
+ icq_packet packet;
+ WORD wAdditionalData = 0;
+ BYTE bXStatus = getContactXStatus(NULL);
+
+ if (m_bAimEnabled)
+ wAdditionalData += 16;
+#ifdef DBG_CAPMTN
+ wAdditionalData += 16;
+#endif
+ if (m_bUtfEnabled)
+ wAdditionalData += 16;
+#ifdef DBG_NEWCAPS
+ wAdditionalData += 16;
+#endif
+#ifdef DBG_CAPXTRAZ
+ wAdditionalData += 16;
+#endif
+#ifdef DBG_OSCARFT
+ wAdditionalData += 16;
+#endif
+ if (m_bAvatarsEnabled)
+ wAdditionalData += 16;
+ if (m_bXStatusEnabled && bXStatus != 0)
+ wAdditionalData += 16;
+#ifdef DBG_CAPHTML
+ wAdditionalData += 16;
+#endif
+#ifdef DBG_AIMCONTACTSEND
+ wAdditionalData += 16;
+#endif
+
+ wAdditionalData += (WORD)CustomCapList.size() * 16;
+
+ //MIM/PackName
+ bool bHasPackName = false;
+ DBVARIANT dbv;
+ if ( !DBGetContactSettingString(NULL, "ICQCaps", "PackName", &dbv )) {
+ //MIM/PackName
+ bHasPackName = true;
+ wAdditionalData += 16;
+ }
+
+ serverPacketInit(&packet, (WORD)(62 + wAdditionalData));
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO);
+
+ /* TLV(5): capability data */
+ packWord(&packet, 0x0005);
+ packWord(&packet, (WORD)(48 + wAdditionalData));
+
+#ifdef DBG_CAPMTN
+ {
+ packDWord(&packet, 0x563FC809); // CAP_TYPING
+ packDWord(&packet, 0x0B6F41BD);
+ packDWord(&packet, 0x9F794226);
+ packDWord(&packet, 0x09DFA2F3);
+ }
+#endif
+ {
+ packShortCapability(&packet, 0x1349); // AIM_CAPS_ICQSERVERRELAY
+ }
+ if (m_bUtfEnabled)
+ {
+ packShortCapability(&packet, 0x134E); // CAP_UTF8MSGS
+ } // Broadcasts the capability to receive UTF8 encoded messages
+#ifdef DBG_NEWCAPS
+ {
+ packShortCapability(&packet, 0x0000); // CAP_SHORTCAPS
+ } // Tells server we understand to new format of caps
+#endif
+#ifdef DBG_CAPXTRAZ
+ {
+ packDWord(&packet, 0x1a093c6c); // CAP_XTRAZ
+ packDWord(&packet, 0xd7fd4ec5); // Broadcasts the capability to handle
+ packDWord(&packet, 0x9d51a647); // Xtraz
+ packDWord(&packet, 0x4e34f5a0);
+ }
+#endif
+ if (m_bAvatarsEnabled)
+ {
+ packShortCapability(&packet, 0x134C); // CAP_DEVILS
+ }
+#ifdef DBG_OSCARFT
+ {
+ packShortCapability(&packet, 0x1343); // CAP_AIM_FILE
+ } // Broadcasts the capability to receive Oscar File Transfers
+#endif
+ if (m_bAimEnabled)
+ {
+ packShortCapability(&packet, 0x134D); // CAP_AIM_COMPATIBLE
+ } // Tells the server we can speak to AIM
+#ifdef DBG_AIMCONTACTSEND
+ {
+ packShortCapability(&packet, 0x134B); // CAP_SENDBUDDYLIST
+ }
+#endif
+ if (m_bXStatusEnabled && bXStatus != 0)
+ {
+ packBuffer(&packet, capXStatus[bXStatus-1], BINARY_CAP_SIZE);
+ }
+
+ packShortCapability(&packet, 0x1344); // CAP_ICQDIRECT
+
+#ifdef DBG_CAPHTML
+ packShortCapability(&packet, 0x0002); // CAP_HTMLMSGS
+#endif
+
+ packDWord(&packet, 0x4D697261); // Miranda Signature
+ packDWord(&packet, 0x6E64614E);
+
+ int v[4] = { MIRANDA_VERSION_FILEVERSION };
+ packWord(&packet, v[0]);
+ packWord(&packet, v[1]);
+ packWord(&packet, v[2]);
+ packWord(&packet, v[3]);
+
+ //MIM/PackName
+ if ( bHasPackName ) {
+ packBuffer(&packet, (BYTE*)dbv.pszVal, 0x10);
+ ICQFreeVariant(&dbv);
+ }
+
+ if(!CustomCapList.empty())
+ {
+ for(std::list<ICQ_CUSTOMCAP*>::iterator it = CustomCapList.begin(), end = CustomCapList.end(); it != end; ++it)
+ packBuffer(&packet, (BYTE*)(*it)->caps, 0x10);
+ }
+
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::handleServUINSettings(int nPort, serverthread_info *info)
+{
+ icq_packet packet;
+
+ setUserInfo();
+
+ /* SNAC 3,4: Tell server who's on our list (deprecated) */
+ /* SNAC 3,15: Try to add unauthorised contacts to temporary list */
+ sendEntireListServ(ICQ_BUDDY_FAMILY, ICQ_USER_ADDTOTEMPLIST, BUL_ALLCONTACTS);
+
+ if (m_iDesiredStatus == ID_STATUS_INVISIBLE)
+ {
+ /* Tell server who's on our visible list (deprecated) */
+ if (!m_bSsiEnabled)
+ sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDVISIBLE, BUL_VISIBLE);
+ else
+ updateServVisibilityCode(3);
+ }
+
+ if (m_iDesiredStatus != ID_STATUS_INVISIBLE)
+ {
+ /* Tell server who's on our invisible list (deprecated) */
+ if (!m_bSsiEnabled)
+ sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDINVISIBLE, BUL_INVISIBLE);
+ else
+ updateServVisibilityCode(4);
+ }
+
+ // SNAC 1,1E: Set status
+ {
+ DWORD dwDirectCookie = rand() ^ (rand() << 16);
+
+ // Get status
+ WORD wStatus = MirandaStatusToIcq(m_iDesiredStatus);
+
+ // Get status note & mood
+ char *szStatusNote = PrepareStatusNote(m_iDesiredStatus);
+ BYTE bXStatus = getContactXStatus(NULL);
+ char szMoodData[32];
+
+ // prepare mood id
+ if (m_bMoodsEnabled && bXStatus && moodXStatus[bXStatus-1] != -1)
+ null_snprintf(szMoodData, SIZEOF(szMoodData), "icqmood%d", moodXStatus[bXStatus-1]);
+ else
+ szMoodData[0] = '\0';
+
+ //! Tricky code, this ensures that the status note will be saved to the directory
+ SetStatusNote(szStatusNote, m_bGatewayMode ? 5000 : 2500, TRUE);
+
+ WORD wStatusNoteLen = strlennull(szStatusNote);
+ WORD wStatusMoodLen = strlennull(szMoodData);
+ WORD wSessionDataLen = (wStatusNoteLen ? wStatusNoteLen + 4 : 0) + 4 + wStatusMoodLen + 4;
+
+ serverPacketInit(&packet, (WORD)(71 + (wSessionDataLen ? wSessionDataLen + 4 : 0)));
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS);
+ packDWord(&packet, 0x00060004); // TLV 6: Status mode and security flags
+ packWord(&packet, GetMyStatusFlags()); // Status flags
+ packWord(&packet, wStatus); // Status
+ packTLVWord(&packet, 0x0008, 0x0A06); // TLV 8: Independent Status Messages
+ packDWord(&packet, 0x000c0025); // TLV C: Direct connection info
+ packDWord(&packet, getSettingDword(NULL, "RealIP", 0));
+ packDWord(&packet, nPort);
+ packByte(&packet, DC_TYPE); // TCP/FLAG firewall settings
+ packWord(&packet, ICQ_VERSION);
+ packDWord(&packet, dwDirectCookie); // DC Cookie
+ packDWord(&packet, WEBFRONTPORT); // Web front port
+ packDWord(&packet, CLIENTFEATURES); // Client features
+ packDWord(&packet, 0x7fffffff); // Abused timestamp
+ packDWord(&packet, ICQ_PLUG_VERSION); // Abused timestamp
+ if (ServiceExists("SecureIM/IsContactSecured"))
+ packDWord(&packet, 0x5AFEC0DE); // SecureIM Abuse
+ else
+ packDWord(&packet, 0x00000000); // Timestamp
+ packWord(&packet, 0x0000); // Unknown
+ packTLVWord(&packet, 0x001F, 0x0000);
+
+ if (wSessionDataLen)
+ { // Pack session data
+ packWord(&packet, 0x1D); // TLV 1D
+ packWord(&packet, wSessionDataLen); // TLV length
+ packWord(&packet, 0x02); // Item Type
+ if (wStatusNoteLen)
+ {
+ packWord(&packet, 0x400 | (WORD)(wStatusNoteLen + 4)); // Flags + Item Length
+ packWord(&packet, wStatusNoteLen); // Text Length
+ packBuffer(&packet, (LPBYTE)szStatusNote, wStatusNoteLen);
+ packWord(&packet, 0); // Encoding not specified (utf-8 is default)
+ }
+ else
+ packWord(&packet, 0); // Flags + Item Length
+ packWord(&packet, 0x0E); // Item Type
+ packWord(&packet, wStatusMoodLen); // Flags + Item Length
+ if (wStatusMoodLen)
+ packBuffer(&packet, (LPBYTE)szMoodData, wStatusMoodLen); // Mood
+
+ // Save current status note & mood
+ setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, szStatusNote);
+ setSettingString(NULL, DBSETTING_STATUS_MOOD, szMoodData);
+ }
+ // Release memory
+ SAFE_FREE(&szStatusNote);
+
+ sendServPacket(&packet);
+ }
+
+ /* SNAC 1,11 */
+ serverPacketInit(&packet, 14);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_IDLE);
+ packDWord(&packet, 0x00000000);
+
+ sendServPacket(&packet);
+ m_bIdleAllow = 0;
+
+ // Change status
+ SetCurrentStatus(m_iDesiredStatus);
+
+ // Finish Login sequence
+ serverPacketInit(&packet, 98);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_READY);
+ packDWord(&packet, 0x00220001); // imitate ICQ 6 behaviour
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00010004);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00130004);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00020001);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00030001);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00150001);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00040001);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00060001);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x00090001);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x000A0001);
+ packDWord(&packet, 0x0110164f);
+ packDWord(&packet, 0x000B0001);
+ packDWord(&packet, 0x0110164f);
+
+ sendServPacket(&packet);
+
+ NetLog_Server(" *** Yeehah, login sequence complete");
+
+ // login sequence is complete enter logged-in mode
+ info->bLoggedIn = 1;
+ m_bConnectionLost = FALSE;
+
+ // enable auto info-update routine
+ icq_EnableUserLookup(TRUE);
+
+ if (!info->isMigrating)
+ { /* Get Offline Messages Reqeust */
+ cookie_offline_messages *ack = (cookie_offline_messages*)SAFE_MALLOC(sizeof(cookie_offline_messages));
+ if (ack)
+ {
+ DWORD dwCookie = AllocateCookie(CKT_OFFLINEMESSAGE, ICQ_MSG_CLI_REQ_OFFLINE, 0, ack);
+
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_CLI_REQ_OFFLINE, 0, dwCookie);
+
+ sendServPacket(&packet);
+ }
+ else
+ icq_LogMessage(LOG_WARNING, LPGEN("Failed to request offline messages. They may be received next time you log in."));
+
+ // Update our information from the server
+ sendOwnerInfoRequest();
+
+ // Request info updates on all contacts
+ icq_RescanInfoUpdate();
+
+ // Start sending Keep-Alive packets
+ StartKeepAlive(info);
+
+ if (m_bAvatarsEnabled)
+ { // Send SNAC 1,4 - request avatar family 0x10 connection
+ icq_requestnewfamily(ICQ_AVATAR_FAMILY, &CIcqProto::StartAvatarThread);
+
+ m_avatarsConnectionPending = TRUE;
+ NetLog_Server("Requesting Avatar family entry point.");
+ }
+ }
+ info->isMigrating = 0;
+
+ if (m_bAimEnabled)
+ {
+ char **szAwayMsg = NULL;
+ icq_lock l(m_modeMsgsMutex);
+
+ szAwayMsg = MirandaStatusToAwayMsg(m_iStatus);
+ if (szAwayMsg)
+ icq_sendSetAimAwayMsgServ(*szAwayMsg);
+ }
+}
diff --git a/protocols/IcqOscarJ/src/fam_02location.cpp b/protocols/IcqOscarJ/src/fam_02location.cpp
new file mode 100644
index 0000000000..2398cf18c6
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_02location.cpp
@@ -0,0 +1,312 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Handles packets from Location family
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+extern const char* cliSpamBot;
+
+void CIcqProto::handleLocationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_LOCATION_RIGHTS_REPLY: // Reply to CLI_REQLOCATION
+ NetLog_Server("Server sent SNAC(x02,x03) - SRV_LOCATION_RIGHTS_REPLY");
+ break;
+
+ case ICQ_LOCATION_USR_INFO_REPLY: // AIM user info reply
+ handleLocationUserInfoReply(pBuffer, wBufferLength, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_ERROR:
+ {
+ WORD wError;
+ HANDLE hCookieContact;
+ cookie_fam15_data *pCookieData;
+
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ if (wError == 4)
+ {
+ if (FindCookie(pSnacHeader->dwRef, &hCookieContact, (void**)&pCookieData) && !getContactUin(hCookieContact) && pCookieData->bRequestType == REQUESTTYPE_PROFILE)
+ {
+ BroadcastAck(hCookieContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0);
+
+ ReleaseCookie(pSnacHeader->dwRef);
+ }
+ }
+
+ LogFamilyError(ICQ_LOCATION_FAMILY, wError);
+ break;
+ }
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LOCATION_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+static char* AimApplyEncoding(char* pszStr, const char* pszEncoding)
+{ // decode encoding to ANSI only
+ if (pszStr && pszEncoding)
+ {
+ const char *szEnc = strstrnull(pszEncoding, "charset=");
+
+ if (szEnc)
+ { // decode custom encoding to Utf-8
+ char *szStr = ApplyEncoding(pszStr, szEnc + 9);
+ // decode utf-8 to ansi
+ char *szRes = NULL;
+
+ SAFE_FREE((void**)&pszStr);
+ utf8_decode(szStr, &szRes);
+ SAFE_FREE((void**)&szStr);
+
+ return szRes;
+ }
+ }
+ return pszStr;
+}
+
+void CIcqProto::handleLocationUserInfoReply(BYTE* buf, WORD wLen, DWORD dwCookie)
+{
+ HANDLE hContact;
+ DWORD dwUIN;
+ uid_str szUID;
+ WORD wTLVCount;
+ WORD wWarningLevel;
+ HANDLE hCookieContact;
+ WORD status;
+ cookie_message_data *pCookieData;
+
+ // Unpack the sender's user ID
+ if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return;
+
+ // Syntax check
+ if (wLen < 4)
+ return;
+
+ // Warning level?
+ unpackWord(&buf, &wWarningLevel);
+ wLen -= 2;
+
+ // TLV count
+ unpackWord(&buf, &wTLVCount);
+ wLen -= 2;
+
+ // Determine contact
+ hContact = HContactFromUID(dwUIN, szUID, NULL);
+
+ // Ignore away status if the user is not already on our list
+ if (hContact == INVALID_HANDLE_VALUE)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Ignoring away reply (%s)", strUID(dwUIN, szUID));
+#endif
+ return;
+ }
+
+ if (!FindCookie(dwCookie, &hCookieContact, (void**)&pCookieData))
+ {
+ NetLog_Server("Error: Received unexpected away reply from %s", strUID(dwUIN, szUID));
+ return;
+ }
+
+ if (hContact != hCookieContact)
+ {
+ NetLog_Server("Error: Away reply Contact does not match Cookie Contact(0x%x != 0x%x)", hContact, hCookieContact);
+
+ ReleaseCookie(dwCookie); // This could be a bad idea, but I think it is safe
+ return;
+ }
+
+ switch (GetCookieType(dwCookie))
+ {
+ case CKT_FAMILYSPECIAL:
+ {
+ ReleaseCookie(dwCookie);
+
+ // Read user info TLVs
+ {
+ oscar_tlv_chain* pChain;
+ BYTE *tmp;
+ char *szMsg = NULL;
+
+ // Syntax check
+ if (wLen < 4)
+ return;
+
+ tmp = buf;
+ // Get general chain
+ if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount)))
+ return;
+
+ disposeChain(&pChain);
+
+ wLen -= (buf - tmp);
+
+ // Get extra chain
+ if (pChain = readIntoTLVChain(&buf, wLen, 2))
+ {
+ oscar_tlv *pTLV;
+ char *szEncoding = NULL;
+
+ // Get Profile encoding TLV
+
+ pTLV = pChain->getTLV(0x05, 1);
+ if (pTLV && (pTLV->wLen > 0))
+ {
+ // store client capabilities
+ BYTE* capBuf = pTLV->pData;
+ WORD capLen = pTLV->wLen;
+ DBCONTACTWRITESETTING dbcws;
+ dbcws.value.type = DBVT_BLOB;
+ dbcws.value.cpbVal = capLen;
+ dbcws.value.pbVal = capBuf;
+ dbcws.szModule = m_szModuleName;
+ dbcws.szSetting = "CapBuf";
+ CallService(MS_DB_CONTACT_WRITESETTING, (WPARAM)hContact, (LPARAM)&dbcws);
+ }
+ else
+ deleteSetting(hContact, "CapBuf");
+
+ pTLV = pChain->getTLV(0x01, 1);
+ if (pTLV && (pTLV->wLen >= 1))
+ {
+ szEncoding = (char*)_alloca(pTLV->wLen + 1);
+ memcpy(szEncoding, pTLV->pData, pTLV->wLen);
+ szEncoding[pTLV->wLen] = '\0';
+ }
+ // Get Profile info TLV
+ pTLV = pChain->getTLV(0x02, 1);
+ if (pTLV && (pTLV->wLen >= 1))
+ {
+ szMsg = (char*)SAFE_MALLOC(pTLV->wLen + 2);
+ memcpy(szMsg, pTLV->pData, pTLV->wLen);
+ szMsg[pTLV->wLen] = '\0';
+ szMsg[pTLV->wLen + 1] = '\0';
+ szMsg = AimApplyEncoding(szMsg, szEncoding);
+ szMsg = EliminateHtml(szMsg, pTLV->wLen);
+ }
+ // Free TLV chain
+ disposeChain(&pChain);
+ }
+
+ setSettingString(hContact, "About", szMsg);
+ BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1 ,0);
+
+ SAFE_FREE((void**)&szMsg);
+ }
+ break;
+ }
+
+ default: // away message
+ {
+ status = AwayMsgTypeToStatus(pCookieData->nAckType);
+ if (status == ID_STATUS_OFFLINE)
+ {
+ NetLog_Server("SNAC(2.6) Ignoring unknown status message from %s", strUID(dwUIN, szUID));
+
+ ReleaseCookie(dwCookie);
+ return;
+ }
+
+ ReleaseCookie(dwCookie);
+
+ // Read user info TLVs
+ {
+ oscar_tlv_chain* pChain;
+ oscar_tlv* pTLV;
+ BYTE *tmp;
+ char *szMsg = NULL;
+ CCSDATA ccs;
+ PROTORECVEVENT pre;
+
+ // Syntax check
+ if (wLen < 4)
+ return;
+
+ tmp = buf;
+ // Get general chain
+ if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount)))
+ return;
+
+ disposeChain(&pChain);
+
+ wLen -= (buf - tmp);
+
+ // Get extra chain
+ if (pChain = readIntoTLVChain(&buf, wLen, 2))
+ {
+ char* szEncoding = NULL;
+
+ // Get Away encoding TLV
+ pTLV = pChain->getTLV(0x03, 1);
+ if (pTLV && (pTLV->wLen >= 1))
+ {
+ szEncoding = (char*)_alloca(pTLV->wLen + 1);
+ memcpy(szEncoding, pTLV->pData, pTLV->wLen);
+ szEncoding[pTLV->wLen] = '\0';
+ }
+ // Get Away info TLV
+ pTLV = pChain->getTLV(0x04, 1);
+ if (pTLV && (pTLV->wLen >= 1))
+ {
+ szMsg = (char*)SAFE_MALLOC(pTLV->wLen + 2);
+ memcpy(szMsg, pTLV->pData, pTLV->wLen);
+ szMsg[pTLV->wLen] = '\0';
+ szMsg[pTLV->wLen + 1] = '\0';
+ szMsg = AimApplyEncoding(szMsg, szEncoding);
+ szMsg = EliminateHtml(szMsg, pTLV->wLen);
+ }
+ // Free TLV chain
+ disposeChain(&pChain);
+ }
+
+ ccs.szProtoService = PSR_AWAYMSG;
+ ccs.hContact = hContact;
+ ccs.wParam = status;
+ ccs.lParam = (LPARAM)&pre;
+ pre.flags = 0;
+ pre.szMessage = szMsg?szMsg:(char *)"";
+ pre.timestamp = time(NULL);
+ pre.lParam = dwCookie;
+
+ CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs);
+
+ SAFE_FREE((void**)&szMsg);
+ }
+ break;
+ }
+ }
+}
diff --git a/protocols/IcqOscarJ/src/fam_03buddy.cpp b/protocols/IcqOscarJ/src/fam_03buddy.cpp
new file mode 100644
index 0000000000..caa41d966c
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_03buddy.cpp
@@ -0,0 +1,787 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Handles packets from Buddy family
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+extern const char* cliSpamBot;
+
+void CIcqProto::handleBuddyFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info)
+{
+ switch (pSnacHeader->wSubtype)
+ {
+ case ICQ_USER_ONLINE:
+ handleUserOnline(pBuffer, wBufferLength, info);
+ break;
+
+ case ICQ_USER_OFFLINE:
+ handleUserOffline(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_USER_SRV_REPLYBUDDY:
+ handleReplyBuddy(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_USER_NOTIFY_REJECTED:
+ handleNotifyRejected(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_ERROR:
+ {
+ WORD wError;
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ LogFamilyError(ICQ_BUDDY_FAMILY, wError);
+ break;
+ }
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_BUDDY_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+
+void CIcqProto::handleReplyBuddy(BYTE *buf, WORD wPackLen)
+{
+ oscar_tlv_chain *pChain = readIntoTLVChain(&buf, wPackLen, 0);
+
+ if (pChain)
+ {
+ DWORD wMaxUins = pChain->getWord(1, 1);
+ DWORD wMaxWatchers = pChain->getWord(2, 1);
+ DWORD wMaxTemporary = pChain->getWord(4, 1);
+
+ NetLog_Server("MaxUINs %u", wMaxUins);
+ NetLog_Server("MaxWatchers %u", wMaxWatchers);
+ NetLog_Server("MaxTemporary %u", wMaxTemporary);
+
+ disposeChain(&pChain);
+ }
+ else
+ {
+ NetLog_Server("Error: Malformed BuddyReply");
+ }
+}
+
+
+int unpackSessionDataItem(oscar_tlv_chain *pChain, WORD wItemType, BYTE **ppItemData, WORD *pwItemSize, BYTE *pbItemFlags)
+{
+ oscar_tlv *tlv = pChain->getTLV(0x1D, 1);
+ int len = 0;
+ BYTE *data;
+
+ if (tlv)
+ {
+ len = tlv->wLen;
+ data = tlv->pData;
+ }
+
+ while (len >= 4)
+ { // parse session data items one by one
+ WORD itemType;
+ BYTE itemFlags;
+ BYTE itemLen;
+
+ unpackWord(&data, &itemType);
+ unpackByte(&data, &itemFlags);
+ unpackByte(&data, &itemLen);
+ len -= 4;
+
+ // just some validity check
+ if (itemLen > len)
+ itemLen = len;
+
+ if (itemType == wItemType)
+ { // found the requested item
+ if (ppItemData)
+ *ppItemData = data;
+ if (pwItemSize)
+ *pwItemSize = itemLen;
+ if (pbItemFlags)
+ *pbItemFlags = itemFlags;
+
+ return 1; // Success
+ }
+ data += itemLen;
+ len -= itemLen;
+ }
+ return 0;
+}
+
+
+// TLV(1) User class
+// TLV(3) Signon time
+// TLV(4) Idle time (in minutes)
+// TLV(5) Member since
+// TLV(6) New status
+// TLV(8) Status Capabilities
+// TLV(A) External IP
+// TLV(C) DC Info
+// TLV(D) Capabilities
+// TLV(F) Session timer (in seconds)
+// TLV(14) Instance number (AIM only)
+// TLV(19) Short capabilities
+// TLV(1D) Session Data (Avatar, Mood, etc.)
+// TLV(1F) User class (upper bytes)
+// TLV(26) AIM Profile update time
+// TLV(27) AIM Away message update time
+// TLV(29) Away status since
+// TLV(2B) URL to protocol icon
+// TLV(2F) unknown key
+// TLV(30) unknown timestamp
+
+void CIcqProto::handleUserOnline(BYTE *buf, WORD wLen, serverthread_info *info)
+{
+ DWORD dwPort = 0;
+ DWORD dwRealIP = 0;
+ DWORD dwUIN;
+ uid_str szUID;
+ DWORD dwDirectConnCookie = 0;
+ DWORD dwWebPort = 0;
+ DWORD dwFT1 = 0, dwFT2 = 0, dwFT3 = 0;
+ const char *szClient = NULL;
+ BYTE bClientId = 0;
+ WORD wVersion = 0;
+ WORD wTLVCount;
+ WORD wWarningLevel;
+ WORD wStatusFlags;
+ WORD wStatus = 0, wOldStatus = 0;
+ BYTE nTCPFlag = 0;
+ char szStrBuf[MAX_PATH];
+
+ // Unpack the sender's user ID
+ if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return;
+
+ // Syntax check
+ if (wLen < 4)
+ return;
+
+ // Warning level?
+ unpackWord(&buf, &wWarningLevel);
+ wLen -= 2;
+
+ // TLV count
+ unpackWord(&buf, &wTLVCount);
+ wLen -= 2;
+
+ // notify that the set status note & mood process is finished
+ if (m_hNotifyNameInfoEvent)
+ SetEvent(m_hNotifyNameInfoEvent);
+
+ // Ignore status notification if the user is not already on our list
+ HANDLE hContact = HContactFromUID(dwUIN, szUID, NULL);
+ if (hContact == INVALID_HANDLE_VALUE)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Ignoring user online (%s)", strUID(dwUIN, szUID));
+#endif
+ return;
+ }
+
+ // Read user info TLVs
+ oscar_tlv_chain *pChain;
+ oscar_tlv *pTLV;
+
+ // Syntax check
+ if (wLen < 4)
+ return;
+
+ // Get chain
+ if (!(pChain = readIntoTLVChain(&buf, wLen, wTLVCount)))
+ return;
+
+ // Get Class word
+ WORD wClass = pChain->getWord(0x01, 1);
+ int nIsICQ = wClass & CLASS_ICQ;
+
+ if (dwUIN)
+ {
+ // Get DC info TLV
+ pTLV = pChain->getTLV(0x0C, 1);
+ if (pTLV && (pTLV->wLen >= 15))
+ {
+ BYTE *pBuffer = pTLV->pData;
+
+ nIsICQ = TRUE;
+
+ unpackDWord(&pBuffer, &dwRealIP);
+ unpackDWord(&pBuffer, &dwPort);
+ unpackByte(&pBuffer, &nTCPFlag);
+ unpackWord(&pBuffer, &wVersion);
+ unpackDWord(&pBuffer, &dwDirectConnCookie);
+ unpackDWord(&pBuffer, &dwWebPort); // Web front port
+ pBuffer += 4; // Client features
+
+ // Get faked time signatures, used to identify clients
+ if (pTLV->wLen >= 0x23)
+ {
+ unpackDWord(&pBuffer, &dwFT1);
+ unpackDWord(&pBuffer, &dwFT2);
+ unpackDWord(&pBuffer, &dwFT3);
+ }
+ }
+ else
+ {
+ // This client doesnt want DCs
+ }
+
+ // Get Status info TLV
+ pTLV = pChain->getTLV(0x06, 1);
+ if (pTLV && (pTLV->wLen >= 4))
+ {
+ BYTE *pBuffer = pTLV->pData;
+
+ unpackWord(&pBuffer, &wStatusFlags);
+ unpackWord(&pBuffer, &wStatus);
+ }
+ else if (!nIsICQ)
+ {
+ // Connected thru AIM client, guess by user class
+ if (wClass & CLASS_AWAY)
+ wStatus = ID_STATUS_AWAY;
+ else if (wClass & CLASS_WIRELESS)
+ wStatus = ID_STATUS_ONTHEPHONE;
+ else
+ wStatus = ID_STATUS_ONLINE;
+
+ wStatusFlags = 0;
+ }
+ else
+ {
+ // Huh? No status TLV? Lets guess then...
+ wStatusFlags = 0;
+ wStatus = ICQ_STATUS_ONLINE;
+ }
+ }
+ else
+ {
+ nIsICQ = FALSE;
+
+ if (wClass & CLASS_AWAY)
+ wStatus = ID_STATUS_AWAY;
+ else if (wClass & CLASS_WIRELESS)
+ wStatus = ID_STATUS_ONTHEPHONE;
+ else
+ wStatus = ID_STATUS_ONLINE;
+
+ wStatusFlags = 0;
+ }
+
+#ifdef _DEBUG
+ NetLog_Server("Flags are %x", wStatusFlags);
+ NetLog_Server("Status is %x", wStatus);
+#endif
+
+ // Get IP TLV
+ DWORD dwIP = pChain->getDWord(0x0A, 1);
+
+ // Get Online Since TLV
+ DWORD dwOnlineSince = pChain->getDWord(0x03, 1);
+
+ // Get Away Since TLV
+ DWORD dwAwaySince = pChain->getDWord(0x29, 1);
+
+ // Get Member Since TLV
+ DWORD dwMemberSince = pChain->getDWord(0x05, 1);
+
+ // Get Idle timer TLV
+ WORD wIdleTimer = pChain->getWord(0x04, 1);
+ time_t tIdleTS = 0;
+ if (wIdleTimer)
+ {
+ time(&tIdleTS);
+ tIdleTS -= (wIdleTimer*60);
+ };
+
+#ifdef _DEBUG
+ if (wIdleTimer)
+ NetLog_Server("Idle timer is %u.", wIdleTimer);
+ NetLog_Server("Online since %s", time2text(dwOnlineSince));
+ if (dwAwaySince)
+ NetLog_Server("Status was set on %s", time2text(dwAwaySince));
+#endif
+
+ // Check client capabilities
+ if (hContact != NULL)
+ {
+ wOldStatus = getContactStatus(hContact);
+
+ // Collect all Capability info from TLV chain
+ BYTE *capBuf = NULL;
+ WORD capLen = 0;
+
+ // Get Location Capability Info TLVs
+ oscar_tlv *pFullTLV = pChain->getTLV(0x0D, 1);
+ oscar_tlv *pShortTLV = pChain->getTLV(0x19, 1);
+
+ if (pFullTLV && (pFullTLV->wLen >= BINARY_CAP_SIZE))
+ capLen += pFullTLV->wLen;
+
+ if (pShortTLV && (pShortTLV->wLen >= 2))
+ capLen += (pShortTLV->wLen * 8);
+
+ capBuf = (BYTE*)_alloca(capLen + BINARY_CAP_SIZE);
+
+ if (capLen)
+ {
+ BYTE *pCapability = capBuf;
+
+ capLen = 0; // we need to recount that
+
+ if (pFullTLV && (pFullTLV->wLen >= BINARY_CAP_SIZE))
+ { // copy classic Capabilities
+ BYTE *cData = pFullTLV->pData;
+ int cLen = pFullTLV->wLen;
+
+ while (cLen)
+ { // be impervious to duplicates (AOL sends them sometimes)
+ if (!capLen || !MatchCapability(capBuf, capLen, (capstr*)cData, BINARY_CAP_SIZE))
+ { // not present, add
+ memcpy(pCapability, cData, BINARY_CAP_SIZE);
+ capLen += BINARY_CAP_SIZE;
+ pCapability += BINARY_CAP_SIZE;
+ }
+ cData += BINARY_CAP_SIZE;
+ cLen -= BINARY_CAP_SIZE;
+ }
+ }
+
+ if (pShortTLV && (pShortTLV->wLen >= 2))
+ { // copy short Capabilities
+ capstr tmp;
+ BYTE *cData = pShortTLV->pData;
+ int cLen = pShortTLV->wLen;
+
+ memcpy(tmp, capShortCaps, BINARY_CAP_SIZE);
+ while (cLen)
+ { // be impervious to duplicates (AOL sends them sometimes)
+ tmp[2] = cData[0];
+ tmp[3] = cData[1];
+
+ if (!capLen || !MatchCapability(capBuf, capLen, &tmp, BINARY_CAP_SIZE))
+ { // not present, add
+ memcpy(pCapability, tmp, BINARY_CAP_SIZE);
+ capLen += BINARY_CAP_SIZE;
+ pCapability += BINARY_CAP_SIZE;
+ }
+ cData += 2;
+ cLen -= 2;
+ }
+ }
+#ifdef _DEBUG
+ NetLog_Server("Detected %d capability items.", capLen / BINARY_CAP_SIZE);
+#endif
+ }
+
+ if (capLen)
+ { // Update the contact's capabilies if present in packet
+ SetCapabilitiesFromBuffer(hContact, capBuf, capLen, wOldStatus == ID_STATUS_OFFLINE);
+
+ char *szCurrentClient = wOldStatus == ID_STATUS_OFFLINE ? NULL : getSettingStringUtf(hContact, "MirVer", NULL);
+
+ szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, szCurrentClient, wVersion, dwFT1, dwFT2, dwFT3, nTCPFlag, dwDirectConnCookie, dwWebPort, capBuf, capLen, &bClientId, szStrBuf);
+ // Check if the client changed, if not do not change
+ if (szCurrentClient && !strcmpnull(szCurrentClient, szClient))
+ szClient = (const char*)-1;
+ SAFE_FREE(&szCurrentClient);
+ }
+ else if (wOldStatus == ID_STATUS_OFFLINE)
+ {
+ // Remove the contact's capabilities if coming from offline
+ ClearAllContactCapabilities(hContact);
+
+ // no capability
+ NetLog_Server("No capability info TLVs");
+
+ szClient = detectUserClient(hContact, nIsICQ, wClass, dwOnlineSince, NULL, wVersion, dwFT1, dwFT2, dwFT3, nTCPFlag, dwDirectConnCookie, dwWebPort, NULL, capLen, &bClientId, szStrBuf);
+ }
+ else
+ {
+ // Capabilities not present in update packet, do not touch
+ szClient = (const char*)-1; // we don't want to client be overwritten
+ }
+
+ // handle Xtraz status
+ char *moodData = NULL;
+ WORD moodSize = 0;
+
+ unpackSessionDataItem(pChain, 0x0E, (BYTE**)&moodData, &moodSize, NULL);
+ if (capLen || wOldStatus == ID_STATUS_OFFLINE)
+ handleXStatusCaps(dwUIN, szUID, hContact, capBuf, capLen, moodData, moodSize);
+ else
+ handleXStatusCaps(dwUIN, szUID, hContact, NULL, 0, moodData, moodSize);
+
+ // Determine support for extended status messages
+ if (pChain->getWord(0x08, 1) == 0x0A06)
+ SetContactCapabilities(hContact, CAPF_STATUS_MESSAGES);
+ else if (wOldStatus == ID_STATUS_OFFLINE)
+ ClearContactCapabilities(hContact, CAPF_STATUS_MESSAGES);
+
+#ifdef _DEBUG
+ if (wOldStatus == ID_STATUS_OFFLINE)
+ {
+ if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY))
+ NetLog_Server("Supports advanced messages");
+ else
+ NetLog_Server("Does NOT support advanced messages");
+ }
+#endif
+
+ if (!nIsICQ)
+ {
+ // AIM clients does not advertise these, but do support them
+ SetContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING);
+ // Server relayed messages are only supported by ICQ clients
+ ClearContactCapabilities(hContact, CAPF_SRV_RELAY);
+
+ if (dwUIN && wOldStatus == ID_STATUS_OFFLINE)
+ NetLog_Server("Logged in with AIM client");
+ }
+
+ if (nIsICQ && wVersion < 8)
+ {
+ ClearContactCapabilities(hContact, CAPF_SRV_RELAY);
+ if (wOldStatus == ID_STATUS_OFFLINE)
+ NetLog_Server("Forcing simple messages due to compability issues");
+ }
+
+ // Process Avatar Hash
+ pTLV = pChain->getTLV(0x1D, 1);
+ if (pTLV)
+ handleAvatarContactHash(dwUIN, szUID, hContact, pTLV->pData, pTLV->wLen, wOldStatus);
+ else
+ handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0, wOldStatus);
+
+ // Process Status Note
+ parseStatusNote(dwUIN, szUID, hContact, pChain);
+ }
+ // Free TLV chain
+ disposeChain(&pChain);
+
+ // Save contacts details in database
+ if (hContact != NULL)
+ {
+ setSettingDword(hContact, "LogonTS", dwOnlineSince);
+ setSettingDword(hContact, "AwayTS", dwAwaySince);
+ setSettingDword(hContact, "IdleTS", tIdleTS);
+
+ if (dwMemberSince)
+ setSettingDword(hContact, "MemberTS", dwMemberSince);
+
+ if (nIsICQ)
+ { // on AIM these are not used
+ setSettingDword(hContact, "DirectCookie", dwDirectConnCookie);
+ setSettingByte(hContact, "DCType", (BYTE)nTCPFlag);
+ setSettingWord(hContact, "UserPort", (WORD)(dwPort & 0xffff));
+ setSettingWord(hContact, "Version", wVersion);
+ }
+ else
+ {
+ deleteSetting(hContact, "DirectCookie");
+ deleteSetting(hContact, "DCType");
+ deleteSetting(hContact, "UserPort");
+ deleteSetting(hContact, "Version");
+ }
+
+ if (!szClient)
+ {
+ // if no detection, set uknown
+ szClient = (nIsICQ ? "Unknown" : "Unknown AIM");
+ }
+ if (szClient != (char*)-1)
+ {
+ setSettingStringUtf(hContact, "MirVer", szClient);
+ setSettingByte(hContact, "ClientID", bClientId);
+ }
+
+ if (wOldStatus == ID_STATUS_OFFLINE)
+ {
+ setSettingDword(hContact, "IP", dwIP);
+ setSettingDword(hContact, "RealIP", dwRealIP);
+ }
+ else
+ { // if not first notification only write significant information
+ if (dwIP)
+ setSettingDword(hContact, "IP", dwIP);
+ if (dwRealIP)
+ setSettingDword(hContact, "RealIP", dwRealIP);
+ }
+ setSettingWord(hContact, "Status", (WORD)IcqStatusToMiranda(wStatus));
+
+ // Update info?
+ if (dwUIN)
+ { // check if the local copy of user details is up-to-date
+ if (IsMetaInfoChanged(hContact))
+ icq_QueueUser(hContact);
+ }
+ }
+
+ if (wOldStatus != IcqStatusToMiranda(wStatus))
+ { // And a small log notice... if status was changed
+ if (nIsICQ)
+ NetLog_Server("%u changed status to %s (v%d).", dwUIN, MirandaStatusToString(IcqStatusToMiranda(wStatus)), wVersion);
+ else
+ NetLog_Server("%s changed status to %s.", strUID(dwUIN, szUID), MirandaStatusToString(IcqStatusToMiranda(wStatus)));
+ }
+#ifdef _DEBUG
+ else
+ {
+ if (nIsICQ)
+ NetLog_Server("%u has status %s (v%d).", dwUIN, MirandaStatusToString(IcqStatusToMiranda(wStatus)), wVersion);
+ else
+ NetLog_Server("%s has status %s.", strUID(dwUIN, szUID), MirandaStatusToString(IcqStatusToMiranda(wStatus)));
+ }
+#endif
+
+ if (szClient == cliSpamBot)
+ {
+ if (getSettingByte(NULL, "KillSpambots", DEFAULT_KILLSPAM_ENABLED) && DBGetContactSettingByte(hContact, "CList", "NotOnList", 0))
+ { // kill spammer
+ icq_DequeueUser(dwUIN);
+ icq_sendRemoveContact(dwUIN, NULL);
+ AddToSpammerList(dwUIN);
+ if (getSettingByte(NULL, "PopupsSpamEnabled", DEFAULT_SPAM_POPUPS_ENABLED))
+ ShowPopUpMsg(hContact, LPGEN("Spambot Detected"), LPGEN("Contact deleted & further events blocked."), POPTYPE_SPAM);
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
+
+ NetLog_Server("Contact %u deleted", dwUIN);
+ }
+ }
+}
+
+void CIcqProto::handleUserOffline(BYTE *buf, WORD wLen)
+{
+ DWORD dwUIN;
+ uid_str szUID;
+
+ do {
+ oscar_tlv_chain *pChain = NULL;
+ WORD wTLVCount;
+ DWORD dwAwaySince;
+
+ // Unpack the sender's user ID
+ if (!unpackUID(&buf, &wLen, &dwUIN, &szUID)) return;
+
+ // Warning level?
+ buf += 2;
+
+ // TLV Count
+ unpackWord(&buf, &wTLVCount);
+ wLen -= 4;
+
+ // Skip the TLV chain
+ while (wTLVCount && wLen >= 4)
+ {
+ WORD wTLVType;
+ WORD wTLVLen;
+
+ unpackWord(&buf, &wTLVType);
+ unpackWord(&buf, &wTLVLen);
+ wLen -= 4;
+
+ // stop parsing overflowed packet
+ if (wTLVLen > wLen)
+ {
+ disposeChain(&pChain);
+ return;
+ }
+
+ if (wTLVType == 0x1D)
+ { // read only TLV with Session data into chain
+ BYTE *pTLV = buf - 4;
+ disposeChain(&pChain);
+ pChain = readIntoTLVChain(&pTLV, wLen + 4, 1);
+ }
+ else if (wTLVType == 0x29 && wTLVLen == sizeof(DWORD))
+ { // get Away Since value
+ BYTE *pData = buf;
+ unpackDWord(&pData, &dwAwaySince);
+ }
+
+ buf += wTLVLen;
+ wLen -= wTLVLen;
+ wTLVCount--;
+ }
+
+ // Determine contact
+ HANDLE hContact = HContactFromUID(dwUIN, szUID, NULL);
+
+ // Skip contacts that are not already on our list or are already offline
+ if (hContact != INVALID_HANDLE_VALUE)
+ {
+ WORD wOldStatus = getContactStatus(hContact);
+
+ // Process Avatar Hash
+ oscar_tlv *pAvatarTLV = pChain ? pChain->getTLV(0x1D, 1) : NULL;
+ if (pAvatarTLV)
+ handleAvatarContactHash(dwUIN, szUID, hContact, pAvatarTLV->pData, pAvatarTLV->wLen, wOldStatus);
+ else
+ handleAvatarContactHash(dwUIN, szUID, hContact, NULL, 0, wOldStatus);
+
+ // Process Status Note (offline status note)
+ parseStatusNote(dwUIN, szUID, hContact, pChain);
+
+ // Update status times
+ setSettingDword(hContact, "IdleTS", 0);
+ setSettingDword(hContact, "AwayTS", dwAwaySince);
+
+ // Clear custom status & mood
+ char tmp = NULL;
+ handleXStatusCaps(dwUIN, szUID, hContact, (BYTE*)&tmp, 0, &tmp, 0);
+
+ if (wOldStatus != ID_STATUS_OFFLINE)
+ {
+ NetLog_Server("%s went offline.", strUID(dwUIN, szUID));
+
+ setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+ // close Direct Connections to that user
+ CloseContactDirectConns(hContact);
+ // Reset DC status
+ setSettingByte(hContact, "DCStatus", 0);
+ }
+#ifdef _DEBUG
+ else
+ NetLog_Server("%s is offline.", strUID(dwUIN, szUID));
+#endif
+ }
+
+ // Release memory
+ disposeChain(&pChain);
+ }
+ while (wLen >= 1);
+}
+
+
+void CIcqProto::parseStatusNote(DWORD dwUin, char *szUid, HANDLE hContact, oscar_tlv_chain *pChain)
+{
+ DWORD dwStatusNoteTS = time(NULL);
+ BYTE *pStatusNoteTS, *pStatusNote;
+ WORD wStatusNoteTSLen, wStatusNoteLen;
+ BYTE bStatusNoteFlags;
+
+ if (unpackSessionDataItem(pChain, 0x0D, &pStatusNoteTS, &wStatusNoteTSLen, NULL) && wStatusNoteTSLen == sizeof(DWORD))
+ unpackDWord(&pStatusNoteTS, &dwStatusNoteTS);
+
+ // Get Status Note session item
+ if (unpackSessionDataItem(pChain, 0x02, &pStatusNote, &wStatusNoteLen, &bStatusNoteFlags))
+ {
+ char *szStatusNote = NULL;
+
+ if ((bStatusNoteFlags & 4) == 4 && wStatusNoteLen >= 4)
+ {
+ BYTE *buf = pStatusNote;
+ WORD buflen = wStatusNoteLen - 2;
+ WORD wTextLen;
+
+ unpackWord(&buf, &wTextLen);
+ if (wTextLen > buflen)
+ wTextLen = buflen;
+
+ if (wTextLen > 0)
+ {
+ szStatusNote = (char*)_alloca(wStatusNoteLen + 1);
+ unpackString(&buf, szStatusNote, wTextLen);
+ szStatusNote[wTextLen] = '\0';
+ buflen -= wTextLen;
+
+ WORD wEncodingType = 0;
+ char *szEncoding = NULL;
+
+ if (buflen >= 2)
+ unpackWord(&buf, &wEncodingType);
+
+ if (wEncodingType == 1 && buflen > 6)
+ { // Encoding specified
+ buf += 2;
+ buflen -= 2;
+ unpackWord(&buf, &wTextLen);
+ if (wTextLen > buflen)
+ wTextLen = buflen;
+ szEncoding = (char*)_alloca(wTextLen + 1);
+ unpackString(&buf, szEncoding, wTextLen);
+ szEncoding[wTextLen] = '\0';
+ }
+ else if (UTF8_IsValid(szStatusNote))
+ szEncoding = "utf-8";
+
+ szStatusNote = ApplyEncoding(szStatusNote, szEncoding);
+ }
+ }
+ // Check if the status note was changed
+ if (dwStatusNoteTS > getSettingDword(hContact, DBSETTING_STATUS_NOTE_TIME, 0))
+ {
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (strlennull(szStatusNote) || (!getSettingString(hContact, DBSETTING_STATUS_NOTE, &dbv) && (dbv.type == DBVT_ASCIIZ || dbv.type == DBVT_UTF8) && strlennull(dbv.pszVal)))
+ NetLog_Server("%s changed status note to \"%s\"", strUID(dwUin, szUid), szStatusNote ? szStatusNote : "");
+
+ ICQFreeVariant(&dbv);
+
+ if (szStatusNote)
+ setSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, szStatusNote);
+ else
+ deleteSetting(hContact, DBSETTING_STATUS_NOTE);
+ setSettingDword(hContact, DBSETTING_STATUS_NOTE_TIME, dwStatusNoteTS);
+
+ if (getContactXStatus(hContact) != 0 || !CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES)) {
+ setStatusMsgVar(hContact, szStatusNote, false);
+
+ TCHAR* tszNote = mir_utf8decodeT(szStatusNote);
+ BroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, NULL, (LPARAM)tszNote);
+ mir_free(tszNote);
+ }
+ }
+ SAFE_FREE(&szStatusNote);
+ }
+ else
+ {
+ if (getContactStatus(hContact) == ID_STATUS_OFFLINE)
+ {
+ setStatusMsgVar(hContact, NULL, false);
+ deleteSetting(hContact, DBSETTING_STATUS_NOTE);
+ setSettingDword(hContact, DBSETTING_STATUS_NOTE_TIME, dwStatusNoteTS);
+ }
+ }
+}
+
+
+void CIcqProto::handleNotifyRejected(BYTE *buf, WORD wPackLen)
+{
+ DWORD dwUIN;
+ uid_str szUID;
+
+ while (wPackLen)
+ if (unpackUID(&buf, &wPackLen, &dwUIN, &szUID))
+ NetLog_Server("%s status notification rejected.", strUID(dwUIN, szUID));
+}
diff --git a/protocols/IcqOscarJ/src/fam_04message.cpp b/protocols/IcqOscarJ/src/fam_04message.cpp
new file mode 100644
index 0000000000..7d1bc6a829
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_04message.cpp
@@ -0,0 +1,3043 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Handles packets from Family 4 ICBM Messages
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::handleMsgFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_MSG_SRV_ERROR: // SNAC(4, 0x01)
+ handleRecvServMsgError(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_MSG_SRV_REPLYICBM: // SNAC(4, 0x05) SRV_REPLYICBM
+ handleReplyICBM(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_MSG_SRV_RECV: // SNAC(4, 0x07)
+ handleRecvServMsg(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_MSG_SRV_MISSED_MESSAGE: // SNAC(4, 0x0A)
+ handleMissedMsg(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_MSG_RESPONSE: // SNAC(4, 0x0B)
+ handleRecvMsgResponse(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_MSG_SRV_ACK: // SNAC(4, 0x0C) Server acknowledgements
+ handleServerAck(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_MSG_MTN: // SNAC(4, 0x14) Typing notifications
+ handleTypingNotification(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_MSG_SRV_OFFLINE_REPLY: // SNAC(4, 0x17) Offline Messages response
+ handleOffineMessagesReply(pBuffer, wBufferLength, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_MSG_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+
+static void setMsgChannelParams(CIcqProto *ppro, WORD wChan, DWORD dwFlags)
+{
+ icq_packet packet;
+
+ // Set message parameters for channel wChan (CLI_SET_ICBM_PARAMS)
+ serverPacketInit(&packet, 26);
+ packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_CLI_SETPARAMS);
+ packWord(&packet, wChan); // Channel
+ packDWord(&packet, dwFlags); // Flags
+ packWord(&packet, MAX_MESSAGESNACSIZE); // Max message snac size
+ packWord(&packet, 0x03E7); // Max sender warning level
+ packWord(&packet, 0x03E7); // Max receiver warning level
+ packWord(&packet, CLIENTRATELIMIT); // Minimum message interval in seconds
+ packWord(&packet, 0x0000); // Unknown
+ ppro->sendServPacket(&packet);
+}
+
+
+void CIcqProto::handleReplyICBM(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef)
+{ // we don't care about the stuff, just change the params
+ DWORD dwFlags = 0x00000303;
+
+#ifdef DBG_CAPHTML
+ dwFlags |= 0x00000400;
+#endif
+#ifdef DBG_CAPMTN
+ dwFlags |= 0x00000008;
+#endif
+ // Set message parameters for all channels (imitate ICQ 6)
+ setMsgChannelParams(this, 0x0000, dwFlags);
+}
+
+
+void CIcqProto::handleRecvServMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef)
+{
+ DWORD dwUin;
+ DWORD dwMsgID1;
+ DWORD dwMsgID2;
+ WORD wTLVCount;
+ WORD wMessageFormat;
+ uid_str szUID;
+
+ if (wLen < 11)
+ { // just do some basic packet checking
+ NetLog_Server("Error: Malformed message thru server");
+ return;
+ }
+
+ // These two values are some kind of reference, we need to save
+ // them to send file request responses for example
+ unpackLEDWord(&buf, &dwMsgID1); // TODO: msg cookies should be main
+ wLen -= 4;
+ unpackLEDWord(&buf, &dwMsgID2);
+ wLen -= 4;
+
+ // The message type used:
+ unpackWord(&buf, &wMessageFormat); // 0x0001: Simple message format
+ wLen -= 2; // 0x0002: Advanced message format
+ // 0x0004: 'New' message format
+ // Sender UIN
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUID)) return;
+
+ if (dwUin && IsOnSpammerList(dwUin))
+ {
+ NetLog_Server("Ignored Message from known Spammer");
+ return;
+ }
+
+ if (wLen < 4)
+ { // just do some basic packet checking
+ NetLog_Server("Error: Malformed message thru server");
+ return;
+ }
+
+ // Warning level?
+ buf += 2;
+ wLen -= 2;
+
+ // Number of following TLVs, until msg-format dependant TLVs
+ unpackWord(&buf, &wTLVCount);
+ wLen -= 2;
+ if (wTLVCount > 0)
+ {
+ // Save current buffer pointer so we can calculate
+ // how much data we have left after the chain read.
+ BYTE *pBufStart = buf;
+ oscar_tlv_chain *chain = readIntoTLVChain(&buf, wLen, wTLVCount);
+
+ // This chain contains info that is filled in by the server.
+ // TLV(1): unknown
+ // TLV(2): date: on since
+ // TLV(3): date: on since
+ // TLV(4): unknown, usually 0000. Not in file-req or auto-msg-req
+ // TLV(6): sender's status
+ // TLV(F): a time in seconds, unknown
+
+ disposeChain(&chain);
+
+ // Update wLen
+ wLen -= buf - pBufStart;
+ }
+
+
+ // This is where the format specific data begins
+
+ switch (wMessageFormat) {
+
+ case 1: // Simple message format
+ handleRecvServMsgType1(buf, wLen, dwUin, szUID, dwMsgID1, dwMsgID2, dwRef);
+ break;
+
+ case 2: // Encapsulated messages
+ handleRecvServMsgType2(buf, wLen, dwUin, szUID, dwMsgID1, dwMsgID2, dwRef);
+ break;
+
+ case 4: // Typed messages
+ handleRecvServMsgType4(buf, wLen, dwUin, szUID, dwMsgID1, dwMsgID2, dwRef);
+ break;
+
+ default:
+ NetLog_Server("Unknown format message thru server - Ref %u, Type: %u, UID: %s", dwRef, wMessageFormat, strUID(dwUin, szUID));
+ break;
+
+ }
+}
+
+
+char* CIcqProto::convertMsgToUserSpecificUtf(HANDLE hContact, const char *szMsg)
+{
+ WORD wCP = getSettingWord(hContact, "CodePage", m_wAnsiCodepage);
+ char *usMsg = NULL;
+
+ if (wCP != CP_ACP)
+ usMsg = ansi_to_utf8_codepage(szMsg, wCP);
+
+ return usMsg;
+}
+
+
+void CIcqProto::handleRecvServMsgType1(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef)
+{
+ WORD wTLVType;
+ WORD wTLVLen;
+ BYTE* pMsgTLV;
+
+ if (wLen < 4)
+ { // just perform basic structure check
+ NetLog_Server("Message (format %u) - Ignoring empty message", 1);
+ return;
+ }
+
+ // Unpack the first TLV(2)
+ unpackTypedTLV(buf, wLen, 2, &wTLVType, &wTLVLen, &pMsgTLV);
+ NetLog_Server("Message (format %u) - UID: %s", 1, strUID(dwUin, szUID));
+
+ // It must be TLV(2)
+ if (wTLVType == 2)
+ {
+ BYTE *pDataBuf = pMsgTLV;
+ oscar_tlv_chain *pChain = readIntoTLVChain(&pDataBuf, wTLVLen, 0);
+
+ // TLV(2) contains yet another TLV chain with the following TLVs:
+ // TLV(1281): Capability
+ // TLV(257): This TLV contains the actual message (can be fragmented)
+
+ if (pChain)
+ {
+ oscar_tlv* pMessageTLV;
+ oscar_tlv* pCapabilityTLV;
+ WORD wMsgPart = 1;
+
+ // Find the capability TLV
+ pCapabilityTLV = pChain->getTLV(0x0501, 1);
+ if (pCapabilityTLV && (pCapabilityTLV->wLen > 0))
+ {
+ WORD wDataLen;
+ BYTE *pDataBuf;
+
+ wDataLen = pCapabilityTLV->wLen;
+ pDataBuf = pCapabilityTLV->pData;
+
+ if (wDataLen > 0)
+ NetLog_Server("Message (format 1) - Message has %d caps.", wDataLen);
+ }
+ else
+ NetLog_Server("Message (format 1) - No message cap.");
+
+ { // Parse the message parts, usually only one 0x0101 TLV containing the message,
+ // but in some cases there can be more 0x0101 TLVs containing message parts in
+ // different encodings (just like the new format of Offline Messages).
+ DWORD dwRecvTime;
+ char* szMsg = NULL;
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+ int bAdded;
+
+ HANDLE hContact = HContactFromUID(dwUin, szUID, &bAdded);
+
+ while (pMessageTLV = pChain->getTLV(0x0101, wMsgPart))
+ { // Loop thru all message parts
+ if (pMessageTLV->wLen > 4)
+ {
+ WORD wMsgLen;
+ BYTE *pMsgBuf;
+ WORD wEncoding;
+ WORD wCodePage;
+ char *szMsgPart = NULL;
+ int bMsgPartUnicode = FALSE;
+
+ // The message begins with a encoding specification
+ // The first WORD is believed to have the following meaning:
+ // 0x00: US-ASCII
+ // 0x02: Unicode UCS-2 Big Endian encoding
+ // 0x03: local 8bit encoding
+ pMsgBuf = pMessageTLV->pData;
+ unpackWord(&pMsgBuf, &wEncoding);
+ unpackWord(&pMsgBuf, &wCodePage);
+
+ wMsgLen = pMessageTLV->wLen - 4;
+ NetLog_Server("Message (format 1) - Part %d: Encoding is 0x%X, page is 0x%X", wMsgPart, wEncoding, wCodePage);
+
+ switch (wEncoding) {
+
+ case 2: // UCS-2
+ {
+ WCHAR* usMsgPart = (WCHAR*)SAFE_MALLOC(wMsgLen + 2);
+
+ unpackWideString(&pMsgBuf, usMsgPart, wMsgLen);
+ usMsgPart[wMsgLen/sizeof(WCHAR)] = 0;
+
+ szMsgPart = make_utf8_string(usMsgPart);
+ if (!IsUSASCII(szMsgPart, strlennull(szMsgPart)))
+ bMsgPartUnicode = TRUE;
+ SAFE_FREE(&usMsgPart);
+
+ break;
+ }
+
+ case 0: // us-ascii
+ case 3: // ANSI
+ default:
+ {
+ // Copy the message text into a new proper string.
+ szMsgPart = (char*)SAFE_MALLOC(wMsgLen + 1);
+ memcpy(szMsgPart, pMsgBuf, wMsgLen);
+ szMsgPart[wMsgLen] = '\0';
+
+ break;
+ }
+ }
+ // Check if the new part is compatible with the message
+ if (!pre.flags && bMsgPartUnicode)
+ { // make the resulting message utf-8 encoded - need to append utf-8 encoded part
+ if (szMsg)
+ { // not necessary to convert - appending first part, only set flags
+ char *szUtfMsg = ansi_to_utf8_codepage(szMsg, getSettingWord(hContact, "CodePage", m_wAnsiCodepage));
+
+ SAFE_FREE(&szMsg);
+ szMsg = szUtfMsg;
+ }
+ pre.flags = PREF_UTF;
+ }
+ if (!bMsgPartUnicode && pre.flags == PREF_UTF)
+ { // convert message part to utf-8 and append
+ char *szUtfPart = ansi_to_utf8_codepage((char*)szMsgPart, getSettingWord(hContact, "CodePage", m_wAnsiCodepage));
+
+ SAFE_FREE(&szMsgPart);
+ szMsgPart = szUtfPart;
+ }
+ // Append the new message part
+ szMsg = (char*)SAFE_REALLOC(szMsg, strlennull(szMsg) + strlennull(szMsgPart) + 1);
+
+ strcat(szMsg, szMsgPart);
+ SAFE_FREE(&szMsgPart);
+ }
+ wMsgPart++;
+ }
+ if (strlennull(szMsg))
+ {
+ if (_strnicmp(szMsg, "<html>", 6) == 0)
+ { // strip HTML formating from AIM message
+ szMsg = EliminateHtml(szMsg, strlennull(szMsg));
+ }
+
+ if (!pre.flags && !IsUSASCII(szMsg, strlennull(szMsg)))
+ { // message is Ansi and contains national characters, create Unicode part by codepage
+ char *usMsg = convertMsgToUserSpecificUtf(hContact, szMsg);
+ if (usMsg)
+ {
+ SAFE_FREE(&szMsg);
+ szMsg = usMsg;
+ pre.flags = PREF_UTF;
+ }
+ }
+
+ dwRecvTime = (DWORD)time(NULL);
+
+ { // Check if the message was received as offline
+ cookie_offline_messages *cookie;
+
+ if (!(dwRef & 0x80000000) && FindCookie(dwRef, NULL, (void**)&cookie))
+ {
+ WORD wTimeTLVType, wTimeTLVLen;
+ BYTE *pTimeTLV;
+
+ cookie->nMessages++;
+
+ unpackTypedTLV(buf, wLen, 0x16, &wTimeTLVType, &wTimeTLVLen, &pTimeTLV);
+ if (pTimeTLV && wTimeTLVType == 0x16 && wTimeTLVLen == 4)
+ { // found Offline timestamp
+ BYTE *pBuf = pTimeTLV;
+
+ unpackDWord(&pBuf, &dwRecvTime);
+ NetLog_Server("Message (format %u) - Offline timestamp is %s", 1, time2text(dwRecvTime));
+ }
+ SAFE_FREE((void**)&pTimeTLV);
+ }
+ }
+ // Create and send the message event
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = dwRecvTime;
+ pre.szMessage = (char *)szMsg;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+
+ NetLog_Server("Message (format 1) received");
+
+ // Save tick value
+ setSettingDword(ccs.hContact, "TickTS", time(NULL) - (dwMsgID1/1000));
+ }
+ else
+ NetLog_Server("Message (format %u) - Ignoring empty message", 1);
+
+ SAFE_FREE(&szMsg);
+ }
+
+ // Free the chain memory
+ disposeChain(&pChain);
+ }
+ else
+ NetLog_Server("Failed to read TLV chain in message (format 1)");
+ }
+ else
+ NetLog_Server("Unsupported TLV (%u) in message (format %u)", wTLVType, 1);
+
+ SAFE_FREE((void**)&pMsgTLV);
+}
+
+
+void CIcqProto::handleRecvServMsgType2(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef)
+{
+ WORD wTLVType;
+ WORD wTLVLen;
+ BYTE *pDataBuf = NULL;
+ BYTE *pBuf;
+
+ if (wLen < 4)
+ {
+ NetLog_Server("Message (format %u) - Ignoring empty message", 2);
+ return;
+ }
+
+ // Unpack the first TLV(5)
+ unpackTypedTLV(buf, wLen, 5, &wTLVType, &wTLVLen, &pDataBuf);
+ NetLog_Server("Message (format %u) - UID: %s", 2, strUID(dwUin, szUID));
+ pBuf = pDataBuf;
+
+ // It must be TLV(5)
+ if (wTLVType == 5)
+ {
+ WORD wCommand;
+ oscar_tlv_chain* chain;
+ oscar_tlv* tlv;
+ DWORD q1,q2,q3,q4;
+
+ if (wTLVLen < 26)
+ { // just check if all basic data is there
+ NetLog_Server("Message (format %u) - Ignoring empty message", 2);
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+
+ unpackWord(&pDataBuf, &wCommand);
+ wTLVLen -= 2; // Command 0x0000 - Normal message/file send request
+#ifdef _DEBUG // 0x0001 - Abort request
+ NetLog_Server("Command is %u", wCommand); // 0x0002 - Acknowledge request
+#endif
+
+ // Some stuff we don't use
+ pDataBuf += 8; // dwID1 and dwID2 again
+ wTLVLen -= 8;
+ unpackDWord(&pDataBuf, &q1);
+ unpackDWord(&pDataBuf, &q2);
+ unpackDWord(&pDataBuf, &q3);
+ unpackDWord(&pDataBuf, &q4); // Message Capability
+ wTLVLen -= 16;
+
+ if (CompareGUIDs(q1,q2,q3,q4, MCAP_SRV_RELAY_FMT))
+ { // we surely have at least 4 bytes for TLV chain
+ HANDLE hContact = HContactFromUID(dwUin, szUID, NULL);
+
+ if (wCommand == 1)
+ {
+ NetLog_Server("Cannot handle abort messages yet... :(");
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+
+ if (wTLVLen < 4)
+ { // just check if at least one tlv is there
+ NetLog_Server("Message (format %u) - Ignoring empty message", 2);
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+
+ // This TLV chain may contain the following TLVs:
+ // TLV(A): Acktype 0x0000 - normal message
+ // 0x0001 - file request / abort request
+ // 0x0002 - file ack
+ // TLV(F): Unknown
+ // TLV(3): External IP
+ // TLV(5): DC port (not to use for filetransfers)
+ // TLV(0x2711): The next message level
+
+ chain = readIntoTLVChain(&pDataBuf, wTLVLen, 0);
+ if (!chain)
+ { // sanity check
+ NetLog_Server("Message (format %u) - Invalid data", 2);
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+
+ WORD wAckType = chain->getWord(0x0A, 1);
+
+ // Update the saved DC info (if contact already exists)
+ if (hContact != INVALID_HANDLE_VALUE)
+ {
+ DWORD dwIP, dwExternalIP;
+ WORD wPort;
+
+ if (dwExternalIP = chain->getDWord(0x03, 1))
+ setSettingDword(hContact, "RealIP", dwExternalIP);
+ if (dwIP = chain->getDWord(0x04, 1))
+ setSettingDword(hContact, "IP", dwIP);
+ if (wPort = chain->getWord(0x05, 1))
+ setSettingWord(hContact, "UserPort", wPort);
+
+ // Save tick value
+ BYTE bClientID = getSettingByte(hContact, "ClientID", 0);
+ if (bClientID == CLID_GENERIC || bClientID == CLID_ICQ6)
+ setSettingDword(hContact, "TickTS", time(NULL) - (dwMsgID1/1000));
+ else
+ setSettingDword(hContact, "TickTS", 0);
+ }
+
+ // Parse the next message level
+ if (tlv = chain->getTLV(0x2711, 1))
+ {
+ parseServRelayData(tlv->pData, tlv->wLen, hContact, dwUin, szUID, dwMsgID1, dwMsgID2, wAckType);
+ }
+ else
+ {
+ NetLog_Server("Warning, no 0x2711 TLV in message (format 2)");
+ }
+ // Clean up
+ disposeChain(&chain);
+ }
+ else if (CompareGUIDs(q1,q2,q3,q4, MCAP_REVERSE_DC_REQ))
+ { // Handle reverse DC request
+ if (wCommand == 1)
+ {
+ NetLog_Server("Cannot handle abort messages yet... :(");
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+ if (wTLVLen < 4)
+ { // just check if at least one tlv is there
+ NetLog_Server("Message (format %u) - Ignoring empty message", 2);
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+ chain = readIntoTLVChain(&pDataBuf, wTLVLen, 0);
+ if (!chain)
+ { // Malformed packet
+ NetLog_Server("Error: Malformed data in packet");
+ SAFE_FREE((void**)&pBuf);
+ return;
+ }
+
+ WORD wAckType = chain->getWord(0x0A, 1);
+ // Parse the next message level
+ if (tlv = chain->getTLV(0x2711, 1))
+ {
+ if (tlv->wLen == 0x1B)
+ {
+ BYTE *buf = tlv->pData;
+ DWORD dwUin;
+
+ unpackLEDWord(&buf, &dwUin);
+
+ HANDLE hContact = HContactFromUIN(dwUin, NULL);
+ if (hContact == INVALID_HANDLE_VALUE)
+ {
+ NetLog_Server("Error: %s from unknown contact %u", "Reverse Connect Request", dwUin);
+ }
+ else
+ {
+ DWORD dwIp, dwPort;
+ WORD wVersion;
+ BYTE bMode;
+
+ unpackDWord(&buf, &dwIp);
+ unpackLEDWord(&buf, &dwPort);
+ unpackByte(&buf, &bMode);
+ buf += 4; // unknown
+ if (dwPort)
+ buf += 4; // port, again?
+ else
+ unpackLEDWord(&buf, &dwPort);
+ unpackLEWord(&buf, &wVersion);
+
+ setSettingDword(hContact, "IP", dwIp);
+ setSettingWord(hContact, "UserPort", (WORD)dwPort);
+ setSettingByte(hContact, "DCType", bMode);
+ setSettingWord(hContact, "Version", wVersion);
+ if (wVersion > 6)
+ {
+ cookie_reverse_connect *pCookie = (cookie_reverse_connect*)SAFE_MALLOC(sizeof(cookie_reverse_connect));
+
+ unpackLEDWord(&buf, (DWORD*)&pCookie->ft);
+ pCookie->dwMsgID1 = dwMsgID1;
+ pCookie->dwMsgID2 = dwMsgID2;
+
+ OpenDirectConnection(hContact, DIRECTCONN_REVERSE, (void*)pCookie);
+ }
+ else
+ NetLog_Server("Warning: Unsupported direct protocol version in %s", "Reverse Connect Request");
+ }
+ }
+ else
+ {
+ NetLog_Server("Malformed %s", "Reverse Connect Request");
+ }
+ }
+ else
+ {
+ NetLog_Server("Warning, no 0x2711 TLV in message (format 2)");
+ }
+ // Clean up
+ disposeChain(&chain);
+ }
+ else if (CompareGUIDs(q1,q2,q3,q4, MCAP_FILE_TRANSFER))
+ { // this is an OFT packet
+ handleRecvServMsgOFT(pDataBuf, wTLVLen, dwUin, szUID, dwMsgID1, dwMsgID2, wCommand);
+ }
+ else if (CompareGUIDs(q1,q2,q3,q4, MCAP_CONTACTS))
+ { // this is Contacts Transfer
+ handleRecvServMsgContacts(pDataBuf, wTLVLen, dwUin, szUID, dwMsgID1, dwMsgID2, wCommand);
+ }
+ else // here should be detection of extra data streams (Xtraz)
+ {
+ NetLog_Server("Unknown Message Format Capability");
+ }
+ }
+ else
+ {
+ NetLog_Server("Unsupported TLV (%u) in message (format %u)", wTLVType, 2);
+ }
+
+ SAFE_FREE((void**)&pBuf);
+}
+
+
+void CIcqProto::parseServRelayData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType)
+{
+ WORD wId;
+
+ if (wLen < 2)
+ {
+ NetLog_Server("Message (format %u) - Ignoring empty message", 2);
+ return;
+ }
+
+ unpackLEWord(&pDataBuf, &wId); // Incorrect identification, but working
+ wLen -= 2;
+
+ // Only 0x1B are real messages
+ if (wId == 0x001B)
+ {
+ WORD wVersion;
+ WORD wCookie;
+ DWORD dwGuid1,dwGuid2,dwGuid3,dwGuid4;
+
+ if (wLen < 31)
+ { // just check if we have data to work with
+ NetLog_Server("Message (format %u) - Ignoring empty message", 2);
+ return;
+ }
+
+ unpackLEWord(&pDataBuf, &wVersion);
+ wLen -= 2;
+
+ if (hContact != INVALID_HANDLE_VALUE)
+ setSettingWord(hContact, "Version", wVersion);
+
+ unpackDWord(&pDataBuf, &dwGuid1); // plugin type GUID
+ unpackDWord(&pDataBuf, &dwGuid2);
+ unpackDWord(&pDataBuf, &dwGuid3);
+ unpackDWord(&pDataBuf, &dwGuid4);
+ wLen -= 16;
+
+ // Skip lots of unused stuff
+ pDataBuf += 9;
+ wLen -= 9;
+
+ unpackLEWord(&pDataBuf, &wId);
+ wLen -= 2;
+
+ unpackLEWord(&pDataBuf, &wCookie);
+ wLen -= 2;
+
+ if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_MESSAGE))
+ { // is this a normal message ?
+ BYTE bMsgType;
+ BYTE bFlags;
+ WORD wStatus, wPritority;
+ WORD wMsgLen;
+
+ if (wLen < 20)
+ { // check if there is everything that should be there
+ NetLog_Server("Message (format %u) - Ignoring empty message", 2);
+ return;
+ }
+
+ pDataBuf += 12; /* all zeroes */
+ wLen -= 12;
+ unpackByte(&pDataBuf, &bMsgType);
+ wLen -= 1;
+ unpackByte(&pDataBuf, &bFlags);
+ wLen -= 1;
+
+ // Status
+ unpackLEWord(&pDataBuf, &wStatus);
+ wLen -= 2;
+
+ // Priority
+ unpackLEWord(&pDataBuf, &wPritority);
+ wLen -= 2;
+ NetLog_Server("Priority: %u", wPritority);
+
+ // Message
+ unpackLEWord(&pDataBuf, &wMsgLen);
+ wLen -= 2;
+
+ // HANDLERS
+ switch (bMsgType)
+ {
+ // File messages, handled by the file module
+ case MTYPE_FILEREQ:
+ {
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+
+ char* szMsg = (char *)_alloca(wMsgLen + 1);
+ memcpy(szMsg, pDataBuf, wMsgLen);
+ szMsg[wMsgLen] = '\0';
+ pDataBuf += wMsgLen;
+ wLen -= wMsgLen;
+
+ if (wAckType == 0 || wAckType == 1)
+ {
+ // File requests 7
+ handleFileRequest(pDataBuf, wLen, dwUin, wCookie, dwMsgID1, dwMsgID2, szMsg, 7, FALSE);
+ }
+ else if (wAckType == 2)
+ {
+ // File reply 7
+ handleFileAck(pDataBuf, wLen, dwUin, wCookie, wStatus, szMsg);
+ }
+ else
+ {
+ NetLog_Server("Ignored strange file message");
+ }
+
+ break;
+ }
+
+ // Chat messages, handled by the chat module
+ case MTYPE_CHAT:
+ { // TODO: this type is deprecated
+ break;
+ }
+
+ // Plugin messages, need further parsing
+ case MTYPE_PLUGIN:
+ {
+ if (wLen < wMsgLen)
+ { // sanity check
+ NetLog_Server("Error: Malformed server Greeting message");
+ return;
+ }
+
+ parseServRelayPluginData(pDataBuf + wMsgLen, wLen - wMsgLen, hContact, dwUin, szUID, dwMsgID1, dwMsgID2, wAckType, bFlags, wStatus, wCookie, wVersion);
+ break;
+ }
+
+ // Everything else
+ default:
+ {
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+ message_ack_params pMsgAck = {0};
+
+ pMsgAck.bType = MAT_SERVER_ADVANCED;
+ pMsgAck.dwUin = dwUin;
+ pMsgAck.dwMsgID1 = dwMsgID1;
+ pMsgAck.dwMsgID2 = dwMsgID2;
+ pMsgAck.wCookie = wCookie;
+ pMsgAck.msgType = bMsgType;
+ pMsgAck.bFlags = bFlags;
+ handleMessageTypes(dwUin, szUID, time(NULL), dwMsgID1, dwMsgID2, wCookie, wVersion, bMsgType, bFlags, wAckType, wLen, wMsgLen, (char*)pDataBuf, 0, &pMsgAck);
+ break;
+ }
+ }
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_INFO_PLUGIN))
+ { // info manager plugin - obsolete
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+
+ BYTE bMsgType;
+ BYTE bLevel;
+
+ pDataBuf += 16; /* unused stuff */
+ wLen -= 16;
+ unpackByte(&pDataBuf, &bMsgType);
+ wLen -= 1;
+
+ pDataBuf += 3; // unknown
+ wLen -= 3;
+ unpackByte(&pDataBuf, &bLevel);
+ if (bLevel != 0 || wLen < 16)
+ {
+ NetLog_Server("Invalid %s Manager Plugin message from %u", "Info", dwUin);
+ return;
+ }
+ unpackDWord(&pDataBuf, &dwGuid1); // plugin request GUID
+ unpackDWord(&pDataBuf, &dwGuid2);
+ unpackDWord(&pDataBuf, &dwGuid3);
+ unpackDWord(&pDataBuf, &dwGuid4);
+ wLen -= 16;
+
+ if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PMSG_QUERY_INFO))
+ {
+ NetLog_Server("User %u requests our %s plugin list. NOT SUPPORTED", dwUin, "info");
+ }
+ else
+ NetLog_Server("Unknown %s Manager message from %u", "Info", dwUin);
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_STATUS_PLUGIN))
+ { // status manager plugin - obsolete
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+
+ BYTE bMsgType;
+ BYTE bLevel;
+
+ pDataBuf += 16; /* unused stuff */
+ wLen -= 16;
+ unpackByte(&pDataBuf, &bMsgType);
+ wLen -= 1;
+
+ pDataBuf += 3; // unknown
+ wLen -= 3;
+ unpackByte(&pDataBuf, &bLevel);
+ if (bLevel != 0 || wLen < 16)
+ {
+ NetLog_Server("Invalid %s Manager Plugin message from %u", "Status", dwUin);
+ return;
+ }
+ unpackDWord(&pDataBuf, &dwGuid1); // plugin request GUID
+ unpackDWord(&pDataBuf, &dwGuid2);
+ unpackDWord(&pDataBuf, &dwGuid3);
+ unpackDWord(&pDataBuf, &dwGuid4);
+ wLen -= 16;
+
+ if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PMSG_QUERY_STATUS))
+ NetLog_Server("User %u requests our %s plugin list. NOT SUPPORTED", dwUin, "status");
+ else
+ NetLog_Server("Unknown %s Manager message from %u", "Status", dwUin);
+ }
+ else
+ NetLog_Server("Unknown signature (%08x-%08x-%08x-%08x) in message (format 2)", dwGuid1, dwGuid2, dwGuid3, dwGuid4);
+ }
+ else
+ NetLog_Server("Unknown wId1 (%u) in message (format 2)", wId);
+}
+
+
+void CIcqProto::parseServRelayPluginData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType, BYTE bFlags, WORD wStatus, WORD wCookie, WORD wVersion)
+{
+ int nTypeId;
+ WORD wFunction;
+
+ NetLog_Server("Parsing Greeting message through server");
+
+ // Message plugin identification
+ if (!unpackPluginTypeId(&pDataBuf, &wLen, &nTypeId, &wFunction, FALSE)) return;
+
+ if (wLen > 8)
+ {
+ DWORD dwLengthToEnd;
+ DWORD dwDataLen;
+
+ // Length of remaining data
+ unpackLEDWord(&pDataBuf, &dwLengthToEnd);
+
+ // Length of message
+ unpackLEDWord(&pDataBuf, &dwDataLen);
+ wLen -= 8;
+
+ if (dwDataLen > wLen)
+ dwDataLen = wLen;
+
+ if (nTypeId == MTYPE_FILEREQ && wAckType == 2)
+ {
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+ NetLog_Server("This is file ack");
+
+ char *szMsg = (char *)_alloca(dwDataLen + 1);
+ memcpy(szMsg, pDataBuf, dwDataLen);
+ szMsg[dwDataLen] = '\0';
+ pDataBuf += dwDataLen;
+ wLen -= (WORD)dwDataLen;
+
+ handleFileAck(pDataBuf, wLen, dwUin, wCookie, wStatus, szMsg);
+ }
+ else if (nTypeId == MTYPE_FILEREQ && wAckType == 1)
+ {
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+ NetLog_Server("This is a file request");
+
+ char *szMsg = (char *)_alloca(dwDataLen + 1);
+ memcpy(szMsg, pDataBuf, dwDataLen);
+ szMsg[dwDataLen] = '\0';
+ pDataBuf += dwDataLen;
+ wLen -= (WORD)dwDataLen;
+
+ handleFileRequest(pDataBuf, wLen, dwUin, wCookie, dwMsgID1, dwMsgID2, szMsg, 8, FALSE);
+ }
+ else if (nTypeId == MTYPE_CHAT && wAckType == 1)
+ { // TODO: this is deprecated
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+ NetLog_Server("This is a chat request");
+
+ char *szMsg = (char *)_alloca(dwDataLen + 1);
+ memcpy(szMsg, pDataBuf, dwDataLen);
+ szMsg[dwDataLen] = '\0';
+ pDataBuf += dwDataLen;
+ wLen -= (WORD)dwDataLen;
+
+ // handleChatRequest(pDataBuf, wLen, dwUin, wCookie, dwMsgID1, dwMsgID2, szMsg, 8);
+ }
+ else if (nTypeId == MTYPE_STATUSMSGEXT && wFunction >= 1 && wFunction <= 3)
+ { // handle extended status message request
+ int nMsgType = 0;
+
+ switch (wFunction)
+ {
+ case 1: // Away
+ if (m_iStatus == ID_STATUS_ONLINE || m_iStatus == ID_STATUS_INVISIBLE)
+ nMsgType = MTYPE_AUTOONLINE;
+ else if (m_iStatus == ID_STATUS_AWAY)
+ nMsgType = MTYPE_AUTOAWAY;
+ else if (m_iStatus == ID_STATUS_FREECHAT)
+ nMsgType = MTYPE_AUTOFFC;
+ break;
+
+ case 2: // Busy
+ if (m_iStatus == ID_STATUS_OCCUPIED)
+ nMsgType = MTYPE_AUTOBUSY;
+ else if (m_iStatus == ID_STATUS_DND)
+ nMsgType = MTYPE_AUTODND;
+ break;
+
+ case 3: // N/A
+ if (m_iStatus == ID_STATUS_NA)
+ nMsgType = MTYPE_AUTONA;
+ }
+ handleMessageTypes(dwUin, szUID, time(NULL), dwMsgID1, dwMsgID2, wCookie, wVersion, nMsgType, bFlags, wAckType, dwLengthToEnd, 0, (char*)pDataBuf, MTF_PLUGIN | MTF_STATUS_EXTENDED, NULL);
+ }
+ else if (nTypeId)
+ {
+ if (!dwUin)
+ { // AIM cannot send this, just sanity
+ NetLog_Server("Error: Malformed UIN in packet");
+ return;
+ }
+ message_ack_params pMsgAck = {0};
+
+ pMsgAck.bType = MAT_SERVER_ADVANCED;
+ pMsgAck.dwUin = dwUin;
+ pMsgAck.dwMsgID1 = dwMsgID1;
+ pMsgAck.dwMsgID2 = dwMsgID2;
+ pMsgAck.wCookie = wCookie;
+ pMsgAck.msgType = nTypeId;
+ pMsgAck.bFlags = bFlags;
+ handleMessageTypes(dwUin, szUID, time(NULL), dwMsgID1, dwMsgID2, wCookie, wVersion, nTypeId, bFlags, wAckType, dwLengthToEnd, (WORD)dwDataLen, (char*)pDataBuf, MTF_PLUGIN, &pMsgAck);
+ }
+ else
+ {
+ NetLog_Server("Unsupported plugin message type %d", nTypeId);
+ }
+ }
+ else
+ NetLog_Server("Error: Malformed server plugin message");
+}
+
+
+void CIcqProto::handleRecvServMsgContacts(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand)
+{
+ HANDLE hContact = HContactFromUID(dwUin, szUID, NULL);
+
+ if (wCommand == 0)
+ { // received contacts
+ if (wLen < 4)
+ { // just check if at least one tlv is there
+ NetLog_Server("Message (format %u) - Ignoring empty contacts message", 2);
+ return;
+ }
+ oscar_tlv_chain *chain = readIntoTLVChain(&buf, wLen, 0);
+ if (!chain)
+ { // sanity check
+ NetLog_Server("Message (format %u) - Invalid data", 2);
+ return;
+ }
+
+ WORD wAckType = chain->getWord(0x0A, 1);
+
+ if (wAckType == 1)
+ { // it is really message containing contacts, parse them
+ oscar_tlv *tlvUins = chain->getTLV(0x2711, 1);
+ oscar_tlv *tlvNames = chain->getTLV(0x2712, 1);
+
+ if (!tlvUins || tlvUins->wLen < 4)
+ {
+ NetLog_Server("Malformed '%s' message", "contacts");
+ disposeChain(&chain);
+ return;
+ }
+ int nContacts = 0x10, iContact = 0;
+ ICQSEARCHRESULT **contacts = (ICQSEARCHRESULT**)SAFE_MALLOC(nContacts * sizeof(ICQSEARCHRESULT*));
+ WORD wContactsGroup = 0;
+ int valid = 1;
+ BYTE *pBuffer = tlvUins->pData;
+ int nLen = tlvUins->wLen;
+
+ while (nLen > 2)
+ { // parse UIDs
+ if (!wContactsGroup)
+ {
+ WORD wGroupLen;
+
+ unpackWord(&pBuffer, &wGroupLen);
+ nLen -= 2;
+ if (nLen >= wGroupLen + 2)
+ {
+ pBuffer += wGroupLen;
+ unpackWord(&pBuffer, &wContactsGroup);
+ nLen -= wGroupLen + 2;
+ }
+ else
+ break;
+ }
+ else
+ { // group parsed, UIDs waiting
+ WORD wUidLen;
+
+ unpackWord(&pBuffer, &wUidLen);
+ nLen -= 2;
+ if (nLen >= wUidLen)
+ {
+ char *szUid = (char*)SAFE_MALLOC(wUidLen + 1);
+ unpackString(&pBuffer, szUid, wUidLen);
+ nLen -= wUidLen;
+
+ if (iContact >= nContacts)
+ { // the list is too small, resize it
+ nContacts += 0x10;
+ contacts = (ICQSEARCHRESULT**)SAFE_REALLOC(contacts, nContacts * sizeof(ICQSEARCHRESULT*));
+ }
+ contacts[iContact] = (ICQSEARCHRESULT*)SAFE_MALLOC(sizeof(ICQSEARCHRESULT));
+ contacts[iContact]->hdr.cbSize = sizeof(ICQSEARCHRESULT);
+ contacts[iContact]->hdr.flags = PSR_TCHAR;
+ contacts[iContact]->hdr.nick = null_strdup(_T(""));
+ contacts[iContact]->hdr.id = ansi_to_tchar(szUid);
+
+ if (IsStringUIN(szUid))
+ { // icq contact
+ contacts[iContact]->uin = atoi(szUid);
+ if (contacts[iContact]->uin == 0)
+ valid = 0;
+ }
+ else
+ { // aim contact
+ if (!strlennull(szUid))
+ valid = 0;
+ }
+ iContact++;
+
+ SAFE_FREE(&szUid);
+ }
+ else
+ {
+ if (wContactsGroup) valid = 0;
+ break;
+ }
+
+ wContactsGroup--;
+ }
+ }
+ if (!iContact || !valid)
+ {
+ NetLog_Server("Malformed '%s' message", "contacts");
+ disposeChain(&chain);
+ for (int i = 0; i < iContact; i++)
+ {
+ SAFE_FREE(&contacts[i]->hdr.id);
+ SAFE_FREE(&contacts[i]->hdr.nick);
+ SAFE_FREE((void**)&contacts[i]);
+ }
+ SAFE_FREE((void**)&contacts);
+ return;
+ }
+ nContacts = iContact;
+ if (tlvNames && tlvNames->wLen >= 4)
+ { // parse names, if available
+ pBuffer = tlvNames->pData;
+ nLen = tlvNames->wLen;
+ iContact = 0;
+
+ while (nLen > 2)
+ { // parse Names
+ if (!wContactsGroup)
+ {
+ WORD wGroupLen;
+
+ unpackWord(&pBuffer, &wGroupLen);
+ nLen -= 2;
+ if (nLen >= wGroupLen + 2)
+ {
+ pBuffer += wGroupLen;
+ unpackWord(&pBuffer, &wContactsGroup);
+ nLen -= wGroupLen + 2;
+ }
+ else
+ break;
+ }
+ else
+ { // group parsed, Names waiting
+ WORD wNickLen;
+
+ unpackWord(&pBuffer, &wNickLen);
+ nLen -= 2;
+ if (nLen >= wNickLen)
+ {
+ WORD wNickTLV, wNickTLVLen;
+ char *pNick = NULL;
+
+ unpackTypedTLV(pBuffer, wNickLen, 0x01, &wNickTLV, &wNickTLVLen, (LPBYTE*)&pNick);
+ if (wNickTLV == 0x01)
+ {
+ SAFE_FREE(&contacts[iContact]->hdr.nick);
+ contacts[iContact]->hdr.nick = utf8_to_tchar(pNick);
+ }
+ else
+ SAFE_FREE(&pNick);
+ pBuffer += wNickLen;
+ nLen -= wNickLen;
+
+ iContact++;
+ if (iContact >= nContacts) break;
+ }
+ else
+ break;
+
+ wContactsGroup--;
+ }
+ }
+ }
+
+ if (!valid)
+ {
+ NetLog_Server("Malformed '%s' message", "contacts");
+ }
+ else
+ {
+ int bAdded;
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+
+ hContact = HContactFromUID(dwUin, szUID, &bAdded);
+
+ // ack the message
+ icq_sendContactsAck(dwUin, szUID, dwID1, dwID2);
+
+ ccs.szProtoService = PSR_CONTACTS;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = (DWORD)time(NULL);
+ pre.szMessage = (char *)contacts;
+ pre.lParam = nContacts;
+ pre.flags = PREF_TCHAR;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+ }
+
+ for (int i = 0; i < iContact; i++)
+ {
+ SAFE_FREE(&contacts[i]->hdr.id);
+ SAFE_FREE(&contacts[i]->hdr.nick);
+ SAFE_FREE((void**)&contacts[i]);
+ }
+ SAFE_FREE((void**)&contacts);
+ }
+ else
+ NetLog_Server("Error: Received unknown contacts message, ignoring.");
+ // Clean up
+ disposeChain(&chain);
+ }
+ else if (wCommand == 1)
+ {
+ NetLog_Server("Cannot handle abort messages yet... :(");
+ return;
+ }
+ else if (wCommand == 2)
+ { // acknowledgement
+ DWORD dwCookie;
+ HANDLE hCookieContact;
+
+ if (FindMessageCookie(dwID1, dwID2, &dwCookie, &hCookieContact, NULL))
+ {
+ if (hCookieContact != hContact)
+ NetLog_Server("Warning: Ack Contact does not match Cookie Contact(0x%x != 0x%x)", hContact, hCookieContact);
+
+ BroadcastAck(hContact, ACKTYPE_CONTACTS, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
+
+ ReleaseCookie(dwCookie);
+ }
+ else
+ NetLog_Server("Warning: Unexpected Contact Transfer ack from %s", strUID(dwUin, szUID));
+ }
+}
+
+
+void CIcqProto::handleRecvServMsgType4(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef)
+{
+ WORD wTLVType;
+ WORD wTLVLen;
+ BYTE* pDataBuf;
+ DWORD dwUin2;
+
+ if (wLen < 2)
+ {
+ NetLog_Server("Message (format %u) - Ignoring empty message", 4);
+ return;
+ }
+
+ // Unpack the first TLV(5)
+ unpackTypedTLV(buf, wLen, 5, &wTLVType, &wTLVLen, &pDataBuf);
+ NetLog_Server("Message (format %u) - UID: %s", 4, strUID(dwUin, szUID));
+
+ // It must be TLV(5)
+ if (wTLVType == 5)
+ {
+ BYTE bMsgType;
+ BYTE bFlags;
+ BYTE* pmsg = pDataBuf;
+ WORD wMsgLen;
+
+
+ unpackLEDWord(&pmsg, &dwUin2);
+
+ if (dwUin2 == dwUin)
+ {
+ unpackByte(&pmsg, &bMsgType);
+ unpackByte(&pmsg, &bFlags);
+ unpackLEWord(&pmsg, &wMsgLen);
+
+ if (bMsgType == 0 && wMsgLen == 1)
+ {
+ NetLog_Server("User %u probably checks his ignore state.", dwUin);
+ }
+ else
+ {
+ cookie_offline_messages *cookie;
+ DWORD dwRecvTime = (DWORD)time(NULL);
+
+ if (!(dwRef & 0x80000000) && FindCookie(dwRef, NULL, (void**)&cookie))
+ {
+ WORD wTimeTLVType, wTimeTLVLen;
+ BYTE *pTimeTLV = NULL;
+
+ cookie->nMessages++;
+
+ unpackTypedTLV(buf, wLen, 0x16, &wTimeTLVType, &wTimeTLVLen, &pTimeTLV);
+ if (pTimeTLV && wTimeTLVType == 0x16 && wTimeTLVLen == 4)
+ { // found Offline timestamp
+ BYTE *pBuf = pTimeTLV;
+
+ unpackDWord(&pBuf, &dwRecvTime);
+ NetLog_Server("Message (format %u) - Offline timestamp is %s", 4, time2text(dwRecvTime));
+ }
+ SAFE_FREE((void**)&pTimeTLV);
+ }
+
+ if (bMsgType == MTYPE_PLUGIN)
+ {
+ WORD wLen = wTLVLen - 8;
+ int typeId;
+
+ NetLog_Server("Parsing Greeting message through server");
+
+ pmsg += wMsgLen;
+ wLen -= wMsgLen;
+
+ if (unpackPluginTypeId(&pmsg, &wLen, &typeId, NULL, FALSE) && wLen > 8)
+ {
+ DWORD dwLengthToEnd;
+ DWORD dwDataLen;
+
+ // Length of remaining data
+ unpackLEDWord(&pmsg, &dwLengthToEnd);
+
+ // Length of message
+ unpackLEDWord(&pmsg, &dwDataLen);
+ wLen -= 8;
+
+ if (dwDataLen > wLen)
+ dwDataLen = wLen;
+
+ if (typeId)
+ {
+ uid_str szUID;
+ handleMessageTypes(dwUin, szUID, dwRecvTime, dwMsgID1, dwMsgID2, 0, 0, typeId, bFlags, 0, dwLengthToEnd, (WORD)dwDataLen, (char*)pmsg, MTF_PLUGIN, NULL);
+ }
+ else
+ {
+ NetLog_Server("Unsupported plugin message type %d", typeId);
+ }
+ }
+ }
+ else
+ {
+ uid_str szUID;
+ handleMessageTypes(dwUin, szUID, dwRecvTime, dwMsgID1, dwMsgID2, 0, 0, bMsgType, bFlags, 0, wTLVLen - 8, wMsgLen, (char*)pmsg, 0, NULL);
+ }
+ }
+ }
+ else
+ {
+ NetLog_Server("Ignoring spoofed TYPE4 message thru server from %d", dwUin);
+ }
+ }
+ else
+ {
+ NetLog_Server("Unsupported TLV (%u) in message (format %u)", wTLVType, 4);
+ }
+
+ SAFE_FREE((void**)&pDataBuf);
+}
+
+
+//
+// Helper functions
+//
+
+static int TypeGUIDToTypeId(DWORD dwGuid1, DWORD dwGuid2, DWORD dwGuid3, DWORD dwGuid4, WORD wType)
+{
+ int nTypeID = MTYPE_UNKNOWN;
+
+ if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_STATUSMSGEXT))
+ {
+ nTypeID = MTYPE_STATUSMSGEXT;
+ }
+ else if (wType==MGTYPE_UNDEFINED)
+ {
+ if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, PSIG_MESSAGE))
+ { // icq6 message ack
+ nTypeID = MTYPE_PLAIN;
+ }
+ }
+ else if (wType==MGTYPE_STANDARD_SEND)
+ {
+ if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_WEBURL))
+ {
+ nTypeID = MTYPE_URL;
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_CONTACTS))
+ {
+ nTypeID = MTYPE_CONTACTS;
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_CHAT))
+ {
+ nTypeID = MTYPE_CHAT;
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_FILE))
+ {
+ nTypeID = MTYPE_FILEREQ;
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_GREETING_CARD))
+ {
+ nTypeID = MTYPE_GREETINGCARD;
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_MESSAGE))
+ {
+ nTypeID = MTYPE_MESSAGE;
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_SMS_MESSAGE))
+ {
+ nTypeID = MTYPE_SMS_MESSAGE;
+ }
+ }
+ else if (wType==MGTYPE_CONTACTS_REQUEST)
+ {
+ if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_CONTACTS))
+ {
+ nTypeID = MTYPE_REQUESTCONTACTS;
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_XTRAZ_SCRIPT))
+ {
+ nTypeID = MTYPE_SCRIPT_DATA;
+ }
+ }
+ else if (CompareGUIDs(dwGuid1, dwGuid2, dwGuid3, dwGuid4, MGTYPE_XTRAZ_SCRIPT))
+ {
+ if (wType==MGTYPE_SCRIPT_INVITATION)
+ {
+ nTypeID = MTYPE_SCRIPT_INVITATION;
+ }
+ else if (wType==MGTYPE_SCRIPT_NOTIFY)
+ {
+ nTypeID = MTYPE_SCRIPT_NOTIFY;
+ }
+ }
+
+ return nTypeID;
+}
+
+
+int CIcqProto::unpackPluginTypeId(BYTE **pBuffer, WORD *pwLen, int *pTypeId, WORD *pFunctionId, BOOL bThruDC)
+{
+ WORD wLen = *pwLen;
+ WORD wInfoLen;
+ DWORD dwPluginNameLen;
+ DWORD q1,q2,q3,q4;
+ WORD qt;
+
+ if (wLen < 24)
+ return 0; // Failure
+
+ unpackLEWord(pBuffer, &wInfoLen);
+
+ unpackDWord(pBuffer, &q1); // get data GUID & function id
+ unpackDWord(pBuffer, &q2);
+ unpackDWord(pBuffer, &q3);
+ unpackDWord(pBuffer, &q4);
+ unpackLEWord(pBuffer, &qt);
+ wLen -= 20;
+
+ if (pFunctionId) *pFunctionId = qt;
+
+ unpackLEDWord(pBuffer, &dwPluginNameLen);
+ wLen -= 4;
+
+ if (dwPluginNameLen > wLen)
+ { // check for malformed plugin name
+ dwPluginNameLen = wLen;
+ NetLog_Uni(bThruDC, "Warning: malformed size of plugin name.");
+ }
+ char *szPluginName = (char *)_alloca(dwPluginNameLen + 1);
+ memcpy(szPluginName, *pBuffer, dwPluginNameLen);
+ szPluginName[dwPluginNameLen] = '\0';
+ wLen -= (WORD)dwPluginNameLen;
+
+ *pBuffer += dwPluginNameLen;
+
+ int typeId = TypeGUIDToTypeId(q1, q2, q3, q4, qt);
+ if (!typeId)
+ NetLog_Uni(bThruDC, "Error: Unknown type {%08x-%08x-%08x-%08x:%04x}: %s", q1,q2,q3,q4,qt, szPluginName);
+
+ if (wInfoLen >= 22 + dwPluginNameLen)
+ { // sanity checking
+ wInfoLen -= (WORD)(22 + dwPluginNameLen);
+
+ // check if enough data is available - skip remaining bytes of info block
+ if (wLen >= wInfoLen)
+ {
+ *pBuffer += wInfoLen;
+ wLen -= wInfoLen;
+ }
+ }
+
+ *pwLen = wLen;
+ *pTypeId = typeId;
+
+ return 1; // Success
+}
+
+
+int getPluginTypeIdLen(int nTypeID)
+{
+ switch (nTypeID)
+ {
+ case MTYPE_SCRIPT_NOTIFY:
+ return 0x51;
+
+ case MTYPE_FILEREQ:
+ return 0x2B;
+
+ case MTYPE_AUTOONLINE:
+ case MTYPE_AUTOAWAY:
+ case MTYPE_AUTOBUSY:
+ case MTYPE_AUTODND:
+ case MTYPE_AUTOFFC:
+ return 0x3C;
+
+ case MTYPE_AUTONA:
+ return 0x3B;
+
+ default:
+ return 0;
+ }
+}
+
+
+void packPluginTypeId(icq_packet *packet, int nTypeID)
+{
+ switch (nTypeID)
+ {
+ case MTYPE_SCRIPT_NOTIFY:
+ packLEWord(packet, 0x04f); // Length
+
+ packGUID(packet, MGTYPE_XTRAZ_SCRIPT); // Message Type GUID
+ packLEWord(packet, MGTYPE_SCRIPT_NOTIFY); // Function ID
+ packLEDWord(packet, 0x002a); // Request type string
+ packBuffer(packet, (LPBYTE)"Script Plug-in: Remote Notification Arrive", 0x002a);
+
+ packDWord(packet, 0x00000100); // Unknown binary stuff
+ packDWord(packet, 0x00000000);
+ packDWord(packet, 0x00000000);
+ packWord(packet, 0x0000);
+ packByte(packet, 0x00);
+
+ break;
+
+ case MTYPE_FILEREQ:
+ packLEWord(packet, 0x029); // Length
+
+ packGUID(packet, MGTYPE_FILE); // Message Type GUID
+ packWord(packet, 0x0000); // Unknown
+ packLEDWord(packet, 0x0004); // Request type string
+ packBuffer(packet, (LPBYTE)"File", 0x0004);
+
+ packDWord(packet, 0x00000100); // More unknown binary stuff
+ packDWord(packet, 0x00010000);
+ packDWord(packet, 0x00000000);
+ packWord(packet, 0x0000);
+ packByte(packet, 0x00);
+
+ break;
+
+ case MTYPE_AUTOONLINE:
+ case MTYPE_AUTOAWAY:
+ case MTYPE_AUTOFFC:
+ packLEWord(packet, 0x03A); // Length
+
+ packGUID(packet, MGTYPE_STATUSMSGEXT); // Message Type GUID
+
+ packLEWord(packet, 1); // Function ID
+ packLEDWord(packet, 0x13); // Request type string
+ packBuffer(packet, (LPBYTE)"Away Status Message", 0x13);
+
+ packDWord(packet, 0x01000000); // Unknown binary stuff
+ packDWord(packet, 0x00000000);
+ packDWord(packet, 0x00000000);
+ packDWord(packet, 0x00000000);
+ packByte(packet, 0x00);
+
+ break;
+
+ case MTYPE_AUTOBUSY:
+ case MTYPE_AUTODND:
+ packLEWord(packet, 0x03A); // Length
+
+ packGUID(packet, MGTYPE_STATUSMSGEXT); // Message Type GUID
+
+ packLEWord(packet, 2); // Function ID
+ packLEDWord(packet, 0x13); // Request type string
+ packBuffer(packet, (LPBYTE)"Busy Status Message", 0x13);
+
+ packDWord(packet, 0x02000000); // Unknown binary stuff
+ packDWord(packet, 0x00000000);
+ packDWord(packet, 0x00000000);
+ packDWord(packet, 0x00000000);
+ packByte(packet, 0x00);
+
+ break;
+
+ case MTYPE_AUTONA:
+ packLEWord(packet, 0x039); // Length
+
+ packGUID(packet, MGTYPE_STATUSMSGEXT); // Message Type GUID
+
+ packLEWord(packet, 3); // Function ID
+ packLEDWord(packet, 0x12); // Request type string
+ packBuffer(packet, (LPBYTE)"N/A Status Message", 0x12);
+
+ packDWord(packet, 0x03000000); // Unknown binary stuff
+ packDWord(packet, 0x00000000);
+ packDWord(packet, 0x00000000);
+ packDWord(packet, 0x00000000);
+ packByte(packet, 0x00);
+
+ break;
+ }
+}
+
+
+void CIcqProto::handleStatusMsgReply(const char *szPrefix, HANDLE hContact, DWORD dwUin, WORD wVersion, int bMsgType, WORD wCookie, const char *szMsg, int nMsgFlags)
+{
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+
+ if (hContact == INVALID_HANDLE_VALUE)
+ {
+ NetLog_Server("%sIgnoring status message from unknown contact %u", szPrefix, dwUin);
+ return;
+ }
+
+ int status = AwayMsgTypeToStatus(bMsgType);
+ if (status == ID_STATUS_OFFLINE)
+ {
+ NetLog_Server("%sIgnoring unknown status message from %u", szPrefix, dwUin);
+ return;
+ }
+
+ // it is probably UTF-8 status reply
+ if (wVersion == 9 || (nMsgFlags & MTF_PLUGIN) && wVersion == 10)
+ {
+ if (UTF8_IsValid(szMsg)) pre.flags |= PREF_UTF;
+ }
+
+ ccs.szProtoService = PSR_AWAYMSG;
+ ccs.hContact = hContact;
+ ccs.wParam = status;
+ ccs.lParam = (LPARAM)&pre;
+ pre.szMessage = (char*)szMsg;
+ pre.timestamp = time(NULL);
+ pre.lParam = wCookie;
+
+ CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs);
+}
+
+
+HANDLE CIcqProto::handleMessageAck(DWORD dwUin, char *szUID, WORD wCookie, WORD wVersion, int type, WORD wMsgLen, PBYTE buf, BYTE bFlags, int nMsgFlags)
+{
+ if (bFlags == 3)
+ {
+ HANDLE hCookieContact;
+ cookie_message_data *pCookieData = NULL;
+
+ HANDLE hContact = HContactFromUID(dwUin, szUID, NULL);
+
+ if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData))
+ {
+ NetLog_Server("%sIgnoring unrequested status message from %u", "handleMessageAck: ", dwUin);
+
+ ReleaseCookie(wCookie);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (hContact != hCookieContact)
+ {
+ NetLog_Server("%sAck Contact does not match Cookie Contact(0x%x != 0x%x)", "handleMessageAck: ", hContact, hCookieContact);
+
+ ReleaseCookie(wCookie);
+ return INVALID_HANDLE_VALUE;
+ }
+ ReleaseCookie(wCookie);
+
+ handleStatusMsgReply("handleMessageAck: ", hContact, dwUin, wVersion, type, wCookie, (char*)buf, nMsgFlags);
+ }
+ else
+ {
+ // Should not happen
+ NetLog_Server("%sIgnored type %u ack message (this should not happen)", "handleMessageAck: ", type);
+ }
+
+ return INVALID_HANDLE_VALUE;
+}
+
+
+/* this function send all acks from handleMessageTypes */
+void CIcqProto::sendMessageTypesAck(HANDLE hContact, int bUnicode, message_ack_params *pArgs)
+{
+ if (pArgs)
+ {
+ if ((pArgs->msgType == MTYPE_PLAIN && !CallService(MS_IGNORE_ISIGNORED, (WPARAM)hContact, IGNOREEVENT_MESSAGE))
+ || (pArgs->msgType == MTYPE_URL && !CallService(MS_IGNORE_ISIGNORED, (WPARAM)hContact, IGNOREEVENT_URL))
+ || pArgs->msgType == MTYPE_CONTACTS)
+ {
+ if (pArgs->bType == MAT_SERVER_ADVANCED)
+ { // Only ack message packets
+ icq_sendAdvancedMsgAck(pArgs->dwUin, pArgs->dwMsgID1, pArgs->dwMsgID2, pArgs->wCookie, (BYTE)pArgs->msgType, pArgs->bFlags);
+ }
+ else if (pArgs->bType == MAT_DIRECT)
+ { // Send acknowledgement
+ icq_sendDirectMsgAck(pArgs->pDC, pArgs->wCookie, (BYTE)pArgs->msgType, pArgs->bFlags, bUnicode ? (char *)CAP_UTF8MSGS : NULL);
+ }
+ }
+ }
+}
+
+
+/* this function also processes direct packets, so it should be bulletproof */
+/* pMsg points to the beginning of the message */
+void CIcqProto::handleMessageTypes(DWORD dwUin, char *szUID, DWORD dwTimestamp, DWORD dwMsgID, DWORD dwMsgID2, WORD wCookie, WORD wVersion, int type, int flags, WORD wAckType, DWORD dwDataLen, WORD wMsgLen, char *pMsg, int nMsgFlags, message_ack_params *pAckParams)
+{
+ HANDLE hContact = INVALID_HANDLE_VALUE;
+ BOOL bThruDC = (nMsgFlags & MTF_DIRECT) == MTF_DIRECT;
+ int bAdded;
+
+
+ if (dwDataLen < wMsgLen)
+ {
+ NetLog_Uni(bThruDC, "Ignoring overflowed message");
+ return;
+ }
+
+ if (wAckType == 2)
+ {
+ handleMessageAck(dwUin, szUID, wCookie, wVersion, type, wMsgLen, (LPBYTE)pMsg, (BYTE)flags, nMsgFlags);
+ return;
+ }
+
+ char *szMsg = (char *)SAFE_MALLOC(wMsgLen + 1);
+ if (wMsgLen > 0)
+ {
+ memcpy(szMsg, pMsg, wMsgLen);
+ pMsg += wMsgLen;
+ dwDataLen -= wMsgLen;
+ }
+ szMsg[wMsgLen] = '\0';
+
+
+ char* pszMsgField[2*MAX_CONTACTSSEND+1];
+ int nMsgFields = 0;
+
+ pszMsgField[0] = szMsg;
+ if (type == MTYPE_URL || type == MTYPE_AUTHREQ || type == MTYPE_ADDED || type == MTYPE_CONTACTS || type == MTYPE_EEXPRESS || type == MTYPE_WWP)
+ {
+ for (char *pszMsg=szMsg, nMsgFields=1; *pszMsg; pszMsg++)
+ {
+ if ((BYTE)*pszMsg == 0xFE)
+ {
+ *pszMsg = '\0';
+ pszMsgField[nMsgFields++] = pszMsg + 1;
+ if (nMsgFields >= SIZEOF(pszMsgField))
+ break;
+ }
+ }
+ }
+
+ switch (type) {
+
+ case MTYPE_PLAIN: /* plain message */
+ {
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+
+ // Check if this message is marked as UTF8 encoded
+ if (dwDataLen > 12)
+ {
+ DWORD dwGuidLen = 0;
+ int bDoubleMsg = 0;
+
+ if (bThruDC)
+ {
+ DWORD dwExtraLen = *(DWORD*)pMsg;
+
+ if (dwExtraLen < dwDataLen && !strncmp(szMsg, "{\\rtf", 5))
+ { // it is icq5 sending us crap, get real message from it
+ WCHAR* usMsg = (WCHAR*)_alloca((dwExtraLen + 1)*sizeof(WCHAR));
+ // make sure it is null-terminated
+ wcsncpy(usMsg, (WCHAR*)(pMsg + 4), dwExtraLen);
+ usMsg[dwExtraLen] = '\0';
+ SAFE_FREE(&szMsg);
+ szMsg = (char*)make_utf8_string(usMsg);
+
+ if (!IsUnicodeAscii(usMsg, dwExtraLen))
+ pre.flags = PREF_UTF; // only mark real non-ascii messages as unicode
+
+ bDoubleMsg = 1;
+ }
+ }
+
+ if (!bDoubleMsg)
+ {
+ dwGuidLen = *(DWORD*)(pMsg+8);
+ dwDataLen -= 12;
+ pMsg += 12;
+ }
+
+ while ((dwGuidLen >= 38) && (dwDataLen >= dwGuidLen))
+ {
+ if (!strncmp(pMsg, CAP_UTF8MSGS, 38))
+ { // Found UTF8 cap, convert message to ansi
+ pre.flags = PREF_UTF;
+ break;
+ }
+ else if (!strncmp(pMsg, CAP_RTFMSGS, 38))
+ { // Found RichText cap
+ NetLog_Uni(bThruDC, "Warning: User %u sends us RichText.", dwUin);
+ break;
+ }
+
+ dwGuidLen -= 38;
+ dwDataLen -= 38;
+ pMsg += 38;
+ }
+ }
+
+ hContact = HContactFromUIN(dwUin, &bAdded);
+ sendMessageTypesAck(hContact, pre.flags & PREF_UTF, pAckParams);
+
+ if (!pre.flags && !IsUSASCII(szMsg, strlennull(szMsg)))
+ { // message is Ansi and contains national characters, create Unicode part by codepage
+ char *usMsg = convertMsgToUserSpecificUtf(hContact, szMsg);
+ if (usMsg)
+ {
+ SAFE_FREE(&szMsg);
+ szMsg = (char*)usMsg;
+ pre.flags = PREF_UTF;
+ }
+ }
+
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = dwTimestamp;
+ pre.szMessage = (char *)szMsg;
+
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+ }
+ break;
+
+ case MTYPE_URL:
+ {
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+
+ if (nMsgFields < 2)
+ {
+ NetLog_Uni(bThruDC, "Malformed '%s' message", "URL");
+ break;
+ }
+
+ hContact = HContactFromUIN(dwUin, &bAdded);
+ sendMessageTypesAck(hContact, 0, pAckParams);
+
+ char *szTitle = ICQTranslateUtf(LPGEN("Incoming URL:"));
+ char *szDataDescr = ansi_to_utf8(pszMsgField[0]);
+ char *szDataUrl = ansi_to_utf8(pszMsgField[1]);
+ char *szBlob = (char *)SAFE_MALLOC(strlennull(szTitle) + strlennull(szDataDescr) + strlennull(szDataUrl) + 8);
+ strcpy(szBlob, szTitle);
+ strcat(szBlob, " ");
+ strcat(szBlob, szDataDescr); // Description
+ strcat(szBlob, "\r\n");
+ strcat(szBlob, szDataUrl); // URL
+ SAFE_FREE(&szTitle);
+ SAFE_FREE(&szDataDescr);
+ SAFE_FREE(&szDataUrl);
+
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = dwTimestamp;
+ pre.szMessage = (char *)szBlob;
+ pre.flags = PREF_UTF;
+
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+
+ SAFE_FREE(&szBlob);
+ }
+ break;
+
+ case MTYPE_AUTHREQ: /* auth request */
+ /* format: nick FE first FE last FE email FE unk-char FE msg 00 */
+ {
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+ char* szBlob;
+ char* pCurBlob;
+
+
+ if (nMsgFields < 6)
+ {
+ NetLog_Server("Malformed '%s' message", "auth req");
+ break;
+ }
+
+ ccs.szProtoService=PSR_AUTH;
+ ccs.hContact=hContact=HContactFromUIN(dwUin, &bAdded);
+ ccs.wParam=0;
+ ccs.lParam=(LPARAM)&pre;
+ pre.timestamp=dwTimestamp;
+ pre.lParam=sizeof(DWORD)+sizeof(HANDLE)+strlennull(pszMsgField[0])+strlennull(pszMsgField[1])+strlennull(pszMsgField[2])+strlennull(pszMsgField[3])+strlennull(pszMsgField[5])+5;
+
+ /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ)*/
+ pCurBlob=szBlob=(char *)_alloca(pre.lParam);
+ memcpy(pCurBlob,&dwUin,sizeof(DWORD)); pCurBlob+=sizeof(DWORD);
+ memcpy(pCurBlob,&hContact,sizeof(HANDLE)); pCurBlob+=sizeof(HANDLE);
+ strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[1]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[2]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[3]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[5]);
+ pre.szMessage=(char *)szBlob;
+
+ CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs);
+ }
+ break;
+
+ case MTYPE_ADDED: /* 'you were added' */
+ /* format: nick FE first FE last FE email 00 */
+ {
+ DWORD cbBlob;
+ PBYTE pBlob, pCurBlob;
+
+ if (nMsgFields < 4)
+ {
+ NetLog_Server("Malformed '%s' message", "you were added");
+ break;
+ }
+
+ hContact = HContactFromUIN(dwUin, &bAdded);
+
+ /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) */
+ cbBlob=sizeof(DWORD)*2+strlennull(pszMsgField[0])+strlennull(pszMsgField[1])+strlennull(pszMsgField[2])+strlennull(pszMsgField[3])+4;
+ pCurBlob=pBlob=(PBYTE)_alloca(cbBlob);
+ *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD);
+ *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD);
+ strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob += strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[1]); pCurBlob += strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[2]); pCurBlob += strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[3]);
+
+ AddEvent(NULL, EVENTTYPE_ADDED, dwTimestamp, 0, cbBlob, pBlob);
+ }
+ break;
+
+ case MTYPE_CONTACTS:
+ {
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+ char* pszNContactsEnd;
+ int nContacts;
+ int i;
+
+
+ if (nMsgFields < 3
+ || (nContacts = strtol(pszMsgField[0], &pszNContactsEnd, 10)) == 0
+ || pszNContactsEnd - pszMsgField[0] != (int)strlennull(pszMsgField[0])
+ || nMsgFields < nContacts * 2 + 1)
+ {
+ NetLog_Uni(bThruDC, "Malformed '%s' message", "contacts");
+ break;
+ }
+
+ int valid = 1;
+ ICQSEARCHRESULT** isrList = (ICQSEARCHRESULT**)_alloca(nContacts * sizeof(ICQSEARCHRESULT*));
+ for (i = 0; i < nContacts; i++)
+ {
+ isrList[i] = (ICQSEARCHRESULT*)SAFE_MALLOC(sizeof(ICQSEARCHRESULT));
+ isrList[i]->hdr.cbSize = sizeof(ICQSEARCHRESULT);
+ isrList[i]->hdr.flags = PSR_TCHAR;
+ if (IsStringUIN(pszMsgField[1 + i * 2]))
+ { // icq contact
+ isrList[i]->uin = atoi(pszMsgField[1 + i * 2]);
+ if (isrList[i]->uin == 0)
+ valid = 0;
+ }
+ else
+ { // aim contact
+ if (!strlennull(pszMsgField[1 + i * 2]))
+ valid = 0;
+ }
+ isrList[i]->hdr.id = ansi_to_tchar(pszMsgField[1 + i * 2]);
+ isrList[i]->hdr.nick = ansi_to_tchar(pszMsgField[2 + i * 2]);
+ }
+
+ if (!valid)
+ {
+ NetLog_Uni(bThruDC, "Malformed '%s' message", "contacts");
+ }
+ else
+ {
+ hContact = HContactFromUIN(dwUin, &bAdded);
+ sendMessageTypesAck(hContact, 0, pAckParams);
+
+ ccs.szProtoService = PSR_CONTACTS;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = dwTimestamp;
+ pre.szMessage = (char *)isrList;
+ pre.lParam = nContacts;
+ pre.flags = PREF_TCHAR;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+ }
+
+ for (i = 0; i < nContacts; i++)
+ {
+ SAFE_FREE(&isrList[i]->hdr.id);
+ SAFE_FREE(&isrList[i]->hdr.nick);
+ SAFE_FREE((void**)&isrList[i]);
+ }
+ }
+ break;
+
+ case MTYPE_PLUGIN: // FIXME: this should be removed - it is never called
+ hContact = NULL;
+
+ switch(dwUin)
+ {
+ case 1111: /* icqmail 'you've got mail' - not processed */
+ break;
+ }
+ break;
+
+ case MTYPE_SMS_MESSAGE:
+ /* it's a SMS message from a mobile - broadcast to SMS plugin */
+ if (dwUin != 1002)
+ {
+ NetLog_Uni(bThruDC, "Malformed '%s' message", "SMS Mobile");
+ break;
+ }
+ NetLog_Server("Received SMS Mobile message");
+
+ BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_SUCCESS, NULL, (LPARAM)szMsg);
+ break;
+
+ case MTYPE_STATUSMSGEXT:
+ /* it's either extended StatusMsg reply from icq2003b or a IcqWebMessage */
+ if (dwUin == 1003)
+ {
+ NetLog_Server("Received ICQWebMessage - NOT SUPPORTED");
+ }
+ break;
+
+ case MTYPE_WWP:
+ /* format: fromname FE FE FE fromemail FE unknownbyte FE 'Sender IP: xxx.xxx.xxx.xxx' 0D 0A body */
+ {
+ DWORD cbBlob;
+ PBYTE pBlob, pCurBlob;
+
+ if (nMsgFields < 6)
+ {
+ NetLog_Server("Malformed '%s' message", "web pager");
+ break;
+ }
+
+ /*blob is: body(ASCIIZ), name(ASCIIZ), email(ASCIIZ) */
+ cbBlob=strlennull(pszMsgField[0])+strlennull(pszMsgField[3])+strlennull(pszMsgField[5])+3;
+ pCurBlob=pBlob=(PBYTE)_alloca(cbBlob);
+ strcpy((char *)pCurBlob,pszMsgField[5]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[3]);
+
+ AddEvent(NULL, ICQEVENTTYPE_WEBPAGER, dwTimestamp, 0, cbBlob, pBlob);
+ }
+ break;
+
+ case MTYPE_EEXPRESS:
+ /* format: fromname FE FE FE fromemail FE unknownbyte FE body */
+ {
+ DWORD cbBlob;
+ PBYTE pBlob, pCurBlob;
+
+ if (nMsgFields < 6)
+ {
+ NetLog_Server("Malformed '%s' message", "e-mail express");
+ break;
+ }
+
+ /*blob is: body(ASCIIZ), name(ASCIIZ), email(ASCIIZ) */
+ cbBlob=strlennull(pszMsgField[0])+strlennull(pszMsgField[3])+strlennull(pszMsgField[5])+3;
+ pCurBlob=pBlob=(PBYTE)_alloca(cbBlob);
+ strcpy((char *)pCurBlob,pszMsgField[5]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[0]); pCurBlob+=strlennull((char *)pCurBlob)+1;
+ strcpy((char *)pCurBlob,pszMsgField[3]);
+
+ AddEvent(NULL, ICQEVENTTYPE_EMAILEXPRESS, dwTimestamp, 0, cbBlob, pBlob);
+ }
+ break;
+
+ case MTYPE_REQUESTCONTACTS:
+ /* it's a contacts-request */
+ NetLog_Uni(bThruDC, "Received %s from %u", "Request for Contacts", dwUin);
+ break;
+
+ case MTYPE_GREETINGCARD:
+ /* it's a greeting card */
+ NetLog_Uni(bThruDC, "Received %s from %u", "Greeting Card", dwUin);
+ break;
+
+ case MTYPE_SCRIPT_NOTIFY:
+ /* it's a xtraz notify request */
+ NetLog_Uni(bThruDC, "Received %s from %u", "Xtraz Notify Request", dwUin);
+ handleXtrazNotify(dwUin, dwMsgID, dwMsgID2, wCookie, szMsg, wMsgLen, bThruDC);
+ break;
+
+ case MTYPE_SCRIPT_INVITATION:
+ /* it's a xtraz invitation to session */
+ NetLog_Uni(bThruDC, "Received %s from %u", "Xtraz Invitation", dwUin);
+ handleXtrazInvitation(dwUin, dwMsgID, dwMsgID2, wCookie, szMsg, wMsgLen, bThruDC);
+ break;
+
+ case MTYPE_SCRIPT_DATA:
+ /* it's a xtraz data packet */
+ NetLog_Uni(bThruDC, "Received %s from %u", "Xtraz data packet", dwUin);
+ handleXtrazData(dwUin, dwMsgID, dwMsgID2, wCookie, szMsg, wMsgLen, bThruDC);
+ break;
+
+ case MTYPE_AUTOONLINE:
+ case MTYPE_AUTOAWAY:
+ case MTYPE_AUTOBUSY:
+ case MTYPE_AUTONA:
+ case MTYPE_AUTODND:
+ case MTYPE_AUTOFFC:
+ {
+ char **szMsg = MirandaStatusToAwayMsg(AwayMsgTypeToStatus(type));
+ if (szMsg)
+ {
+ struct rates_status_message_response: public rates_queue_item
+ {
+ protected:
+ virtual rates_queue_item* copyItem(rates_queue_item *aDest = NULL)
+ {
+ rates_status_message_response *pDest = (rates_status_message_response*)aDest;
+ if (!pDest)
+ pDest = new rates_status_message_response(ppro, wGroup);
+
+ pDest->bExtended = bExtended;
+ pDest->dwMsgID1 = dwMsgID1;
+ pDest->dwMsgID2 = dwMsgID2;
+ pDest->wCookie = wCookie;
+ pDest->wVersion = wVersion;
+ pDest->nMsgType = nMsgType;
+
+ return rates_queue_item::copyItem(pDest);
+ };
+ public:
+ rates_status_message_response(CIcqProto *ppro, WORD wGroup): rates_queue_item(ppro, wGroup) { };
+ virtual ~rates_status_message_response() { };
+
+ virtual void execute()
+ {
+ char **pszMsg = ppro->MirandaStatusToAwayMsg(AwayMsgTypeToStatus(nMsgType));
+ if (bExtended)
+ ppro->icq_sendAwayMsgReplyServExt(dwUin, szUid, dwMsgID1, dwMsgID2, wCookie, wVersion, nMsgType, pszMsg);
+ else if (dwUin)
+ ppro->icq_sendAwayMsgReplyServ(dwUin, dwMsgID1, dwMsgID2, wCookie, wVersion, (BYTE)nMsgType, pszMsg);
+ else
+ ppro->NetLog_Server("Error: Malformed UIN in packet");
+ };
+
+ BOOL bExtended;
+ DWORD dwMsgID1;
+ DWORD dwMsgID2;
+ WORD wCookie;
+ WORD wVersion;
+ int nMsgType;
+ };
+
+ m_ratesMutex->Enter();
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE);
+ m_ratesMutex->Leave();
+
+ rates_status_message_response rr(this, wGroup);
+ rr.bExtended = (nMsgFlags & MTF_STATUS_EXTENDED) == MTF_STATUS_EXTENDED;
+ rr.hContact = hContact;
+ rr.dwUin = dwUin;
+ rr.szUid = szUID;
+ rr.dwMsgID1 = dwMsgID;
+ rr.dwMsgID2 = dwMsgID2;
+ rr.wCookie = wCookie;
+ rr.wVersion = wVersion;
+ rr.nMsgType = type;
+
+ handleRateItem(&rr, RQT_RESPONSE);
+ }
+
+ break;
+ }
+
+ case MTYPE_FILEREQ: // Never happens
+ default:
+ NetLog_Uni(bThruDC, "Unprocessed message type %d", type);
+ break;
+
+ }
+
+ SAFE_FREE(&szMsg);
+}
+
+
+void CIcqProto::handleRecvMsgResponse(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ DWORD dwCookie;
+ WORD wMessageFormat;
+ WORD wStatus;
+ WORD bMsgType = 0;
+ BYTE bFlags;
+ WORD wLength;
+ HANDLE hCookieContact;
+ DWORD dwMsgID1, dwMsgID2;
+ WORD wVersion = 0;
+ cookie_message_data *pCookieData = NULL;
+
+
+ unpackLEDWord(&buf, &dwMsgID1); // Message ID
+ unpackLEDWord(&buf, &dwMsgID2);
+ wLen -= 8;
+
+ unpackWord(&buf, &wMessageFormat);
+ wLen -= 2;
+ if (wMessageFormat != 2)
+ {
+ NetLog_Server("SNAC(4.B) Unknown type");
+ return;
+ }
+
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return;
+
+ HANDLE hContact = HContactFromUID(dwUin, szUid, NULL);
+
+ buf += 2; // 3. unknown
+ wLen -= 2;
+
+ if (!FindMessageCookie(dwMsgID1, dwMsgID2, &dwCookie, &hCookieContact, &pCookieData))
+ {
+ NetLog_Server("SNAC(4.B) Received an ack that I did not ask for from (%u)", dwUin);
+ return;
+ }
+
+ if (IsValidOscarTransfer(pCookieData))
+ { // it is OFT response
+ handleRecvServResponseOFT(buf, wLen, dwUin, szUid, pCookieData);
+ return;
+ }
+
+ if (!dwUin)
+ { // AIM cannot send this - just sanity
+ NetLog_Server("Error: Invalid UID in message response.");
+ return;
+ }
+
+ // Length of sub chunk?
+ if (wLen >= 2)
+ {
+ unpackLEWord(&buf, &wLength);
+ wLen -= 2;
+ }
+ else
+ wLength = 0;
+
+ if (wLength == 0x1b && pCookieData->bMessageType != MTYPE_REVERSE_REQUEST)
+ { // this can be v8 greeting message reply
+ WORD wCookie;
+
+ unpackLEWord(&buf, &wVersion);
+ buf += 27; /* unknowns from the msg we sent */
+ wLen -= 29;
+
+ // Message sequence (SEQ2)
+ unpackLEWord(&buf, &wCookie);
+ wLen -= 2;
+
+ // Unknown (12 bytes)
+ buf += 12;
+ wLen -= 12;
+
+ // Message type
+ unpackByte(&buf, (BYTE*)&bMsgType);
+ unpackByte(&buf, &bFlags);
+ wLen -= 2;
+
+ // Status
+ unpackLEWord(&buf, &wStatus);
+ wLen -= 2;
+
+ // Priority?
+ buf += 2;
+ wLen -= 2;
+
+ if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData))
+ { // use old reliable method
+ NetLog_Server("Warning: Invalid cookie in %s from (%u)", "message response", dwUin);
+
+ if (pCookieData->bMessageType != MTYPE_AUTOAWAY && bFlags == 3)
+ { // most probably a broken ack of some kind (e.g. from R&Q), try to fix that
+ bMsgType = pCookieData->bMessageType;
+ bFlags = 0;
+
+ NetLog_Server("Warning: Invalid message type in %s from (%u)", "message response", dwUin);
+ }
+ }
+ else if (bMsgType != MTYPE_PLUGIN && pCookieData->bMessageType != MTYPE_AUTOAWAY)
+ { // just because some clients break it...
+ dwCookie = wCookie;
+
+ if (bMsgType != pCookieData->bMessageType)
+ NetLog_Server("Warning: Invalid message type in %s from (%u)", "message response", dwUin);
+
+ bMsgType = pCookieData->bMessageType;
+ }
+ else if (pCookieData->bMessageType == MTYPE_AUTOAWAY && bMsgType != MTYPE_PLUGIN)
+ {
+ if (bMsgType != pCookieData->nAckType)
+ NetLog_Server("Warning: Invalid message type in %s from (%u)", "message response", dwUin);
+ }
+ }
+ else
+ {
+ bMsgType = pCookieData->bMessageType;
+ bFlags = 0;
+ }
+
+ if (hCookieContact != hContact)
+ {
+ NetLog_Server("SNAC(4.B) Ack Contact does not match Cookie Contact(0x%x != 0x%x)", hContact, hCookieContact);
+
+ ReleaseCookie(dwCookie); // This could be a bad idea, but I think it is safe
+ return;
+ }
+
+ if (bFlags == 3) // A status message reply
+ {
+ handleStatusMsgReply("SNAC(4.B) ", hContact, dwUin, wVersion, bMsgType, (WORD)dwCookie, (char*)(buf + 2), 0);
+ }
+ else
+ { // An ack of some kind
+ int ackType;
+
+
+ if (hContact == NULL || hContact == INVALID_HANDLE_VALUE)
+ {
+ NetLog_Server("SNAC(4.B) Message from unknown contact (%u)", dwUin);
+
+ ReleaseCookie(dwCookie); // This could be a bad idea, but I think it is safe
+ return;
+ }
+
+ switch (bMsgType) {
+
+ case MTYPE_FILEREQ:
+ {
+ char* szMsg;
+ WORD wMsgLen;
+
+ // Message length
+ unpackLEWord(&buf, &wMsgLen);
+ wLen -= 2;
+ szMsg = (char *)_alloca(wMsgLen + 1);
+ szMsg[wMsgLen] = '\0';
+ if (wMsgLen > 0)
+ {
+ memcpy(szMsg, buf, wMsgLen);
+ buf += wMsgLen;
+ wLen -= wMsgLen;
+ }
+ handleFileAck(buf, wLen, dwUin, dwCookie, wStatus, szMsg);
+ // No success protoack will be sent here, since all file requests
+ // will have been 'sent' when the server returns its ack
+ return;
+ }
+
+ case MTYPE_PLUGIN:
+ {
+ WORD wMsgLen;
+ DWORD dwLengthToEnd;
+ DWORD dwDataLen;
+ int typeId;
+ WORD wFunctionId;
+
+
+ if (wLength != 0x1B)
+ {
+ NetLog_Server("Invalid Greeting %s", "message response");
+
+ ReleaseCookie(dwCookie);
+ return;
+ }
+
+ NetLog_Server("Parsing Greeting %s", "message response");
+
+ // Message
+ unpackLEWord(&buf, &wMsgLen);
+ wLen -= 2;
+ buf += wMsgLen;
+ wLen -= wMsgLen;
+
+ // This packet is malformed. Possibly a file accept from Miranda IM 0.1.2.1
+ if (wLen < 20)
+ {
+ ReleaseCookie(dwCookie);
+ return;
+ }
+
+ if (!unpackPluginTypeId(&buf, &wLen, &typeId, &wFunctionId, FALSE))
+ {
+ ReleaseCookie(dwCookie);
+ return;
+ }
+
+ if (wLen < 4)
+ {
+ NetLog_Server("Error: Invalid greeting %s", "message response");
+
+ ReleaseCookie(dwCookie);
+ return;
+ }
+
+ // Length of remaining data
+ unpackLEDWord(&buf, &dwLengthToEnd);
+ wLen -= 4;
+
+ if (wLen >= 4 && dwLengthToEnd > 0)
+ unpackLEDWord(&buf, &dwDataLen); // Length of message
+ else
+ dwDataLen = 0;
+
+
+ switch (typeId)
+ {
+ case MTYPE_PLAIN:
+ if (pCookieData && pCookieData->bMessageType == MTYPE_AUTOAWAY && dwLengthToEnd >= 4)
+ { // ICQ 6 invented this
+ char *szMsg = (char*)_alloca(dwDataLen + 1);
+
+ if (dwDataLen > 0)
+ memcpy(szMsg, buf, dwDataLen);
+ szMsg[dwDataLen] = '\0';
+ handleStatusMsgReply("SNAC(4.B) ", hContact, dwUin, wVersion, pCookieData->nAckType, (WORD)dwCookie, szMsg, 0);
+
+ ReleaseCookie(dwCookie);
+ return;
+ }
+ else
+ ackType = ACKTYPE_MESSAGE;
+ break;
+
+ case MTYPE_URL:
+ ackType = ACKTYPE_URL;
+ break;
+
+ case MTYPE_CONTACTS:
+ ackType = ACKTYPE_CONTACTS;
+ break;
+
+ case MTYPE_FILEREQ:
+ {
+ NetLog_Server("This is file ack");
+
+ char *szMsg = (char *)_alloca(dwDataLen + 1);
+
+ if (dwDataLen > 0)
+ memcpy(szMsg, buf, dwDataLen);
+ szMsg[dwDataLen] = '\0';
+ buf += dwDataLen;
+ wLen -= (WORD)dwDataLen;
+
+ handleFileAck(buf, wLen, dwUin, dwCookie, wStatus, szMsg);
+ // No success protoack will be sent here, since all file requests
+ // will have been 'sent' when the server returns its ack
+ }
+ return;
+
+ case MTYPE_SCRIPT_NOTIFY:
+ {
+ char *szMsg = (char*)_alloca(dwDataLen + 1);
+
+ if (dwDataLen > 0)
+ memcpy(szMsg, buf, dwDataLen);
+ szMsg[dwDataLen] = '\0';
+
+ handleXtrazNotifyResponse(dwUin, hContact, (WORD)dwCookie, szMsg, dwDataLen);
+
+ ReleaseCookie(dwCookie);
+ }
+ return;
+
+ case MTYPE_STATUSMSGEXT:
+ { // handle Away Message response (ICQ 6)
+ char *szMsg = (char*)SAFE_MALLOC(dwDataLen + 1);
+
+ if (dwDataLen > 0)
+ memcpy(szMsg, buf, dwDataLen);
+ szMsg[dwDataLen] = '\0';
+ szMsg = EliminateHtml(szMsg, dwDataLen);
+
+ handleStatusMsgReply("SNAC(4.B) ", hContact, dwUin, wVersion, pCookieData->nAckType, (WORD)dwCookie, szMsg, MTF_PLUGIN | MTF_STATUS_EXTENDED);
+
+ SAFE_FREE(&szMsg);
+
+ ReleaseCookie(dwCookie);
+ }
+ return;
+
+ default:
+ NetLog_Server("Error: Unknown plugin message response, type %d.", typeId);
+ return;
+ }
+ }
+ break;
+
+ case MTYPE_PLAIN:
+ ackType = ACKTYPE_MESSAGE;
+ break;
+
+ case MTYPE_URL:
+ ackType = ACKTYPE_URL;
+ break;
+
+ case MTYPE_AUTHOK:
+ case MTYPE_AUTHDENY:
+ ackType = ACKTYPE_AUTHREQ;
+ break;
+
+ case MTYPE_ADDED:
+ ackType = ACKTYPE_ADDED;
+ break;
+
+ case MTYPE_CONTACTS:
+ ackType = ACKTYPE_CONTACTS;
+ break;
+
+ case MTYPE_REVERSE_REQUEST:
+ {
+ cookie_reverse_connect *pReverse = (cookie_reverse_connect*)pCookieData;
+
+ if (pReverse->ft)
+ {
+ filetransfer *ft = (filetransfer*)pReverse->ft;
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
+ }
+ NetLog_Server("Reverse Connect request failed");
+ // Set DC status to failed
+ setSettingByte(hContact, "DCStatus", 2);
+
+ ReleaseCookie(dwCookie);
+ }
+ return;
+
+ case MTYPE_CHAT:
+ default:
+ NetLog_Server("SNAC(4.B) Unknown message type (%u) in switch", bMsgType);
+ return;
+ }
+
+ if ((ackType == MTYPE_PLAIN && pCookieData && (pCookieData->nAckType == ACKTYPE_CLIENT)) || ackType != MTYPE_PLAIN)
+ {
+ BroadcastAck(hContact, ackType, ACKRESULT_SUCCESS, (HANDLE)(WORD)dwCookie, 0);
+ }
+ }
+
+ ReleaseCookie(dwCookie);
+}
+
+
+// A response to a CLI_SENDMSG
+void CIcqProto::handleRecvServMsgError(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwSequence)
+{
+ WORD wError;
+ char *pszErrorMessage;
+ HANDLE hContact;
+ cookie_message_data *pCookieData = NULL;
+ int nMessageType;
+
+
+ if (wLen < 2)
+ return;
+
+ if (FindCookie((WORD)dwSequence, &hContact, (void**)&pCookieData))
+ { // all packet cookies from msg family has command 0 in the queue
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ { // Invalid contact
+ FreeCookie((WORD)dwSequence);
+ return;
+ }
+
+ // Error code
+ unpackWord(&buf, &wError);
+
+ if (wError == 9 && pCookieData->bMessageType == MTYPE_AUTOAWAY)
+ { // we failed to request away message the normal way, try it AIM way
+ icq_packet packet;
+
+ serverPacketInit(&packet, (WORD)(13 + getUINLen(dwUin)));
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, (WORD)dwSequence);
+ packWord(&packet, 0x03);
+ packUIN(&packet, dwUin);
+
+ sendServPacket(&packet);
+ return;
+ }
+
+ // Not all of these are actually used in family 4
+ // This will be moved into a special error handling function later
+ switch (wError) {
+
+ case 0x0002: // Server rate limit exceeded
+ pszErrorMessage = Translate("You are sending too fast. Wait a while and try again.\r\nSNAC(4.1) Error x02");
+ break;
+
+ case 0x0003: // Client rate limit exceeded
+ pszErrorMessage = Translate("You are sending too fast. Wait a while and try again.\r\nSNAC(4.1) Error x03");
+ break;
+
+ case 0x0004: // Recipient is not logged in (resend in a offline message)
+ if (pCookieData->bMessageType == MTYPE_PLAIN)
+ {
+ if (pCookieData->isOffline)
+ { // offline failed - most probably to AIM contact
+ pszErrorMessage = Translate("The contact does not support receiving offline messages.");
+ break;
+ }
+ // TODO: this needs better solution
+ setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+ }
+ pszErrorMessage = Translate("The user has logged off. Select 'Retry' to send an offline message.\r\nSNAC(4.1) Error x04");
+ break;
+
+ case 0x0005: // Requested service unavailable
+ pszErrorMessage = Translate("The messaging service is temporarily unavailable. Wait a while and try again.\r\nSNAC(4.1) Error x05");
+ break;
+
+ case 0x0009: // Not supported by client (resend in a simpler format)
+ pszErrorMessage = Translate("The receiving client does not support this type of message.\r\nSNAC(4.1) Error x09");
+ break;
+
+ case 0x000A: // Refused by client
+ pszErrorMessage = Translate("You sent too long message. The receiving client does not support it.\r\nSNAC(4.1) Error x0A");
+ break;
+
+ case 0x000E: // Incorrect SNAC format
+ pszErrorMessage = Translate("The SNAC format was rejected by the server.\nSNAC(4.1) Error x0E");
+ break;
+
+ case 0x0013: // User temporarily unavailable
+ pszErrorMessage = Translate("The user is temporarily unavailable. Wait a while and try again.\r\nSNAC(4.1) Error x13");
+ break;
+
+ case 0x0001: // Invalid SNAC header
+ case 0x0006: // Requested service not defined
+ case 0x0007: // You sent obsolete SNAC
+ case 0x0008: // Not supported by server
+ case 0x000B: // Reply too big
+ case 0x000C: // Responses lost
+ case 0x000D: // Request denied
+ case 0x000F: // Insufficient rights
+ case 0x0010: // In local permit/deny (recipient blocked)
+ case 0x0011: // Sender too evil
+ case 0x0012: // Receiver too evil
+ case 0x0014: // No match
+ case 0x0015: // List overflow
+ case 0x0016: // Request ambiguous
+ case 0x0017: // Server queue full
+ case 0x0018: // Not while on AOL
+ default:
+ if (pszErrorMessage = (char*)_alloca(256))
+ null_snprintf(pszErrorMessage, 256, Translate("SNAC(4.1) SENDMSG Error (x%02x)"), wError);
+ break;
+ }
+
+
+ switch (pCookieData->bMessageType) {
+
+ case MTYPE_PLAIN:
+ nMessageType = ACKTYPE_MESSAGE;
+ break;
+
+ case MTYPE_CHAT:
+ nMessageType = ACKTYPE_CHAT;
+ break;
+
+ case MTYPE_FILEREQ:
+ nMessageType = ACKTYPE_FILE;
+ break;
+
+ case MTYPE_URL:
+ nMessageType = ACKTYPE_URL;
+ break;
+
+ case MTYPE_CONTACTS:
+ nMessageType = ACKTYPE_CONTACTS;
+ break;
+
+ default:
+ nMessageType = -1;
+ break;
+ }
+
+ if (nMessageType != -1)
+ {
+ BroadcastAck(hContact, nMessageType, ACKRESULT_FAILED, (HANDLE)(WORD)dwSequence, (LPARAM)pszErrorMessage);
+ }
+ else
+ {
+ NetLog_Server("Error: Message delivery to %u failed: %s", dwUin, pszErrorMessage);
+ }
+
+ FreeCookie((WORD)dwSequence);
+
+ if (pCookieData->bMessageType != MTYPE_FILEREQ)
+ SAFE_FREE((void**)&pCookieData);
+ }
+ else
+ {
+ unpackWord(&buf, &wError);
+
+ LogFamilyError(ICQ_MSG_FAMILY, wError);
+ }
+}
+
+
+void CIcqProto::handleServerAck(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwSequence)
+{
+ DWORD dwUin;
+ uid_str szUID;
+ WORD wChannel;
+ cookie_message_data *pCookieData;
+
+
+ if (wLen < 13)
+ {
+ NetLog_Server("Ignoring SNAC(4,C) Packet to short");
+ return;
+ }
+
+ buf += 8; // Skip first 8 bytes
+ wLen -= 8;
+
+ // Message channel
+ unpackWord(&buf, &wChannel);
+ wLen -= 2;
+
+ // Sender
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUID)) return;
+
+ HANDLE hContact = HContactFromUID(dwUin, szUID, NULL);
+
+ if (FindCookie((WORD)dwSequence, NULL, (void**)&pCookieData))
+ {
+ // If the user requested a full ack, the
+ // server ack should be ignored here.
+ if (pCookieData && (pCookieData->nAckType == ACKTYPE_SERVER))
+ {
+ if ((hContact != NULL) && (hContact != INVALID_HANDLE_VALUE))
+ {
+ int ackType;
+ int ackRes = ACKRESULT_SUCCESS;
+
+ switch (pCookieData->bMessageType) {
+ case MTYPE_PLAIN:
+ ackType = ACKTYPE_MESSAGE;
+ break;
+
+ case MTYPE_CONTACTS:
+ ackType = ACKTYPE_CONTACTS;
+ break;
+
+ case MTYPE_URL:
+ ackType = ACKTYPE_URL;
+ break;
+
+ case MTYPE_FILEREQ:
+ ackType = ACKTYPE_FILE;
+ ackRes = ACKRESULT_SENTREQUEST;
+ // Note 1: We are not allowed to free the cookie here because it
+ // contains the filetransfer struct that we will need later
+ // Note 2: The cookiedata is NOT a message_cookie_data*, it is a
+ // filetransfer*. IMPORTANT! (it's one of those silly things)
+ break;
+
+ default:
+ ackType = -1;
+ NetLog_Server("Error: Unknown message type %d in ack", pCookieData->bMessageType);
+ break;
+ }
+ if (ackType != -1)
+ BroadcastAck(hContact, ackType, ackRes, (HANDLE)(WORD)dwSequence, 0);
+
+ if (pCookieData->bMessageType != MTYPE_FILEREQ)
+ SAFE_FREE((void**)&pCookieData); // this could be a bad idea, but I think it is safe
+ }
+ FreeCookie((WORD)dwSequence);
+ }
+ else if (pCookieData && (pCookieData->nAckType == ACKTYPE_CLIENT))
+ NetLog_Server("Received a server ack, waiting for client ack.");
+ else
+ NetLog_Server("Ignored a server ack I did not ask for");
+ }
+}
+
+
+void CIcqProto::handleMissedMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ WORD wChannel;
+ WORD wWarningLevel;
+ WORD wCount;
+ WORD wError;
+ WORD wTLVCount;
+
+ if (wLen < 14)
+ return; // Too short
+
+ // Message channel?
+ unpackWord(&buf, &wChannel);
+ wLen -= 2;
+
+ // Sender
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return;
+
+ if (wLen < 8)
+ return; // Too short
+
+ // Warning level?
+ unpackWord(&buf, &wWarningLevel);
+ wLen -= 2;
+
+ // TLV count
+ unpackWord(&buf, &wTLVCount);
+ wLen -= 2;
+
+ // Read past user info TLVs
+ oscar_tlv_chain *pChain = readIntoTLVChain(&buf, (WORD)(wLen-4), wTLVCount);
+ if (pChain)
+ disposeChain(&pChain);
+
+ if (wLen < 4)
+ return; // Too short
+
+ // Number of missed messages
+ unpackWord(&buf, &wCount);
+ wLen -= 2;
+
+ // Error code
+ unpackWord(&buf, &wError);
+ wLen -= 2;
+
+ { // offline retrieval process in progress, note that we received missed message notification
+ cookie_offline_messages *cookie;
+
+ if (FindCookieByType(CKT_OFFLINEMESSAGE, NULL, NULL, (void**)&cookie))
+ cookie->nMissed++;
+ }
+
+ switch (wError) {
+
+ case 0: // The message was invalid
+ case 1: // The message was too long
+ case 2: // The sender has flooded the server
+ case 4: // You are too evil
+ break;
+
+ default:
+ // 3 = Sender too evil (sender warn level > your max_msg_sevil)
+ return;
+ break;
+ }
+
+ // Create event to notify user
+ int bAdded;
+
+ AddEvent(HContactFromUID(dwUin, szUid, &bAdded), ICQEVENTTYPE_MISSEDMESSAGE, time(NULL), 0, sizeof(wError), (PBYTE)&wError);
+}
+
+
+void CIcqProto::handleOffineMessagesReply(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef)
+{
+ cookie_offline_messages *cookie;
+
+ if (FindCookie(dwRef, NULL, (void**)&cookie))
+ {
+ NetLog_Server("End of offline msgs, %u received", cookie->nMessages);
+ if (cookie->nMissed)
+ { // NASTY WORKAROUND!!
+ // The ICQ server has a bug that causes offline messages to be received again and again when some
+ // missed message notification is present (most probably it is not processed correctly and causes
+ // the server to fail the purging process); try to purge them using the old offline messages
+ // protocol. 2008/05/21
+ NetLog_Server("Warning: Received %u missed message notifications, trying to fix the server.", cookie->nMissed);
+
+ icq_packet packet;
+ // This will delete the messages stored on server
+ serverPacketInit(&packet, 24);
+ packFNACHeader(&packet, ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST);
+ packWord(&packet, 1); // TLV Type
+ packWord(&packet, 10); // TLV Length
+ packLEWord(&packet, 8); // Data length
+ packLEDWord(&packet, m_dwLocalUIN); // My UIN
+ packLEWord(&packet, CLI_DELETE_OFFLINE_MSGS_REQ); // Ack offline msgs
+ packLEWord(&packet, 0x0000); // Request sequence number (we dont use this for now)
+
+ // Send it
+ sendServPacket(&packet);
+ }
+
+ ReleaseCookie(dwRef);
+ }
+ else
+ NetLog_Server("Error: Received unexpected end of offline msgs.");
+}
+
+
+void CIcqProto::handleTypingNotification(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ WORD wChannel;
+ WORD wNotification;
+
+ if (wLen < 14)
+ {
+ NetLog_Server("Ignoring SNAC(4.x11) Packet to short");
+ return;
+ }
+
+#ifndef DBG_CAPMTN
+ {
+ NetLog_Server("Ignoring unexpected typing notification");
+ return;
+ }
+#endif
+
+ // The message ID, unused?
+ buf += 8;
+ wLen -= 8;
+
+ // Message channel, unused?
+ unpackWord(&buf, &wChannel);
+ wLen -= 2;
+
+ // Sender
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return;
+
+ HANDLE hContact = HContactFromUID(dwUin, szUid, NULL);
+
+ if (hContact == INVALID_HANDLE_VALUE) return;
+
+ // Typing notification code
+ unpackWord(&buf, &wNotification);
+ wLen -= 2;
+
+ SetContactCapabilities(hContact, CAPF_TYPING);
+
+ // Notify user
+ switch (wNotification) {
+
+ case MTN_FINISHED:
+ case MTN_TYPED:
+ CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, (LPARAM)PROTOTYPE_CONTACTTYPING_OFF);
+ NetLog_Server("%s has stopped typing (ch %u).", strUID(dwUin, szUid), wChannel);
+ break;
+
+ case MTN_BEGUN:
+ CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, (LPARAM)60);
+ NetLog_Server("%s is typing a message (ch %u).", strUID(dwUin, szUid), wChannel);
+ break;
+
+ case MTN_WINDOW_CLOSED:
+ {
+ char szFormat[MAX_PATH];
+ char szMsg[MAX_PATH];
+ char *nick = NickFromHandleUtf(hContact);
+
+ null_snprintf(szMsg, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" has closed the message window."), szFormat, MAX_PATH), nick);
+ ShowPopUpMsg(hContact, ICQTranslateUtfStatic(LPGEN("ICQ Note"), szFormat, MAX_PATH), szMsg, LOG_NOTE);
+ SAFE_FREE((void**)&nick);
+
+ NetLog_Server("%s has closed the message window.", strUID(dwUin, szUid));
+ }
+ break;
+
+ default:
+ NetLog_Server("Unknown typing notification from %s, type %u (ch %u)", strUID(dwUin, szUid), wNotification, wChannel);
+ break;
+ }
+}
+
+
+void CIcqProto::sendTypingNotification(HANDLE hContact, WORD wMTNCode)
+{
+ _ASSERTE((wMTNCode == MTN_FINISHED) || (wMTNCode == MTN_TYPED) || (wMTNCode == MTN_BEGUN) || (wMTNCode == MTN_WINDOW_CLOSED));
+
+ DWORD dwUin;
+ uid_str szUid;
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return; // Invalid contact
+
+ WORD wLen = getUIDLen(dwUin, szUid);
+
+ icq_packet packet;
+ serverPacketInit(&packet, 23 + wLen);
+ packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_MTN);
+ packLEDWord(&packet, 0x0000); // Msg ID
+ packLEDWord(&packet, 0x0000); // Msg ID
+ packWord(&packet, 0x01); // Channel
+ packUID(&packet, dwUin, szUid); // User ID
+ packWord(&packet, wMTNCode); // Notification type
+
+ sendServPacketAsync(&packet);
+}
diff --git a/protocols/IcqOscarJ/src/fam_09bos.cpp b/protocols/IcqOscarJ/src/fam_09bos.cpp
new file mode 100644
index 0000000000..327e045a96
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_09bos.cpp
@@ -0,0 +1,106 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void CIcqProto::handleBosFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_PRIVACY_RIGHTS_REPLY: // Reply to CLI_REQBOS
+ handlePrivacyRightsReply(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_ERROR:
+ {
+ WORD wError;
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ LogFamilyError(ICQ_BOS_FAMILY, wError);
+ break;
+ }
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_BOS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ }
+}
+
+void CIcqProto::handlePrivacyRightsReply(unsigned char *pBuffer, WORD wBufferLength)
+{
+ if (wBufferLength >= 12)
+ {
+ oscar_tlv_chain* pChain = readIntoTLVChain(&pBuffer, wBufferLength, 0);
+ if (pChain)
+ {
+ WORD wMaxVisibleContacts;
+ WORD wMaxInvisibleContacts;
+ WORD wMaxTemporaryVisibleContacts;
+
+ wMaxVisibleContacts = pChain->getWord(0x0001, 1);
+ wMaxInvisibleContacts = pChain->getWord(0x0002, 1);
+ wMaxTemporaryVisibleContacts = pChain->getWord(0x0003, 1);
+
+ disposeChain(&pChain);
+
+ NetLog_Server("PRIVACY: Max visible %u, max invisible %u, max temporary visible %u items.", wMaxVisibleContacts, wMaxInvisibleContacts, wMaxTemporaryVisibleContacts);
+
+ // Success
+ return;
+ }
+ }
+
+ // Failure
+ NetLog_Server("Warning: Malformed SRV_PRIVACY_RIGHTS_REPLY");
+}
+
+void CIcqProto::makeContactTemporaryVisible(HANDLE hContact)
+{
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getSettingByte(hContact, "TemporaryVisible", 0))
+ return; // already there
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return; // Invalid contact
+
+ icq_sendGenericContact(dwUin, szUid, ICQ_BOS_FAMILY, ICQ_CLI_ADDTEMPVISIBLE);
+
+ setSettingByte(hContact, "TemporaryVisible", 1);
+
+#ifdef _DEBUG
+ NetLog_Server("Added contact %s to temporary visible list", strUID(dwUin, szUid));
+#endif
+}
diff --git a/protocols/IcqOscarJ/src/fam_0alookup.cpp b/protocols/IcqOscarJ/src/fam_0alookup.cpp
new file mode 100644
index 0000000000..da77295ecb
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_0alookup.cpp
@@ -0,0 +1,130 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void CIcqProto::handleLookupFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_LOOKUP_EMAIL_REPLY: // AIM search reply
+ handleLookupEmailReply(pBuffer, wBufferLength, pSnacHeader->dwRef);
+ break;
+
+ case ICQ_ERROR:
+ {
+ WORD wError;
+ cookie_search *pCookie;
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookie))
+ {
+ if (wError == 0x14)
+ NetLog_Server("Lookup: No results");
+
+ ReleaseLookupCookie(pSnacHeader->dwRef, pCookie);
+
+ if (wError == 0x14) return;
+ }
+
+ LogFamilyError(ICQ_LOOKUP_FAMILY, wError);
+ break;
+ }
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LOOKUP_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+void CIcqProto::ReleaseLookupCookie(DWORD dwCookie, cookie_search *pCookie)
+{
+ FreeCookie(dwCookie);
+ SAFE_FREE(&pCookie->szObject);
+
+ if (pCookie->dwMainId && !pCookie->dwStatus)
+ { // we need to wait for main search
+ pCookie->dwStatus = 1;
+ }
+ else
+ { // finish everything
+ if (pCookie->dwMainId)
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)pCookie->dwMainId, 0);
+ else // we are single
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
+
+ SAFE_FREE((void**)&pCookie);
+ }
+}
+
+void CIcqProto::handleLookupEmailReply(BYTE* buf, WORD wLen, DWORD dwCookie)
+{
+ ICQSEARCHRESULT sr = {0};
+ oscar_tlv_chain *pChain;
+ cookie_search *pCookie;
+
+ if (!FindCookie(dwCookie, NULL, (void**)&pCookie))
+ {
+ NetLog_Server("Error: Received unexpected lookup reply");
+ return;
+ }
+
+ NetLog_Server("SNAC(0x0A,0x3): Lookup reply");
+
+ sr.hdr.cbSize = sizeof(sr);
+ sr.hdr.flags = PSR_TCHAR;
+ sr.hdr.email = ansi_to_tchar(pCookie->szObject);
+
+ // Syntax check, read chain
+ if (wLen >= 4 && (pChain = readIntoTLVChain(&buf, wLen, 0)))
+ {
+ for (WORD i = 1; TRUE; i++)
+ { // collect the results
+ char *szUid = pChain->getString(0x01, i);
+ if (!szUid) break;
+ sr.hdr.id = ansi_to_tchar(szUid);
+ sr.hdr.nick = sr.hdr.id;
+ // broadcast the result
+ if (pCookie->dwMainId)
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)pCookie->dwMainId, (LPARAM)&sr);
+ else
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)dwCookie, (LPARAM)&sr);
+ SAFE_FREE(&sr.hdr.id);
+ SAFE_FREE(&szUid);
+ }
+ disposeChain(&pChain);
+ }
+ SAFE_FREE(&sr.hdr.email);
+
+ ReleaseLookupCookie(dwCookie, pCookie);
+}
diff --git a/protocols/IcqOscarJ/src/fam_0bstatus.cpp b/protocols/IcqOscarJ/src/fam_0bstatus.cpp
new file mode 100644
index 0000000000..7a979007e2
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_0bstatus.cpp
@@ -0,0 +1,62 @@
+// ---------------------------------------------------------------------------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,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater
+// Copyright © 2004,2005,2006 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::handleStatusFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_STATS_MINREPORTINTERVAL:
+ {
+ WORD wInterval;
+ unpackWord(&pBuffer, &wInterval);
+ NetLog_Server("Server sent SNAC(x0B,x02) - SRV_SET_MINREPORTINTERVAL (Value: %u hours)", wInterval);
+ }
+ break;
+
+ case ICQ_ERROR:
+ {
+ WORD wError;
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ LogFamilyError(ICQ_STATS_FAMILY, wError);
+ break;
+ }
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_STATS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
diff --git a/protocols/IcqOscarJ/src/fam_13servclist.cpp b/protocols/IcqOscarJ/src/fam_13servclist.cpp
new file mode 100644
index 0000000000..5df30ee5ec
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_13servclist.cpp
@@ -0,0 +1,2068 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength);
+
+
+void CIcqProto::handleServCListFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, serverthread_info *info)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_LISTS_ACK: // UPDATE_ACK
+ if (wBufferLength >= 2)
+ {
+ WORD wError;
+ cookie_servlist_action* sc;
+
+ unpackWord(&pBuffer, &wError);
+
+ if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc))
+ { // look for action cookie
+#ifdef _DEBUG
+ NetLog_Server("Received expected server list ack, action: %d, result: %d", sc->dwAction, wError);
+#endif
+ FreeCookie(pSnacHeader->dwRef); // release cookie
+
+ if (sc->dwAction == SSA_ACTION_GROUP)
+ { // group cookie, handle sub-items
+ int i;
+
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Grouped action contains %d actions.", sc->dwGroupCount);
+#endif
+ pBuffer -= 2; // revoke unpack
+ if (wBufferLength != 2*sc->dwGroupCount)
+ NetLog_Server("Error: Server list ack does not contain expected amount of result codes (%u != %u)", wBufferLength/2, sc->dwGroupCount);
+
+ for (i = 0; i < sc->dwGroupCount; i++)
+ {
+ if (wBufferLength >= 2)
+ { // get proper result code
+ unpackWord(&pBuffer, &wError);
+ wBufferLength -= 2;
+ }
+ else // missing result code, give some special
+ wError = -1;
+
+#ifdef _DEBUG
+ NetLog_Server("Action: %d, ack result: %d", sc->pGroupItems[i]->dwAction, wError);
+#endif
+ // call normal ack handler
+ handleServerCListAck(sc->pGroupItems[i], wError);
+ }
+ // Release cookie
+ SAFE_FREE((void**)&sc->pGroupItems);
+ SAFE_FREE((void**)&sc);
+ }
+ else // single ack
+ handleServerCListAck(sc, wError);
+ }
+ else
+ {
+ NetLog_Server("Received unexpected server list ack %u", wError);
+ }
+ }
+ break;
+
+ case ICQ_LISTS_SRV_REPLYLISTS:
+ { /* received server-list rights */
+ handleServerCListRightsReply(pBuffer, wBufferLength);
+
+#ifdef _DEBUG
+ NetLog_Server("Server sent SNAC(x13,x03) - SRV_REPLYLISTS");
+#endif
+ }
+ break;
+
+ case ICQ_LISTS_LIST: // SRV_REPLYROSTER
+ {
+ cookie_servlist_action* sc;
+ BOOL blWork;
+
+ blWork = bIsSyncingCL;
+ bIsSyncingCL = TRUE; // this is not used if cookie takes place
+
+ if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc))
+ { // we do it by reliable cookie
+ if (!sc->lParam)
+ { // is this first packet ?
+ ResetSettingsOnListReload();
+ sc->lParam = 1;
+ }
+ handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info);
+ if (!(pSnacHeader->wFlags & 0x0001))
+ { // was that last packet ?
+ ReleaseCookie(pSnacHeader->dwRef); // yes, release cookie
+ }
+ }
+ else
+ { // use old fake
+ if (!blWork)
+ { // this can fail on some crazy situations
+ ResetSettingsOnListReload();
+ }
+ handleServerCListReply(pBuffer, wBufferLength, pSnacHeader->wFlags, info);
+ }
+ break;
+ }
+
+ case ICQ_LISTS_UPTODATE: // SRV_REPLYROSTEROK
+ {
+ cookie_servlist_action* sc;
+
+ bIsSyncingCL = FALSE;
+
+ if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc))
+ { // we requested servlist check
+#ifdef _DEBUG
+ NetLog_Server("Server stated roster is ok.");
+#endif
+ ReleaseCookie(pSnacHeader->dwRef);
+ LoadServerIDs();
+ }
+ else
+ NetLog_Server("Server sent unexpected SNAC(x13,x0F) - SRV_REPLYROSTEROK");
+
+ // This will activate the server side list
+ sendRosterAck(); // this must be here, cause of failures during cookie alloc
+ handleServUINSettings(wListenPort, info);
+ break;
+ }
+
+ case ICQ_LISTS_CLI_MODIFYSTART:
+ NetLog_Server("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYSTART, "Server is modifying contact list");
+ break;
+
+ case ICQ_LISTS_CLI_MODIFYEND:
+ NetLog_Server("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_CLI_MODIFYEND, "End of server modification");
+ break;
+
+ case ICQ_LISTS_ADDTOLIST:
+ case ICQ_LISTS_UPDATEGROUP:
+ case ICQ_LISTS_REMOVEFROMLIST:
+ {
+ int nItems = 0;
+
+ while (wBufferLength >= 10)
+ {
+ WORD wGroupId, wItemId, wItemType, wTlvLen;
+ uid_str szRecordName;
+
+ if (unpackServerListItem(&pBuffer, &wBufferLength, szRecordName, &wGroupId, &wItemId, &wItemType, &wTlvLen))
+ {
+ BYTE *buf = pBuffer;
+ oscar_tlv_chain *pChain = NULL;
+
+ nItems++;
+
+ // parse possible item's data
+ if (wBufferLength >= wTlvLen && wTlvLen > 0)
+ {
+ pChain = readIntoTLVChain(&buf, wTlvLen, 0);
+ pBuffer += wTlvLen;
+ wBufferLength -= wTlvLen;
+ }
+ else if (wTlvLen > 0)
+ wBufferLength = 0;
+
+ // process item change
+ if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST)
+ handleServerCListItemAdd(szRecordName, wGroupId, wItemId, wItemType, pChain);
+ else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP)
+ handleServerCListItemUpdate(szRecordName, wGroupId, wItemId, wItemType, pChain);
+ else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST)
+ handleServerCListItemDelete(szRecordName, wGroupId, wItemId, wItemType, pChain);
+
+ // release memory
+ disposeChain(&pChain);
+ }
+ }
+ { // log packet basics
+ char *szChange;
+ char szLogText[MAX_PATH];
+
+ if (pSnacHeader->wSubtype == ICQ_LISTS_ADDTOLIST)
+ szChange = "Server added %u item(s) to list";
+ else if (pSnacHeader->wSubtype == ICQ_LISTS_UPDATEGROUP)
+ szChange = "Server updated %u item(s) on list";
+ else if (pSnacHeader->wSubtype == ICQ_LISTS_REMOVEFROMLIST)
+ szChange = "Server removed %u item(s) from list";
+
+ null_snprintf(szLogText, MAX_PATH, szChange, nItems);
+ NetLog_Server("Server sent SNAC(x13,x%02x) - %s", pSnacHeader->wSubtype, szLogText);
+ }
+ }
+ break;
+
+ case ICQ_LISTS_AUTHREQUEST:
+ handleRecvAuthRequest(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_LISTS_SRV_AUTHRESPONSE:
+ handleRecvAuthResponse(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_LISTS_AUTHGRANTED:
+ NetLog_Server("Server sent SNAC(x13,x%02x) - %s", ICQ_LISTS_AUTHGRANTED, "User granted us future authorization");
+ break;
+
+ case ICQ_LISTS_YOUWEREADDED:
+ handleRecvAdded(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_LISTS_ERROR:
+ if (wBufferLength >= 2)
+ {
+ WORD wError;
+ cookie_servlist_action* sc;
+
+ unpackWord(&pBuffer, &wError);
+
+ if (FindCookie(pSnacHeader->dwRef, NULL, (void**)&sc))
+ { // look for action cookie
+#ifdef _DEBUG
+ NetLog_Server("Received server list error, action: %d, result: %d", sc->dwAction, wError);
+#endif
+ FreeCookie(pSnacHeader->dwRef); // release cookie
+
+ if (sc->dwAction==SSA_CHECK_ROSTER)
+ { // the serv-list is unavailable turn it off
+ icq_LogMessage(LOG_ERROR, LPGEN("Server contact list is unavailable, Miranda will use local contact list."));
+ m_bSsiEnabled = 0;
+ handleServUINSettings(wListenPort, info);
+ }
+ /// FIXME: properly release pending operations & cookie memory
+ SAFE_FREE((void**)&sc);
+ }
+ else
+ {
+ LogFamilyError(ICQ_LISTS_FAMILY, wError);
+ }
+ }
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_LISTS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+
+static int unpackServerListItem(BYTE **pbuf, WORD *pwLen, char *pszRecordName, WORD *pwGroupId, WORD *pwItemId, WORD *pwItemType, WORD *pwTlvLength)
+{
+ WORD wRecordNameLen;
+
+ // The name of the entry. If this is a group header, then this
+ // is the name of the group. If it is a plain contact list entry,
+ // then it's the UIN of the contact.
+ unpackWord(pbuf, &wRecordNameLen);
+ if (*pwLen < 10 + wRecordNameLen || wRecordNameLen >= MAX_PATH)
+ return 0; // Failure
+
+ unpackString(pbuf, pszRecordName, wRecordNameLen);
+ if (pszRecordName)
+ pszRecordName[wRecordNameLen] = '\0';
+
+ // The group identifier this entry belongs to. If 0, this is meta information or
+ // a contact without a group
+ unpackWord(pbuf, pwGroupId);
+
+ // The ID of this entry. Group headers have ID 0. Otherwise, this
+ // is a random number generated when the user is added to the
+ // contact list, or when the user is ignored. See CLI_ADDBUDDY.
+ unpackWord(pbuf, pwItemId);
+
+ // This field indicates what type of entry this is
+ unpackWord(pbuf, pwItemType);
+
+ // The length in bytes of the following TLV chain
+ unpackWord(pbuf, pwTlvLength);
+
+ *pwLen -= wRecordNameLen + 10;
+
+ return 1; // Success
+}
+
+
+void CIcqProto::handleServerCListRightsReply(BYTE *buf, WORD wLen)
+{ /* received list rights, store the item limits for future use */
+ oscar_tlv_chain* chain;
+
+ memset(m_wServerListLimits, -1, sizeof(m_wServerListLimits));
+ m_wServerListGroupMaxContacts = 0;
+ m_wServerListRecordNameMaxLength = 0xFFFF;
+
+ if (chain = readIntoTLVChain(&buf, wLen, 0))
+ {
+ oscar_tlv* pTLV;
+
+ // determine max number of contacts in a group
+ m_wServerListGroupMaxContacts = chain->getWord(0x0C, 1);
+ // determine length limit for server-list item's name
+ m_wServerListRecordNameMaxLength = chain->getWord(0x06, 1);
+
+ if (pTLV = chain->getTLV(0x04, 1))
+ { // limits for item types
+ int i;
+ WORD *pLimits = (WORD*)pTLV->pData;
+
+ for (i = 0; i < pTLV->wLen / 2; i++)
+ {
+ m_wServerListLimits[i] = (pLimits[i] & 0xFF) << 8 | (pLimits[i] >> 8);
+
+ if (i + 1 >= SIZEOF(m_wServerListLimits)) break;
+ }
+
+ NetLog_Server("SSI: Max %d contacts (%d per group), %d groups, %d permit, %d deny, %d ignore items.", m_wServerListLimits[SSI_ITEM_BUDDY], m_wServerListGroupMaxContacts, m_wServerListLimits[SSI_ITEM_GROUP], m_wServerListLimits[SSI_ITEM_PERMIT], m_wServerListLimits[SSI_ITEM_DENY], m_wServerListLimits[SSI_ITEM_IGNORE]);
+ }
+
+ disposeChain(&chain);
+ }
+}
+
+
+DWORD CIcqProto::updateServerGroupData(WORD wGroupId, void *groupData, int groupSize, DWORD dwOperationFlags)
+{
+ DWORD dwCookie;
+ cookie_servlist_action* ack;
+
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (!ack)
+ {
+ NetLog_Server("Updating of group on server list failed (malloc error)");
+ return 0;
+ }
+ ack->dwAction = SSA_GROUP_UPDATE;
+ ack->szGroupName = getServListGroupName(wGroupId);
+ ack->wGroupId = wGroupId;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack);
+
+ return icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, ack->wGroupId, ack->szGroupName, groupData, groupSize, dwOperationFlags);
+}
+
+
+void CIcqProto::handleServerCListAck(cookie_servlist_action* sc, WORD wError)
+{
+ switch (sc->dwAction)
+ {
+ case SSA_VISIBILITY:
+ {
+ if (wError)
+ NetLog_Server("Server visibility update failed, error %d", wError);
+ break;
+ }
+ case SSA_CONTACT_UPDATE:
+ {
+ servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, wError ? PENDING_RESULT_FAILED : PENDING_RESULT_SUCCESS);
+ if (wError)
+ {
+ NetLog_Server("Updating of server contact failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Updating of server contact failed."));
+ }
+ break;
+ }
+ case SSA_PRIVACY_ADD:
+ {
+ if (wError)
+ {
+ NetLog_Server("Adding of privacy item to server list failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Adding of privacy item to server list failed."));
+ }
+ break;
+ }
+ case SSA_PRIVACY_REMOVE:
+ {
+ if (wError)
+ {
+ NetLog_Server("Removing of privacy item from server list failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Removing of privacy item from server list failed."));
+ }
+ FreeServerID(sc->wContactId, SSIT_ITEM); // release server id
+ break;
+ }
+ case SSA_CONTACT_ADD:
+ {
+ if (wError)
+ {
+ if (wError == 0xE) // server refused to add contact w/o auth, add with
+ {
+ DWORD dwCookie;
+
+ NetLog_Server("Contact could not be added without authorization, add with await auth flag.");
+
+ setSettingByte(sc->hContact, "Auth", 1); // we need auth
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc);
+ icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wGroupId, sc->wContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 500, NULL);
+
+ sc = NULL; // we do not want it to be freed now
+ break;
+ }
+ FreeServerID(sc->wContactId, SSIT_ITEM);
+
+ NetLog_Server("Adding of contact to server list failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Adding of contact to server list failed."));
+
+ servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_FAILED);
+
+ servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here
+ }
+ else
+ {
+ void* groupData;
+ int groupSize;
+
+ setSettingWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wContactId);
+ setSettingWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wGroupId);
+
+ servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_SUCCESS);
+
+ if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize))
+ { // the group is not empty, just update it
+ updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION);
+ SAFE_FREE((void**)&groupData);
+ }
+ else
+ { // this should never happen
+ NetLog_Server("Group update failed.");
+ servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here
+ }
+ }
+ break;
+ }
+ case SSA_GROUP_ADD:
+ {
+ if (wError)
+ {
+ FreeServerID(sc->wGroupId, SSIT_GROUP);
+ NetLog_Server("Adding of group to server list failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Adding of group to server list failed."));
+
+ servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED);
+ }
+ else // group added, we need to update master group
+ {
+ void* groupData;
+ int groupSize;
+ cookie_servlist_action* ack;
+ DWORD dwCookie;
+
+ setServListGroupName(sc->wGroupId, sc->szGroupName); // add group to namelist
+ { // add group to known
+ char *szCListGroup = getServListGroupCListPath(sc->wGroupId);
+
+ // create link to the original CList group
+ setServListGroupLinkID(sc->szGroup, sc->wGroupId);
+
+ servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS);
+ SAFE_FREE((void**)&szCListGroup);
+ }
+
+ groupData = collectGroups(&groupSize);
+ groupData = SAFE_REALLOC(groupData, groupSize+2);
+ *(((WORD*)groupData)+(groupSize>>1)) = sc->wGroupId; // add this new group id
+ groupSize += 2;
+
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (ack)
+ {
+ ack->dwAction = SSA_GROUP_UPDATE;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack);
+
+ icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, SSOF_END_OPERATION);
+ }
+ else // end server modifications here
+ servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100);
+
+ SAFE_FREE((void**)&groupData);
+ }
+ if (sc->szGroup != sc->szGroupName)
+ SAFE_FREE((void**)&sc->szGroup);
+
+ SAFE_FREE((void**)&sc->szGroupName);
+ break;
+ }
+ case SSA_CONTACT_REMOVE:
+ {
+ if (!wError)
+ {
+ void* groupData;
+ int groupSize;
+
+ setSettingWord(sc->hContact, DBSETTING_SERVLIST_ID, 0); // clear the values
+ setSettingWord(sc->hContact, DBSETTING_SERVLIST_GROUP, 0);
+
+ FreeServerID(sc->wContactId, SSIT_ITEM);
+
+ servlistPendingRemoveContact(sc->hContact, 0, sc->wGroupId, PENDING_RESULT_SUCCESS);
+
+ if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize))
+ { // the group is still not empty, just update it
+ updateServerGroupData(sc->wGroupId, groupData, groupSize, SSOF_END_OPERATION);
+ }
+ else // the group is empty, delete it
+ {
+ char *szGroup = getServListGroupCListPath(sc->wGroupId);
+
+ servlistRemoveGroup(szGroup, sc->wGroupId);
+ SAFE_FREE((void**)&szGroup);
+ }
+ SAFE_FREE((void**)&groupData); // free the memory
+ }
+ else
+ {
+ NetLog_Server("Removing of contact from server list failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Removing of contact from server list failed."));
+
+ servlistPendingRemoveContact(sc->hContact, sc->wContactId, sc->wGroupId, PENDING_RESULT_FAILED);
+
+ servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here
+ }
+ break;
+ }
+ case SSA_GROUP_UPDATE:
+ {
+ if (wError)
+ {
+ NetLog_Server("Updating of group on server list failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Updating of group on server list failed."));
+ }
+ SAFE_FREE((void**)&sc->szGroupName);
+ break;
+ }
+ case SSA_GROUP_REMOVE:
+ {
+ SAFE_FREE((void**)&sc->szGroupName);
+ if (wError)
+ {
+ NetLog_Server("Removing of group from server list failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Removing of group from server list failed."));
+
+ servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_FAILED);
+
+ servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here
+ SAFE_FREE((void**)&sc->szGroup);
+ }
+ else // group removed, we need to update master group
+ {
+ void* groupData;
+ int groupSize;
+ DWORD dwCookie;
+
+ setServListGroupName(sc->wGroupId, NULL); // clear group from namelist
+ FreeServerID(sc->wGroupId, SSIT_GROUP);
+ removeGroupPathLinks(sc->wGroupId);
+
+ servlistPendingRemoveGroup(sc->szGroup, 0, PENDING_RESULT_SUCCESS);
+ SAFE_FREE((void**)&sc->szGroup);
+
+ groupData = collectGroups(&groupSize);
+ sc->wGroupId = 0;
+ sc->dwAction = SSA_GROUP_UPDATE;
+ sc->szGroupName = NULL;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, sc);
+
+ icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, sc->szGroupName, groupData, groupSize, SSOF_END_OPERATION);
+ // end server modifications here
+
+ sc = NULL; // we do not want to be freed here
+
+ SAFE_FREE((void**)&groupData);
+ }
+ break;
+ }
+ case SSA_CONTACT_SET_GROUP:
+ { // we moved contact to another group
+ if (sc->lParam == -1)
+ { // the first was an error
+ break;
+ }
+ if (wError)
+ {
+ if (wError == 0x0E && sc->lParam == 1)
+ { // second ack - adding failed with error 0x0E, try to add with AVAIT_AUTH flag
+ DWORD dwCookie;
+
+ if (!getSettingByte(sc->hContact, "Auth", 0))
+ { // we tried without AWAIT_AUTH, try again with it
+ NetLog_Server("Contact could not be added without authorization, add with await auth flag.");
+
+ setSettingByte(sc->hContact, "Auth", 1); // we need auth
+ }
+ else
+ { // we tried with AWAIT_AUTH, try again without
+ NetLog_Server("Contact count not be added awaiting authorization, try authorized.");
+
+ setSettingByte(sc->hContact, "Auth", 0);
+ }
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, sc->hContact, sc);
+ icq_sendServerContact(sc->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, sc->wNewGroupId, sc->wNewContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 400, NULL);
+
+ sc->lParam = 2; // do not cycle
+ sc = NULL; // we do not want to be freed here
+ break;
+ }
+ FreeServerID(sc->wNewContactId, SSIT_ITEM);
+ NetLog_Server("Moving of user to another group on server list failed, error %d", wError);
+ icq_LogMessage(LOG_ERROR, LPGEN("Moving of user to another group on server list failed."));
+
+ servlistPendingRemoveContact(sc->hContact, 0, (WORD)(sc->lParam ? sc->wGroupId : sc->wNewGroupId), PENDING_RESULT_FAILED);
+
+ servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100); // end server modifications here
+
+ if (!sc->lParam) // is this first ack ?
+ {
+ sc->lParam = -1;
+ sc = NULL; // this can't be freed here
+ }
+ break;
+ }
+ if (sc->lParam) // is this the second ack ?
+ {
+ void* groupData;
+ int groupSize;
+ int bEnd = 1; // shall we end the sever modifications
+
+ setSettingWord(sc->hContact, DBSETTING_SERVLIST_ID, sc->wNewContactId);
+ setSettingWord(sc->hContact, DBSETTING_SERVLIST_GROUP, sc->wNewGroupId);
+
+ servlistPendingRemoveContact(sc->hContact, sc->wNewContactId, sc->wNewGroupId, PENDING_RESULT_SUCCESS);
+
+ if (groupData = collectBuddyGroup(sc->wGroupId, &groupSize)) // update the group we moved from
+ { // the group is still not empty, just update it
+ updateServerGroupData(sc->wGroupId, groupData, groupSize, 0);
+ SAFE_FREE((void**)&groupData); // free the memory
+ }
+ else
+ { // the group is empty, delete it
+ char* szGroup = getServListGroupCListPath(sc->wGroupId);
+
+ servlistRemoveGroup(szGroup, sc->wGroupId);
+ SAFE_FREE((void**)&szGroup);
+ bEnd = 0; // here the modifications go on
+ }
+
+ groupData = collectBuddyGroup(sc->wNewGroupId, &groupSize); // update the group we moved to
+ updateServerGroupData(sc->wNewGroupId, groupData, groupSize, bEnd ? SSOF_END_OPERATION : 0);
+ // end server modifications here
+ SAFE_FREE((void**)&groupData);
+
+ }
+ else // contact was deleted from server-list
+ {
+ deleteSetting(sc->hContact, DBSETTING_SERVLIST_ID);
+ deleteSetting(sc->hContact, DBSETTING_SERVLIST_GROUP);
+ FreeServerID(sc->wContactId, SSIT_ITEM); // release old contact id
+ sc->lParam = 1;
+ sc = NULL; // wait for second ack
+ }
+ break;
+ }
+ case SSA_CONTACT_FIX_AUTH:
+ {
+ if (wError)
+ { // FIXME: something failed, we should handle it properly
+ }
+ break;
+ }
+ case SSA_GROUP_RENAME:
+ {
+ if (wError)
+ {
+ NetLog_Server("Renaming of server group failed, error %d", wError);
+ icq_LogMessage(LOG_WARNING, LPGEN("Renaming of server group failed."));
+
+ servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_FAILED);
+ }
+ else
+ {
+ setServListGroupName(sc->wGroupId, sc->szGroupName);
+ removeGroupPathLinks(sc->wGroupId);
+ { // add group to known
+ char *szCListGroup = getServListGroupCListPath(sc->wGroupId);
+
+ /// FIXME: need to create link to the new group name before unique item name correction as well
+ setServListGroupLinkID(szCListGroup, sc->wGroupId);
+ SAFE_FREE((void**)&szCListGroup);
+ }
+ servlistPendingRemoveGroup(sc->szGroup, sc->wGroupId, PENDING_RESULT_SUCCESS);
+ }
+ SAFE_FREE((void**)&sc->szGroupName);
+ SAFE_FREE((void**)&sc->szGroup);
+ break;
+ }
+ case SSA_SETAVATAR:
+ {
+ if (wError)
+ {
+ NetLog_Server("Uploading of avatar hash failed.");
+ if (sc->wGroupId) // is avatar added or updated?
+ {
+ FreeServerID(sc->wContactId, SSIT_ITEM);
+ deleteSetting(NULL, DBSETTING_SERVLIST_AVATAR); // to fix old versions
+ }
+ }
+ else
+ {
+ setSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, sc->wContactId);
+ }
+ break;
+ }
+ case SSA_REMOVEAVATAR:
+ {
+ if (wError)
+ NetLog_Server("Removing of avatar hash failed.");
+ else
+ {
+ FreeServerID(sc->wContactId, SSIT_ITEM);
+ deleteSetting(NULL, DBSETTING_SERVLIST_AVATAR);
+ }
+ break;
+ }
+ case SSA_SERVLIST_ACK:
+ {
+ BroadcastAck(sc->hContact, ICQACKTYPE_SERVERCLIST, wError?ACKRESULT_FAILED:ACKRESULT_SUCCESS, (HANDLE)sc->lParam, wError);
+ break;
+ }
+ case SSA_IMPORT:
+ {
+ if (wError)
+ NetLog_Server("Re-starting import sequence failed, error %d", wError);
+ else
+ {
+ setSettingWord(NULL, "SrvImportID", 0);
+ deleteSetting(NULL, "ImportTS");
+ }
+ break;
+ }
+ default:
+ NetLog_Server("Server ack cookie type (%d) not recognized.", sc->dwAction);
+ }
+ SAFE_FREE((void**)&sc); // free the memory
+
+ return;
+}
+
+
+HANDLE CIcqProto::HContactFromRecordName(const char* szRecordName, int *bAdded)
+{
+ HANDLE hContact = INVALID_HANDLE_VALUE;
+
+ if (!IsStringUIN(szRecordName))
+ { // probably AIM contact
+ hContact = HContactFromUID(0, szRecordName, bAdded);
+ }
+ else
+ { // this should be ICQ number
+ DWORD dwUin = atoi(szRecordName);
+
+ hContact = HContactFromUIN(dwUin, bAdded);
+ }
+ return hContact;
+}
+
+
+int CIcqProto::getServerDataFromItemTLV(oscar_tlv_chain* pChain, unsigned char *buf) /// FIXME: need to keep original order
+{ // get server-list item's TLV data
+ oscar_tlv_chain* list = pChain;
+ int datalen = 0;
+ icq_packet pBuf;
+
+ // Initialize our handy data buffer
+ pBuf.wPlace = 0;
+ pBuf.pData = buf;
+
+ while (list)
+ { // collect non-standard TLVs and save them to DB
+ if (list->tlv.wType != SSI_TLV_AWAITING_AUTH &&
+ list->tlv.wType != SSI_TLV_NAME &&
+ list->tlv.wType != SSI_TLV_COMMENT &&
+ list->tlv.wType != SSI_TLV_METAINFO_TOKEN &&
+ list->tlv.wType != SSI_TLV_METAINFO_TIME)
+ { // only TLVs which we do not handle on our own
+ packTLV(&pBuf, list->tlv.wType, list->tlv.wLen, list->tlv.pData);
+
+ datalen += list->tlv.wLen + 4;
+ }
+ list = list->next;
+ }
+ return datalen;
+}
+
+
+void CIcqProto::handleServerCListReply(BYTE *buf, WORD wLen, WORD wFlags, serverthread_info *info)
+{
+ BYTE bySSIVersion;
+ WORD wRecordCount;
+ WORD wRecord;
+ WORD wGroupId;
+ WORD wItemId;
+ WORD wTlvType;
+ WORD wTlvLength;
+ BOOL bIsLastPacket;
+ uid_str szRecordName;
+ oscar_tlv_chain* pChain = NULL;
+ oscar_tlv* pTLV = NULL;
+ char *szActiveSrvGroup = NULL;
+ WORD wActiveSrvGroupId = -1;
+
+
+ // If flag bit 1 is set, this is not the last
+ // packet. If it is 0, this is the last packet
+ // and there will be a timestamp at the end.
+ if (wFlags & 0x0001)
+ bIsLastPacket = FALSE;
+ else
+ bIsLastPacket = TRUE;
+
+ if (wLen < 3)
+ return;
+
+ // Version number of SSI protocol?
+ unpackByte(&buf, &bySSIVersion);
+ wLen -= 1;
+
+ // Total count of following entries. This is the size of the server
+ // side contact list and should be saved and sent with CLI_CHECKROSTER.
+ // NOTE: When the entries are split up in several packets, each packet
+ // has it's own count and they must be added to get the total size of
+ // server list.
+ unpackWord(&buf, &wRecordCount);
+ wLen -= 2;
+ NetLog_Server("SSI: number of entries is %u, version is %u", wRecordCount, bySSIVersion);
+
+
+ // Loop over all items in the packet
+ for (wRecord = 0; wRecord < wRecordCount; wRecord++)
+ {
+ NetLog_Server("SSI: parsing record %u", wRecord + 1);
+
+ if (wLen < 10)
+ { // minimum: name length (zero), group ID, item ID, empty TLV
+ NetLog_Server("Warning: SSI parsing error (%d)", 0);
+ break;
+ }
+
+ if (!unpackServerListItem(&buf, &wLen, szRecordName, &wGroupId, &wItemId, &wTlvType, &wTlvLength))
+ { // unpack basic structure
+ NetLog_Server("Warning: SSI parsing error (%d)", 1);
+ break;
+ }
+
+ NetLog_Server("Name: '%s', GroupID: %u, EntryID: %u, EntryType: %u, TLVlength: %u",
+ szRecordName, wGroupId, wItemId, wTlvType, wTlvLength);
+
+ if (wLen < wTlvLength)
+ {
+ NetLog_Server("Warning: SSI parsing error (%d)", 2);
+ break;
+ }
+
+ // Initialize the tlv chain
+ if (wTlvLength > 0)
+ {
+ pChain = readIntoTLVChain(&buf, wTlvLength, 0);
+ wLen -= wTlvLength;
+ }
+ else
+ {
+ pChain = NULL;
+ }
+
+
+ switch (wTlvType)
+ {
+
+ case SSI_ITEM_BUDDY:
+ {
+ /* this is a contact */
+ HANDLE hContact;
+ int bAdded;
+
+ hContact = HContactFromRecordName(szRecordName, &bAdded);
+
+ if (hContact != INVALID_HANDLE_VALUE)
+ {
+ int bRegroup = 0;
+ int bNicked = 0;
+
+ if (bAdded)
+ { // Not already on list: added
+ NetLog_Server("SSI added new %s contact '%s'", "ICQ", szRecordName);
+
+ AddJustAddedContact(hContact);
+ }
+ else
+ { // we should add new contacts and this contact was just added, show it
+ if (IsContactJustAdded(hContact))
+ {
+ setContactHidden(hContact, 0);
+ bAdded = 1; // we want details for new contacts
+ }
+ else
+ NetLog_Server("SSI ignoring existing contact '%s'", szRecordName);
+ // Contact on server is always on list
+ DBWriteContactSettingByte(hContact, "CList", "NotOnList", 0);
+ }
+
+ // Save group and item ID
+ setSettingWord(hContact, DBSETTING_SERVLIST_ID, wItemId);
+ setSettingWord(hContact, DBSETTING_SERVLIST_GROUP, wGroupId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+
+ if (!bAdded && getSettingByte(NULL, "LoadServerDetails", DEFAULT_SS_LOAD))
+ { // check if the contact has been moved on the server
+ if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup)
+ {
+ SAFE_FREE(&szActiveSrvGroup);
+ szActiveSrvGroup = getServListGroupCListPath(wGroupId);
+ wActiveSrvGroupId = wGroupId;
+ }
+ char *szLocalGroup = getContactCListGroup(hContact);
+
+ if (!strlennull(szLocalGroup))
+ { // no CListGroup
+ SAFE_FREE(&szLocalGroup);
+
+ szLocalGroup = null_strdup(DEFAULT_SS_GROUP);
+ }
+
+ if (strcmpnull(szActiveSrvGroup, szLocalGroup) &&
+ (strlennull(szActiveSrvGroup) >= strlennull(szLocalGroup) || _strnicmp(szActiveSrvGroup, szLocalGroup, strlennull(szLocalGroup))))
+ { // contact moved to new group or sub-group or not to master group
+ bRegroup = 1;
+ }
+ if (bRegroup && !stricmpnull(DEFAULT_SS_GROUP, szActiveSrvGroup)) /// TODO: invent something more clever for "root" group
+ { // is it the default "General" group ?
+ bRegroup = 0; // if yes, do not move to it - cause it would hide the contact
+ }
+ SAFE_FREE(&szLocalGroup);
+ }
+
+ if (bRegroup || bAdded)
+ { // if we should load server details or contact was just added, update its group
+ if (wActiveSrvGroupId != wGroupId || !szActiveSrvGroup)
+ {
+ SAFE_FREE(&szActiveSrvGroup);
+ szActiveSrvGroup = getServListGroupCListPath(wGroupId);
+ wActiveSrvGroupId = wGroupId;
+ }
+
+ if (szActiveSrvGroup)
+ { // try to get Miranda Group path from groupid, if succeeded save to db
+ moveContactToCListGroup(hContact, szActiveSrvGroup);
+ }
+ }
+
+ if (pChain)
+ { // Look for nickname TLV and copy it to the db if necessary
+ if (pTLV = pChain->getTLV(SSI_TLV_NAME, 1))
+ {
+ if (pTLV->pData && (pTLV->wLen > 0))
+ {
+ char *pszNick;
+ WORD wNickLength;
+
+ wNickLength = pTLV->wLen;
+
+ pszNick = (char*)SAFE_MALLOC(wNickLength + 1);
+ // Copy buffer to utf-8 buffer
+ memcpy(pszNick, pTLV->pData, wNickLength);
+ pszNick[wNickLength] = 0; // Terminate string
+
+ NetLog_Server("Nickname is '%s'", pszNick);
+
+ bNicked = 1;
+
+ // Write nickname to database
+ if (getSettingByte(NULL, "LoadServerDetails", DEFAULT_SS_LOAD) || bAdded)
+ { // if just added contact, save details always - does no harm
+ char *szOldNick;
+
+ if (szOldNick = getSettingStringUtf(hContact, "CList", "MyHandle", NULL))
+ {
+ if ((strcmpnull(szOldNick, pszNick)) && (strlennull(pszNick) > 0))
+ { // check if the truncated nick changed, i.e. do not overwrite locally stored longer nick
+ if (strlennull(szOldNick) <= strlennull(pszNick) || strncmp(szOldNick, pszNick, null_strcut(szOldNick, MAX_SSI_TLV_NAME_SIZE)))
+ {
+ // Yes, we really do need to delete it first. Otherwise the CLUI nick
+ // cache isn't updated (I'll look into it)
+ DBDeleteContactSetting(hContact,"CList","MyHandle");
+ setSettingStringUtf(hContact, "CList", "MyHandle", pszNick);
+ }
+ }
+ SAFE_FREE(&szOldNick);
+ }
+ else if (strlennull(pszNick) > 0)
+ {
+ DBDeleteContactSetting(hContact,"CList","MyHandle");
+ setSettingStringUtf(hContact, "CList", "MyHandle", pszNick);
+ }
+ }
+ SAFE_FREE(&pszNick);
+ }
+ else
+ {
+ NetLog_Server("Invalid nickname");
+ }
+ }
+ if (bAdded && !bNicked)
+ icq_QueueUser(hContact); // queue user without nick for fast auto info update
+
+ // Look for comment TLV and copy it to the db if necessary
+ if (pTLV = pChain->getTLV(SSI_TLV_COMMENT, 1))
+ {
+ if (pTLV->pData && (pTLV->wLen > 0))
+ {
+ char *pszComment;
+ WORD wCommentLength;
+
+
+ wCommentLength = pTLV->wLen;
+
+ pszComment = (char*)SAFE_MALLOC(wCommentLength + 1);
+ // Copy buffer to utf-8 buffer
+ memcpy(pszComment, pTLV->pData, wCommentLength);
+ pszComment[wCommentLength] = 0; // Terminate string
+
+ NetLog_Server("Comment is '%s'", pszComment);
+
+ // Write comment to database
+ if (getSettingByte(NULL, "LoadServerDetails", DEFAULT_SS_LOAD) || bAdded)
+ { // if just added contact, save details always - does no harm
+ char *szOldComment;
+
+ if (szOldComment = getSettingStringUtf(hContact, "UserInfo", "MyNotes", NULL))
+ {
+ if ((strcmpnull(szOldComment, pszComment)) && (strlennull(pszComment) > 0))
+ { // check if the truncated comment changed, i.e. do not overwrite locally stored longer comment
+ if (strlennull(szOldComment) <= strlennull(pszComment) || strncmp((char*)szOldComment, (char*)pszComment, null_strcut(szOldComment, MAX_SSI_TLV_COMMENT_SIZE)))
+ {
+ setSettingStringUtf(hContact, "UserInfo", "MyNotes", pszComment);
+ }
+ }
+ SAFE_FREE((void**)&szOldComment);
+ }
+ else if (strlennull(pszComment) > 0)
+ {
+ setSettingStringUtf(hContact, "UserInfo", "MyNotes", pszComment);
+ }
+ }
+ SAFE_FREE((void**)&pszComment);
+ }
+ else
+ {
+ NetLog_Server("Invalid comment");
+ }
+ }
+
+ // Look for need-authorization TLV
+ if (pChain->getTLV(SSI_TLV_AWAITING_AUTH, 1))
+ {
+ setSettingByte(hContact, "Auth", 1);
+ NetLog_Server("SSI contact need authorization");
+ }
+ else
+ {
+ setSettingByte(hContact, "Auth", 0);
+ }
+
+ if (pTLV = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1))
+ {
+ setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pTLV->pData, pTLV->wLen);
+ if (pChain->getTLV(SSI_TLV_METAINFO_TIME, 1))
+ setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1));
+ NetLog_Server("SSI contact has meta info token");
+ }
+ else
+ {
+ deleteSetting(hContact, DBSETTING_METAINFO_TOKEN);
+ deleteSetting(hContact, DBSETTING_METAINFO_TIME);
+ }
+
+ { // store server-list item's TLV data
+ BYTE* data = (BYTE*)SAFE_MALLOC(wTlvLength);
+ int datalen = getServerDataFromItemTLV(pChain, data);
+
+ if (datalen > 0)
+ setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen);
+ else
+ deleteSetting(hContact, DBSETTING_SERVLIST_DATA);
+
+ SAFE_FREE((void**)&data);
+ }
+ }
+ }
+ else
+ { // failed to add or other error
+ NetLog_Server("SSI failed to handle %s Item '%s'", "Buddy", szRecordName);
+ }
+ }
+ break;
+
+ case SSI_ITEM_GROUP:
+ if ((wGroupId == 0) && (wItemId == 0))
+ {
+ /* list of groups. wTlvType=1, data is TLV(C8) containing list of WORDs which */
+ /* is the group ids
+ /* we don't need to use this. Our processing is on-the-fly */
+ /* this record is always sent first in the first packet only, */
+ }
+ else if (wGroupId != 0)
+ {
+ /* wGroupId != 0: a group record */
+ if (wItemId == 0)
+ { /* no item ID: this is a group */
+ /* pszRecordName is the name of the group */
+ ReserveServerID(wGroupId, SSIT_GROUP, 0);
+
+ setServListGroupName(wGroupId, szRecordName);
+
+ NetLog_Server("Group %s added to known groups.", szRecordName);
+
+ /* demangle full grouppath, set it to known */
+ SAFE_FREE(&szActiveSrvGroup);
+ szActiveSrvGroup = getServListGroupCListPath(wGroupId);
+ wActiveSrvGroupId = wGroupId;
+
+ /* TLV contains a TLV(C8) with a list of WORDs of contained contact IDs */
+ /* our processing is good enough that we don't need this duplication */
+ }
+ else
+ {
+ NetLog_Server("Unhandled type 0x01, wItemID != 0");
+ }
+ }
+ else
+ {
+ NetLog_Server("Unhandled type 0x01");
+ }
+ break;
+
+ case SSI_ITEM_PERMIT:
+ {
+ /* item on visible list */
+ /* wItemId not related to contact ID */
+ /* pszRecordName is the UIN */
+ HANDLE hContact;
+ int bAdded;
+
+ hContact = HContactFromRecordName(szRecordName, &bAdded);
+
+ if (hContact != INVALID_HANDLE_VALUE)
+ {
+ if (bAdded)
+ {
+ NetLog_Server("SSI added new %s contact '%s'", "Permit", szRecordName);
+ // It wasn't previously in the list, we hide it so it only appears in the visible list
+ setContactHidden(hContact, 1);
+ // Add it to the list, so it can be added properly if proper contact
+ AddJustAddedContact(hContact);
+ }
+ else
+ NetLog_Server("SSI %s contact already exists '%s'", "Permit", szRecordName);
+
+ // Save permit ID
+ setSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, wItemId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+ // Set apparent mode
+ setSettingWord(hContact, "ApparentMode", ID_STATUS_ONLINE);
+ NetLog_Server("Visible-contact (%s)", szRecordName);
+ }
+ else
+ { // failed to add or other error
+ NetLog_Server("SSI failed to handle %s Item '%s'", "Permit", szRecordName);
+
+ ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED);
+ }
+ }
+ break;
+
+ case SSI_ITEM_DENY:
+ {
+ /* Item on invisible list */
+ /* wItemId not related to contact ID */
+ /* pszRecordName is the UIN */
+ HANDLE hContact;
+ int bAdded;
+
+ hContact = HContactFromRecordName(szRecordName, &bAdded);
+
+ if (hContact != INVALID_HANDLE_VALUE)
+ {
+ if (bAdded)
+ {
+ /* not already on list: added */
+ NetLog_Server("SSI added new %s contact '%s'", "Deny", szRecordName);
+ // It wasn't previously in the list, we hide it so it only appears in the visible list
+ setContactHidden(hContact, 1);
+ // Add it to the list, so it can be added properly if proper contact
+ AddJustAddedContact(hContact);
+ }
+ else
+ NetLog_Server("SSI %s contact already exists '%s'", "Deny", szRecordName);
+
+ // Save Deny ID
+ setSettingWord(hContact, DBSETTING_SERVLIST_DENY, wItemId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+
+ // Set apparent mode
+ setSettingWord(hContact, "ApparentMode", ID_STATUS_OFFLINE);
+ NetLog_Server("Invisible-contact (%s)", szRecordName);
+ }
+ else
+ { // failed to add or other error
+ NetLog_Server("SSI failed to handle %s Item '%s'", "Deny", szRecordName);
+
+ ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED);
+ }
+ }
+ break;
+
+ case SSI_ITEM_VISIBILITY: /* My visibility settings */
+ {
+ BYTE bVisibility;
+
+ // Look for visibility TLV
+ if (bVisibility = pChain->getByte(SSI_TLV_VISIBILITY, 1))
+ { // found it, store the id, we do not need current visibility - we do not rely on it
+ setSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, wItemId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+
+ NetLog_Server("Visibility is %u", bVisibility);
+ }
+ else
+ ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED);
+ }
+ break;
+
+ case SSI_ITEM_IGNORE:
+ {
+ /* item on ignore list */
+ /* wItemId not related to contact ID */
+ /* pszRecordName is the UIN */
+ HANDLE hContact;
+ int bAdded;
+
+ hContact = HContactFromRecordName(szRecordName, &bAdded);
+
+ if (hContact != INVALID_HANDLE_VALUE)
+ {
+ if (bAdded)
+ {
+ /* not already on list: add */
+ NetLog_Server("SSI added new %s contact '%s'", "Ignore", szRecordName);
+ // It wasn't previously in the list, we hide it
+ setContactHidden(hContact, 1);
+ // Add it to the list, so it can be added properly if proper contact
+ AddJustAddedContact(hContact);
+ }
+ else
+ NetLog_Server("SSI %s contact already exists '%s'", "Ignore", szRecordName);
+
+ // Save Ignore ID
+ setSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, wItemId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+
+ // Set apparent mode & ignore
+ setSettingWord(hContact, "ApparentMode", ID_STATUS_OFFLINE);
+ // set ignore all events
+ CallService(MS_IGNORE_IGNORE, (WPARAM)hContact, IGNOREEVENT_ALL);
+ NetLog_Server("Ignore-contact (%s)", szRecordName);
+ }
+ else
+ { // failed to add or other error
+ NetLog_Server("SSI failed to handle %s Item '%s'", "Ignore", szRecordName);
+
+ ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED);
+ }
+ }
+ break;
+
+ case SSI_ITEM_UNKNOWN2:
+ NetLog_Server("SSI unknown type 0x11");
+
+ ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED);
+ break;
+
+ case SSI_ITEM_IMPORTTIME:
+ if (wGroupId == 0)
+ {
+ /* time our list was first imported */
+ /* pszRecordName is "Import Time" */
+ /* data is TLV(13) {TLV(D4) {time_t importTime}} */
+ setSettingDword(NULL, "ImportTS", pChain->getDWord(SSI_TLV_TIMESTAMP, 1));
+ setSettingWord(NULL, "SrvImportID", wItemId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+ NetLog_Server("SSI %s item recognized", "first import");
+ }
+ break;
+
+ case SSI_ITEM_BUDDYICON:
+ if (wGroupId == 0)
+ {
+ /* our avatar MD5-hash */
+ /* pszRecordName is "1" */
+ /* data is TLV(D5) hash */
+ /* we ignore this, just save the id */
+ /* cause we get the hash again after login */
+ if (!strcmpnull(szRecordName, "12"))
+ { // need to handle Photo Item separately
+ setSettingWord(NULL, DBSETTING_SERVLIST_PHOTO, wItemId);
+ NetLog_Server("SSI %s item recognized", "Photo");
+ }
+ else
+ {
+ setSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, wItemId);
+ NetLog_Server("SSI %s item recognized", "Avatar");
+ }
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+ }
+ break;
+
+ case SSI_ITEM_METAINFO:
+ if (wGroupId == 0)
+ {
+ /* our meta info token & last update time */
+ /* pszRecordName is "ICQ-MDIR" */
+ /* data is TLV(15C) and TLV(15D) */
+ oscar_tlv* pToken = pChain->getTLV(SSI_TLV_METAINFO_TOKEN, 1);
+ oscar_tlv* pTime = pChain->getTLV(SSI_TLV_METAINFO_TIME, 1);
+ if (pToken)
+ setSettingBlob(NULL, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen);
+ if (pTime)
+ setSettingDouble(NULL, DBSETTING_METAINFO_TIME, pChain->getDouble(SSI_TLV_METAINFO_TIME, 1));
+
+ setSettingWord(NULL, DBSETTING_SERVLIST_METAINFO, wItemId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+
+ NetLog_Server("SSI %s item recognized", "Meta info");
+ }
+ break;
+
+ case SSI_ITEM_CLIENTDATA:
+ if (wGroupId == 0)
+ {
+ /* ICQ2k ShortcutBar Items */
+ /* data is TLV(CD) text */
+ if (wItemId)
+ ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED);
+ }
+
+ case SSI_ITEM_SAVED:
+ case SSI_ITEM_PREAUTH:
+ break;
+
+ default:
+ NetLog_Server("SSI unhandled item %2x", wTlvType);
+
+ if (wItemId)
+ ReserveServerID(wItemId, SSIT_ITEM, SSIF_UNHANDLED);
+ break;
+ }
+
+ disposeChain(&pChain);
+ } // end for
+
+ // Release Memory
+ SAFE_FREE(&szActiveSrvGroup);
+
+ NetLog_Server("Bytes left: %u", wLen);
+
+ setSettingWord(NULL, "SrvRecordCount", (WORD)(wRecord + getSettingWord(NULL, "SrvRecordCount", 0)));
+
+ if (bIsLastPacket)
+ {
+ // No contacts left to sync
+ bIsSyncingCL = FALSE;
+
+ StoreServerIDs();
+
+ icq_RescanInfoUpdate();
+
+ if (wLen >= 4)
+ {
+ DWORD dwLastUpdateTime;
+
+ /* finally we get a time_t of the last update time */
+ unpackDWord(&buf, &dwLastUpdateTime);
+ setSettingDword(NULL, "SrvLastUpdate", dwLastUpdateTime);
+ NetLog_Server("Last update of server list was (%u) %s", dwLastUpdateTime, time2text(dwLastUpdateTime));
+
+ sendRosterAck();
+ handleServUINSettings(wListenPort, info);
+
+ servlistProcessLogin();
+ }
+ else
+ {
+ NetLog_Server("Last packet missed update time...");
+ }
+ if (getSettingWord(NULL, "SrvRecordCount", 0) == 0)
+ { // we got empty serv-list, create master group
+ cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (ack)
+ {
+ DWORD dwCookie;
+
+ ack->dwAction = SSA_GROUP_UPDATE;
+ ack->szGroupName = null_strdup("");
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack);
+ icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, 0, ack->szGroupName, NULL, 0, 0);
+ }
+ }
+ // serv-list sync finished, clear just added contacts
+ FlushJustAddedContacts();
+ }
+ else
+ {
+ NetLog_Server("Waiting for more packets");
+ }
+}
+
+
+void CIcqProto::handleServerCListItemAdd(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData)
+{
+ if (wItemType == SSI_ITEM_IMPORTTIME)
+ {
+ if (pItemData)
+ {
+ setSettingDword(NULL, "ImportTS", pItemData->getDWord(SSI_TLV_TIMESTAMP, 1));
+ setSettingWord(NULL, "SrvImportID", wItemId);
+ ReserveServerID(wItemId, SSIT_ITEM, 0);
+
+ NetLog_Server("Server added Import timestamp to list");
+
+ return;
+ }
+ }
+ // Reserve server-list ID
+ ReserveServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM, SSIF_UNHANDLED);
+}
+
+
+void CIcqProto::handleServerCListItemUpdate(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData)
+{
+ HANDLE hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL;
+
+ if (hContact != INVALID_HANDLE_VALUE && wItemType == SSI_ITEM_BUDDY)
+ { // a contact was updated on server
+ if (pItemData)
+ {
+ oscar_tlv* pAuth = pItemData->getTLV(SSI_TLV_AWAITING_AUTH, 1);
+ BYTE bAuth = getSettingByte(hContact, "Auth", 0);
+
+ if (bAuth && !pAuth)
+ { // server authorized our contact
+ char str[MAX_PATH];
+ char msg[MAX_PATH];
+ char *nick = NickFromHandleUtf(hContact);
+
+ setSettingByte(hContact, "Auth", 0);
+ null_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" was authorized in the server list."), msg, MAX_PATH), nick);
+ icq_LogMessage(LOG_WARNING, str);
+ SAFE_FREE(&nick);
+ }
+ else if (!bAuth && pAuth)
+ { // server took away authorization of our contact
+ char str[MAX_PATH];
+ char msg[MAX_PATH];
+ char *nick = NickFromHandleUtf(hContact);
+
+ setSettingByte(hContact, "Auth", 1);
+ null_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("Contact \"%s\" lost its authorization in the server list."), msg, MAX_PATH), nick);
+ icq_LogMessage(LOG_WARNING, str);
+ SAFE_FREE(&nick);
+ }
+
+ { // update metainfo data
+ DBVARIANT dbv = {0};
+ oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1);
+ oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1);
+
+ if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv))
+ {
+ if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal))
+ {
+ if (!pToken)
+ NetLog_Server("Contact %s, meta info token removed", szRecordName);
+ else
+ NetLog_Server("Contact %s, meta info token changed", szRecordName);
+
+ // user info was changed, refresh
+ if (IsMetaInfoChanged(hContact))
+ icq_QueueUser(hContact);
+ }
+
+ ICQFreeVariant(&dbv);
+ }
+ else if (pToken)
+ {
+ NetLog_Server("Contact %s, meta info token added", szRecordName);
+
+ // user info was changed, refresh
+ if (IsMetaInfoChanged(hContact))
+ icq_QueueUser(hContact);
+ }
+
+ if (pToken)
+ setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen);
+ if (pTime)
+ setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1));
+ }
+
+ { // update server's data - otherwise consequent operations can fail with 0x0E
+ BYTE *data = (BYTE*)_alloca(pItemData->getChainLength());
+ int datalen = getServerDataFromItemTLV(pItemData, data);
+
+ if (datalen > 0)
+ setSettingBlob(hContact, DBSETTING_SERVLIST_DATA, data, datalen);
+ else
+ deleteSetting(hContact, DBSETTING_SERVLIST_DATA);
+ }
+ }
+ }
+ else if (wItemType == SSI_ITEM_METAINFO)
+ { // owner MetaInfo data updated
+ if (pItemData)
+ {
+ DBVARIANT dbv = {0};
+ oscar_tlv *pToken = pItemData->getTLV(SSI_TLV_METAINFO_TOKEN, 1);
+ oscar_tlv *pTime = pItemData->getTLV(SSI_TLV_METAINFO_TIME, 1);
+
+ if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv))
+ {
+ if (!pToken || dbv.cpbVal != pToken->wLen || memcmp(dbv.pbVal, pToken->pData, dbv.cpbVal))
+ {
+ if (!pToken)
+ NetLog_Server("Owner meta info token removed");
+ else
+ NetLog_Server("Owner meta info token changed");
+ }
+
+ ICQFreeVariant(&dbv);
+ }
+
+ if (pToken)
+ setSettingBlob(hContact, DBSETTING_METAINFO_TOKEN, pToken->pData, pToken->wLen);
+ if (pTime)
+ setSettingDouble(hContact, DBSETTING_METAINFO_TIME, pItemData->getDouble(SSI_TLV_METAINFO_TIME, 1));
+ }
+ }
+ else if (wItemType == SSI_ITEM_GROUP)
+ { // group updated
+ NetLog_Server("Server updated our group \"%s\" on list", szRecordName);
+ }
+}
+
+
+void CIcqProto::handleServerCListItemDelete(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData)
+{
+ HANDLE hContact = (wItemType == SSI_ITEM_BUDDY || wItemType == SSI_ITEM_DENY || wItemType == SSI_ITEM_PERMIT || wItemType == SSI_ITEM_IGNORE) ? HContactFromRecordName(szRecordName, NULL) : NULL;
+
+ if (hContact != INVALID_HANDLE_VALUE && wItemType == SSI_ITEM_BUDDY)
+ { // a contact was removed from our list
+ if (getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) == wItemId)
+ {
+ deleteSetting(hContact, DBSETTING_SERVLIST_ID);
+ deleteSetting(hContact, DBSETTING_SERVLIST_GROUP);
+ deleteSetting(hContact, "Auth");
+
+ {
+ char str[MAX_PATH];
+ char msg[MAX_PATH];
+ char *nick = NickFromHandleUtf(hContact);
+
+ null_snprintf(str, MAX_PATH, ICQTranslateUtfStatic(LPGEN("User \"%s\" was removed from server list."), msg, MAX_PATH), nick);
+ icq_LogMessage(LOG_WARNING, str);
+ SAFE_FREE(&nick);
+ }
+ }
+ }
+ // Release server-list ID
+ FreeServerID(wItemId, wItemType == SSI_ITEM_GROUP ? SSIT_GROUP : SSIT_ITEM);
+}
+
+
+void CIcqProto::handleRecvAuthRequest(unsigned char *buf, WORD wLen)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ int bAdded;
+
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return;
+
+ if (dwUin && IsOnSpammerList(dwUin))
+ {
+ NetLog_Server("Ignored Message from known Spammer");
+ return;
+ }
+
+ WORD wReasonLen;
+ unpackWord(&buf, &wReasonLen);
+ wLen -= 2;
+ if (wReasonLen > wLen)
+ return;
+
+ HANDLE hContact = HContactFromUID(dwUin, szUid, &bAdded);
+ CCSDATA ccs;
+ PROTORECVEVENT pre;
+
+ ccs.szProtoService = PSR_AUTH;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.flags = 0;
+ pre.timestamp = time(NULL);
+ pre.lParam = sizeof(DWORD) + sizeof(HANDLE) + 5;
+ // Prepare reason
+ char *szReason = (char*)SAFE_MALLOC(wReasonLen + 1);
+ int nReasonLen = 0;
+ if (szReason)
+ {
+ memcpy(szReason, buf, wReasonLen);
+ szReason[wReasonLen] = '\0';
+ nReasonLen = strlennull(szReason);
+
+ char *temp = (char*)_alloca(nReasonLen + 2);
+ if (!IsUSASCII(szReason, nReasonLen) && UTF8_IsValid(szReason) && utf8_decode_static(szReason, temp, nReasonLen + 1))
+ pre.flags |= PREF_UTF;
+ }
+ // Read nick name from DB
+ char *szNick = NULL;
+ if (dwUin)
+ {
+ DBVARIANT dbv = { 0 };
+ if (pre.flags & PREF_UTF)
+ szNick = getSettingStringUtf(hContact, "Nick", NULL);
+ else if (!getSettingString(hContact, "Nick", &dbv))
+ {
+ szNick = null_strdup(dbv.pszVal);
+ ICQFreeVariant(&dbv);
+ }
+ }
+ else
+ szNick = null_strdup(szUid);
+ int nNickLen = strlennull(szNick);
+
+ pre.lParam += nNickLen + nReasonLen;
+
+ setSettingByte(ccs.hContact, "Grant", 1);
+
+ /*blob is: uin(DWORD), hcontact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ), reason(ASCIIZ)*/
+ char *szBlob = (char *)_alloca(pre.lParam);
+ char *pCurBlob = szBlob;
+ memcpy(pCurBlob, &dwUin, sizeof(DWORD)); pCurBlob += sizeof(DWORD);
+ memcpy(pCurBlob, &hContact, sizeof(HANDLE)); pCurBlob += sizeof(HANDLE);
+ if (nNickLen)
+ { // if we have nick we add it, otherwise keep trailing zero
+ memcpy(pCurBlob, szNick, nNickLen);
+ pCurBlob += nNickLen;
+ }
+ *pCurBlob = 0; pCurBlob++; // Nick
+ *pCurBlob = 0; pCurBlob++; // FirstName
+ *pCurBlob = 0; pCurBlob++; // LastName
+ *pCurBlob = 0; pCurBlob++; // email
+ if (nReasonLen)
+ {
+ memcpy(pCurBlob, szReason, nReasonLen);
+ pCurBlob += nReasonLen;
+ }
+ *pCurBlob = 0; // Reason
+ pre.szMessage = szBlob;
+
+ // TODO: Change for new auth system, include all known informations
+ CallService(MS_PROTO_CHAINRECV,0,(LPARAM)&ccs);
+
+ SAFE_FREE(&szNick);
+ SAFE_FREE(&szReason);
+ return;
+}
+
+
+void CIcqProto::handleRecvAdded(unsigned char *buf, WORD wLen)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ DWORD cbBlob;
+ PBYTE pBlob,pCurBlob;
+ int bAdded;
+ char* szNick;
+ int nNickLen;
+ DBVARIANT dbv = {0};
+
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return;
+
+ if (dwUin && IsOnSpammerList(dwUin))
+ {
+ NetLog_Server("Ignored Message from known Spammer");
+ return;
+ }
+
+ HANDLE hContact = HContactFromUID(dwUin, szUid, &bAdded);
+
+ cbBlob=sizeof(DWORD)*2+4;
+
+ if (dwUin)
+ {
+ if (getSettingString(hContact, "Nick", &dbv))
+ nNickLen = 0;
+ else
+ {
+ szNick = dbv.pszVal;
+ nNickLen = strlennull(szNick);
+ }
+ }
+ else
+ nNickLen = strlennull(szUid);
+
+ cbBlob += nNickLen;
+
+ pCurBlob=pBlob=(PBYTE)_alloca(cbBlob);
+ /*blob is: uin(DWORD), hContact(HANDLE), nick(ASCIIZ), first(ASCIIZ), last(ASCIIZ), email(ASCIIZ) */
+ *(DWORD*)pCurBlob = dwUin; pCurBlob += sizeof(DWORD);
+ *(DWORD*)pCurBlob = DWORD(hContact); pCurBlob += sizeof(DWORD);
+ if (nNickLen && dwUin)
+ { // if we have nick we add it, otherwise keep trailing zero
+ memcpy(pCurBlob, szNick, nNickLen);
+ pCurBlob+=nNickLen;
+ }
+ else
+ {
+ memcpy(pCurBlob, szUid, nNickLen);
+ pCurBlob+=nNickLen;
+ }
+ *(char *)pCurBlob = 0; pCurBlob++;
+ *(char *)pCurBlob = 0; pCurBlob++;
+ *(char *)pCurBlob = 0; pCurBlob++;
+ *(char *)pCurBlob = 0;
+ // TODO: Change for new auth system
+
+ AddEvent(NULL, EVENTTYPE_ADDED, time(NULL), 0, cbBlob, pBlob);
+}
+
+
+void CIcqProto::handleRecvAuthResponse(unsigned char *buf, WORD wLen)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ char* szNick = NULL;
+ WORD nReasonLen;
+ char* szReason;
+ int bAdded;
+
+ BYTE bResponse = 0xFF;
+
+ if (!unpackUID(&buf, &wLen, &dwUin, &szUid)) return;
+
+ if (dwUin && IsOnSpammerList(dwUin))
+ {
+ NetLog_Server("Ignored Message from known Spammer");
+ return;
+ }
+
+ HANDLE hContact = HContactFromUID(dwUin, szUid, &bAdded);
+
+ if (hContact != INVALID_HANDLE_VALUE) szNick = NickFromHandle(hContact);
+
+ if (wLen > 0)
+ {
+ unpackByte(&buf, &bResponse);
+ wLen -= 1;
+ }
+ if (wLen >= 2)
+ {
+ unpackWord(&buf, &nReasonLen);
+ wLen -= 2;
+ if (wLen >= nReasonLen)
+ {
+ szReason = (char*)_alloca(nReasonLen+1);
+ unpackString(&buf, szReason, nReasonLen);
+ szReason[nReasonLen] = '\0';
+ }
+ }
+
+ switch (bResponse)
+ {
+
+ case 0:
+ NetLog_Server("Authorization request %s by %s", "denied", strUID(dwUin, szUid));
+ // TODO: Add to system history as soon as new auth system is ready
+ break;
+
+ case 1:
+ setSettingByte(hContact, "Auth", 0);
+ NetLog_Server("Authorization request %s by %s", "granted", strUID(dwUin, szUid));
+ // TODO: Add to system history as soon as new auth system is ready
+ break;
+
+ default:
+ NetLog_Server("Unknown Authorization request response (%u) from %s", bResponse, strUID(dwUin, szUid));
+ break;
+
+ }
+ SAFE_FREE(&szNick);
+}
+
+
+// Updates the visibility code used while in SSI mode. If a server ID is
+// not stored in the local DB, a new ID will be added to the server list.
+//
+// Possible values are:
+// 01 - Allow all users to see you
+// 02 - Block all users from seeing you
+// 03 - Allow only users in the permit list to see you
+// 04 - Block only users in the invisible list from seeing you
+// 05 - Allow only users in the buddy list to see you
+//
+void CIcqProto::updateServVisibilityCode(BYTE bCode)
+{
+ icq_packet packet;
+ WORD wVisibilityID;
+ WORD wCommand;
+
+ if ((bCode > 0) && (bCode < 6))
+ {
+ cookie_servlist_action* ack;
+ DWORD dwCookie;
+ BYTE bVisibility = getSettingByte(NULL, "SrvVisibility", 0);
+
+ if (bVisibility == bCode) // if no change was made, not necescary to update that
+ return;
+ setSettingByte(NULL, "SrvVisibility", bCode);
+
+ // Do we have a known server visibility ID? We should, unless we just subscribed to the serv-list for the first time
+ if ((wVisibilityID = getSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, 0)) == 0)
+ {
+ // No, create a new random ID
+ wVisibilityID = GenerateServerID(SSIT_ITEM, 0);
+ setSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, wVisibilityID);
+ wCommand = ICQ_LISTS_ADDTOLIST;
+#ifdef _DEBUG
+ NetLog_Server("Made new srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode);
+#endif
+ }
+ else
+ {
+#ifdef _DEBUG
+ NetLog_Server("Reused srvVisibilityID, id is %u, code is %u", wVisibilityID, bCode);
+#endif
+ wCommand = ICQ_LISTS_UPDATEGROUP;
+ }
+
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (!ack)
+ {
+ NetLog_Server("Cookie alloc failure.");
+ return; // out of memory, go away
+ }
+ ack->dwAction = SSA_VISIBILITY; // update visibility
+ dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie
+
+ // Build and send packet
+ serverPacketInit(&packet, 25);
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, wCommand, 0, dwCookie);
+ packWord(&packet, 0); // Name (null)
+ packWord(&packet, 0); // GroupID (0 if not relevant)
+ packWord(&packet, wVisibilityID); // EntryID
+ packWord(&packet, SSI_ITEM_VISIBILITY); // EntryType
+ packWord(&packet, 5); // Length in bytes of following TLV
+ packTLV(&packet, SSI_TLV_VISIBILITY, 1, &bCode); // TLV (Visibility)
+ sendServPacket(&packet);
+ // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or
+ // ICQ_LISTS_CLI_MODIFYEND when modifying the visibility code
+ }
+}
+
+// Updates the avatar hash used while in SSI mode. If a server ID is
+// not stored in the local DB, a new ID will be added to the server list.
+void CIcqProto::updateServAvatarHash(BYTE *pHash, int size)
+{
+ void** pDoubleObject = NULL;
+ void* doubleObject = NULL;
+ DWORD dwOperationFlags = 0;
+ WORD wAvatarID;
+ WORD wCommand;
+ DBVARIANT dbvHash;
+ int bResetHash = 0;
+ char szItemName[2] = {0, 0};
+
+ if (!getSetting(NULL, "AvatarHash", &dbvHash))
+ {
+ szItemName[0] = 0x30 + dbvHash.pbVal[1];
+
+ if (memcmp(pHash, dbvHash.pbVal, 2) != 0)
+ {
+ /** add code to remove old hash from server */
+ bResetHash = 1;
+ }
+ ICQFreeVariant(&dbvHash);
+ }
+
+ if (bResetHash) // start update session
+ { // pair the packets (need to be send in the correct order
+ dwOperationFlags |= SSOF_BEGIN_OPERATION | SSOF_END_OPERATION;
+ pDoubleObject = &doubleObject;
+ }
+
+ if (bResetHash || !pHash)
+ {
+ cookie_servlist_action* ack;
+ DWORD dwCookie;
+
+ // Do we have a known server avatar ID?
+ if (wAvatarID = getSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0))
+ {
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (!ack)
+ {
+ NetLog_Server("Cookie alloc failure.");
+ return; // out of memory, go away
+ }
+ ack->dwAction = SSA_REMOVEAVATAR; // update avatar hash
+ ack->wContactId = wAvatarID;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack); // take cookie
+
+ icq_sendServerItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, wAvatarID, szItemName, NULL, 0, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject);
+ }
+ }
+
+ if (pHash)
+ {
+ cookie_servlist_action* ack;
+ DWORD dwCookie;
+ WORD wTLVlen;
+ icq_packet pBuffer;
+ WORD hashsize = size - 2;
+
+ // Do we have a known server avatar ID? We should, unless we just subscribed to the serv-list for the first time
+ if (bResetHash || (wAvatarID = getSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0)) == 0)
+ {
+ // No, create a new random ID
+ wAvatarID = GenerateServerID(SSIT_ITEM, 0);
+ wCommand = ICQ_LISTS_ADDTOLIST;
+#ifdef _DEBUG
+ NetLog_Server("Made new srvAvatarID, id is %u", wAvatarID);
+#endif
+ }
+ else
+ {
+#ifdef _DEBUG
+ NetLog_Server("Reused srvAvatarID, id is %u", wAvatarID);
+#endif
+ wCommand = ICQ_LISTS_UPDATEGROUP;
+ }
+
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (!ack)
+ {
+ NetLog_Server("Cookie alloc failure.");
+ return; // out of memory, go away
+ }
+ ack->dwAction = SSA_SETAVATAR; // update avatar hash
+ ack->wContactId = wAvatarID;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, wCommand, 0, ack); // take cookie
+
+ szItemName[0] = 0x30 + pHash[1];
+
+ // Build the packet
+ wTLVlen = 8 + hashsize;
+
+ // Initialize our handy data buffer
+ pBuffer.wPlace = 0;
+ pBuffer.pData = (BYTE *)_alloca(wTLVlen);
+ pBuffer.wLen = wTLVlen;
+
+ packTLV(&pBuffer, SSI_TLV_NAME, 0, NULL); // TLV (Name)
+ packTLV(&pBuffer, SSI_TLV_AVATARHASH, hashsize, pHash + 2); // TLV (Hash)
+
+ icq_sendServerItem(dwCookie, wCommand, 0, wAvatarID, szItemName, pBuffer.pData, wTLVlen, SSI_ITEM_BUDDYICON, SSOP_ITEM_ACTION | dwOperationFlags, 400, pDoubleObject);
+ // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or
+ // ICQ_LISTS_CLI_MODIFYEND when modifying the avatar hash
+ }
+}
+
+// Should be called before the server list is modified. When all
+// modifications are done, call icq_sendServerEndOperation().
+// Called automatically thru server-list update board!
+void CIcqProto::icq_sendServerBeginOperation(int bImport)
+{
+ icq_packet packet;
+ WORD wImportID = getSettingWord(NULL, "SrvImportID", 0);
+
+ if (bImport && wImportID)
+ { // we should be importing, check if already have import item
+ if (getSettingDword(NULL, "ImportTS", 0) + 604800 < getSettingDword(NULL, "LogonTS", 0))
+ { // is the timestamp week older, clear it and begin new import
+ DWORD dwCookie;
+ cookie_servlist_action* ack;
+
+ if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)))
+ { // we have cookie good, go on
+ ack->dwAction = SSA_IMPORT;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack);
+
+ icq_sendSimpleItem(dwCookie, ICQ_LISTS_REMOVEFROMLIST, 0, "ImportTime", 0, wImportID, SSI_ITEM_IMPORTTIME, SSOP_ITEM_ACTION | SSOF_SEND_DIRECTLY, 100);
+ }
+ }
+ }
+
+ serverPacketInit(&packet, (WORD)(bImport?14:10));
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYSTART);
+ if (bImport) packDWord(&packet, 1<<0x10);
+ sendServPacket(&packet);
+}
+
+// Should be called after the server list has been modified to inform
+// the server that we are done.
+// Called automatically thru server-list update board!
+void CIcqProto::icq_sendServerEndOperation()
+{
+ icq_packet packet;
+
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_MODIFYEND);
+ sendServPacket(&packet);
+}
+
+// Sent when the last roster packet has been received
+void CIcqProto::sendRosterAck(void)
+{
+ icq_packet packet;
+
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_GOTLIST);
+ sendServPacket(&packet);
+
+#ifdef _DEBUG
+ NetLog_Server("Sent SNAC(x13,x07) - CLI_ROSTERACK");
+#endif
+}
diff --git a/protocols/IcqOscarJ/src/fam_15icqserver.cpp b/protocols/IcqOscarJ/src/fam_15icqserver.cpp
new file mode 100644
index 0000000000..3f332e4f27
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_15icqserver.cpp
@@ -0,0 +1,1201 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void CIcqProto::handleIcqExtensionsFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_META_ERROR:
+ handleExtensionError(pBuffer, wBufferLength);
+ break;
+
+ case ICQ_META_SRV_REPLY:
+ handleExtensionServerInfo(pBuffer, wBufferLength, pSnacHeader->wFlags);
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_EXTENSIONS_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+
+void CIcqProto::handleExtensionError(BYTE *buf, WORD wPackLen)
+{
+ WORD wErrorCode;
+
+ if (wPackLen < 2)
+ wErrorCode = 0;
+
+ if (wPackLen >= 2 && wPackLen <= 6)
+ unpackWord(&buf, &wErrorCode);
+ else
+ { // TODO: cookies need to be handled and freed here on error
+ oscar_tlv_chain *chain = NULL;
+
+ unpackWord(&buf, &wErrorCode);
+ wPackLen -= 2;
+ chain = readIntoTLVChain(&buf, wPackLen, 0);
+ if (chain)
+ {
+ oscar_tlv* pTLV;
+
+ pTLV = chain->getTLV(0x21, 1); // get meta error data
+ if (pTLV && pTLV->wLen >= 8)
+ {
+ BYTE *pBuffer = pTLV->pData;
+ WORD wData;
+ pBuffer += 6;
+ unpackLEWord(&pBuffer, &wData); // get request type
+ switch (wData)
+ {
+ case CLI_META_INFO_REQ:
+ if (pTLV->wLen >= 12)
+ {
+ WORD wSubType;
+ WORD wCookie;
+
+ unpackWord(&pBuffer, &wCookie);
+ unpackLEWord(&pBuffer, &wSubType);
+ // more sofisticated detection, send ack
+ if (wSubType == META_REQUEST_FULL_INFO)
+ {
+ HANDLE hContact;
+ cookie_fam15_data *pCookieData = NULL;
+ int foundCookie;
+
+ foundCookie = FindCookie(wCookie, &hContact, (void**)&pCookieData);
+ if (foundCookie && pCookieData)
+ {
+ BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0);
+
+ ReleaseCookie(wCookie); // we do not leak cookie and memory
+ }
+
+ NetLog_Server("Full info request error 0x%02x received", wErrorCode);
+ }
+ else if (wSubType == META_SET_PASSWORD_REQ)
+ { // failed to change user password, report to UI
+ BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0);
+
+ NetLog_Server("Meta change password request failed, error 0x%02x", wErrorCode);
+ }
+ else
+ NetLog_Server("Meta request error 0x%02x received", wErrorCode);
+ }
+ else
+ NetLog_Server("Meta request error 0x%02x received", wErrorCode);
+
+ break;
+
+ default:
+ NetLog_Server("Unknown request 0x%02x error 0x%02x received", wData, wErrorCode);
+ }
+ disposeChain(&chain);
+ return;
+ }
+ disposeChain(&chain);
+ }
+ }
+ LogFamilyError(ICQ_EXTENSIONS_FAMILY, wErrorCode);
+}
+
+
+void CIcqProto::handleExtensionServerInfo(BYTE *buf, WORD wPackLen, WORD wFlags)
+{
+ oscar_tlv_chain *chain;
+ oscar_tlv *dataTlv;
+
+ // The entire packet is encapsulated in a TLV type 1
+ chain = readIntoTLVChain(&buf, wPackLen, 0);
+ if (chain == NULL)
+ {
+ NetLog_Server("Error: Broken snac 15/3 %d", 1);
+ return;
+ }
+
+ dataTlv = chain->getTLV(0x0001, 1);
+ if (dataTlv == NULL)
+ {
+ disposeChain(&chain);
+ NetLog_Server("Error: Broken snac 15/3 %d", 2);
+ return;
+ }
+ BYTE *databuf = dataTlv->pData;
+ wPackLen -= 4;
+
+ _ASSERTE(dataTlv->wLen == wPackLen);
+ _ASSERTE(wPackLen >= 10);
+
+ if ((dataTlv->wLen == wPackLen) && (wPackLen >= 10))
+ {
+ WORD wBytesRemaining;
+ WORD wRequestType;
+ WORD wCookie;
+ DWORD dwMyUin;
+
+ unpackLEWord(&databuf, &wBytesRemaining);
+ unpackLEDWord(&databuf, &dwMyUin);
+ unpackLEWord(&databuf, &wRequestType);
+ unpackWord(&databuf, &wCookie);
+
+ _ASSERTE(wBytesRemaining == (wPackLen - 2));
+ if (wBytesRemaining == (wPackLen - 2))
+ {
+ wPackLen -= 10;
+ switch (wRequestType)
+ {
+ case SRV_META_INFO_REPLY: // SRV_META request replies
+ handleExtensionMetaResponse(databuf, wPackLen, wCookie, wFlags);
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignoring Meta response - Unknown type %d", wRequestType);
+ break;
+ }
+ }
+ }
+ else
+ NetLog_Server("Error: Broken snac 15/3 %d", 3);
+
+ if (chain)
+ disposeChain(&chain);
+}
+
+
+void CIcqProto::handleExtensionMetaResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wFlags)
+{
+ WORD wReplySubtype;
+ BYTE bResultCode;
+
+ _ASSERTE(wPacketLen >= 3);
+ if (wPacketLen >= 3)
+ {
+ // Reply subtype
+ unpackLEWord(&databuf, &wReplySubtype);
+ wPacketLen -= 2;
+
+ // Success byte
+ unpackByte(&databuf, &bResultCode);
+ wPacketLen -= 1;
+
+ switch (wReplySubtype)
+ {
+ case META_SET_PASSWORD_ACK:
+ parseUserInfoUpdateAck(databuf, wPacketLen, wCookie, wReplySubtype, bResultCode);
+ break;
+
+ case SRV_RANDOM_FOUND:
+ case SRV_USER_FOUND:
+ case SRV_LAST_USER_FOUND:
+ parseSearchReplies(databuf, wPacketLen, wCookie, wReplySubtype, bResultCode);
+ break;
+
+ case META_PROCESSING_ERROR: // Meta processing error server reply
+ // Todo: We only use this as an SMS ack, that will have to change
+ {
+ // Terminate buffer
+ char *pszInfo = (char *)_alloca(wPacketLen + 1);
+ if (wPacketLen > 0)
+ memcpy(pszInfo, databuf, wPacketLen);
+ pszInfo[wPacketLen] = 0;
+
+ BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_FAILED, (HANDLE)wCookie, (LPARAM)pszInfo);
+ FreeCookie(wCookie);
+ break;
+ }
+ break;
+
+ case META_SMS_DELIVERY_RECEIPT:
+ // Todo: This overlaps with META_SET_AFFINFO_ACK.
+ // Todo: Check what happens if result != A
+ if (wPacketLen > 8)
+ {
+ WORD wNetworkNameLen;
+ WORD wAckLen;
+ char *pszInfo;
+
+
+ databuf += 6; // Some unknowns
+ wPacketLen -= 6;
+
+ unpackWord(&databuf, &wNetworkNameLen);
+ if (wPacketLen >= (wNetworkNameLen + 2))
+ {
+ databuf += wNetworkNameLen;
+ wPacketLen -= wNetworkNameLen;
+
+ unpackWord(&databuf, &wAckLen);
+ if (pszInfo = (char *)_alloca(wAckLen + 1))
+ {
+ // Terminate buffer
+ if (wAckLen > 0)
+ memcpy(pszInfo, databuf, wAckLen);
+ pszInfo[wAckLen] = 0;
+
+ BroadcastAck(NULL, ICQACKTYPE_SMS, ACKRESULT_SENTREQUEST, (HANDLE)wCookie, (LPARAM)pszInfo);
+ FreeCookie(wCookie);
+
+ // Parsing success
+ break;
+ }
+ }
+ }
+
+ // Parsing failure
+ NetLog_Server("Error: Failure parsing META_SMS_DELIVERY_RECEIPT");
+ break;
+
+ case META_DIRECTORY_DATA:
+ case META_DIRECTORY_RESPONSE:
+ if (bResultCode == 0x0A)
+ handleDirectoryQueryResponse(databuf, wPacketLen, wCookie, wReplySubtype, wFlags);
+ else
+ NetLog_Server("Error: Directory request failed, code %u", bResultCode);
+ break;
+
+ case META_DIRECTORY_UPDATE_ACK:
+ if (bResultCode == 0x0A)
+ handleDirectoryUpdateResponse(databuf, wPacketLen, wCookie, wReplySubtype);
+ else
+ NetLog_Server("Error: Directory request failed, code %u", bResultCode);
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignored 15/03 replysubtype x%x", wReplySubtype);
+ // _ASSERTE(0);
+ break;
+ }
+
+ // Success
+ return;
+ }
+
+ // Failure
+ NetLog_Server("Warning: Broken 15/03 ExtensionMetaResponse");
+}
+
+
+void CIcqProto::ReleaseSearchCookie(DWORD dwCookie, cookie_search *pCookie)
+{
+ if (pCookie)
+ {
+ FreeCookie(dwCookie);
+ if (pCookie->dwMainId)
+ {
+ if (pCookie->dwStatus)
+ {
+ SAFE_FREE((void**)&pCookie);
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
+ }
+ else
+ pCookie->dwStatus = 1;
+ }
+ else
+ {
+ SAFE_FREE((void**)&pCookie);
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
+ }
+ }
+ else
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
+}
+
+
+void CIcqProto::parseSearchReplies(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode)
+{
+ BYTE bParsingOK = FALSE; // For debugging purposes only
+ BOOL bLastUser = FALSE;
+ cookie_search *pCookie;
+
+ if (!FindCookie(wCookie, NULL, (void**)&pCookie))
+ {
+ NetLog_Server("Warning: Received unexpected search reply");
+ pCookie = NULL;
+ }
+
+ switch (wReplySubtype)
+ {
+
+ case SRV_LAST_USER_FOUND: // Search: last user found reply
+ bLastUser = TRUE;
+
+ case SRV_USER_FOUND: // Search: user found reply
+ if (bLastUser)
+ NetLog_Server("SNAC(0x15,0x3): Last search reply");
+ else
+ NetLog_Server("SNAC(0x15,0x3): Search reply");
+
+ if (bResultCode == 0xA)
+ {
+ ICQSEARCHRESULT sr = {0};
+ DWORD dwUin;
+ char szUin[UINMAXLEN];
+ WORD wLen;
+
+ sr.hdr.cbSize = sizeof(sr);
+
+ // Remaining bytes
+ if (wPacketLen < 2)
+ break;
+ unpackLEWord(&databuf, &wLen);
+ wPacketLen -= 2;
+
+ _ASSERTE(wLen <= wPacketLen);
+ if (wLen > wPacketLen)
+ break;
+
+ // Uin
+ if (wPacketLen < 4)
+ break;
+ unpackLEDWord(&databuf, &dwUin); // Uin
+ wPacketLen -= 4;
+ sr.uin = dwUin;
+ _itoa(dwUin, szUin, 10);
+ sr.hdr.id = (FNAMECHAR*)szUin;
+
+ // Nick
+ if (wPacketLen < 2)
+ break;
+ unpackLEWord(&databuf, &wLen);
+ wPacketLen -= 2;
+ if (wLen > 0)
+ {
+ if (wPacketLen < wLen || (databuf[wLen-1] != 0))
+ break;
+ sr.hdr.nick = (FNAMECHAR*)databuf;
+ databuf += wLen;
+ }
+ else
+ {
+ sr.hdr.nick = NULL;
+ }
+
+ // First name
+ if (wPacketLen < 2)
+ break;
+ unpackLEWord(&databuf, &wLen);
+ wPacketLen -= 2;
+ if (wLen > 0)
+ {
+ if (wPacketLen < wLen || (databuf[wLen-1] != 0))
+ break;
+ sr.hdr.firstName = (FNAMECHAR*)databuf;
+ databuf += wLen;
+ }
+ else
+ {
+ sr.hdr.firstName = NULL;
+ }
+
+ // Last name
+ if (wPacketLen < 2)
+ break;
+ unpackLEWord(&databuf, &wLen);
+ wPacketLen -= 2;
+ if (wLen > 0)
+ {
+ if (wPacketLen < wLen || (databuf[wLen-1] != 0))
+ break;
+ sr.hdr.lastName = (FNAMECHAR*)databuf;
+ databuf += wLen;
+ }
+ else
+ {
+ sr.hdr.lastName = NULL;
+ }
+
+ // E-mail name
+ if (wPacketLen < 2)
+ break;
+ unpackLEWord(&databuf, &wLen);
+ wPacketLen -= 2;
+ if (wLen > 0)
+ {
+ if (wPacketLen < wLen || (databuf[wLen-1] != 0))
+ break;
+ sr.hdr.email = (FNAMECHAR*)databuf;
+ databuf += wLen;
+ }
+ else
+ {
+ sr.hdr.email = NULL;
+ }
+
+ // Authentication needed flag
+ if (wPacketLen < 1)
+ break;
+ unpackByte(&databuf, &sr.auth);
+
+ // Finally, broadcast the result
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)wCookie, (LPARAM)&sr);
+
+ // Broadcast "Last result" ack if this was the last user found
+ if (wReplySubtype == SRV_LAST_USER_FOUND)
+ {
+ if (wPacketLen>=10)
+ {
+ DWORD dwLeft;
+
+ databuf += 5;
+ unpackLEDWord(&databuf, &dwLeft);
+ if (dwLeft)
+ NetLog_Server("Warning: %d search results omitted", dwLeft);
+ }
+ ReleaseSearchCookie(wCookie, pCookie);
+ }
+ bParsingOK = TRUE;
+ }
+ else
+ {
+ // Failed search
+ NetLog_Server("SNAC(0x15,0x3): Search error %u", bResultCode);
+
+ ReleaseSearchCookie(wCookie, pCookie);
+
+ bParsingOK = TRUE;
+ }
+ break;
+
+ case SRV_RANDOM_FOUND: // Random search server reply
+ default:
+ if (pCookie)
+ ReleaseCookie(wCookie);
+ break;
+ }
+
+ // For debugging purposes only
+ if (!bParsingOK)
+ {
+ NetLog_Server("Warning: Parsing error in 15/03 search reply type x%x", wReplySubtype);
+ _ASSERTE(!bParsingOK);
+ }
+}
+
+
+void CIcqProto::parseUserInfoUpdateAck(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode)
+{
+ switch (wReplySubtype) {
+ case META_SET_PASSWORD_ACK: // Set user password server ack
+
+ if (bResultCode == 0xA)
+ BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0);
+ else
+ BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0);
+
+ FreeCookie(wCookie);
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignored 15/03 user info update ack type x%x", wReplySubtype);
+ break;
+ }
+}
+
+
+UserInfoRecordItem rEmail[] = {
+ {0x64, DBVT_ASCIIZ, "e-mail%u"}
+};
+
+UserInfoRecordItem rAddress[] = {
+ {0x64, DBVT_UTF8, "Street"},
+ {0x6E, DBVT_UTF8, "City"},
+ {0x78, DBVT_UTF8, "State"},
+ {0x82, DBVT_UTF8, "ZIP"},
+ {0x8C, DBVT_WORD, "Country"}
+};
+
+UserInfoRecordItem rOriginAddress[] = {
+ {0x64, DBVT_UTF8, "OriginStreet"},
+ {0x6E, DBVT_UTF8, "OriginCity"},
+ {0x78, DBVT_UTF8, "OriginState"},
+ {0x8C, DBVT_WORD, "OriginCountry"}
+};
+
+UserInfoRecordItem rCompany[] = {
+ {0x64, DBVT_UTF8, "CompanyPosition"},
+ {0x6E, DBVT_UTF8, "Company"},
+ {0x7D, DBVT_UTF8, "CompanyDepartment"},
+ {0x78, DBVT_ASCIIZ, "CompanyHomepage"},
+ {0x82, DBVT_WORD, "CompanyIndustry"},
+ {0xAA, DBVT_UTF8, "CompanyStreet"},
+ {0xB4, DBVT_UTF8, "CompanyCity"},
+ {0xBE, DBVT_UTF8, "CompanyState"},
+ {0xC8, DBVT_UTF8, "CompanyZIP"},
+ {0xD2, DBVT_WORD, "CompanyCountry"}
+};
+
+UserInfoRecordItem rEducation[] = {
+ {0x64, DBVT_WORD, "StudyLevel"},
+ {0x6E, DBVT_UTF8, "StudyInstitute"},
+ {0x78, DBVT_UTF8, "StudyDegree"},
+ {0x8C, DBVT_WORD, "StudyYear"}
+};
+
+UserInfoRecordItem rInterest[] = {
+ {0x64, DBVT_UTF8, "Interest%uText"},
+ {0x6E, DBVT_WORD, "Interest%uCat"}
+};
+
+
+int CIcqProto::parseUserInfoRecord(HANDLE hContact, oscar_tlv *pData, UserInfoRecordItem pRecordDef[], int nRecordDef, int nMaxRecords)
+{
+ int nRecords = 0;
+
+ if (pData && pData->wLen >= 2)
+ {
+ BYTE *pRecords = pData->pData;
+ WORD wRecordCount;
+ unpackWord(&pRecords, &wRecordCount);
+ oscar_tlv_record_list *cData = readIntoTLVRecordList(&pRecords, pData->wLen - 2, nMaxRecords > wRecordCount ? wRecordCount : nMaxRecords);
+ oscar_tlv_record_list *cDataItem = cData;
+ while (cDataItem)
+ {
+ oscar_tlv_chain *cItem = cDataItem->item;
+
+ for (int i = 0; i < nRecordDef; i++)
+ {
+ char szItemKey[MAX_PATH];
+
+ null_snprintf(szItemKey, MAX_PATH, pRecordDef[i].szDbSetting, nRecords);
+
+ switch (pRecordDef[i].dbType)
+ {
+ case DBVT_ASCIIZ:
+ writeDbInfoSettingTLVString(hContact, szItemKey, cItem, pRecordDef[i].wTLV);
+ break;
+
+ case DBVT_UTF8:
+ writeDbInfoSettingTLVStringUtf(hContact, szItemKey, cItem, pRecordDef[i].wTLV);
+ break;
+
+ case DBVT_WORD:
+ writeDbInfoSettingTLVWord(hContact, szItemKey, cItem, pRecordDef[i].wTLV);
+ break;
+ }
+ }
+ nRecords++;
+
+ cDataItem = cDataItem->next;
+ }
+ // release memory
+ disposeRecordList(&cData);
+ }
+ // remove old data from database
+ if (!nRecords || nMaxRecords > 1)
+ for (int i = nRecords; i <= nMaxRecords; i++)
+ for (int j = 0; j < nRecordDef; j++)
+ {
+ char szItemKey[MAX_PATH];
+
+ null_snprintf(szItemKey, MAX_PATH, pRecordDef[j].szDbSetting, i);
+
+ deleteSetting(hContact, szItemKey);
+ }
+
+ return nRecords;
+}
+
+
+void CIcqProto::handleDirectoryQueryResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, WORD wFlags)
+{
+ WORD wBytesRemaining = 0;
+ snac_header requestSnac = {0};
+ BYTE requestResult;
+
+#ifdef _DEBUG
+ NetLog_Server("Received directory query response");
+#endif
+ if (wPacketLen >= 2)
+ unpackLEWord(&databuf, &wBytesRemaining);
+ wPacketLen -= 2;
+ _ASSERTE(wPacketLen == wBytesRemaining);
+
+ if (!unpackSnacHeader(&requestSnac, &databuf, &wPacketLen) || !requestSnac.bValid)
+ {
+ NetLog_Server("Error: Failed to parse directory response");
+ return;
+ }
+
+ cookie_directory_data *pCookieData;
+ HANDLE hContact;
+ // check request cookie
+ if (!FindCookie(wCookie, &hContact, (void**)&pCookieData) || !pCookieData)
+ {
+ NetLog_Server("Warning: Ignoring unrequested directory reply type (x%x, x%x)", requestSnac.wFamily, requestSnac.wSubtype);
+ return;
+ }
+ /// FIXME: we should really check the snac contents according to cookie data here ??
+
+ // Check if this is the last packet for this request
+ BOOL bMoreDataFollows = wFlags&0x0001 && requestSnac.wFlags&0x0001;
+
+ if (wPacketLen >= 3)
+ unpackByte(&databuf, &requestResult);
+ else
+ {
+ NetLog_Server("Error: Malformed directory response");
+ if (!bMoreDataFollows)
+ ReleaseCookie(wCookie);
+ return;
+ }
+ if (requestResult != 1 && requestResult != 4)
+ {
+ NetLog_Server("Error: Directory request failed, status %u", requestResult);
+
+ if (!bMoreDataFollows)
+ {
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER)
+ BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0);
+ else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH)
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that
+ ReleaseCookie(wCookie);
+ }
+ return;
+ }
+ WORD wLen;
+
+ unpackWord(&databuf, &wLen);
+ wPacketLen -= 3;
+ if (wLen)
+ NetLog_Server("Warning: Data in error message present!");
+
+ if (wPacketLen <= 0x16)
+ { // sanity check
+ NetLog_Server("Error: Malformed directory response");
+
+ if (!bMoreDataFollows)
+ {
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER)
+ BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0);
+ else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH)
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that
+ ReleaseCookie(wCookie);
+ }
+ return;
+ }
+ databuf += 0x10; // unknown stuff
+ wPacketLen -= 0x10;
+
+ DWORD dwItemCount;
+ WORD wPageCount;
+
+ /// FIXME: check itemcount, pagecount against the cookie data ???
+
+ unpackDWord(&databuf, &dwItemCount);
+ unpackWord(&databuf, &wPageCount);
+ wPacketLen -= 6;
+
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH && !bMoreDataFollows)
+ NetLog_Server("Directory Search: %d contacts found (%u pages)", dwItemCount, wPageCount);
+
+ if (wPacketLen <= 2)
+ { // sanity check, block expected
+ NetLog_Server("Error: Malformed directory response");
+
+ if (!bMoreDataFollows)
+ {
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER)
+ BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0);
+ else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH)
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that
+ ReleaseCookie(wCookie);
+ }
+ return;
+ }
+ WORD wData;
+
+ unpackWord(&databuf, &wData); // This probably the count of items following (a block)
+ wPacketLen -= 2;
+ if (wPacketLen >= 2 && wData >= 1)
+ {
+ unpackWord(&databuf, &wLen); // This is the size of the first item
+ wPacketLen -= 2;
+ }
+
+ if (wData == 0 && pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH)
+ {
+ NetLog_Server("Directory Search: No contacts found");
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0);
+ ReleaseCookie(wCookie);
+ return;
+ }
+
+ _ASSERTE(wData == 1 && wPacketLen == wLen);
+ if (wData != 1 || wPacketLen != wLen)
+ {
+ NetLog_Server("Error: Malformed directory response (missing data)");
+
+ if (!bMoreDataFollows)
+ {
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER)
+ BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_FAILED, (HANDLE)1 ,0);
+ else if (pCookieData->bRequestType == DIRECTORYREQUEST_SEARCH)
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0); // should report error here, but Find/Add module does not support that
+ ReleaseCookie(wCookie);
+ }
+ return;
+ }
+ oscar_tlv_chain *pDirectoryData = readIntoTLVChain(&databuf, wLen, -1);
+ if (pDirectoryData)
+ {
+ switch (pCookieData->bRequestType)
+ {
+ case DIRECTORYREQUEST_INFOOWNER:
+ parseDirectoryUserDetailsData(NULL, pDirectoryData, wCookie, pCookieData, wReplySubtype);
+ break;
+
+ case DIRECTORYREQUEST_INFOUSER:
+ {
+ DWORD dwUin = 0;
+ char *szUid = pDirectoryData->getString(0x32, 1);
+ if (!szUid)
+ {
+ NetLog_Server("Error: Received unrecognized data from the directory");
+ break;
+ }
+
+ if (IsStringUIN(szUid))
+ dwUin = atoi(szUid);
+
+ if (hContact != HContactFromUID(dwUin, szUid, NULL))
+ {
+ NetLog_Server("Error: Received data does not match cookie contact, ignoring.");
+ SAFE_FREE(&szUid);
+ break;
+ }
+ else
+ SAFE_FREE(&szUid);
+ }
+
+ case DIRECTORYREQUEST_INFOMULTI:
+ parseDirectoryUserDetailsData(hContact, pDirectoryData, wCookie, pCookieData, wReplySubtype);
+ break;
+
+ case DIRECTORYREQUEST_SEARCH:
+ parseDirectorySearchData(pDirectoryData, wCookie, pCookieData, wReplySubtype);
+ break;
+
+ default:
+ NetLog_Server("Error: Unknown cookie type %x for directory response!", pCookieData->bRequestType);
+ }
+ disposeChain(&pDirectoryData);
+ }
+ else
+ NetLog_Server("Error: Failed parsing directory response");
+
+ // Release Memory
+ if (!bMoreDataFollows)
+ ReleaseCookie(wCookie);
+}
+
+
+static int calcAgeFromBirthDate(double dDate)
+{
+ if (dDate > 0)
+ { // date is stored as double with unit equal to a day, incrementing since 1/1/1900 0:00 GMT
+ SYSTEMTIME sDate = {0};
+ if (VariantTimeToSystemTime(dDate + 2, &sDate))
+ {
+ SYSTEMTIME sToday = {0};
+
+ GetLocalTime(&sToday);
+
+ int nAge = sToday.wYear - sDate.wYear;
+
+ if (sToday.wMonth < sDate.wMonth || (sToday.wMonth == sDate.wMonth && sToday.wDay < sDate.wDay))
+ nAge--;
+
+ return nAge;
+ }
+ }
+ return 0;
+}
+
+
+void CIcqProto::parseDirectoryUserDetailsData(HANDLE hContact, oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType)
+{
+ oscar_tlv *pTLV;
+ WORD wRecordCount;
+
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOMULTI && !hContact)
+ {
+ DWORD dwUin = 0;
+ char *szUid = cDetails->getString(0x32, 1);
+ if (!szUid)
+ {
+ NetLog_Server("Error: Received unrecognized data from the directory");
+ return;
+ }
+
+ if (IsStringUIN(szUid))
+ dwUin = atoi(szUid);
+
+ hContact = HContactFromUID(dwUin, szUid, NULL);
+ if (hContact == INVALID_HANDLE_VALUE)
+ {
+ NetLog_Server("Error: Received details for unknown contact \"%s\"", szUid);
+ SAFE_FREE(&szUid);
+ return;
+ }
+#ifdef _DEBUG
+ else
+ NetLog_Server("Received user info for %s from directory", szUid);
+#endif
+ SAFE_FREE(&szUid);
+ }
+#ifdef _DEBUG
+ else
+ {
+ char *szUid = cDetails->getString(0x32, 1);
+
+ if (!hContact)
+ NetLog_Server("Received owner user info from directory");
+ else
+ NetLog_Server("Received user info for %s from directory", szUid);
+ SAFE_FREE(&szUid);
+ }
+#endif
+
+ pTLV = cDetails->getTLV(0x50, 1);
+ if (pTLV && pTLV->wLen > 0)
+ writeDbInfoSettingTLVString(hContact, "e-mail", cDetails, 0x50); // Verified e-mail
+ else
+ writeDbInfoSettingTLVString(hContact, "e-mail", cDetails, 0x55); // Pending e-mail
+
+ writeDbInfoSettingTLVStringUtf(hContact, "FirstName", cDetails, 0x64);
+ writeDbInfoSettingTLVStringUtf(hContact, "LastName", cDetails, 0x6E);
+ writeDbInfoSettingTLVStringUtf(hContact, "Nick", cDetails, 0x78);
+ // Home Address
+ parseUserInfoRecord(hContact, cDetails->getTLV(0x96, 1), rAddress, SIZEOF(rAddress), 1);
+ // Origin Address
+ parseUserInfoRecord(hContact, cDetails->getTLV(0xA0, 1), rOriginAddress, SIZEOF(rOriginAddress), 1);
+ // Phones
+ pTLV = cDetails->getTLV(0xC8, 1);
+ if (pTLV && pTLV->wLen >= 2)
+ {
+ BYTE *pRecords = pTLV->pData;
+ unpackWord(&pRecords, &wRecordCount);
+ oscar_tlv_record_list *cPhones = readIntoTLVRecordList(&pRecords, pTLV->wLen - 2, wRecordCount);
+ if (cPhones)
+ {
+ oscar_tlv_chain *cPhone;
+ cPhone = cPhones->getRecordByTLV(0x6E, 1);
+ writeDbInfoSettingTLVString(hContact, "Phone", cPhone, 0x64);
+ cPhone = cPhones->getRecordByTLV(0x6E, 2);
+ writeDbInfoSettingTLVString(hContact, "CompanyPhone", cPhone, 0x64);
+ cPhone = cPhones->getRecordByTLV(0x6E, 3);
+ writeDbInfoSettingTLVString(hContact, "Cellular", cPhone, 0x64);
+ cPhone = cPhones->getRecordByTLV(0x6E, 4);
+ writeDbInfoSettingTLVString(hContact, "Fax", cPhone, 0x64);
+ cPhone = cPhones->getRecordByTLV(0x6E, 5);
+ writeDbInfoSettingTLVString(hContact, "CompanyFax", cPhone, 0x64);
+
+ disposeRecordList(&cPhones);
+ }
+ else
+ { // Remove old data when phones not available
+ deleteSetting(hContact, "Phone");
+ deleteSetting(hContact, "CompanyPhone");
+ deleteSetting(hContact, "Cellular");
+ deleteSetting(hContact, "Fax");
+ deleteSetting(hContact, "CompanyFax");
+ }
+ }
+ else
+ { // Remove old data when phones not available
+ deleteSetting(hContact, "Phone");
+ deleteSetting(hContact, "CompanyPhone");
+ deleteSetting(hContact, "Cellular");
+ deleteSetting(hContact, "Fax");
+ deleteSetting(hContact, "CompanyFax");
+ }
+ // Emails
+ parseUserInfoRecord(hContact, cDetails->getTLV(0x8C, 1), rEmail, SIZEOF(rEmail), 4);
+
+ writeDbInfoSettingTLVByte(hContact, "Timezone", cDetails, 0x17C);
+ // Company
+ parseUserInfoRecord(hContact, cDetails->getTLV(0x118, 1), rCompany, SIZEOF(rCompany), 1);
+ // Education
+ parseUserInfoRecord(hContact, cDetails->getTLV(0x10E, 1), rEducation, SIZEOF(rEducation), 1);
+
+ switch (cDetails->getNumber(0x82, 1))
+ {
+ case 1:
+ setSettingByte(hContact, "Gender", 'F');
+ break;
+ case 2:
+ setSettingByte(hContact, "Gender", 'M');
+ break;
+ default:
+ deleteSetting(hContact, "Gender");
+ }
+
+ writeDbInfoSettingTLVString(hContact, "Homepage", cDetails, 0xFA);
+ writeDbInfoSettingTLVDate(hContact, "BirthYear", "BirthMonth", "BirthDay", cDetails, 0x1A4);
+
+ writeDbInfoSettingTLVByte(hContact, "Language1", cDetails, 0xAA);
+ writeDbInfoSettingTLVByte(hContact, "Language2", cDetails, 0xB4);
+ writeDbInfoSettingTLVByte(hContact, "Language3", cDetails, 0xBE);
+
+ writeDbInfoSettingTLVByte(hContact, "MaritalStatus", cDetails, 0x12C);
+ // Interests
+ parseUserInfoRecord(hContact, cDetails->getTLV(0x122, 1), rInterest, SIZEOF(rInterest), 4);
+
+ writeDbInfoSettingTLVStringUtf(hContact, "About", cDetails, 0x186);
+
+// if (hContact)
+// writeDbInfoSettingTLVStringUtf(hContact, DBSETTING_STATUS_NOTE, cDetails, 0x226);
+// else
+ if (!hContact)
+ { // Owner contact needs special processing, in the database is current status note for the client
+ // We just received the last status note set on directory, if it differs call SetStatusNote() to
+ // ensure the directory will be updated (it should be in process anyway)
+ char *szClientStatusNote = getSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, NULL);
+ char *szDirectoryStatusNote = cDetails->getString(0x226, 1);
+
+ if (strcmpnull(szClientStatusNote, szDirectoryStatusNote))
+ SetStatusNote(szClientStatusNote, 1000, TRUE);
+
+ // Release memory
+ SAFE_FREE(&szDirectoryStatusNote);
+ SAFE_FREE(&szClientStatusNote);
+ }
+
+ writeDbInfoSettingTLVByte(hContact, "PrivacyLevel", cDetails, 0x1F9);
+
+ if (!hContact)
+ {
+ setSettingByte(hContact, "Auth", !cDetails->getByte(0x19A, 1));
+ writeDbInfoSettingTLVByte(hContact, "WebAware", cDetails, 0x212);
+ writeDbInfoSettingTLVByte(hContact, "AllowSpam", cDetails, 0x1EA);
+ }
+
+ writeDbInfoSettingTLVWord(hContact, "InfoCP", cDetails, 0x1C2);
+
+ if (hContact)
+ { // Handle deprecated setting (Age & Birthdate are not separate fields anymore)
+ int nAge = calcAgeFromBirthDate(cDetails->getDouble(0x1A4, 1));
+
+ if (nAge)
+ setSettingWord(hContact, "Age", nAge);
+ else
+ deleteSetting(hContact, "Age");
+ }
+ else // we do not need to calculate age for owner
+ deleteSetting(hContact, "Age");
+
+ { // Save user info last update time and privacy token
+ double dInfoTime;
+ BYTE pbEmptyMetaToken[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+ int bHasMetaToken = FALSE;
+
+ // Check if the details arrived with privacy token!
+ if ((pTLV = cDetails->getTLV(0x3C, 1)) && pTLV->wLen == 0x10 && memcmp(pTLV->pData, pbEmptyMetaToken, 0x10))
+ bHasMetaToken = TRUE;
+
+ // !Important, we need to save the MDir server-item time - it can be newer than the one from the directory
+ if ((dInfoTime = getSettingDouble(hContact, DBSETTING_METAINFO_TIME, 0)) > 0)
+ setSettingDouble(hContact, DBSETTING_METAINFO_SAVED, dInfoTime);
+ else if (bHasMetaToken || !hContact)
+ writeDbInfoSettingTLVDouble(hContact, DBSETTING_METAINFO_SAVED, cDetails, 0x1CC);
+ else
+ setSettingDword(hContact, DBSETTING_METAINFO_SAVED, time(NULL));
+ }
+
+ if (wReplySubType == META_DIRECTORY_RESPONSE)
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_INFOUSER)
+ BroadcastAck(hContact, ACKTYPE_GETINFO, ACKRESULT_SUCCESS, (HANDLE)1 ,0);
+
+ // Remove user from info update queue. Removing is fast so we always call this
+ // even if it is likely that the user is not queued at all.
+ if (hContact)
+ icq_DequeueUser(getContactUin(hContact));
+}
+
+
+void CIcqProto::parseDirectorySearchData(oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType)
+{
+ ICQSEARCHRESULT isr = {0};
+ char *szUid = cDetails->getString(0x32, 1); // User ID
+
+#ifdef _DEBUG
+ NetLog_Server("Directory Search: Found user %s", szUid);
+#endif
+ isr.hdr.cbSize = sizeof(ICQSEARCHRESULT);
+ isr.hdr.flags = PSR_TCHAR;
+ isr.hdr.id = ansi_to_tchar(szUid);
+
+ if (IsStringUIN(szUid))
+ isr.uin = atoi(szUid);
+ else
+ isr.uin = 0;
+
+ SAFE_FREE(&szUid);
+
+ oscar_tlv *pTLV = cDetails->getTLV(0x50, 1);
+ char *szData = NULL;
+
+ if (pTLV && pTLV->wLen > 0)
+ szData = cDetails->getString(0x50, 1); // Verified e-mail
+ else
+ szData = cDetails->getString(0x55, 1); // Pending e-mail
+ if (strlennull(szData))
+ isr.hdr.email = ansi_to_tchar(szData);
+ SAFE_FREE(&szData);
+
+ szData = cDetails->getString(0x64, 1); // First Name
+ if (strlennull(szData))
+ isr.hdr.firstName = utf8_to_tchar(szData);
+ SAFE_FREE(&szData);
+
+ szData = cDetails->getString(0x6E, 1); // Last Name
+ if (strlennull(szData))
+ isr.hdr.lastName = utf8_to_tchar(szData);
+ SAFE_FREE(&szData);
+
+ szData = cDetails->getString(0x78, 1); // Nick
+ if (strlennull(szData))
+ isr.hdr.nick = utf8_to_tchar(szData);
+ SAFE_FREE(&szData);
+
+ switch (cDetails->getNumber(0x82, 1)) // Gender
+ {
+ case 1:
+ isr.gender = 'F';
+ break;
+ case 2:
+ isr.gender = 'M';
+ break;
+ }
+
+ pTLV = cDetails->getTLV(0x96, 1);
+ if (pTLV && pTLV->wLen >= 4)
+ {
+ BYTE *buf = pTLV->pData;
+ oscar_tlv_chain *chain = readIntoTLVChain(&buf, pTLV->wLen, 0);
+ if (chain)
+ isr.country = chain->getDWord(0x8C, 1); // Home Country
+ disposeChain(&chain);
+ }
+
+ isr.auth = !cDetails->getByte(0x19A, 1); // Require Authorization
+ isr.maritalStatus = cDetails->getNumber(0x12C, 1); // Marital Status
+
+ // calculate Age if Birthdate is available
+ isr.age = calcAgeFromBirthDate(cDetails->getDouble(0x1A4, 1));
+
+ // Finally, broadcast the result
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)dwCookie, (LPARAM)&isr);
+
+ // Release memory
+ SAFE_FREE(&isr.hdr.id);
+ SAFE_FREE(&isr.hdr.nick);
+ SAFE_FREE(&isr.hdr.firstName);
+ SAFE_FREE(&isr.hdr.lastName);
+ SAFE_FREE(&isr.hdr.email);
+
+ // Search is over, broadcast final ack
+ if (wReplySubType == META_DIRECTORY_RESPONSE)
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)dwCookie, 0);
+}
+
+
+void CIcqProto::handleDirectoryUpdateResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype)
+{
+ WORD wBytesRemaining = 0;
+ snac_header requestSnac = {0};
+ BYTE requestResult;
+
+#ifdef _DEBUG
+ NetLog_Server("Received directory update response");
+#endif
+ if (wPacketLen >= 2)
+ unpackLEWord(&databuf, &wBytesRemaining);
+ wPacketLen -= 2;
+ _ASSERTE(wPacketLen == wBytesRemaining);
+
+ if (!unpackSnacHeader(&requestSnac, &databuf, &wPacketLen) || !requestSnac.bValid)
+ {
+ NetLog_Server("Error: Failed to parse directory response");
+ return;
+ }
+
+ cookie_directory_data *pCookieData;
+ HANDLE hContact;
+ // check request cookie
+ if (!FindCookie(wCookie, &hContact, (void**)&pCookieData) || !pCookieData)
+ {
+ NetLog_Server("Warning: Ignoring unrequested directory reply type (x%x, x%x)", requestSnac.wFamily, requestSnac.wSubtype);
+ return;
+ }
+ /// FIXME: we should really check the snac contents according to cookie data here ??
+
+ if (wPacketLen >= 3)
+ unpackByte(&databuf, &requestResult);
+ else
+ {
+ NetLog_Server("Error: Malformed directory response");
+ ReleaseCookie(wCookie);
+ return;
+ }
+ if (requestResult != 1 && requestResult != 4)
+ {
+ NetLog_Server("Error: Directory request failed, status %u", requestResult);
+
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_UPDATEOWNER)
+ BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_FAILED, (HANDLE)wCookie, 0);
+
+ ReleaseCookie(wCookie);
+ return;
+ }
+ WORD wLen;
+
+ unpackWord(&databuf, &wLen);
+ wPacketLen -= 3;
+ if (wLen)
+ NetLog_Server("Warning: Data in error message present!");
+
+ if (pCookieData->bRequestType == DIRECTORYREQUEST_UPDATEOWNER)
+ BroadcastAck(NULL, ACKTYPE_SETINFO, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0);
+ if (wPacketLen == 0x18)
+ {
+ DWORD64 qwMetaTime;
+ BYTE pbEmptyMetaToken[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+
+ unpackQWord(&databuf, &qwMetaTime);
+ setSettingBlob(NULL, DBSETTING_METAINFO_TIME, (BYTE*)&qwMetaTime, 8);
+
+ if (memcmp(databuf, pbEmptyMetaToken, 0x10))
+ setSettingBlob(NULL, DBSETTING_METAINFO_TOKEN, databuf, 0x10);
+ }
+ ReleaseCookie(wCookie);
+}
diff --git a/protocols/IcqOscarJ/src/fam_17signon.cpp b/protocols/IcqOscarJ/src/fam_17signon.cpp
new file mode 100644
index 0000000000..b35a432a48
--- /dev/null
+++ b/protocols/IcqOscarJ/src/fam_17signon.cpp
@@ -0,0 +1,175 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void CIcqProto::handleAuthorizationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_SIGNON_ERROR:
+ {
+ WORD wError;
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ LogFamilyError(ICQ_AUTHORIZATION_FAMILY, wError);
+ break;
+ }
+
+ case ICQ_SIGNON_AUTH_KEY:
+ handleAuthKeyResponse(pBuffer, wBufferLength, info);
+ break;
+
+ case ICQ_SIGNON_LOGIN_REPLY:
+ handleLoginReply(pBuffer, wBufferLength, info);
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_AUTHORIZATION_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+static void icq_encryptPassword(const char *szPassword, BYTE *encrypted)
+{
+ BYTE table[] =
+ {
+ 0xf3, 0x26, 0x81, 0xc4,
+ 0x39, 0x86, 0xdb, 0x92,
+ 0x71, 0xa3, 0xb9, 0xe6,
+ 0x53, 0x7a, 0x95, 0x7c
+ };
+
+ for (int i = 0; szPassword[i]; i++)
+ encrypted[i] = (szPassword[i] ^ table[i % 16]);
+}
+
+void CIcqProto::sendClientAuth(const char *szKey, WORD wKeyLen, BOOL bSecure)
+{
+ char szUin[UINMAXLEN];
+ WORD wUinLen;
+ icq_packet packet;
+
+ wUinLen = strlennull(strUID(m_dwLocalUIN, szUin));
+
+ packet.wLen = 70 + sizeof(CLIENT_ID_STRING) + wUinLen + wKeyLen + (m_bSecureConnection ? 4 : 0);
+
+ if (bSecure)
+ {
+ serverPacketInit(&packet, (WORD)(packet.wLen + 10));
+ packFNACHeader(&packet, ICQ_AUTHORIZATION_FAMILY, ICQ_SIGNON_LOGIN_REQUEST, 0, 0);
+ }
+ else
+ {
+ write_flap(&packet, ICQ_LOGIN_CHAN);
+ packDWord(&packet, 0x00000001);
+ }
+ packTLV(&packet, 0x0001, wUinLen, (LPBYTE)szUin);
+
+ if (bSecure)
+ { // Pack MD5 auth digest
+ packTLV(&packet, 0x0025, wKeyLen, (BYTE*)szKey);
+ packDWord(&packet, 0x004C0000); // empty TLV(0x4C): unknown
+ }
+ else
+ { // Pack old style password hash
+ BYTE hash[20];
+
+ icq_encryptPassword(szKey, hash);
+ packTLV(&packet, 0x0002, wKeyLen, hash);
+ }
+
+ // Pack client identification details.
+ packTLV(&packet, 0x0003, (WORD)sizeof(CLIENT_ID_STRING)-1, (LPBYTE)CLIENT_ID_STRING);
+ packTLVWord(&packet, 0x0017, CLIENT_VERSION_MAJOR);
+ packTLVWord(&packet, 0x0018, CLIENT_VERSION_MINOR);
+ packTLVWord(&packet, 0x0019, CLIENT_VERSION_LESSER);
+ packTLVWord(&packet, 0x001a, CLIENT_VERSION_BUILD);
+ packTLVWord(&packet, 0x0016, CLIENT_ID_CODE);
+ packTLVDWord(&packet, 0x0014, CLIENT_DISTRIBUTION);
+ packTLV(&packet, 0x000f, 0x0002, (LPBYTE)CLIENT_LANGUAGE);
+ packTLV(&packet, 0x000e, 0x0002, (LPBYTE)CLIENT_COUNTRY);
+ packTLV(&packet, 0x0094, 0x0001, &m_bConnectionLost); // CLIENT_RECONNECT flag
+ if (m_bSecureConnection)
+ packDWord(&packet, 0x008C0000); // empty TLV(0x8C): use SSL
+
+ sendServPacket(&packet);
+}
+
+void CIcqProto::handleAuthKeyResponse(BYTE *buf, WORD wPacketLen, serverthread_info *info)
+{
+ WORD wKeyLen;
+ char szKey[64] = {0};
+ mir_md5_state_t state;
+ mir_md5_byte_t digest[16];
+
+#ifdef _DEBUG
+ NetLog_Server("Received %s", "ICQ_SIGNON_AUTH_KEY");
+#endif
+
+ if (wPacketLen < 2)
+ {
+ NetLog_Server("Malformed %s", "ICQ_SIGNON_AUTH_KEY");
+ icq_LogMessage(LOG_FATAL, LPGEN("Secure login failed.\nInvalid server response."));
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ return;
+ }
+
+ unpackWord(&buf, &wKeyLen);
+ wPacketLen -= 2;
+
+ if (!wKeyLen || wKeyLen > wPacketLen || wKeyLen > sizeof(szKey))
+ {
+ NetLog_Server("Invalid length in %s: %u", "ICQ_SIGNON_AUTH_KEY", wKeyLen);
+ icq_LogMessage(LOG_FATAL, LPGEN("Secure login failed.\nInvalid key length."));
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ return;
+ }
+
+ unpackString(&buf, szKey, wKeyLen);
+
+ mir_md5_init(&state);
+ mir_md5_append(&state, info->szAuthKey, info->wAuthKeyLen);
+ mir_md5_finish(&state, digest);
+
+ mir_md5_init(&state);
+ mir_md5_append(&state, (LPBYTE)szKey, wKeyLen);
+ mir_md5_append(&state, digest, 16);
+ mir_md5_append(&state, (LPBYTE)CLIENT_MD5_STRING, sizeof(CLIENT_MD5_STRING)-1);
+ mir_md5_finish(&state, digest);
+
+#ifdef _DEBUG
+ NetLog_Server("Sending ICQ_SIGNON_LOGIN_REQUEST to login server");
+#endif
+ sendClientAuth((char*)digest, 0x10, TRUE);
+}
diff --git a/protocols/IcqOscarJ/src/families.h b/protocols/IcqOscarJ/src/families.h
new file mode 100644
index 0000000000..61ac38f17a
--- /dev/null
+++ b/protocols/IcqOscarJ/src/families.h
@@ -0,0 +1,74 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Declaration for handlers of Channel 2 SNAC Families
+//
+// -----------------------------------------------------------------------------
+#ifndef __FAMILIES_H
+#define __FAMILIES_H
+
+
+struct message_ack_params
+{
+ BYTE bType;
+ DWORD dwUin;
+ DWORD dwMsgID1;
+ DWORD dwMsgID2;
+ directconnect *pDC;
+ WORD wCookie;
+ int msgType;
+ BYTE bFlags;
+};
+
+#define MAT_SERVER_ADVANCED 0
+#define MAT_DIRECT 1
+
+
+/* handleMessageTypes(): mMsgFlags constants */
+#define MTF_DIRECT 1
+#define MTF_PLUGIN 2
+#define MTF_STATUS_EXTENDED 4
+
+
+struct UserInfoRecordItem
+{
+ WORD wTLV;
+ int dbType;
+ char *szDbSetting;
+};
+
+/*---------* Functions *---------------*/
+
+int getPluginTypeIdLen(int nTypeID);
+void packPluginTypeId(icq_packet *packet, int nTypeID);
+
+#define BUL_ALLCONTACTS 0
+#define BUL_VISIBLE 1
+#define BUL_INVISIBLE 2
+#define BUL_TEMPVISIBLE 4
+
+
+#endif /* __FAMILIES_H */
diff --git a/protocols/IcqOscarJ/src/globals.h b/protocols/IcqOscarJ/src/globals.h
new file mode 100644
index 0000000000..c0d8326689
--- /dev/null
+++ b/protocols/IcqOscarJ/src/globals.h
@@ -0,0 +1,57 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Contains global types & variables declarations.
+//
+// -----------------------------------------------------------------------------
+
+#ifndef __GLOBALS_H
+#define __GLOBALS_H
+
+
+typedef char uid_str[MAX_PATH];
+
+// from init.cpp
+extern HINSTANCE hInst;
+extern DWORD MIRANDA_VERSION;
+
+extern IcqIconHandle hStaticIcons[];
+
+extern const int moodXStatus[];
+
+// from fam_04message.cpp
+struct icq_mode_messages
+{
+ char *szOnline;
+ char *szAway;
+ char *szNa;
+ char *szDnd;
+ char *szOccupied;
+ char *szFfc;
+};
+
+
+#endif /* __GLOBALS_H */
diff --git a/protocols/IcqOscarJ/src/guids.h b/protocols/IcqOscarJ/src/guids.h
new file mode 100644
index 0000000000..24073deec4
--- /dev/null
+++ b/protocols/IcqOscarJ/src/guids.h
@@ -0,0 +1,81 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Contains helper functions to handle oscar message GUIDs.
+//
+// -----------------------------------------------------------------------------
+
+#ifndef __GUIDS_H
+#define __GUIDS_H
+
+
+typedef DWORD plugin_guid[4];
+
+// Message Capability GUIDs
+static const plugin_guid MCAP_SRV_RELAY_FMT = {MCAP_SRV_RELAY_FMT_s};
+static const plugin_guid MCAP_REVERSE_DC_REQ = {MCAP_REVERSE_DC_REQ_s};
+static const plugin_guid MCAP_FILE_TRANSFER = {MCAP_FILE_TRANSFER_s};
+static const plugin_guid MCAP_CONTACTS = {MCAP_CONTACTS_s};
+
+// Plugin GUIDs
+static const plugin_guid PSIG_MESSAGE = {PSIG_MESSAGE_s};
+static const plugin_guid PSIG_INFO_PLUGIN = {PSIG_INFO_PLUGIN_s};
+static const plugin_guid PSIG_STATUS_PLUGIN = {PSIG_STATUS_PLUGIN_s};
+
+// Plugin Message GUIDs
+static const plugin_guid PMSG_QUERY_INFO = {PMSG_QUERY_INFO_s};
+static const plugin_guid PMSG_QUERY_STATUS = {PMSG_QUERY_STATUS_s};
+
+// Message GUIDs
+static const plugin_guid MGTYPE_MESSAGE = {MGTYPE_MESSAGE_s};
+static const plugin_guid MGTYPE_STATUSMSGEXT = {MGTYPE_STATUSMSGEXT_s};
+static const plugin_guid MGTYPE_FILE = {MGTYPE_FILE_s};
+static const plugin_guid MGTYPE_WEBURL = {MGTYPE_WEBURL_s};
+static const plugin_guid MGTYPE_CONTACTS = {MGTYPE_CONTACTS_s};
+static const plugin_guid MGTYPE_GREETING_CARD = {MGTYPE_GREETING_CARD_s};
+static const plugin_guid MGTYPE_CHAT = {MGTYPE_CHAT_s};
+static const plugin_guid MGTYPE_SMS_MESSAGE = {MGTYPE_SMS_MESSAGE_s};
+static const plugin_guid MGTYPE_XTRAZ_SCRIPT = {MGTYPE_XTRAZ_SCRIPT_s};
+
+
+// make GUID checks easy
+static BOOL CompareGUIDs(DWORD q1,DWORD q2,DWORD q3,DWORD q4, const plugin_guid guid)
+{
+ return ((q1 == guid[0]) && (q2 == guid[1]) && (q3 == guid[2]) && (q4 == guid[3]))?TRUE:FALSE;
+}
+
+
+// pack entire GUID into icq packet
+static __inline void packGUID(icq_packet *packet, const plugin_guid guid)
+{
+ packDWord(packet, guid[0]);
+ packDWord(packet, guid[1]);
+ packDWord(packet, guid[2]);
+ packDWord(packet, guid[3]);
+}
+
+
+#endif /* __GUIDS_H */
diff --git a/protocols/IcqOscarJ/src/i18n.cpp b/protocols/IcqOscarJ/src/i18n.cpp
new file mode 100644
index 0000000000..535feebcdf
--- /dev/null
+++ b/protocols/IcqOscarJ/src/i18n.cpp
@@ -0,0 +1,541 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Contains helper functions to convert text messages between different
+// character sets.
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+static BOOL bHasCP_UTF8 = FALSE;
+
+
+void InitI18N(void)
+{
+ CPINFO CPInfo;
+
+
+ bHasCP_UTF8 = GetCPInfo(CP_UTF8, &CPInfo);
+}
+
+
+
+// Returns true if the buffer only contains 7-bit characters.
+BOOL __stdcall IsUSASCII(const char *pBuffer, int nSize)
+{
+ for (int nIndex = 0; nIndex < nSize; nIndex++)
+ if (BYTE(pBuffer[nIndex]) > 0x7F)
+ return FALSE;
+
+ return TRUE;
+}
+
+// Returns true if the unicode buffer only contains 7-bit characters.
+BOOL __stdcall IsUnicodeAscii(const WCHAR *pBuffer, int nSize)
+{
+ for (int nIndex = 0; nIndex < nSize; nIndex++)
+ if (WORD(pBuffer[nIndex]) > 0x7F)
+ return FALSE;
+
+ return TRUE;
+}
+
+
+// Scans a string encoded in UTF-8 to verify that it contains
+// only valid sequences. It will return 1 if the string contains
+// only legitimate encoding sequences; otherwise it will return 0;
+// From 'Secure Programming Cookbook', John Viega & Matt Messier, 2003
+int __stdcall UTF8_IsValid(const char *pszInput)
+{
+ int nb;
+ if (!pszInput)
+ return 0;
+
+ for ( BYTE* c = ( BYTE*)pszInput; *c; c += (nb + 1))
+ {
+ if (!(*c & 0x80))
+ nb = 0;
+ else if ((*c & 0xc0) == 0x80) return 0;
+ else if ((*c & 0xe0) == 0xc0) nb = 1;
+ else if ((*c & 0xf0) == 0xe0) nb = 2;
+ else if ((*c & 0xf8) == 0xf0) nb = 3;
+ else if ((*c & 0xfc) == 0xf8) nb = 4;
+ else if ((*c & 0xfe) == 0xfc) nb = 5;
+ else nb = 0;
+
+ for (int i = 1; i<=nb; i++) // we this forward, do not cross end of string
+ if ((*(c + i) & 0xc0) != 0x80)
+ return 0;
+ }
+
+ return 1;
+}
+
+
+int __stdcall get_utf8_size(const WCHAR *unicode)
+{
+ int size = 0;
+ int index = 0;
+ /* calculate the size of the utf-8 string */
+ WCHAR c = unicode[index++];
+ while (c)
+ {
+ if (c < 0x0080)
+ size += 1;
+ else if (c < 0x0800)
+ size += 2;
+ else
+ size += 3;
+ c = unicode[index++];
+ }
+ return size;
+}
+
+
+// returns ansi string in all cases
+char* __stdcall detect_decode_utf8(const char *from)
+{
+ char *temp = NULL;
+
+ if (IsUSASCII(from, strlennull(from)) || !UTF8_IsValid(from) || !utf8_decode(from, &temp)) return (char*)from;
+ SAFE_FREE((void**)&from);
+
+ return temp;
+}
+
+
+/*
+* The following UTF8 routines are
+*
+* Copyright (C) 2001 Peter Harris <peter.harris@hummingbird.com>
+* Copyright (C) 2001 Edmund Grimley Evans <edmundo@rano.org>
+*
+* under a GPL license
+*
+* --------------------------------------------------------------
+* Convert a string between UTF-8 and the locale's charset.
+* Invalid bytes are replaced by '#', and characters that are
+* not available in the target encoding are replaced by '?'.
+*
+* If the locale's charset is not set explicitly then it is
+* obtained using nl_langinfo(CODESET), where available, the
+* environment variable CHARSET, or assumed to be US-ASCII.
+*
+* Return value of conversion functions:
+*
+* -1 : memory allocation failed
+* 0 : data was converted exactly
+* 1 : valid data was converted approximately (using '?')
+* 2 : input was invalid (but still converted, using '#')
+* 3 : unknown encoding (but still converted, using '?')
+*/
+
+
+
+/*
+* Convert a string between UTF-8 and the locale's charset.
+*/
+char* __stdcall make_utf8_string_static(const WCHAR *unicode, char *utf8, size_t utf_size)
+{
+ int index = 0;
+ unsigned int out_index = 0;
+ unsigned short c;
+
+ c = unicode[index++];
+ while (c)
+ {
+ if (c < 0x080)
+ {
+ if (out_index + 1 >= utf_size) break;
+ utf8[out_index++] = (unsigned char)c;
+ }
+ else if (c < 0x800)
+ {
+ if (out_index + 2 >= utf_size) break;
+ utf8[out_index++] = 0xc0 | (c >> 6);
+ utf8[out_index++] = 0x80 | (c & 0x3f);
+ }
+ else
+ {
+ if (out_index + 3 >= utf_size) break;
+ utf8[out_index++] = 0xe0 | (c >> 12);
+ utf8[out_index++] = 0x80 | ((c >> 6) & 0x3f);
+ utf8[out_index++] = 0x80 | (c & 0x3f);
+ }
+ c = unicode[index++];
+ }
+ utf8[out_index] = 0x00;
+
+ return utf8;
+}
+
+
+char* __stdcall make_utf8_string(const WCHAR *unicode)
+{
+ if (!unicode) return NULL;
+
+ /* first calculate the size of the target string */
+ size_t size = get_utf8_size(unicode);
+
+ char *out = (char*)SAFE_MALLOC(size + 1);
+ if (!out)
+ return NULL;
+
+ return make_utf8_string_static(unicode, out, size + 1);
+}
+
+
+WCHAR* __stdcall make_unicode_string_static(const char *utf8, WCHAR *unicode, size_t unicode_size)
+{
+ unsigned int out_index = 0;
+
+ if (utf8)
+ {
+ unsigned int index = 0;
+ unsigned char c = utf8[index++];
+
+ while (c)
+ {
+ if (out_index + 1 >= unicode_size) break;
+ if ((c & 0x80) == 0)
+ {
+ unicode[out_index++] = c;
+ }
+ else if ((c & 0xe0) == 0xe0)
+ {
+ unicode[out_index] = (c & 0x1F) << 12;
+ c = utf8[index++];
+ unicode[out_index] |= (c & 0x3F) << 6;
+ c = utf8[index++];
+ unicode[out_index++] |= (c & 0x3F);
+ }
+ else
+ {
+ unicode[out_index] = (c & 0x3F) << 6;
+ c = utf8[index++];
+ unicode[out_index++] |= (c & 0x3F);
+ }
+ c = utf8[index++];
+ }
+ }
+ unicode[out_index] = 0;
+
+ return unicode;
+}
+
+
+WCHAR* __stdcall make_unicode_string(const char *utf8)
+{
+ int size = 0, index = 0;
+
+ if (!utf8) return NULL;
+
+ /* first calculate the size of the target string */
+ unsigned char c = utf8[index++];
+ while (c)
+ {
+ if ((c & 0x80) == 0)
+ {
+ index += 0;
+ }
+ else if ((c & 0xe0) == 0xe0)
+ {
+ index += 2;
+ }
+ else
+ {
+ index += 1;
+ }
+ size += 1;
+ c = utf8[index++];
+ }
+
+ WCHAR *out = (WCHAR*)SAFE_MALLOC((size + 1) * sizeof(WCHAR));
+ if (!out)
+ return NULL;
+ else
+ return make_unicode_string_static(utf8, out, size + 1);
+}
+
+
+int __stdcall utf8_encode(const char *from, char **to)
+{
+ int wchars = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from, strlennull(from), NULL, 0);
+
+ if (wchars == 0)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ return -1;
+ }
+
+ WCHAR *unicode = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR));
+ ZeroMemory(unicode, (wchars + 1) * sizeof(WCHAR));
+
+ int err = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, from, strlennull(from), unicode, wchars);
+ if (err != wchars)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ return -1;
+ }
+
+ /* On NT-based windows systems, we could use WideCharToMultiByte(), but
+ * MS doesn't actually have a consistent API across win32.
+ */
+ *to = make_utf8_string(unicode);
+ return 0;
+}
+
+
+char* __stdcall ansi_to_utf8(const char *ansi)
+{
+ char *szUtf = NULL;
+
+ if (strlennull(ansi))
+ {
+ utf8_encode(ansi, &szUtf);
+ return szUtf;
+ }
+
+ return null_strdup("");
+}
+
+
+char* __stdcall ansi_to_utf8_codepage(const char *ansi, WORD wCp)
+{
+ int wchars = strlennull(ansi);
+ WCHAR *unicode = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR));
+ ZeroMemory(unicode, (wchars + 1) * sizeof(WCHAR));
+
+ MultiByteToWideChar(wCp, MB_PRECOMPOSED, ansi, wchars, unicode, wchars);
+
+ return make_utf8_string(unicode);
+}
+
+
+// Returns 0 on error, 1 on success
+int __stdcall utf8_decode_codepage(const char *from, char **to, WORD wCp)
+{
+ int nResult = 0;
+
+ _ASSERTE(!(*to)); // You passed a non-zero pointer, make sure it doesnt point to unfreed memory
+
+ // Validate the string
+ if (!UTF8_IsValid(from))
+ return 0;
+
+ // Use the native conversion routines when available
+ if (bHasCP_UTF8)
+ {
+ int inlen = strlennull(from) + 1;
+ WCHAR *wszTemp = (WCHAR *)_alloca(inlen * sizeof(WCHAR));
+ ZeroMemory(wszTemp, inlen * sizeof(WCHAR));
+
+ // Convert the UTF-8 string to UCS
+ if (MultiByteToWideChar(CP_UTF8, 0, from, -1, wszTemp, inlen))
+ {
+ // Convert the UCS string to local ANSI codepage
+ *to = (char*)SAFE_MALLOC(inlen);
+ if (WideCharToMultiByte(wCp, 0, wszTemp, -1, *to, inlen, NULL, NULL))
+ {
+ nResult = 1;
+ }
+ else
+ {
+ SAFE_FREE(to);
+ }
+ }
+ }
+ else
+ {
+ int chars = strlennull(from) + 1;
+ WCHAR *unicode = (WCHAR*)_alloca(chars * sizeof(WCHAR));
+ make_unicode_string_static(from, unicode, chars);
+
+ chars = WideCharToMultiByte(wCp, WC_COMPOSITECHECK, unicode, -1, NULL, 0, NULL, NULL);
+
+ if (chars == 0)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ return 0;
+ }
+
+ *to = (char*)SAFE_MALLOC((chars + 1)*sizeof(char));
+ if (*to == NULL)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Out of memory processing string to local charset\n");
+#endif
+ return 0;
+ }
+
+ int err = WideCharToMultiByte(wCp, WC_COMPOSITECHECK, unicode, -1, *to, chars, NULL, NULL);
+ if (err != chars)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ SAFE_FREE(to);
+ return 0;
+ }
+
+ nResult = 1;
+ }
+
+ return nResult;
+}
+
+
+// Standard version with current codepage
+int __stdcall utf8_decode(const char *from, char **to)
+{
+ return utf8_decode_codepage(from, to, CP_ACP);
+}
+
+
+// Returns 0 on error, 1 on success
+int __stdcall utf8_decode_static(const char *from, char *to, int to_size)
+{
+ int nResult = 0;
+
+ _ASSERTE(to); // You passed a zero pointer
+
+ // Validate the string
+ if (!UTF8_IsValid(from))
+ return 0;
+
+ // Clear target
+ ZeroMemory(to, to_size);
+
+ // Use the native conversion routines when available
+ if (bHasCP_UTF8)
+ {
+ int inlen = strlennull(from) + 1;
+ WCHAR *wszTemp = (WCHAR*)_alloca(inlen * sizeof(WCHAR));
+ ZeroMemory(wszTemp, inlen * sizeof(WCHAR));
+
+ // Convert the UTF-8 string to UCS
+ if (MultiByteToWideChar(CP_UTF8, 0, from, -1, wszTemp, inlen))
+ {
+ // Convert the UCS string to local ANSI codepage
+ if (WideCharToMultiByte(CP_ACP, 0, wszTemp, -1, to, to_size, NULL, NULL))
+ {
+ nResult = 1;
+ }
+ }
+ }
+ else
+ {
+ size_t chars = strlennull(from) + 1;
+ WCHAR *unicode = (WCHAR*)_alloca(chars * sizeof(WCHAR));
+
+ make_unicode_string_static(from, unicode, chars);
+
+ WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, -1, to, to_size, NULL, NULL);
+
+ nResult = 1;
+ }
+
+ return nResult;
+}
+
+
+WCHAR* __stdcall ansi_to_unicode(const char *ansi)
+{
+ int wchars = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ansi, strlennull(ansi), NULL, 0);
+
+ if (wchars == 0)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ return NULL;
+ }
+
+ WCHAR *unicode = (WCHAR*)SAFE_MALLOC((wchars + 1) * sizeof(WCHAR));
+
+ int err = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, ansi, strlennull(ansi), unicode, wchars);
+ if (err != wchars)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ SAFE_FREE(&unicode);
+ return NULL;
+ }
+ return unicode;
+}
+
+
+char* __stdcall unicode_to_ansi_static(const WCHAR *unicode, char *ansi, int ansi_size)
+{
+ ZeroMemory(ansi, ansi_size);
+
+ if (WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, strlennull(unicode), ansi, ansi_size, NULL, NULL) > 1)
+ return ansi;
+
+ return NULL;
+}
+
+
+char* __stdcall unicode_to_ansi(const WCHAR *unicode)
+{
+ int chars = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, strlennull(unicode), NULL, 0, NULL, NULL);
+
+ if (chars == 0)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ return NULL;
+ }
+
+ char* ansi = (char*)SAFE_MALLOC((chars + 1)*sizeof(char));
+ if (ansi == NULL)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Out of memory processing string to local charset\n");
+#endif
+ return NULL;
+ }
+
+ int err = WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK, unicode, strlennull(unicode), ansi, chars, NULL, NULL);
+ if (err != chars)
+ {
+#ifdef _DEBUG
+ fprintf(stderr, "Unicode translation error %d\n", GetLastError());
+#endif
+ return NULL;
+ }
+
+ return ansi;
+}
diff --git a/protocols/IcqOscarJ/src/i18n.h b/protocols/IcqOscarJ/src/i18n.h
new file mode 100644
index 0000000000..eaeaad54f3
--- /dev/null
+++ b/protocols/IcqOscarJ/src/i18n.h
@@ -0,0 +1,70 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Helper functions to convert text messages between different character sets.
+//
+// -----------------------------------------------------------------------------
+
+#ifndef __I18N_H
+#define __I18N_H
+
+
+BOOL __stdcall IsUSASCII(const char *pBuffer, int nSize);
+BOOL __stdcall IsUnicodeAscii(const WCHAR *pBuffer, int nSize);
+int __stdcall UTF8_IsValid(const char *pszInput);
+
+int __stdcall get_utf8_size(const WCHAR *unicode);
+
+char* __stdcall detect_decode_utf8(const char *from);
+
+WCHAR* __stdcall make_unicode_string(const char *utf8);
+WCHAR* __stdcall make_unicode_string_static(const char *utf8, WCHAR *unicode, size_t unicode_size);
+
+char* __stdcall make_utf8_string(const WCHAR *unicode);
+char* __stdcall make_utf8_string_static(const WCHAR *unicode, char *utf8, size_t utf_size);
+
+char* __stdcall ansi_to_utf8(const char *ansi);
+char* __stdcall ansi_to_utf8_codepage(const char *ansi, WORD wCp);
+
+WCHAR* __stdcall ansi_to_unicode(const char *ansi);
+char* __stdcall unicode_to_ansi(const WCHAR *unicode);
+char* __stdcall unicode_to_ansi_static(const WCHAR *unicode, char *ansi, int ansi_size);
+
+int __stdcall utf8_encode(const char *from, char **to);
+int __stdcall utf8_decode(const char *from, char **to);
+int __stdcall utf8_decode_codepage(const char *from, char **to, WORD wCp);
+int __stdcall utf8_decode_static(const char *from, char *to, int to_size);
+
+#define tchar_to_utf8 make_utf8_string
+#define utf8_to_tchar_static make_unicode_string_static
+#define utf8_to_tchar make_unicode_string
+#define ansi_to_tchar ansi_to_unicode
+#define tchar_to_ansi unicode_to_ansi
+
+void InitI18N(void);
+
+
+#endif /* __I18N_H */
diff --git a/protocols/IcqOscarJ/src/iconlib.cpp b/protocols/IcqOscarJ/src/iconlib.cpp
new file mode 100644
index 0000000000..7c93bae17a
--- /dev/null
+++ b/protocols/IcqOscarJ/src/iconlib.cpp
@@ -0,0 +1,92 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Support for IcoLib plug-in
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+#include "m_icolib.h"
+
+IcqIconHandle IconLibDefine(const char *desc, const char *section, const char *module, const char *ident, const TCHAR *def_file, int def_idx)
+{
+ SKINICONDESC sid = {0};
+ sid.cbSize = sizeof(sid);
+ sid.pwszSection = make_unicode_string(section);
+ sid.pwszDescription = make_unicode_string(desc);
+ sid.flags = SIDF_ALL_TCHAR;
+
+ char szName[MAX_PATH + 128];
+ null_snprintf(szName, sizeof(szName), "%s_%s", module ? module : ICQ_PROTOCOL_NAME, ident);
+ sid.pszName = szName;
+ sid.ptszDefaultFile = (TCHAR*)def_file;
+ sid.iDefaultIndex = def_idx;
+
+ IcqIconHandle hIcon = (IcqIconHandle)SAFE_MALLOC(sizeof(IcqIconHandle_s));
+ hIcon->szName = null_strdup(sid.pszName);
+ hIcon->hIcoLib = Skin_AddIcon(&sid);
+
+ SAFE_FREE(&sid.pwszSection);
+ SAFE_FREE(&sid.pwszDescription);
+
+ return hIcon;
+}
+
+
+void IconLibRemove(IcqIconHandle *phIcon)
+{
+ if (phIcon && *phIcon)
+ {
+ IcqIconHandle hIcon = *phIcon;
+
+ CallService(MS_SKIN2_REMOVEICON, 0, (LPARAM)hIcon->szName);
+ SAFE_FREE(&hIcon->szName);
+ SAFE_FREE((void**)phIcon);
+ }
+}
+
+
+HANDLE IcqIconHandle_s::Handle()
+{
+ if (this)
+ return hIcoLib;
+
+ return NULL;
+}
+
+
+HICON IcqIconHandle_s::GetIcon(bool big)
+{
+ if (this)
+ return (HICON)CallService(MS_SKIN2_GETICONBYHANDLE, big, (LPARAM)hIcoLib);
+
+ return NULL;
+}
+
+void IcqIconHandle_s::ReleaseIcon(bool big)
+{
+ CallService(big ? MS_SKIN2_RELEASEICONBIG : MS_SKIN2_RELEASEICON, 0, (LPARAM)szName);
+}
+
diff --git a/protocols/IcqOscarJ/src/iconlib.h b/protocols/IcqOscarJ/src/iconlib.h
new file mode 100644
index 0000000000..3a7482f872
--- /dev/null
+++ b/protocols/IcqOscarJ/src/iconlib.h
@@ -0,0 +1,52 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Headers for IconLib Plugin / module support
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICONLIB_H
+#define __ICONLIB_H
+
+
+struct IcqIconHandle_s
+{
+ char *szName;
+ HANDLE hIcoLib;
+
+ HANDLE Handle();
+ HICON GetIcon(bool big = false);
+ void ReleaseIcon(bool big = false);
+};
+
+typedef IcqIconHandle_s *IcqIconHandle;
+
+
+IcqIconHandle IconLibDefine(const char *desc, const char *section, const char *module, const char *ident, const TCHAR *def_file, int def_idx);
+void IconLibRemove(IcqIconHandle *phIcon);
+
+
+
+#endif /* __ICONLIB_H */
diff --git a/protocols/IcqOscarJ/src/icq_advsearch.cpp b/protocols/IcqOscarJ/src/icq_advsearch.cpp
new file mode 100644
index 0000000000..6c3a3ffa89
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_advsearch.cpp
@@ -0,0 +1,185 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+static void InitComboBox(HWND hwndCombo, const FieldNamesItem *names)
+{
+ int iItem;
+ int i;
+
+ iItem = ComboBoxAddStringUtf(hwndCombo, NULL, 0);
+ SendMessage(hwndCombo, CB_SETCURSEL, iItem, 0);
+
+ if (names){
+ for (i = 0; names[i].text; i++) {
+ iItem = ComboBoxAddStringUtf(hwndCombo, names[i].text, names[i].code);
+ }
+ }
+ else {
+ int ctryCount;
+ struct CountryListEntry *countries;
+ CallService( MS_UTILS_GETCOUNTRYLIST, ( WPARAM )&ctryCount, ( LPARAM )&countries );
+ for (i = 0; i < ctryCount; i++) {
+ if (countries[i].id != 0xFFFF && countries[i].id != 0)
+ iItem = ComboBoxAddStringUtf(hwndCombo, LPGEN(countries[i].szName), countries[i].id);
+ }
+ }
+}
+
+INT_PTR CALLBACK AdvancedSearchDlgProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ switch(message)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_GENDER), genderField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_AGERANGE), agesField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_MARITALSTATUS), maritalField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_WORKFIELD), occupationField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_ORGANISATION), affiliationField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_LANGUAGE), languageField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_COUNTRY), countryField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_INTERESTSCAT), interestsField);
+ InitComboBox(GetDlgItem(hwndDlg, IDC_PASTCAT), pastField);
+
+ return TRUE;
+
+ case WM_COMMAND:
+ {
+ switch(LOWORD(wParam))
+ {
+
+ case IDOK:
+ SendMessage(GetParent(hwndDlg), WM_COMMAND, MAKEWPARAM(IDOK, BN_CLICKED), (LPARAM)GetDlgItem(GetParent(hwndDlg), IDOK));
+ break;
+
+ case IDCANCEL:
+ // CheckDlgButton(GetParent(hwndDlg),IDC_ADVANCED,BST_UNCHECKED);
+ // SendMessage(GetParent(hwndDlg),WM_COMMAND,MAKEWPARAM(IDC_ADVANCED,BN_CLICKED),(LPARAM)GetDlgItem(GetParent(hwndDlg),IDC_ADVANCED));
+ break;
+
+ default:
+ break;
+
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+static DWORD getCurItemData(HWND hwndDlg, UINT iCtrl)
+{
+ return SendDlgItemMessage(hwndDlg, iCtrl, CB_GETITEMDATA, SendDlgItemMessage(hwndDlg, iCtrl, CB_GETCURSEL, 0, 0), 0);
+}
+
+static void searchPackTLVLNTS(PBYTE *buf, int *buflen, HWND hwndDlg, UINT idControl, WORD wType)
+{
+ char str[512];
+
+ GetDlgItemTextA(hwndDlg, idControl, str, sizeof(str));
+
+ ppackLETLVLNTS(buf, buflen, str, wType, 0);
+}
+
+static void searchPackTLVWordLNTS(PBYTE *buf, int *buflen, HWND hwndDlg, UINT idControl, WORD w, WORD wType)
+{
+ char str[512];
+
+ GetDlgItemTextA(hwndDlg, idControl, str, sizeof(str));
+
+ ppackLETLVWordLNTS(buf, buflen, w, str, wType, 0);
+}
+
+static PBYTE createAdvancedSearchStructureTLV(HWND hwndDlg, int *length)
+{
+ PBYTE buf = NULL;
+ int buflen = 0;
+
+ ppackLEWord(&buf, &buflen, META_SEARCH_GENERIC); /* subtype: full search */
+
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_FIRSTNAME, TLV_FIRSTNAME);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_LASTNAME, TLV_LASTNAME);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_NICK, TLV_NICKNAME);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_EMAIL, TLV_EMAIL);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_CITY, TLV_CITY);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_STATE, TLV_STATE);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_COMPANY, TLV_COMPANY);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_DEPARTMENT, TLV_DEPARTMENT);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_POSITION, TLV_POSITION);
+ searchPackTLVLNTS(&buf, &buflen, hwndDlg, IDC_KEYWORDS, TLV_KEYWORDS);
+
+ ppackLETLVDWord(&buf, &buflen, (DWORD)getCurItemData(hwndDlg, IDC_AGERANGE), TLV_AGERANGE, 0);
+
+ BYTE b = (BYTE)getCurItemData(hwndDlg, IDC_GENDER);
+ switch (b) {
+ case 'F': b = 1; break;
+ case 'M': b = 2; break;
+ default: b = 0;
+ };
+ ppackLETLVByte(&buf, &buflen, b, TLV_GENDER, 0);
+ ppackLETLVByte(&buf, &buflen, (BYTE)getCurItemData(hwndDlg, IDC_MARITALSTATUS), TLV_MARITAL, 0);
+ ppackLETLVWord(&buf, &buflen, (WORD)getCurItemData(hwndDlg, IDC_LANGUAGE), TLV_LANGUAGE, 0);
+ ppackLETLVWord(&buf, &buflen, (WORD)getCurItemData(hwndDlg, IDC_COUNTRY), TLV_COUNTRY, 0);
+ ppackLETLVWord(&buf, &buflen, (WORD)getCurItemData(hwndDlg, IDC_WORKFIELD), TLV_OCUPATION, 0);
+
+ WORD w = (WORD)getCurItemData(hwndDlg, IDC_PASTCAT);
+ searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_PASTKEY, w, TLV_PASTINFO);
+
+ w = (WORD)getCurItemData(hwndDlg, IDC_INTERESTSCAT);
+ searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_INTERESTSKEY, w, TLV_INTERESTS);
+
+ w = (WORD)getCurItemData(hwndDlg, IDC_ORGANISATION);
+ searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_ORGKEYWORDS, w, TLV_AFFILATIONS);
+
+ w = (WORD)getCurItemData(hwndDlg, IDC_HOMEPAGECAT);
+ if (w != 0xFFFF)
+ searchPackTLVWordLNTS(&buf, &buflen, hwndDlg, IDC_HOMEPAGEKEY, w, TLV_HOMEPAGE);
+
+ if (IsDlgButtonChecked(hwndDlg, IDC_ONLINEONLY))
+ ppackLETLVByte(&buf, &buflen, 1, TLV_ONLINEONLY, 1);
+
+ if (length)
+ *length = buflen;
+
+ return buf;
+}
+
+PBYTE createAdvancedSearchStructure(HWND hwndDlg, int *length)
+{
+ if (!hwndDlg)
+ return NULL;
+
+ return createAdvancedSearchStructureTLV(hwndDlg, length);
+}
diff --git a/protocols/IcqOscarJ/src/icq_advsearch.h b/protocols/IcqOscarJ/src/icq_advsearch.h
new file mode 100644
index 0000000000..83bf35af93
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_advsearch.h
@@ -0,0 +1,32 @@
+// ---------------------------------------------------------------------------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,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+
+
+INT_PTR CALLBACK AdvancedSearchDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam);
+PBYTE createAdvancedSearchStructure(HWND hwndDlg,int *length);
diff --git a/protocols/IcqOscarJ/src/icq_avatar.cpp b/protocols/IcqOscarJ/src/icq_avatar.cpp
new file mode 100644
index 0000000000..6e61f2bc0d
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_avatar.cpp
@@ -0,0 +1,1814 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Manages Avatar connection, provides internal service for handling avatars
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+#include "m_folders.h"
+
+
+BYTE hashEmptyAvatar[9] = {0x00, 0x01, 0x00, 0x05, 0x02, 0x01, 0xD2, 0x04, 0x72};
+
+
+avatars_request::avatars_request(int type)
+{
+ this->type = type;
+}
+
+
+avatars_request::~avatars_request()
+{
+ if (this)
+ {
+ switch (type)
+ {
+ case ART_UPLOAD:
+ SAFE_FREE((void**)&pData);
+ break;
+ case ART_GET:
+ SAFE_FREE((void**)&hash);
+ SAFE_FREE(&szFile);
+ break;
+ case ART_BLOCK:
+ break;
+ }
+ }
+}
+
+
+avatars_request* CIcqProto::ReleaseAvatarRequestInQueue(avatars_request *request)
+{
+ avatars_request *pNext = request->pNext;
+
+ avatars_request **par = &m_avatarsQueue;
+ avatars_request *ar = m_avatarsQueue;
+
+ while (ar)
+ {
+ if (ar == request)
+ { // found it, remove
+ *par = ar->pNext;
+ break;
+ }
+ par = &ar->pNext;
+ ar = ar->pNext;
+ }
+
+ delete request;
+ return pNext;
+}
+
+
+void CIcqProto::InitAvatars()
+{
+ if (!bAvatarsFolderInited)
+ { // do it only once
+ bAvatarsFolderInited = TRUE;
+
+ if (ServiceExists(MS_FOLDERS_REGISTER_PATH))
+ { // check if it does make sense
+ TCHAR tszPath[MAX_PATH * 2];
+ null_snprintf(tszPath, MAX_PATH * 2, _T("%%miranda_avatarcache%%\\") _T(TCHAR_STR_PARAM) _T("\\"), m_szModuleName);
+
+ hAvatarsFolder = FoldersRegisterCustomPathT(m_szModuleName, "Avatars Cache", tszPath);
+ }
+ }
+}
+
+
+TCHAR* CIcqProto::GetOwnAvatarFileName()
+{
+ DBVARIANT dbvFile = {DBVT_DELETED};
+
+ if (!getSettingStringT(NULL, "AvatarFile", &dbvFile))
+ {
+ TCHAR tmp[MAX_PATH * 2];
+ CallService(MS_UTILS_PATHTOABSOLUTET, (WPARAM)dbvFile.ptszVal, (LPARAM)tmp);
+ ICQFreeVariant(&dbvFile);
+
+ return null_strdup(tmp);
+ }
+ return NULL;
+}
+
+
+void CIcqProto::GetFullAvatarFileName(int dwUin, const char *szUid, int dwFormat, TCHAR *pszDest, int cbLen)
+{
+ GetAvatarFileName(dwUin, szUid, pszDest, cbLen);
+ AddAvatarExt(dwFormat, pszDest);
+}
+
+
+void CIcqProto::GetAvatarFileName(int dwUin, const char *szUid, TCHAR *pszDest, int cbLen)
+{
+ TCHAR szPath[MAX_PATH * 2];
+ FOLDERSGETDATA fgd = {0};
+
+ InitAvatars();
+
+ fgd.cbSize = sizeof(FOLDERSGETDATA);
+ fgd.nMaxPathSize = MAX_PATH * 2;
+ fgd.szPathT = szPath;
+ fgd.flags = FF_TCHAR;
+ if (CallService(MS_FOLDERS_GET_PATH, (WPARAM)hAvatarsFolder, (LPARAM)&fgd))
+ {
+ TCHAR *tmpPath = Utils_ReplaceVarsT(_T("%miranda_avatarcache%"));
+ null_snprintf(szPath, MAX_PATH * 2, _T("%s\\") _T(TCHAR_STR_PARAM) _T("\\"), tmpPath, m_szModuleName);
+ mir_free(tmpPath);
+ }
+ else
+ _tcscat(szPath, _T("\\"));
+
+ // fill the destination
+ lstrcpyn(pszDest, szPath, cbLen - 1);
+ int tPathLen = strlennull(pszDest);
+
+ // make sure the avatar cache directory exists
+ CallService(MS_UTILS_CREATEDIRTREET, 0, (LPARAM)szPath);
+
+ if (dwUin != 0)
+ {
+ _ltot(dwUin, pszDest + tPathLen, 10);
+ }
+ else if (szUid)
+ {
+ TCHAR* p = mir_a2t(szUid);
+ _tcscpy(pszDest + tPathLen, p);
+ mir_free( p );
+ }
+ else
+ {
+ TCHAR szBuf[MAX_PATH];
+
+ if (CallService(MS_DB_GETPROFILENAMET, MAX_PATH, (LPARAM)szBuf))
+ _tcscpy(pszDest + tPathLen, _T("avatar"));
+ else
+ {
+ TCHAR *szLastDot = _tcsrchr(szBuf, '.');
+ if (szLastDot) szLastDot[0] = '\0';
+
+ _tcscpy(pszDest + tPathLen, szBuf);
+ _tcscat(pszDest + tPathLen, _T("_avt"));
+ }
+ }
+}
+
+
+void AddAvatarExt(int dwFormat, TCHAR *pszDest)
+{
+ if (dwFormat == PA_FORMAT_JPEG)
+ _tcscat(pszDest, _T(".jpg"));
+ else if (dwFormat == PA_FORMAT_GIF)
+ _tcscat(pszDest, _T(".gif"));
+ else if (dwFormat == PA_FORMAT_PNG)
+ _tcscat(pszDest, _T(".png"));
+ else if (dwFormat == PA_FORMAT_BMP)
+ _tcscat(pszDest, _T(".bmp"));
+ else if (dwFormat == PA_FORMAT_XML)
+ _tcscat(pszDest, _T(".xml"));
+ else if (dwFormat == PA_FORMAT_SWF)
+ _tcscat(pszDest, _T(".swf"));
+ else
+ _tcscat(pszDest, _T(".dat"));
+}
+
+
+int DetectAvatarFormatBuffer(const char *pBuffer)
+{
+ if (!strncmp(pBuffer, "%PNG", 4))
+ return PA_FORMAT_PNG;
+
+ if (!strncmp(pBuffer, "GIF8", 4))
+ return PA_FORMAT_GIF;
+
+ if (!_strnicmp(pBuffer, "<?xml", 5))
+ return PA_FORMAT_XML;
+
+ if ((((DWORD*)pBuffer)[0] == 0xE0FFD8FFul) || (((DWORD*)pBuffer)[0] == 0xE1FFD8FFul))
+ return PA_FORMAT_JPEG;
+
+ if (!strncmp(pBuffer, "BM", 2))
+ return PA_FORMAT_BMP;
+
+ return PA_FORMAT_UNKNOWN;
+}
+
+
+int DetectAvatarFormat(const TCHAR *tszFile)
+{
+ int src = _topen(tszFile, _O_BINARY | _O_RDONLY, 0);
+ if (src == -1)
+ return PA_FORMAT_UNKNOWN;
+
+ char pBuf[32];
+ _read(src, pBuf, 32);
+ _close(src);
+
+ return DetectAvatarFormatBuffer(pBuf);
+}
+
+
+#define MD5_BLOCK_SIZE 1024*1024 /* use 1MB blocks */
+
+BYTE* calcMD5HashOfFile(const TCHAR *tszFile)
+{
+ BYTE *res = NULL;
+
+ HANDLE hFile = NULL, hMap = NULL;
+
+ if ((hFile = CreateFile(tszFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL )) != INVALID_HANDLE_VALUE)
+ {
+ if ((hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL)) != NULL)
+ {
+ long cbFileSize = GetFileSize(hFile, NULL);
+
+ res = (BYTE*)SAFE_MALLOC(16 * sizeof(mir_md5_byte_t));
+ if (cbFileSize != 0 && res)
+ {
+ mir_md5_state_t state;
+ mir_md5_byte_t digest[16];
+ int dwOffset = 0;
+
+ mir_md5_init(&state);
+ while (dwOffset < cbFileSize)
+ {
+ BYTE *ppMap = NULL;
+ int dwBlockSize = min(MD5_BLOCK_SIZE, cbFileSize-dwOffset);
+
+ if (!(ppMap = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, dwOffset, dwBlockSize)))
+ break;
+ mir_md5_append(&state, (const mir_md5_byte_t *)ppMap, dwBlockSize);
+ UnmapViewOfFile(ppMap);
+ dwOffset += dwBlockSize;
+ }
+ mir_md5_finish(&state, digest);
+ memcpy(res, digest, 16 * sizeof(mir_md5_byte_t));
+ }
+ }
+
+ if (hMap != NULL) CloseHandle(hMap);
+ if (hFile != NULL) CloseHandle(hFile);
+ }
+
+ return res;
+}
+
+
+int CIcqProto::IsAvatarChanged(HANDLE hContact, const BYTE *pHash, int nHashLen)
+{
+ DBVARIANT dbvSaved = {0};
+
+ if (!getSetting(hContact, "AvatarSaved", &dbvSaved))
+ {
+ if ((dbvSaved.cpbVal != nHashLen) || memcmp(dbvSaved.pbVal, pHash, nHashLen))
+ { // the hashes are different
+ ICQFreeVariant(&dbvSaved);
+
+ return 2;
+ }
+ ICQFreeVariant(&dbvSaved);
+
+ return 0; // hash is there and is the same - Success
+ }
+ return 1; // saved Avatar hash is missing
+}
+
+
+void CIcqProto::StartAvatarThread(HANDLE hConn, char *cookie, WORD cookieLen) // called from event
+{
+ if (!hConn)
+ {
+ icq_lock l(m_avatarsMutex); // place avatars lock
+
+ if (m_avatarsConnection && m_avatarsConnection->isPending())
+ {
+ NetLog_Server("Avatar, Multiple start thread attempt, ignored.");
+ SAFE_FREE((void**)&cookie);
+ return;
+ }
+ NetLog_Server("Avatars: Connection failed");
+
+ m_avatarsConnectionPending = FALSE;
+
+ { // check if any upload request are waiting in the queue
+ avatars_request *ar = m_avatarsQueue;
+ int bYet = 0;
+
+ while (ar)
+ {
+ if (ar->type == ART_UPLOAD)
+ { // we found it, return error
+ if (!bYet)
+ {
+ icq_LogMessage(LOG_WARNING, LPGEN("Error uploading avatar to server, server temporarily unavailable."));
+ bYet = 1;
+ }
+ // remove upload request from queue
+ ar = ReleaseAvatarRequestInQueue(ar);
+ continue;
+ }
+ ar = ar->pNext;
+ }
+ }
+ SAFE_FREE((void**)&cookie);
+
+ return;
+ }
+
+ icq_lock l(m_avatarsMutex);
+
+ if (m_avatarsConnection && m_avatarsConnection->isPending())
+ {
+ NetLog_Server("Avatar, Multiple start thread attempt, ignored.");
+
+ NetLib_CloseConnection(&hConn, FALSE);
+
+ SAFE_FREE((void**)&cookie);
+
+ return;
+ }
+ else if (m_avatarsConnection)
+ m_avatarsConnection->closeConnection();
+
+ m_avatarsConnection = new avatars_server_connection(this, hConn, cookie, cookieLen); // the old connection should not be used anymore
+
+ // connection object created, remove the connection request pending flag
+ m_avatarsConnectionPending = FALSE;
+}
+
+
+void CIcqProto::StopAvatarThread()
+{
+ icq_lock l(m_avatarsMutex); // the connection is about to close
+
+ if (m_avatarsConnection)
+ {
+ m_avatarsConnection->shutdownConnection(); // make the thread stop
+ }
+
+ return;
+}
+
+
+#ifdef _DEBUG
+static void NetLog_Hash(CIcqProto *ppro, const char *pszIdent, const BYTE *pHash, int nHashLen)
+{
+ if (nHashLen == 0x14)
+ ppro->NetLog_Server("Avatars: %s hash: %02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", pszIdent, pHash[0], pHash[1], pHash[2], pHash[3], pHash[4], pHash[5], pHash[6], pHash[7], pHash[8], pHash[9], pHash[10], pHash[11], pHash[12], pHash[13], pHash[14], pHash[15], pHash[16], pHash[17], pHash[18], pHash[19]);
+ else if (nHashLen == 0x09)
+ ppro->NetLog_Server("Avatars: %s hash: %02X%02X%02X%02X%02X%02X%02X%02X%02X", pszIdent, pHash[0], pHash[1], pHash[2], pHash[3], pHash[4], pHash[5], pHash[6], pHash[7], pHash[8]);
+ else
+ ppro->NetLog_Server("Avatars: %s hash: Unknown hash format.", pszIdent);
+}
+#endif
+
+
+// handle Owner's avatar hash changes
+void CIcqProto::handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BYTE nDataLen)
+{
+ if ((nDataLen >= 0x14) && m_bAvatarsEnabled)
+ {
+ switch (bFlags)
+ {
+ case 1: // our avatar is on the server
+ {
+ setSettingBlob(NULL, "AvatarHash", pData, 0x14); /// TODO: properly handle multiple avatar items (more formats)
+
+ setUserInfo();
+ // here we need to find a file, check its hash, if invalid get avatar from server
+ TCHAR *file = GetOwnAvatarFileName();
+ if (!file)
+ { // we have no avatar file, download from server
+ TCHAR szFile[MAX_PATH * 2 + 4];
+#ifdef _DEBUG
+ NetLog_Server("We have no avatar, requesting from server.");
+#endif
+ GetAvatarFileName(0, NULL, szFile, MAX_PATH * 2);
+ GetAvatarData(NULL, m_dwLocalUIN, NULL, pData, 0x14, szFile);
+ }
+ else
+ { // we know avatar filename
+ BYTE *hash = calcMD5HashOfFile(file);
+
+ if (!hash)
+ { // hash could not be calculated - probably missing file, get avatar from server
+ TCHAR szFile[MAX_PATH * 2 + 4];
+#ifdef _DEBUG
+ NetLog_Server("We have no avatar, requesting from server.");
+#endif
+ GetAvatarFileName(0, NULL, szFile, MAX_PATH * 2);
+ GetAvatarData(NULL, m_dwLocalUIN, NULL, pData, 0x14, szFile);
+ } // check if we had set any avatar if yes set our, if not download from server
+ else if (memcmp(hash, pData + 4, 0x10))
+ { // we have different avatar, sync that
+ if (m_bSsiEnabled && getSettingByte(NULL, "ForceOurAvatar", 1))
+ { // we want our avatar, update hash
+ DWORD dwPaFormat = DetectAvatarFormat(file);
+ BYTE *pHash = (BYTE*)_alloca(0x14);
+
+ NetLog_Server("Our avatar is different, setting our new hash.");
+
+ pHash[0] = 0;
+ pHash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC;
+ pHash[2] = 1; // state of the hash
+ pHash[3] = 0x10; // len of the hash
+ memcpy(pHash + 4, hash, 0x10);
+ updateServAvatarHash(pHash, 0x14);
+ }
+ else
+ { // get avatar from server
+ TCHAR tszFile[MAX_PATH * 2 + 4];
+#ifdef _DEBUG
+ NetLog_Server("We have different avatar, requesting new from server.");
+#endif
+ GetAvatarFileName(0, NULL, tszFile, MAX_PATH * 2);
+ GetAvatarData(NULL, m_dwLocalUIN, NULL, pData, 0x14, tszFile);
+ }
+ }
+ SAFE_FREE((void**)&hash);
+ SAFE_FREE(&file);
+ }
+ break;
+ }
+ case 0x41: // request to upload avatar data
+ case 0x81:
+ { // request to re-upload avatar data
+ if (!m_bSsiEnabled) break; // we could not change serv-list if it is disabled...
+
+ TCHAR *file = GetOwnAvatarFileName();
+ if (!file)
+ { // we have no file to upload, remove hash from server
+ NetLog_Server("We do not have avatar, removing hash.");
+ SetMyAvatar(0, 0);
+ break;
+ }
+ DWORD dwPaFormat = DetectAvatarFormat(file);
+ BYTE *hash = calcMD5HashOfFile(file);
+
+ if (!hash)
+ { // the hash could not be calculated, remove from server
+ NetLog_Server("We could not obtain hash, removing hash.");
+ SetMyAvatar(0, 0);
+ }
+ else if (!memcmp(hash, pData + 4, 0x10))
+ { // we have the right file
+ HANDLE hFile = NULL, hMap = NULL;
+ BYTE *ppMap = NULL;
+ long cbFileSize = 0;
+
+ NetLog_Server("Uploading our avatar data.");
+
+ if ((hFile = CreateFile(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL )) != INVALID_HANDLE_VALUE)
+ if ((hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL)) != NULL)
+ if ((ppMap = (BYTE*)MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0)) != NULL)
+ cbFileSize = GetFileSize(hFile, NULL);
+
+ if (cbFileSize != 0)
+ {
+ SetAvatarData(NULL, (WORD)(dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC), ppMap, cbFileSize);
+ }
+
+ if (ppMap != NULL) UnmapViewOfFile(ppMap);
+ if (hMap != NULL) CloseHandle(hMap);
+ if (hFile != NULL) CloseHandle(hFile);
+ SAFE_FREE((void**)&hash);
+ }
+ else
+ {
+ BYTE *pHash = (BYTE*)_alloca(0x14);
+
+ NetLog_Server("Our file is different, set our new hash.");
+
+ pHash[0] = 0;
+ pHash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC;
+ pHash[2] = 1; // state of the hash
+ pHash[3] = 0x10; // len of the hash
+ memcpy(pHash + 4, hash, 0x10);
+ updateServAvatarHash(pHash, 0x14);
+
+ SAFE_FREE((void**)&hash);
+ }
+
+ SAFE_FREE(&file);
+ break;
+ }
+ default:
+ NetLog_Server("Received UNKNOWN Avatar Status.");
+ }
+ }
+}
+
+
+// handle Contact's avatar hash
+void CIcqProto::handleAvatarContactHash(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *pHash, int nHashLen, WORD wOldStatus)
+{
+ int bJob = FALSE;
+ BOOL avatarInfoPresent = FALSE;
+ int avatarType = -1;
+ BYTE *pAvatarHash = NULL;
+ int cbAvatarHash;
+ BYTE emptyItem[0x10] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
+
+ if (!m_bAvatarsEnabled) return; // only if enabled
+
+ if (nHashLen < 4) return; // nothing to work with
+
+ while (nHashLen >= 4)
+ { // parse online message items one by one
+ WORD itemType = pHash[0] << 8 | pHash[1];
+ BYTE itemLen = pHash[3];
+ BYTE itemFlags = pHash[2];
+
+ // just some validity check
+ if (itemLen + 4 > nHashLen)
+ itemLen = nHashLen - 4;
+
+ if (itemLen && memcmp(pHash + 4, emptyItem, itemLen > 0x10 ? 0x10 : itemLen))
+ { // Item types
+ // 0000: AIM mini avatar
+ // 0001: AIM/ICQ avatar ID/hash (len 5 or 16 bytes)
+ // 0002: iChat online message
+ // 0008: ICQ Flash avatar hash (16 bytes)
+ // 0009: iTunes music store link
+ // 000C: ICQ contact photo (16 bytes)
+ // 000D: Last update time of online message
+ // 000E: Status mood
+ if (itemType == AVATAR_HASH_MINI && itemLen == 0x05 && avatarType == -1)
+ { // mini avatar
+ pAvatarHash = pHash;
+ cbAvatarHash = itemLen + 4;
+ avatarType = itemType;
+ }
+ else if (itemType == AVATAR_HASH_STATIC && (itemLen == 0x05 || itemLen == 0x10) && (avatarType == -1 || avatarType == AVATAR_HASH_MINI))
+ { // normal avatar
+ pAvatarHash = pHash;
+ cbAvatarHash = itemLen + 4;
+ avatarType = itemType;
+ }
+ else if (itemType == AVATAR_HASH_FLASH && itemLen == 0x10 && (avatarType == -1 || avatarType == AVATAR_HASH_MINI || avatarType == AVATAR_HASH_STATIC))
+ { // flash avatar
+ pAvatarHash = pHash;
+ cbAvatarHash = itemLen + 4;
+ avatarType = itemType;
+ }
+ else if (itemType == AVATAR_HASH_PHOTO && itemLen == 0x10)
+ { // big avatar (ICQ 6+)
+ pAvatarHash = pHash;
+ cbAvatarHash = itemLen + 4;
+ avatarType = itemType;
+ }
+ }
+ else if ((itemLen == 0) && (itemType == AVATAR_HASH_MINI || itemType == AVATAR_HASH_STATIC || itemType == AVATAR_HASH_FLASH || itemType == AVATAR_HASH_PHOTO))
+ { // empty item - indicating that avatar of that type was removed
+ avatarInfoPresent = TRUE;
+ }
+
+ pHash += itemLen + 4;
+ nHashLen -= itemLen + 4;
+ }
+
+ if (avatarType != -1)
+ { // check settings, should we request avatar immediatelly?
+ DBVARIANT dbv = {DBVT_DELETED};
+ TCHAR tszAvatar[MAX_PATH * 2 +4];
+ BYTE bAutoLoad = getSettingByte(NULL, "AvatarsAutoLoad", DEFAULT_LOAD_AVATARS);
+
+ if ((avatarType == AVATAR_HASH_STATIC || avatarType == AVATAR_HASH_MINI) && cbAvatarHash == 0x09 && !memcmp(pAvatarHash + 4, hashEmptyAvatar + 4, 0x05))
+ { // empty avatar - unlink image, clear hash
+ if (!getSetting(hContact, "AvatarHash", &dbv))
+ { // contact had avatar, clear hash, notify UI
+#ifdef _DEBUG
+ NetLog_Hash(this, "old", dbv.pbVal, dbv.cpbVal);
+#endif
+ ICQFreeVariant(&dbv);
+ NetLog_Server("%s has removed Avatar.", strUID(dwUIN, szUID));
+
+ deleteSetting(hContact, "AvatarHash");
+ BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL);
+ }
+#ifdef _DEBUG
+ else
+ NetLog_Server("%s has empty Avatar.", strUID(dwUIN, szUID));
+#endif
+ return;
+ }
+
+ if (getSetting(hContact, "AvatarHash", &dbv))
+ { // we did not find old avatar hash, i.e. get new avatar
+ int avatarState = IsAvatarChanged(hContact, pAvatarHash, cbAvatarHash);
+
+ // check saved hash and file, if equal only store hash
+ if (!avatarState)
+ { // hashes are the same
+ int dwPaFormat = getSettingByte(hContact, "AvatarType", PA_FORMAT_UNKNOWN);
+
+ GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, tszAvatar, MAX_PATH * 2);
+ if (_taccess(tszAvatar, 0) == 0)
+ { // the file is there, link to contactphoto, save hash
+ NetLog_Server("%s has published Avatar. Image was found in the cache.", strUID(dwUIN, szUID));
+#ifdef _DEBUG
+ NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash);
+#endif
+ setSettingBlob(hContact, "AvatarHash", pAvatarHash, cbAvatarHash);
+ BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL);
+ }
+ else
+ { // the file was lost, request avatar again
+ NetLog_Server("%s has published Avatar.", strUID(dwUIN, szUID));
+#ifdef _DEBUG
+ NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash);
+#endif
+ bJob = TRUE;
+ }
+ }
+ else
+ { // the hash is not the one we want, request avatar
+ NetLog_Server("%s has published a new Avatar.", strUID(dwUIN, szUID));
+#ifdef _DEBUG
+ NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash);
+#endif
+ bJob = TRUE;
+ }
+ }
+ else
+ { // we found hash check if it changed or not
+ if ((dbv.cpbVal != cbAvatarHash) || memcmp(dbv.pbVal, pAvatarHash, cbAvatarHash))
+ { // the hash is different, request new avatar
+#ifdef _DEBUG
+ NetLog_Hash(this, "old", dbv.pbVal, dbv.cpbVal);
+#endif
+ NetLog_Server("%s has changed Avatar.", strUID(dwUIN, szUID));
+#ifdef _DEBUG
+ NetLog_Hash(this, "new", pAvatarHash, cbAvatarHash);
+#endif
+ bJob = TRUE;
+ }
+ else
+ { // the hash was not changed, check if we have the correct file
+ int avatarState = IsAvatarChanged(hContact, pAvatarHash, cbAvatarHash);
+
+ // we should have file, check if the file really exists
+ if (!avatarState)
+ {
+ int dwPaFormat = getSettingByte(hContact, "AvatarType", PA_FORMAT_UNKNOWN);
+ if (dwPaFormat == PA_FORMAT_UNKNOWN)
+ { // we do not know the format, get avatar again
+#ifdef _DEBUG
+ NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal);
+#endif
+ NetLog_Server("%s has Avatar. Image is missing.", strUID(dwUIN, szUID));
+ bJob = 2;
+ }
+ else
+ {
+ GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, tszAvatar, MAX_PATH * 2);
+ if (_taccess(tszAvatar, 0) != 0)
+ { // the file was lost, get it again
+#ifdef _DEBUG
+ NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal);
+#endif
+ NetLog_Server("%s has Avatar. Image is missing.", strUID(dwUIN, szUID));
+ bJob = 2;
+ }
+#ifdef _DEBUG
+ else
+ {
+ NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal);
+
+ NetLog_Server("%s has Avatar. Image was found in the cache.", strUID(dwUIN, szUID));
+ }
+#endif
+ }
+ }
+ else
+ { // the hash is not the one we want, request avatar
+#ifdef _DEBUG
+ NetLog_Hash(this, "current", dbv.pbVal, dbv.cpbVal);
+#endif
+ NetLog_Server("%s has Avatar. Image was not retrieved yet.", strUID(dwUIN, szUID));
+ bJob = 2;
+ }
+ }
+ ICQFreeVariant(&dbv);
+ }
+
+ if (bJob)
+ {
+ if (bJob == TRUE)
+ { // Remove possible block - hash changed, try again.
+ icq_lock l(m_avatarsMutex);
+
+ avatars_request *ar = m_avatarsQueue;
+
+ while (ar)
+ {
+ if (ar->hContact == hContact && ar->type == ART_BLOCK)
+ { // found one, remove
+ ReleaseAvatarRequestInQueue(ar);
+ break;
+ }
+ ar = ar->pNext;
+ }
+ }
+
+ setSettingBlob(hContact, "AvatarHash", pAvatarHash, cbAvatarHash);
+
+ BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL);
+
+ if (bAutoLoad)
+ { // auto-load is on, so request the avatar now, otherwise we are done
+ GetAvatarFileName(dwUIN, szUID, tszAvatar, MAX_PATH * 2);
+ GetAvatarData(hContact, dwUIN, szUID, pAvatarHash, cbAvatarHash, tszAvatar);
+ } // avatar request sent or added to queue
+ }
+ }
+ else if (avatarInfoPresent)
+ { // hash was not found, clear the hash
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (!getSetting(hContact, "AvatarHash", &dbv))
+ { // contact had avatar, clear hash, notify UI
+#ifdef _DEBUG
+ NetLog_Hash(this, "old", dbv.pbVal, dbv.cpbVal);
+#endif
+ ICQFreeVariant(&dbv);
+ NetLog_Server("%s has removed Avatar.", strUID(dwUIN, szUID));
+
+ deleteSetting(hContact, "AvatarHash");
+ BroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, (LPARAM)NULL);
+ }
+#ifdef _DEBUG
+ else
+ NetLog_Server("%s has no Avatar.", strUID(dwUIN, szUID));
+#endif
+ }
+}
+
+
+// request avatar data from server
+int CIcqProto::GetAvatarData(HANDLE hContact, DWORD dwUin, const char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file)
+{
+ uid_str szUidData;
+ char *pszUid = NULL;
+ if (!dwUin && szUid)
+ { // create a copy in local writable buffer
+ strcpy(szUidData, szUid);
+ pszUid = szUidData;
+ }
+
+ m_avatarsMutex->Enter();
+
+ if (m_avatarsConnection && m_avatarsConnection->isReady()) // check if we are ready
+ { // check if requests for this user are not blocked
+ DWORD dwNow = GetTickCount();
+ avatars_request *ar = m_avatarsQueue;
+
+ while (ar)
+ {
+ if (ar->hContact == hContact && ar->type == ART_BLOCK)
+ { // found a block item
+ if (GetTickCount() > ar->timeOut)
+ { // remove timeouted block
+ ar = ReleaseAvatarRequestInQueue(ar);
+ continue;
+ }
+ m_avatarsMutex->Leave();
+ NetLog_Server("Avatars: Requests for %s avatar are blocked.", strUID(dwUin, pszUid));
+ return 0;
+ }
+ ar = ar->pNext;
+ }
+
+ avatars_server_connection *pConnection = m_avatarsConnection;
+
+ pConnection->_Lock();
+ m_avatarsMutex->Leave();
+
+ DWORD dwCookie = pConnection->sendGetAvatarRequest(hContact, dwUin, pszUid, hash, hashlen, file);
+
+ m_avatarsMutex->Enter();
+ pConnection->_Release();
+
+ if (dwCookie)
+ { // return now if the request was sent successfully
+ m_avatarsMutex->Leave();
+ return dwCookie;
+ }
+ }
+ // we failed to send request, or avatar thread not ready
+
+ // check if any request for this user is not already in the queue
+ avatars_request *ar = m_avatarsQueue;
+
+ while (ar)
+ {
+ if (ar->hContact == hContact)
+ { // we found it, return error
+ if (ar->type == ART_BLOCK && GetTickCount() > ar->timeOut)
+ { // remove timeouted block
+ ar = ReleaseAvatarRequestInQueue(ar);
+ continue;
+ }
+ m_avatarsMutex->Leave();
+ NetLog_Server("Avatars: Ignoring duplicate get %s avatar request.", strUID(dwUin, pszUid));
+
+ // make sure avatar connection is in progress
+ requestAvatarConnection();
+ return 0;
+ }
+ ar = ar->pNext;
+ }
+ // add request to queue, processed after successful login
+ ar = new avatars_request(ART_GET); // get avatar
+ if (!ar)
+ { // out of memory, go away
+ m_avatarsMutex->Leave();
+ return 0;
+ }
+ ar->hContact = hContact;
+ ar->dwUin = dwUin;
+ if (!dwUin)
+ strcpy(ar->szUid, szUid);
+ ar->hash = (BYTE*)SAFE_MALLOC(hashlen);
+ if (!ar->hash)
+ { // alloc failed
+ m_avatarsMutex->Leave();
+ delete ar;
+ return 0;
+ }
+ memcpy(ar->hash, hash, hashlen); // copy the data
+ ar->hashlen = hashlen;
+ ar->szFile = null_strdup(file); // duplicate the string
+ ar->pNext = m_avatarsQueue;
+ m_avatarsQueue = ar;
+ m_avatarsMutex->Leave();
+
+ NetLog_Server("Avatars: Request to get %s image added to queue.", strUID(dwUin, pszUid));
+
+ // make sure avatar connection is in progress
+ requestAvatarConnection();
+
+ return -1; // we added to queue
+}
+
+
+// upload avatar data to server
+int CIcqProto::SetAvatarData(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen)
+{
+ m_avatarsMutex->Enter();
+
+ if (m_avatarsConnection && m_avatarsConnection->isReady()) // check if we are ready
+ {
+ avatars_server_connection *pConnection = m_avatarsConnection;
+
+ pConnection->_Lock();
+ m_avatarsMutex->Leave();
+
+ DWORD dwCookie = pConnection->sendUploadAvatarRequest(hContact, wRef, data, datalen);
+
+ m_avatarsMutex->Enter();
+ pConnection->_Release();
+
+ if (dwCookie)
+ { // return now if the request was sent successfully
+ m_avatarsMutex->Leave();
+ return dwCookie;
+ }
+ }
+ // we failed to send request, or avatar thread not ready
+
+ // check if any request for this user is not already in the queue
+ avatars_request *ar = m_avatarsQueue;
+ int bYet = 0;
+
+ while (ar)
+ {
+ if (ar->hContact == hContact && ar->type == ART_UPLOAD)
+ { // we found it, return error
+ m_avatarsMutex->Leave();
+ NetLog_Server("Avatars: Ignoring duplicate upload avatar request.");
+
+ // make sure avatar connection is in progress
+ requestAvatarConnection();
+ return 0;
+ }
+ ar = ar->pNext;
+ }
+ // add request to queue, processed after successful login
+ ar = new avatars_request(ART_UPLOAD); // upload avatar
+ if (!ar)
+ { // out of memory, go away
+ m_avatarsMutex->Leave();
+ return 0;
+ }
+ ar->hContact = hContact;
+ ar->pData = (BYTE*)SAFE_MALLOC(datalen);
+ if (!ar->pData)
+ { // alloc failed
+ m_avatarsMutex->Leave();
+ delete ar;
+ return 0;
+ }
+ memcpy(ar->pData, data, datalen); // copy the data
+ ar->cbData = datalen;
+ ar->wRef = wRef;
+ ar->pNext = m_avatarsQueue;
+ m_avatarsQueue = ar;
+ m_avatarsMutex->Leave();
+
+ NetLog_Server("Avatars: Request to upload image added to queue.");
+
+ // make sure avatar connection is in progress
+ requestAvatarConnection();
+
+ return -1; // we added to queue
+}
+
+
+void CIcqProto::requestAvatarConnection()
+{
+ m_avatarsMutex->Enter();
+ if (!m_avatarsConnectionPending && (!m_avatarsConnection || (!m_avatarsConnection->isPending() && !m_avatarsConnection->isReady())))
+ { // avatar connection is not pending, request new one
+ m_avatarsConnectionPending = TRUE;
+ m_avatarsMutex->Leave();
+
+ icq_requestnewfamily(ICQ_AVATAR_FAMILY, &CIcqProto::StartAvatarThread);
+ }
+ else
+ m_avatarsMutex->Leave();
+}
+
+
+void __cdecl CIcqProto::AvatarThread(avatars_server_connection *pInfo)
+{
+ NetLog_Server("%s thread started.", "Avatar");
+
+ // Execute connection handler
+ pInfo->connectionThread();
+
+ { // Remove connection reference
+ icq_lock l(m_avatarsMutex);
+ if (m_avatarsConnection == pInfo)
+ m_avatarsConnection = NULL;
+ }
+
+ { // Release connection handler
+ icq_lock l(m_avatarsMutex);
+ delete pInfo;
+ }
+
+ NetLog_Server("%s thread ended.", "Avatar");
+}
+
+
+avatars_server_connection::avatars_server_connection(CIcqProto *ppro, HANDLE hConnection, char *pCookie, WORD wCookieLen):
+isLoggedIn(FALSE), stopThread(FALSE), isActive(FALSE)
+{
+ this->ppro = ppro;
+ this->hConnection = hConnection;
+ this->pCookie = pCookie;
+ this->wCookieLen = wCookieLen;
+
+ // Initialize packet sequence
+ localSeqMutex = new icq_critical_section();
+ wLocalSequence = generate_flap_sequence();
+
+ // Initialize rates
+ m_ratesMutex = new icq_critical_section();
+
+ // Create connection thread
+ ppro->ForkThread(( IcqThreadFunc )&CIcqProto::AvatarThread, this);
+}
+
+
+avatars_server_connection::~avatars_server_connection()
+{
+ delete m_ratesMutex;
+ delete localSeqMutex;
+}
+
+
+int avatars_server_connection::NetLog_Server(const char *fmt,...)
+{
+ va_list va;
+ char szText[1024 + 9];
+
+ strcpy(szText, "Avatars: ");
+ va_start(va, fmt);
+ mir_vsnprintf(szText + 9, sizeof(szText) - 9, fmt, va);
+ va_end(va);
+ return CallService(MS_NETLIB_LOG,(WPARAM)ppro->m_hServerNetlibUser,(LPARAM)szText);
+}
+
+
+void avatars_server_connection::closeConnection()
+{
+ stopThread = TRUE;
+
+ icq_lock l(localSeqMutex);
+ if (hConnection)
+ NetLib_SafeCloseHandle(&hConnection);
+}
+
+
+void avatars_server_connection::shutdownConnection()
+{
+ stopThread = TRUE;
+
+ icq_lock l(localSeqMutex);
+ if (hConnection)
+ Netlib_Shutdown(hConnection);
+}
+
+DWORD avatars_server_connection::sendGetAvatarRequest(HANDLE hContact, DWORD dwUin, char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file)
+{
+ int i;
+ DWORD dwNow = GetTickCount();
+
+ ppro->m_avatarsMutex->Enter();
+
+ for(i = 0; i < runCount;)
+ { // look for timeouted requests
+ if (runTime[i] < dwNow)
+ { // found outdated, remove
+ runContact[i] = runContact[runCount - 1];
+ runTime[i] = runTime[runCount - 1];
+ runCount--;
+ }
+ else
+ i++;
+ }
+
+ for(i = 0; i < runCount; i++)
+ {
+ if (runContact[i] == hContact)
+ {
+ ppro->m_avatarsMutex->Leave();
+ NetLog_Server("Ignoring duplicate get %s image request.", strUID(dwUin, szUid));
+
+ return -1; // Success: request ignored
+ }
+ }
+
+ if (runCount < 4)
+ { // 4 concurent requests at most
+ int bSendNow = TRUE;
+
+ { // rate management
+ icq_lock l(m_ratesMutex);
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_AVATAR_FAMILY, ICQ_AVATAR_GET_REQUEST);
+
+ if (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_ALERT))
+ { // we will be over quota if we send the request now, add to queue instead
+ bSendNow = FALSE;
+#ifdef _DEBUG
+ NetLog_Server("Rates: Delay avatar request.");
+#endif
+ }
+ }
+
+ if (bSendNow)
+ {
+ runContact[runCount] = hContact;
+ runTime[runCount] = GetTickCount() + 30000; // 30sec to complete request
+ runCount++;
+
+ ppro->m_avatarsMutex->Leave();
+
+ int nUinLen = getUIDLen(dwUin, szUid);
+
+ cookie_avatar *ack = (cookie_avatar*)SAFE_MALLOC(sizeof(cookie_avatar));
+ if (!ack) return 0; // Failure: out of memory
+
+ ack->dwUin = 1; //dwUin; // I should be damned for this - only to identify get request
+ ack->hContact = hContact;
+ ack->hash = (BYTE*)SAFE_MALLOC(hashlen);
+ memcpy(ack->hash, hash, hashlen); // copy the data
+ ack->hashlen = hashlen;
+ ack->szFile = null_strdup(file); // duplicate the string
+
+ DWORD dwCookie = ppro->AllocateCookie(CKT_AVATAR, ICQ_AVATAR_GET_REQUEST, hContact, ack);
+ icq_packet packet;
+
+ serverPacketInit(&packet, (WORD)(12 + nUinLen + hashlen));
+ packFNACHeader(&packet, ICQ_AVATAR_FAMILY, ICQ_AVATAR_GET_REQUEST, 0, dwCookie);
+ packUID(&packet, dwUin, szUid);
+ packByte(&packet, 1); // unknown, probably type of request: 1 = get icon :)
+ packBuffer(&packet, hash, (WORD)hashlen);
+
+ if (sendServerPacket(&packet))
+ {
+ NetLog_Server("Request to get %s image sent.", strUID(dwUin, szUid));
+
+ return dwCookie;
+ }
+ ppro->FreeCookie(dwCookie); // sending failed, free resources
+ SAFE_FREE(&ack->szFile);
+ SAFE_FREE((void**)&ack->hash);
+ SAFE_FREE((void**)&ack);
+ }
+ else
+ ppro->m_avatarsMutex->Leave();
+ }
+ else
+ ppro->m_avatarsMutex->Leave();
+
+ return 0; // Failure
+}
+
+
+DWORD avatars_server_connection::sendUploadAvatarRequest(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen)
+{
+ cookie_avatar *ack = (cookie_avatar*)SAFE_MALLOC(sizeof(cookie_avatar));
+ if (!ack) return 0; // Failure: out of memory
+
+ ack->hContact = hContact;
+
+ DWORD dwCookie = ppro->AllocateCookie(CKT_AVATAR, ICQ_AVATAR_UPLOAD_REQUEST, 0, ack);
+ icq_packet packet;
+
+ serverPacketInit(&packet, (WORD)(14 + datalen));
+ packFNACHeader(&packet, ICQ_AVATAR_FAMILY, ICQ_AVATAR_UPLOAD_REQUEST, 0, dwCookie);
+ packWord(&packet, wRef); // unknown, probably reference
+ packWord(&packet, (WORD)datalen);
+ packBuffer(&packet, data, (WORD)datalen);
+
+ if (sendServerPacket(&packet))
+ {
+ NetLog_Server("Upload image packet sent.");
+
+ return dwCookie;
+ }
+ ppro->ReleaseCookie(dwCookie); // failed to send, free resources
+
+ return 0;
+}
+
+
+void avatars_server_connection::checkRequestQueue()
+{
+#ifdef _DEBUG
+ NetLog_Server("Checking request queue...");
+#endif
+
+ ppro->m_avatarsMutex->Enter();
+
+ while (ppro->m_avatarsQueue && runCount < 3) // pick up an request and send it - happens immediatelly after login
+ { // do not fill queue to top, leave one place free
+ avatars_request *pRequest = ppro->m_avatarsQueue;
+
+ { // rate management
+ icq_lock l(m_ratesMutex);
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_AVATAR_FAMILY, (WORD)(pRequest->type == ART_UPLOAD ? ICQ_AVATAR_GET_REQUEST : ICQ_AVATAR_UPLOAD_REQUEST));
+
+ if (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_ALERT))
+ { // we are over rate, leave queue and wait
+#ifdef _DEBUG
+ NetLog_Server("Rates: Leaving avatar queue processing");
+#endif
+ break;
+ }
+ }
+
+ if (pRequest->type == ART_BLOCK)
+ { // block contact processing
+ avatars_request **ppRequest = &ppro->m_avatarsQueue;
+
+ while (pRequest)
+ {
+ if (GetTickCount() > pRequest->timeOut)
+ { // expired contact block, remove
+ *ppRequest = pRequest->pNext;
+ delete pRequest;
+ }
+ else // it is not time, move to next request
+ ppRequest = &pRequest->pNext;
+
+ pRequest = *ppRequest;
+ }
+ // end queue processing (only block requests follows)
+ break;
+ }
+ else
+ ppro->m_avatarsQueue = pRequest->pNext;
+
+ ppro->m_avatarsMutex->Leave();
+
+#ifdef _DEBUG
+ NetLog_Server("Picked up the %s request from queue.", strUID(pRequest->dwUin, pRequest->szUid));
+#endif
+ switch (pRequest->type)
+ {
+ case ART_GET: // get avatar
+ sendGetAvatarRequest(pRequest->hContact, pRequest->dwUin, pRequest->szUid, pRequest->hash, pRequest->hashlen, pRequest->szFile);
+ break;
+
+ case ART_UPLOAD: // set avatar
+ sendUploadAvatarRequest(pRequest->hContact, pRequest->wRef, pRequest->pData, pRequest->cbData);
+ break;
+ }
+ delete pRequest;
+
+ ppro->m_avatarsMutex->Enter();
+ }
+
+ ppro->m_avatarsMutex->Leave();
+}
+
+
+void avatars_server_connection::connectionThread()
+{
+ // This is the "infinite" loop that receives the packets from the ICQ avatar server
+ NETLIBPACKETRECVER packetRecv = {0};
+ DWORD wLastKeepAlive = 0; // we send keep-alive at most one per 30secs
+ DWORD dwKeepAliveInterval = ppro->getSettingDword(NULL, "KeepAliveInterval", KEEPALIVE_INTERVAL);
+
+ hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)hConnection, 65536);
+ packetRecv.cbSize = sizeof(packetRecv);
+ packetRecv.dwTimeout = dwKeepAliveInterval < KEEPALIVE_INTERVAL ? dwKeepAliveInterval: KEEPALIVE_INTERVAL; // timeout - for stopThread to work
+ while (!stopThread)
+ {
+ int recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)hPacketRecver, (LPARAM)&packetRecv);
+
+ if (recvResult == 0)
+ {
+ NetLog_Server("Clean closure of server socket");
+ break;
+ }
+
+ if (recvResult == SOCKET_ERROR)
+ {
+ if (GetLastError() == ERROR_TIMEOUT)
+ { // timeout, check if we should be still running
+ if (Miranda_Terminated())
+ { // we must stop here, cause due to a hack in netlib, we always get timeout, even if the connection is already dead
+ stopThread = 1;
+ continue;
+ }
+#ifdef _DEBUG
+ else
+ NetLog_Server("Thread is Idle.");
+#endif
+ if (GetTickCount() > wLastKeepAlive)
+ { // limit frequency (HACK: on some systems select() does not work well)
+ if (!ppro->m_bGatewayMode && ppro->getSettingByte(NULL, "KeepAlive", DEFAULT_KEEPALIVE_ENABLED))
+ { // send keep-alive packet
+ icq_packet packet;
+
+ packet.wLen = 0;
+ write_flap(&packet, ICQ_PING_CHAN);
+ sendServerPacket(&packet);
+ }
+ wLastKeepAlive = GetTickCount() + dwKeepAliveInterval;
+ }
+ else
+ { // this is bad, the system does not handle select() properly
+#ifdef _DEBUG
+ NetLog_Server("Thread is Forcing Idle.");
+#endif
+ SleepEx(500, TRUE); // wait some time, can we do anything else ??
+ if (Miranda_Terminated())
+ {
+ stopThread = 1;
+ continue;
+ }
+ }
+ // check if we got something to request
+ checkRequestQueue();
+ continue;
+ }
+ if (!stopThread)
+ NetLog_Server("Abortive closure of server socket, error: %d", GetLastError());
+ else
+ NetLog_Server("Connection closed.");
+ break;
+ }
+
+ // Deal with the packet
+ packetRecv.bytesUsed = handleServerPackets(packetRecv.buffer, packetRecv.bytesAvailable);
+
+ if (isActive && (packetRecv.bytesAvailable == packetRecv.bytesUsed)) // no packets pending
+ { // process request queue
+ checkRequestQueue();
+ }
+ }
+ { // release connection
+ icq_lock l(localSeqMutex);
+ NetLib_SafeCloseHandle(&hPacketRecver); // Close the packet receiver
+ NetLib_CloseConnection(&hConnection, FALSE); // Close the connection
+ }
+
+ { // release rates
+ icq_lock l(m_ratesMutex);
+ SAFE_DELETE((MZeroedObject**)&m_rates);
+ }
+
+ SAFE_FREE((void**)&pCookie);
+}
+
+
+int avatars_server_connection::sendServerPacket(icq_packet *pPacket)
+{
+ int lResult = 0;
+
+ // This critsec makes sure that the sequence order doesn't get screwed up
+ localSeqMutex->Enter();
+
+ if (hConnection)
+ {
+ int nRetries;
+ int nSendResult;
+
+ // :IMPORTANT:
+ // The FLAP sequence must be a WORD. When it reaches 0xFFFF it should wrap to
+ // 0x0000, otherwise we'll get kicked by server.
+ wLocalSequence++;
+
+ // Pack sequence number
+ pPacket->pData[2] = ((wLocalSequence & 0xff00) >> 8);
+ pPacket->pData[3] = (wLocalSequence & 0x00ff);
+
+ for (nRetries = 3; nRetries >= 0; nRetries--)
+ {
+ nSendResult = Netlib_Send(hConnection, (const char *)pPacket->pData, pPacket->wLen, 0);
+
+ if (nSendResult != SOCKET_ERROR)
+ break;
+
+ Sleep(1000);
+ }
+
+ // Send error
+ if (nSendResult == SOCKET_ERROR)
+ { // thread stops automatically
+ NetLog_Server("Your connection with the ICQ avatar server was abortively closed");
+ }
+ else
+ {
+ lResult = 1; // packet sent successfully
+
+ icq_lock l(m_ratesMutex);
+ if (m_rates)
+ m_rates->packetSent(pPacket);
+ }
+ }
+ else
+ {
+ NetLog_Server("Error: Failed to send packet (no connection)");
+ }
+
+ localSeqMutex->Leave();
+
+ SAFE_FREE((void**)&pPacket->pData);
+
+ return lResult;
+}
+
+
+int avatars_server_connection::handleServerPackets(BYTE *buf, int buflen)
+{
+ BYTE channel;
+ WORD sequence;
+ WORD datalen;
+ int bytesUsed = 0;
+
+ while (buflen > 0)
+ {
+ // All FLAPS begin with 0x2a
+ if (*buf++ != FLAP_MARKER)
+ break;
+
+ if (buflen < 6)
+ break;
+
+ unpackByte(&buf, &channel);
+ unpackWord(&buf, &sequence);
+ unpackWord(&buf, &datalen);
+
+ if (buflen < 6 + datalen)
+ break;
+
+#ifdef _DEBUG
+ NetLog_Server("Server FLAP: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen);
+#endif
+
+ switch (channel)
+ {
+ case ICQ_LOGIN_CHAN:
+ handleLoginChannel(buf, datalen);
+ break;
+
+ case ICQ_DATA_CHAN:
+ handleDataChannel(buf, datalen);
+ break;
+
+ default:
+ NetLog_Server("Warning: Unhandled Server FLAP Channel: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen);
+ break;
+ }
+
+ /* Increase pointers so we can check for more FLAPs */
+ buf += datalen;
+ buflen -= (datalen + 6);
+ bytesUsed += (datalen + 6);
+ }
+
+ return bytesUsed;
+}
+
+
+void avatars_server_connection::handleLoginChannel(BYTE *buf, WORD datalen)
+{
+ icq_packet packet;
+
+ if (*(DWORD*)buf == 0x1000000)
+ { // here check if we received SRV_HELLO
+ wLocalSequence = generate_flap_sequence();
+
+ serverCookieInit(&packet, (LPBYTE)pCookie, wCookieLen);
+ sendServerPacket(&packet);
+
+#ifdef _DEBUG
+ NetLog_Server("Sent CLI_IDENT to %s", "avatar server");
+#endif
+
+ SAFE_FREE((void**)&pCookie);
+ wCookieLen = 0;
+ }
+ else
+ {
+ NetLog_Server("Invalid Server response, Channel 1.");
+ }
+}
+
+
+void avatars_server_connection::handleDataChannel(BYTE *buf, WORD datalen)
+{
+ snac_header snacHeader = {0};
+
+ if (!unpackSnacHeader(&snacHeader, &buf, &datalen) || !snacHeader.bValid)
+ {
+ NetLog_Server("Error: Failed to parse SNAC header");
+ }
+ else
+ {
+#ifdef _DEBUG
+ if (snacHeader.wFlags & 0x8000)
+ NetLog_Server(" Received SNAC(x%02X,x%02X), version %u", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wVersion);
+ else
+ NetLog_Server(" Received SNAC(x%02X,x%02X)", snacHeader.wFamily, snacHeader.wSubtype);
+#endif
+
+ switch (snacHeader.wFamily)
+ {
+
+ case ICQ_SERVICE_FAMILY:
+ handleServiceFam(buf, datalen, &snacHeader);
+ break;
+
+ case ICQ_AVATAR_FAMILY:
+ handleAvatarFam(buf, datalen, &snacHeader);
+ break;
+
+ default:
+ NetLog_Server("Ignoring SNAC(x%02X,x%02X) - FAMILYx%02X not implemented", snacHeader.wFamily, snacHeader.wSubtype, snacHeader.wFamily);
+ break;
+ }
+ }
+}
+
+
+void avatars_server_connection::handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader)
+{
+ icq_packet packet;
+
+ switch (pSnacHeader->wSubtype)
+ {
+
+ case ICQ_SERVER_READY:
+#ifdef _DEBUG
+ NetLog_Server("Server is ready and is requesting my Family versions");
+ NetLog_Server("Sending my Families");
+#endif
+
+ // Miranda mimics the behaviour of Icq5
+ serverPacketInit(&packet, 18);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_FAMILIES);
+ packDWord(&packet, 0x00010004);
+ packDWord(&packet, 0x00100001);
+ sendServerPacket(&packet);
+ break;
+
+ case ICQ_SERVER_FAMILIES2:
+ /* This is a reply to CLI_FAMILIES and it tells the client which families and their versions that this server understands.
+ * We send a rate request packet */
+#ifdef _DEBUG
+ NetLog_Server("Server told me his Family versions");
+ NetLog_Server("Requesting Rate Information");
+#endif
+ serverPacketInit(&packet, 10);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_REQ_RATE_INFO);
+ sendServerPacket(&packet);
+ break;
+
+ case ICQ_SERVER_RATE_INFO:
+#ifdef _DEBUG
+ NetLog_Server("Server sent Rate Info");
+#endif
+ /* init rates management */
+ m_rates = new rates(ppro, pBuffer, wBufferLength);
+ /* ack rate levels */
+#ifdef _DEBUG
+ NetLog_Server("Sending Rate Info Ack");
+#endif
+ m_rates->initAckPacket(&packet);
+ sendServerPacket(&packet);
+
+ // send cli_ready
+ serverPacketInit(&packet, 26);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_READY);
+ packDWord(&packet, 0x00010004); // mimic ICQ 6
+ packDWord(&packet, 0x0010164f);
+ packDWord(&packet, 0x00100001);
+ packDWord(&packet, 0x0010164f);
+ sendServerPacket(&packet);
+
+ isActive = TRUE; // we are ready to process requests
+ isLoggedIn = TRUE;
+
+ NetLog_Server(" *** Yeehah, login sequence complete");
+ break;
+
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_SERVICE_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+ }
+}
+
+
+void avatars_server_connection::handleAvatarFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader)
+{
+ switch (pSnacHeader->wSubtype) {
+
+ case ICQ_AVATAR_GET_REPLY: // received avatar data, store to file
+ { // handle new avatar, notify
+ cookie_avatar *pCookieData;
+
+ if (ppro->FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData))
+ {
+ PROTO_AVATAR_INFORMATIONT ai = {0};
+ BYTE bResult;
+
+ { // remove from active request list
+ icq_lock l(ppro->m_avatarsMutex);
+ for(int i = 0; i < runCount; i++)
+ { // look for our record
+ if (runContact[i] == pCookieData->hContact)
+ { // found, remove
+ runContact[i] = runContact[runCount - 1];
+ runTime[i] = runTime[runCount - 1];
+ runCount--;
+ break;
+ }
+ }
+ }
+
+ ai.cbSize = sizeof(PROTO_AVATAR_INFORMATIONT);
+ ai.format = PA_FORMAT_JPEG; // this is for error only
+ ai.hContact = pCookieData->hContact;
+ lstrcpyn(ai.filename, pCookieData->szFile, SIZEOF(ai.filename));
+ AddAvatarExt(PA_FORMAT_JPEG, ai.filename);
+
+ ppro->FreeCookie(pSnacHeader->dwRef);
+
+ BYTE len;
+ WORD datalen;
+
+ unpackByte(&pBuffer, &len);
+ if (wBufferLength < ((pCookieData->hashlen)<<1)+4+len)
+ {
+ NetLog_Server("Received invalid avatar reply.");
+
+ ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0);
+
+ SAFE_FREE(&pCookieData->szFile);
+ SAFE_FREE((void**)&pCookieData->hash);
+ SAFE_FREE((void**)&pCookieData);
+
+ break;
+ }
+
+ pBuffer += len;
+ pBuffer += pCookieData->hashlen;
+ unpackByte(&pBuffer, &bResult);
+ pBuffer += pCookieData->hashlen;
+ unpackWord(&pBuffer, &datalen);
+
+ wBufferLength -= 4 + len + (pCookieData->hashlen<<1);
+ if (datalen > wBufferLength)
+ {
+ datalen = wBufferLength;
+ NetLog_Server("Avatar reply broken, trying to do my best.");
+ }
+
+ if (datalen > 4)
+ { // store to file...
+ int aValid = 1;
+
+ if (pCookieData->hashlen == 0x14 && pCookieData->hash[3] == 0x10 && ppro->getSettingByte(NULL, "StrictAvatarCheck", DEFAULT_AVATARS_CHECK))
+ { // check only standard hashes
+ mir_md5_state_t state;
+ mir_md5_byte_t digest[16];
+
+ mir_md5_init(&state);
+ mir_md5_append(&state, (const mir_md5_byte_t *)pBuffer, datalen);
+ mir_md5_finish(&state, digest);
+ // check if received data corresponds to specified hash
+ if (memcmp(pCookieData->hash+4, digest, 0x10)) aValid = 0;
+ }
+
+ if (aValid)
+ {
+ NetLog_Server("Received user avatar, storing (%d bytes).", datalen);
+
+ int dwPaFormat = DetectAvatarFormatBuffer((char*)pBuffer);
+ TCHAR *tszImageFile = (TCHAR*)_alloca(sizeof(TCHAR)*(strlennull(pCookieData->szFile) + 6));
+
+ _tcscpy(tszImageFile, pCookieData->szFile);
+ AddAvatarExt(dwPaFormat, tszImageFile);
+
+ ppro->setSettingByte(pCookieData->hContact, "AvatarType", (BYTE)dwPaFormat);
+ ai.format = dwPaFormat; // set the format
+ lstrcpyn(ai.filename, tszImageFile, SIZEOF(ai.filename));
+
+ int out = _topen(tszImageFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE);
+ if (out != -1)
+ {
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ _write(out, pBuffer, datalen);
+ _close(out);
+
+ if (!pCookieData->hContact) // our avatar, set filename
+ {
+ TCHAR tmp[MAX_PATH * 2];
+ CallService(MS_UTILS_PATHTORELATIVET, (WPARAM)tszImageFile, (LPARAM)tmp);
+ ppro->setSettingStringT(NULL, "AvatarFile", tmp);
+ }
+ else
+ { // contact's avatar set hash
+ if (!ppro->getSetting(pCookieData->hContact, "AvatarHash", &dbv))
+ {
+ if (ppro->setSettingBlob(pCookieData->hContact, "AvatarSaved", dbv.pbVal, dbv.cpbVal))
+ NetLog_Server("Failed to set file hash.");
+
+ ICQFreeVariant(&dbv);
+ }
+ else
+ {
+ NetLog_Server("Warning: DB error (no hash in DB).");
+ // the hash was lost, try to fix that
+ if (ppro->setSettingBlob(pCookieData->hContact, "AvatarSaved", pCookieData->hash, pCookieData->hashlen) ||
+ ppro->setSettingBlob(pCookieData->hContact, "AvatarHash", pCookieData->hash, pCookieData->hashlen))
+ {
+ NetLog_Server("Failed to save avatar hash to DB");
+ }
+ }
+ }
+
+ ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, (HANDLE)&ai, 0);
+ }
+ }
+ else
+ { // avatar is broken
+ NetLog_Server("Error: Avatar data does not match avatar hash, ignoring.");
+
+ if (pCookieData->hContact)
+ {
+ avatars_request *ar = new avatars_request(ART_BLOCK);
+
+ icq_lock l(ppro->m_avatarsMutex);
+
+ if (ar)
+ {
+ avatars_request *last = ppro->m_avatarsQueue;
+
+ ar->hContact = pCookieData->hContact;
+ ar->timeOut = GetTickCount() + 14400000; // do not allow re-request four hours
+
+ // add it to the end of queue, i.e. do not block other requests
+ while (last && last->pNext) last = last->pNext;
+ if (last)
+ last->pNext = ar;
+ else
+ ppro->m_avatarsQueue = ar;
+ }
+ }
+ ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0);
+ }
+ }
+ else
+ { // the avatar is empty
+ NetLog_Server("Received empty avatar, nothing written (error 0x%x).", bResult);
+
+ ppro->BroadcastAck(pCookieData->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, (HANDLE)&ai, 0);
+ }
+ SAFE_FREE(&pCookieData->szFile);
+ SAFE_FREE((void**)&pCookieData->hash);
+ SAFE_FREE((void**)&pCookieData);
+ }
+ else
+ {
+ NetLog_Server("Warning: Received unexpected Avatar Reply SNAC(x10,x07).");
+ }
+
+ break;
+ }
+ case ICQ_AVATAR_UPLOAD_ACK:
+ {
+ // upload completed, notify
+ BYTE res;
+ unpackByte(&pBuffer, &res);
+ if (!res && (wBufferLength == 0x15))
+ {
+ cookie_avatar *pCookieData;
+ if (ppro->FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData))
+ {
+ // here we store the local hash
+ ppro->ReleaseCookie(pSnacHeader->dwRef);
+ }
+ else
+ {
+ NetLog_Server("Warning: Received unexpected Upload Avatar Reply SNAC(x10,x03).");
+ }
+ }
+ else if (res)
+ {
+ NetLog_Server("Error uploading avatar to server, #%d", res);
+
+ ppro->icq_LogMessage(LOG_WARNING, LPGEN("Error uploading avatar to server, server refused to accept the image."));
+ }
+ else
+ NetLog_Server("Received invalid upload avatar ack.");
+
+ break;
+ }
+ case ICQ_ERROR:
+ {
+ WORD wError;
+ cookie_avatar *pCookieData;
+
+ if (ppro->FindCookie(pSnacHeader->dwRef, NULL, (void**)&pCookieData))
+ {
+ if (pCookieData->dwUin)
+ {
+ NetLog_Server("Error: Avatar request failed");
+ SAFE_FREE(&pCookieData->szFile);
+ SAFE_FREE((void**)&pCookieData->hash);
+ }
+ else
+ {
+ NetLog_Server("Error: Avatar upload failed");
+ }
+ ppro->ReleaseCookie(pSnacHeader->dwRef);
+ }
+
+ if (wBufferLength >= 2)
+ unpackWord(&pBuffer, &wError);
+ else
+ wError = 0;
+
+ ppro->LogFamilyError(ICQ_AVATAR_FAMILY, wError);
+ break;
+ }
+ default:
+ NetLog_Server("Warning: Ignoring SNAC(x%02x,x%02x) - Unknown SNAC (Flags: %u, Ref: %u)", ICQ_AVATAR_FAMILY, pSnacHeader->wSubtype, pSnacHeader->wFlags, pSnacHeader->dwRef);
+ break;
+
+ }
+}
diff --git a/protocols/IcqOscarJ/src/icq_avatar.h b/protocols/IcqOscarJ/src/icq_avatar.h
new file mode 100644
index 0000000000..6bfcb65c45
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_avatar.h
@@ -0,0 +1,127 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Avatars connection support declarations
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_AVATAR_H
+#define __ICQ_AVATAR_H
+
+
+extern BYTE hashEmptyAvatar[9];
+
+#define AVATAR_HASH_MINI 0x00
+#define AVATAR_HASH_STATIC 0x01
+#define AVATAR_HASH_FLASH 0x08
+#define AVATAR_HASH_PHOTO 0x0C
+
+struct CIcqProto;
+
+struct avatars_server_connection : public lockable_struct
+{
+protected:
+ CIcqProto *ppro;
+ HANDLE hConnection; // handle to the connection
+ HANDLE hPacketRecver;
+ WORD wLocalSequence;
+ icq_critical_section *localSeqMutex;
+
+ BOOL isLoggedIn;
+ BOOL isActive;
+ BOOL stopThread; // horrible, but simple - signal for thread to stop
+
+ char *pCookie; // auth to server
+ WORD wCookieLen;
+
+ int sendServerPacket(icq_packet *pPacket);
+
+ int handleServerPackets(BYTE *buf, int buflen);
+
+ void handleLoginChannel(BYTE *buf, WORD datalen);
+ void handleDataChannel(BYTE *buf, WORD datalen);
+
+ void handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader);
+ void handleAvatarFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader);
+
+ rates *m_rates;
+ icq_critical_section *m_ratesMutex;
+
+ int NetLog_Server(const char *fmt,...);
+
+ HANDLE runContact[4];
+ DWORD runTime[4];
+ int runCount;
+ void checkRequestQueue();
+public:
+ avatars_server_connection(CIcqProto *ppro, HANDLE hConnection, char *pCookie, WORD wCookieLen);
+ virtual ~avatars_server_connection();
+
+ void connectionThread();
+ void closeConnection();
+ void shutdownConnection();
+
+ __inline BOOL isPending() { return !isLoggedIn; };
+ __inline BOOL isReady() { return isLoggedIn && isActive && !stopThread; };
+
+ DWORD sendGetAvatarRequest(HANDLE hContact, DWORD dwUin, char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file);
+ DWORD sendUploadAvatarRequest(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen);
+};
+
+__inline static void SAFE_DELETE(avatars_server_connection **p) { SAFE_DELETE((lockable_struct**)p); };
+
+
+struct avatars_request : public MZeroedObject
+{
+ int type;
+ HANDLE hContact;
+ DWORD dwUin;
+ uid_str szUid;
+ BYTE *hash;
+ unsigned int hashlen;
+ TCHAR *szFile;
+ BYTE *pData;
+ unsigned int cbData;
+ WORD wRef;
+ DWORD timeOut;
+ avatars_request *pNext;
+public:
+ avatars_request(int type);
+ virtual ~avatars_request();
+};
+
+__inline static void SAFE_DELETE(avatars_request **p) { SAFE_DELETE((MZeroedObject**)p); };
+
+#define ART_GET 1
+#define ART_UPLOAD 2
+#define ART_BLOCK 4
+
+
+int DetectAvatarFormat(const TCHAR *szFile);
+void AddAvatarExt(int dwFormat, TCHAR *pszDest);
+
+BYTE* calcMD5HashOfFile(const TCHAR *szFile);
+
+#endif /* __ICQ_AVATAR_H */
diff --git a/protocols/IcqOscarJ/src/icq_clients.cpp b/protocols/IcqOscarJ/src/icq_clients.cpp
new file mode 100644
index 0000000000..bb1ebb5621
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_clients.cpp
@@ -0,0 +1,1155 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Provides capability & signature based client detection
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+const capstr capShortCaps = {0x09, 0x46, 0x00, 0x00, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00}; // CAP_AIM_BUDDYICON
+
+static const char* makeClientVersion(char *szBuf, const char *szClient, unsigned v1, unsigned v2, unsigned v3, unsigned v4)
+{
+ if (v4)
+ null_snprintf(szBuf, 64, "%s%u.%u.%u.%u", szClient, v1, v2, v3, v4);
+ else if (v3)
+ null_snprintf(szBuf, 64, "%s%u.%u.%u", szClient, v1, v2, v3);
+ else
+ null_snprintf(szBuf, 64, "%s%u.%u", szClient, v1, v2);
+
+ return szBuf;
+}
+
+static void verToStr(char *szStr, int v)
+{
+ char szVer[64];
+
+ makeClientVersion(szVer, "", (v>>24)&0x7F, (v>>16)&0xFF, (v>>8)&0xFF, v&0xFF);
+ strcat(szStr, szVer);
+ if (v&0x80000000) strcat(szStr, " alpha");
+}
+
+static char* MirandaVersionToStringEx(char* szStr, int bUnicode, const char* szPlug, int v, int m)
+{
+ if (!v) // this is not Miranda
+ return NULL;
+
+ strcpy(szStr, "Miranda IM ");
+
+ if (!m && v == 1)
+ verToStr(szStr, 0x80010200);
+ else if (!m && (v&0x7FFFFFFF) <= 0x030301)
+ verToStr(szStr, v);
+ else {
+ if (m) {
+ verToStr(szStr, m);
+ strcat(szStr, " ");
+ }
+ if (bUnicode)
+ strcat(szStr, "Unicode ");
+
+ strcat(szStr, "(");
+ strcat(szStr, szPlug);
+ strcat(szStr, " v");
+ verToStr(szStr, v);
+ strcat(szStr, ")");
+ }
+
+ return szStr;
+}
+
+char* MirandaVersionToString(char* szStr, int bUnicode, int v, int m)
+{
+ return MirandaVersionToStringEx(szStr, bUnicode, "ICQ", v, m);
+}
+
+char* MirandaModToString(char* szStr, capstr* capId, int bUnicode, const char* szModName)
+{ // decode icqj mod version
+ char* szClient;
+ DWORD mver = (*capId)[0x4] << 0x18 | (*capId)[0x5] << 0x10 | (*capId)[0x6] << 8 | (*capId)[0x7];
+ DWORD iver = (*capId)[0x8] << 0x18 | (*capId)[0x9] << 0x10 | (*capId)[0xA] << 8 | (*capId)[0xB];
+ DWORD scode = (*capId)[0xC] << 0x18 | (*capId)[0xD] << 0x10 | (*capId)[0xE] << 8 | (*capId)[0xF];
+
+ szClient = MirandaVersionToStringEx(szStr, bUnicode, szModName, iver, mver);
+ if (scode == 0x5AFEC0DE)
+ {
+ strcat(szClient, " + SecureIM");
+ }
+ return szClient;
+}
+
+const capstr capMirandaIm = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'M', 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capMirandaNg = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'N', 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capIcqJs7 = {'i', 'c', 'q', 'j', ' ', 'S', 'e', 'c', 'u', 'r', 'e', ' ', 'I', 'M', 0, 0};
+const capstr capIcqJSin = {'s', 'i', 'n', 'j', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Miranda ICQJ S!N
+const capstr capIcqJp = {'i', 'c', 'q', 'p', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capAimOscar = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'A', 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capMimMobile = {'M', 'i', 'r', 'a', 'n', 'd', 'a', 'M', 'o', 'b', 'i', 'l', 'e', 0, 0, 0};
+const capstr capMimPack = {'M', 'I', 'M', '/', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Custom Miranda Pack
+const capstr capTrillian = {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x09};
+const capstr capTrilCrypt = {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb, 0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00};
+const capstr capSim = {'S', 'I', 'M', ' ', 'c', 'l', 'i', 'e', 'n', 't', ' ', ' ', 0, 0, 0, 0};
+const capstr capSimOld = {0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x00};
+const capstr capLicq = {'L', 'i', 'c', 'q', ' ', 'c', 'l', 'i', 'e', 'n', 't', ' ', 0, 0, 0, 0};
+const capstr capKopete = {'K', 'o', 'p', 'e', 't', 'e', ' ', 'I', 'C', 'Q', ' ', ' ', 0, 0, 0, 0};
+const capstr capmIcq = {'m', 'I', 'C', 'Q', ' ', 0xA9, ' ', 'R', '.', 'K', '.', ' ', 0, 0, 0, 0};
+const capstr capClimm = {'c', 'l', 'i', 'm', 'm', 0xA9, ' ', 'R', '.', 'K', '.', ' ', 0, 0, 0, 0};
+const capstr capAndRQ = {'&', 'R', 'Q', 'i', 'n', 's', 'i', 'd', 'e', 0, 0, 0, 0, 0, 0, 0};
+const capstr capRAndQ = {'R', '&', 'Q', 'i', 'n', 's', 'i', 'd', 'e', 0, 0, 0, 0, 0, 0, 0};
+const capstr capIMadering = {'I', 'M', 'a', 'd', 'e', 'r', 'i', 'n', 'g', ' ', 'C', 'l', 'i', 'e', 'n', 't'};
+const capstr capmChat = {'m', 'C', 'h', 'a', 't', ' ', 'i', 'c', 'q', ' ', 0, 0, 0, 0, 0, 0};
+const capstr capJimm = {'J', 'i', 'm', 'm', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capCorePager = {'C', 'O', 'R', 'E', ' ', 'P', 'a', 'g', 'e', 'r', 0, 0, 0, 0, 0, 0};
+const capstr capDiChat = {'D', '[', 'i', ']', 'C', 'h', 'a', 't', ' ', 0, 0, 0, 0, 0, 0, 0};
+const capstr capVmIcq = {'V', 'm', 'I', 'C', 'Q', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capSmapeR = {'S', 'm', 'a', 'p', 'e', 'r', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capAnastasia = {0x44, 0xE5, 0xBF, 0xCE, 0xB0, 0x96, 0xE5, 0x47, 0xBD, 0x65, 0xEF, 0xD6, 0xA3, 0x7E, 0x36, 0x02};
+const capstr capPalmJicq = {'J', 'I', 'C', 'Q', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capInluxMsgr = {0xA7, 0xE4, 0x0A, 0x96, 0xB3, 0xA0, 0x47, 0x9A, 0xB8, 0x45, 0xC9, 0xE4, 0x67, 0xC5, 0x6B, 0x1F};
+const capstr capYapp = {'Y', 'a', 'p', 'p', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capMipClient = {0x4d, 0x49, 0x50, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x76, 0x00, 0x00, 0x00, 0x00};
+const capstr capPigeon = {'P', 'I', 'G', 'E', 'O', 'N', '!', 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capDigsbyBeta= {0x09, 0x46, 0x01, 0x05, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x45, 0x53, 0x54, 0x00};
+const capstr capDigsby = {'d', 'i', 'g', 's', 'b', 'y', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capJapp = {0x6a, 0x61, 0x70, 0x70, 0xa9, 0x20, 0x62, 0x79, 0x20, 0x53, 0x65, 0x72, 0x67, 0x6f, 0x00, 0x00};
+const capstr capNaim = {0xFF, 0xFF, 0xFF, 0xFF, 'n', 'a', 'i', 'm', 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capCitron = {0x09, 0x19, 0x19, 0x82, 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00};
+const capstr capQip = {0x56, 0x3F, 0xC8, 0x09, 0x0B, 0x6F, 0x41, 'Q', 'I', 'P', ' ', '2', '0', '0', '5', 'a'};
+const capstr capQipPDA = {0x56, 0x3F, 0xC8, 0x09, 0x0B, 0x6F, 0x41, 'Q', 'I', 'P', ' ', ' ', ' ', ' ', ' ', '!'};
+const capstr capQipSymbian= {0x51, 0xAD, 0xD1, 0x90, 0x72, 0x04, 0x47, 0x3D, 0xA1, 0xA1, 0x49, 0xF4, 0xA3, 0x97, 0xA4, 0x1F};
+const capstr capQipIphone = {0x60, 0xDE, 0x5C, 0x8A, 0xDF, 0x8C, 0x4E, 0x1D, 0xA4, 0xC8, 0xBC, 0x3B, 0xD9, 0x79, 0x4D, 0xD8};
+const capstr capQipMobile = {0xB0, 0x82, 0x62, 0xF6, 0x7F, 0x7C, 0x45, 0x61, 0xAD, 0xC1, 0x1C, 0x6D, 0x75, 0x70, 0x5E, 0xC5};
+const capstr capQipInfium = {0x7C, 0x73, 0x75, 0x02, 0xC3, 0xBE, 0x4F, 0x3E, 0xA6, 0x9F, 0x01, 0x53, 0x13, 0x43, 0x1E, 0x1A};
+const capstr capQip2010 = {0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0x0A, 0x03, 0x0B, 0x04, 0x01, 0x53, 0x00, 0x00, 0x00, 0x00};
+const capstr capQip2012 = {0x7F, 0x7F, 0x7C, 0x7D, 0x7E, 0x7F, 0x0A, 0x03, 0x0B, 0x04, 0x01, 0x53, 0x13, 0x43, 0x1E, 0x1A};
+const capstr capIm2 = {0x74, 0xED, 0xC3, 0x36, 0x44, 0xDF, 0x48, 0x5B, 0x8B, 0x1C, 0x67, 0x1A, 0x1F, 0x86, 0x09, 0x9F}; // IM2 Ext Msg
+const capstr capQutIm = {'q', 'u', 't', 'i', 'm', 0x30, 0x2e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const capstr capBayan = {'b', 'a', 'y', 'a', 'n', 'I', 'C', 'Q', 0, 0, 0, 0, 0, 0, 0, 0};
+const capstr capJabberJIT = {'J', 'I', 'T', ' ', 0x76, 0x2E, 0x31, 0x2E, 0x78, 0x2E, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00};
+const capstr capIcqKid2 = {'I', 'c', 'q', 'K', 'i', 'd', '2', 0x00, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+const capstr capWebIcqPro = {'W', 'e', 'b', 'I', 'c', 'q', 'P', 'r', 'o', ' ', 0, 0, 0, 0, 0, 0};
+const capstr capMraJava = {0x4a, 0x32, 0x4d, 0x45, 0x20, 0x6d, 0x40, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x00, 0x00};
+const capstr capMacIcq = {0xdd, 0x16, 0xf2, 0x02, 0x84, 0xe6, 0x11, 0xd4, 0x90, 0xdb, 0x00, 0x10, 0x4b, 0x9b, 0x4b, 0x7d};
+const capstr capIs2001 = {0x2e, 0x7a, 0x64, 0x75, 0xfa, 0xdf, 0x4d, 0xc8, 0x88, 0x6f, 0xea, 0x35, 0x95, 0xfd, 0xb6, 0xdf};
+const capstr capIs2002 = {0x10, 0xcf, 0x40, 0xd1, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00};
+const capstr capComm20012 = {0xa0, 0xe9, 0x3f, 0x37, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00};
+const capstr capStrIcq = {0xa0, 0xe9, 0x3f, 0x37, 0x4f, 0xe9, 0xd3, 0x11, 0xbc, 0xd2, 0x00, 0x04, 0xac, 0x96, 0xdd, 0x96};
+const shortcapstr capAimIcon = {0x13, 0x46}; // CAP_AIM_BUDDYICON
+const shortcapstr capAimDirect = {0x13, 0x45}; // CAP_AIM_DIRECTIM
+const shortcapstr capAimFileShare = {0x13, 0x48}; // CAP_AIM_FILE_SHARE
+const shortcapstr capIcqDevils = {0x13, 0x4C}; // CAP_DEVILS
+const shortcapstr capAimSmartCaps = {0x01, 0xFF};
+const shortcapstr capAimLiveVideo = {0x01, 0x01}; // CAP_AIM_LIVE_VIDEO
+const shortcapstr capAimLiveAudio = {0x01, 0x04}; // CAP_AIM_LIVE_AUDIO
+const shortcapstr capStatusTextAware = {0x01, 0x0A}; // CAP_HOST_STATUS_TEXT_AWARE
+const capstr capIcqLiteNew= {0xc8, 0x95, 0x3a, 0x9f, 0x21, 0xf1, 0x4f, 0xaa, 0xb0, 0xb2, 0x6d, 0xe6, 0x63, 0xab, 0xf5, 0xb7};
+const capstr capXtrazVideo= {0x17, 0x8C, 0x2D, 0x9B, 0xDA, 0xA5, 0x45, 0xBB, 0x8D, 0xDB, 0xF3, 0xBD, 0xBD, 0x53, 0xA1, 0x0A};
+const capstr capOscarChat = {0x74, 0x8F, 0x24, 0x20, 0x62, 0x87, 0x11, 0xD1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00};
+const capstr capUim = {0xA7, 0xE4, 0x0A, 0x96, 0xB3, 0xA0, 0x47, 0x9A, 0xB8, 0x45, 0xC9, 0xE4, 0x67, 0xC5, 0x6B, 0x1F};
+const capstr capRambler = {0x7E, 0x11, 0xB7, 0x78, 0xA3, 0x53, 0x49, 0x26, 0xA8, 0x02, 0x44, 0x73, 0x52, 0x08, 0xC4, 0x2A};
+const capstr capAbv = {0x00, 0xE7, 0xE0, 0xDF, 0xA9, 0xD0, 0x4F, 0xe1, 0x91, 0x62, 0xC8, 0x90, 0x9A, 0x13, 0x2A, 0x1B};
+const capstr capNetvigator= {0x4C, 0x6B, 0x90, 0xA3, 0x3D, 0x2D, 0x48, 0x0E, 0x89, 0xD6, 0x2E, 0x4B, 0x2C, 0x10, 0xD9, 0x9F};
+const capstr captZers = {0xb2, 0xec, 0x8f, 0x16, 0x7c, 0x6f, 0x45, 0x1b, 0xbd, 0x79, 0xdc, 0x58, 0x49, 0x78, 0x88, 0xb9}; // CAP_TZERS
+const capstr capSimpLite = {0x53, 0x49, 0x4D, 0x50, 0x53, 0x49, 0x4D, 0x50, 0x53, 0x49, 0x4D, 0x50, 0x53, 0x49, 0x4D, 0x50};
+const capstr capSimpPro = {0x53, 0x49, 0x4D, 0x50, 0x5F, 0x50, 0x52, 0x4F, 0x53, 0x49, 0x4D, 0x50, 0x5F, 0x50, 0x52, 0x4F};
+const capstr capIMsecure = {'I', 'M', 's', 'e', 'c', 'u', 'r', 'e', 'C', 'p', 'h', 'r', 0x00, 0x00, 0x06, 0x01}; // ZoneLabs
+const capstr capIMSecKey1 = {1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // ZoneLabs
+const capstr capIMSecKey2 = {2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // ZoneLabs
+const capstr capFakeHtml = {0x01, 0x38, 0xca, 0x7b, 0x76, 0x9a, 0x49, 0x15, 0x88, 0xf2, 0x13, 0xfc, 0x00, 0x97, 0x9e, 0xa8};
+
+const char* cliLibicq2k = "libicq2000";
+const char* cliLicqVer = "Licq ";
+const char* cliCentericq = "Centericq";
+const char* cliLibicqUTF = "libicq2000 (Unicode)";
+const char* cliTrillian = "Trillian";
+const char* cliTrillian4 = "Trillian Astra";
+const char* cliQip = "QIP %s";
+const char* cliIM2 = "IM2";
+const char* cliSpamBot = "Spam Bot";
+
+const char* CIcqProto::detectUserClient(HANDLE hContact, int nIsICQ, WORD wUserClass, DWORD dwOnlineSince, const char *szCurrentClient,
+ WORD wVersion, DWORD dwFT1, DWORD dwFT2, DWORD dwFT3, BYTE bDirectFlag, DWORD dwDirectCookie, DWORD dwWebPort, /* ICQ specific */
+ BYTE *caps, WORD wLen, /* Client capabilities */
+ BYTE *bClientId, /* Output: detected client-type */
+ char *szClientBuf)
+{
+ LPCSTR szClient = NULL;
+ int bMirandaIM = FALSE;
+
+ *bClientId = CLID_ALTERNATIVE; // Most clients does not tick as MsgIDs
+
+ // Is this a Miranda IM client?
+ if (dwFT1 == 0xffffffff)
+ {
+ if (dwFT2 == 0xffffffff)
+ { // This is Gaim not Miranda
+ szClient = "Gaim";
+ }
+ else if (!dwFT2 && wVersion == 7)
+ { // This is WebICQ not Miranda
+ szClient = "WebICQ";
+ }
+ else if (!dwFT2 && dwFT3 == 0x3B7248ED)
+ { // And this is most probably Spam Bot
+ szClient = cliSpamBot;
+ }
+ else
+ { // Yes this is most probably Miranda, get the version info
+ szClient = MirandaVersionToString(szClientBuf, 0, dwFT2, 0);
+ *bClientId = CLID_MIRANDA;
+ bMirandaIM = TRUE;
+ }
+ }
+ else if (dwFT1 == 0x7fffffff)
+ { // This is Miranda with unicode core
+ szClient = MirandaVersionToString(szClientBuf, 1, dwFT2, 0);
+ *bClientId = CLID_MIRANDA;
+ bMirandaIM = TRUE;
+ }
+ else if ((dwFT1 & 0xFF7F0000) == 0x7D000000)
+ { // This is probably an Licq client
+ DWORD ver = dwFT1 & 0xFFFF;
+
+ szClient = makeClientVersion(szClientBuf, cliLicqVer, ver / 1000, (ver / 10) % 100, ver % 10, 0);
+ if (dwFT1 & 0x00800000)
+ strcat(szClientBuf, "/SSL");
+ }
+ else if (dwFT1 == 0xffffff8f)
+ {
+ szClient = "StrICQ";
+ }
+ else if (dwFT1 == 0xffffff42)
+ {
+ szClient = "mICQ";
+ }
+ else if (dwFT1 == 0xffffffbe)
+ {
+ unsigned ver1 = (dwFT2>>24)&0xFF;
+ unsigned ver2 = (dwFT2>>16)&0xFF;
+ unsigned ver3 = (dwFT2>>8)&0xFF;
+
+ szClient = makeClientVersion(szClientBuf, "Alicq ", ver1, ver2, ver3, 0);
+ }
+ else if (dwFT1 == 0xFFFFFF7F)
+ {
+ szClient = "&RQ";
+ }
+ else if (dwFT1 == 0xFFFFFFAB)
+ {
+ szClient = "YSM";
+ }
+ else if (dwFT1 == 0x04031980)
+ {
+ szClient = "vICQ";
+ }
+ else if ((dwFT1 == 0x3AA773EE) && (dwFT2 == 0x3AA66380))
+ {
+ szClient = cliLibicq2k;
+ }
+ else if (dwFT1 == 0x3B75AC09)
+ {
+ szClient = cliTrillian;
+ }
+ else if (dwFT1 == 0x3BA8DBAF) // FT2: 0x3BEB5373; FT3: 0x3BEB5262;
+ {
+ if (wVersion == 2)
+ szClient = "stICQ";
+ }
+ else if (dwFT1 == 0xFFFFFFFE && dwFT3 == 0xFFFFFFFE)
+ {
+ szClient = "Jimm";
+ }
+ else if (dwFT1 == 0x3FF19BEB && dwFT3 == 0x3FF19BEB)
+ {
+ szClient = cliIM2;
+ }
+ else if (dwFT1 == 0xDDDDEEFF && !dwFT2 && !dwFT3)
+ {
+ szClient = "SmartICQ";
+ }
+ else if ((dwFT1 & 0xFFFFFFF0) == 0x494D2B00 && !dwFT2 && !dwFT3)
+ { // last byte of FT1: (5 = Win32, 3 = SmartPhone, Pocket PC)
+ szClient = "IM+";
+ }
+ else if (dwFT1 == 0x3B4C4C0C && !dwFT2 && dwFT3 == 0x3B7248ed)
+ {
+ szClient = "KXicq2";
+ }
+ else if (dwFT1 == 0xFFFFF666 && !dwFT3)
+ { // this is R&Q (Rapid Edition)
+ null_snprintf(szClientBuf, 64, "R&Q %u", (unsigned)dwFT2);
+ szClient = szClientBuf;
+ }
+ else if (dwFT1 == 0x66666666 && dwFT3 == 0x66666666)
+ { // http://darkjimm.ucoz.ru/
+ if (dwFT2 == 0x10000)
+ {
+ strcpy(szClientBuf, "D[i]Chat v.");
+ strcat(szClientBuf, "0.1a");
+ }
+ else
+ {
+ makeClientVersion(szClientBuf, "D[i]Chat v.", (dwFT2 >> 8) & 0x0F, (dwFT2 >> 4) & 0x0F, 0, 0);
+ if ((dwFT2 & 0x0F) == 1)
+ strcat(szClientBuf, " alpha");
+ else if ((dwFT2 & 0x0F) == 2)
+ strcat(szClientBuf, " beta");
+ else if ((dwFT2 & 0x0F) == 3)
+ strcat(szClientBuf, " final");
+ }
+ szClient = szClientBuf;
+ }
+ else if (dwFT1 == dwFT2 && dwFT2 == dwFT3 && wVersion == 8)
+ {
+ if ((dwFT1 < dwOnlineSince + 3600) && (dwFT1 > (dwOnlineSince - 3600)))
+ {
+ szClient = cliSpamBot;
+ }
+ }
+ else if (!dwFT1 && !dwFT2 && !dwFT3 && !wVersion && !wLen && dwWebPort == 0x75BB)
+ {
+ szClient = cliSpamBot;
+ }
+ else if (dwFT1 == 0x44F523B0 && dwFT2 == 0x44F523A6 && dwFT3 == 0x44F523A6 && wVersion == 8)
+ {
+ szClient = "Virus";
+ }
+
+ { // capabilities based detection
+ capstr* capId;
+
+ if (nIsICQ && caps)
+ {
+ // check capabilities for client identification
+ if (capId = MatchCapability(caps, wLen, &capMirandaIm, 8)) {
+ // new Miranda Signature
+ DWORD iver = (*capId)[0xC] << 0x18 | (*capId)[0xD] << 0x10 | (*capId)[0xE] << 8 | (*capId)[0xF];
+ DWORD mver = (*capId)[0x8] << 0x18 | (*capId)[0x9] << 0x10 | (*capId)[0xA] << 8 | (*capId)[0xB];
+
+ szClient = MirandaVersionToString(szClientBuf, dwFT1 == 0x7fffffff, iver, mver);
+
+ if (MatchCapability(caps, wLen, &capIcqJs7, 0x4)) {
+ // detect mod
+ strcat(szClientBuf, " (s7 & sss)");
+ if (MatchCapability(caps, wLen, &capIcqJs7, 0xE))
+ strcat(szClientBuf, " + SecureIM");
+ }
+ else if ((dwFT1 & 0x7FFFFFFF) == 0x7FFFFFFF)
+ {
+ if (MatchCapability(caps, wLen, &capMimMobile))
+ strcat(szClientBuf, " (Mobile)");
+
+ if (dwFT3 == 0x5AFEC0DE)
+ strcat(szClientBuf, " + SecureIM");
+ }
+ *bClientId = CLID_MIRANDA;
+ bMirandaIM = TRUE;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capMirandaNg, 8)) {
+ WORD v[4];
+ BYTE *buf = *capId + 8;
+ unpackWord(&buf, &v[0]); unpackWord(&buf, &v[1]); unpackWord(&buf, &v[2]); unpackWord(&buf, &v[3]);
+ mir_snprintf(szClientBuf, MAX_PATH, "Miranda NG ICQ %d.%d.%d.%d", v[0], v[1], v[2], v[3]);
+
+ szClient = szClientBuf;
+ if ((dwFT1 & 0x7FFFFFFF) == 0x7FFFFFFF && dwFT3 == 0x5AFEC0DE)
+ strcat(szClientBuf, " + SecureIM");
+
+ *bClientId = CLID_MIRANDA;
+ bMirandaIM = TRUE;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capIcqJs7, 4))
+ { // detect newer icqj mod
+ szClient = MirandaModToString(szClientBuf, capId, dwFT3 == 0x80000000, "ICQ S7 & SSS");
+ bMirandaIM = TRUE;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capIcqJSin, 4))
+ { // detect newer icqj mod
+ szClient = MirandaModToString(szClientBuf, capId, dwFT3 == 0x80000000, "ICQ S!N");
+ bMirandaIM = TRUE;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capIcqJp, 4))
+ { // detect icqj plus mod
+ szClient = MirandaModToString(szClientBuf, capId, dwFT3 == 0x80000000, "ICQ Plus");
+ bMirandaIM = TRUE;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capMraJava, 12))
+ {
+ unsigned ver1 = (*capId)[13];
+ unsigned ver2 = (*capId)[14];
+
+ szClient = makeClientVersion(szClientBuf, "Mail.ru Agent (Java) v", ver1, ver2, 0, 0);
+ }
+ else if (MatchCapability(caps, wLen, &capTrillian) || MatchCapability(caps, wLen, &capTrilCrypt))
+ { // this is Trillian, check for new versions
+ if (CheckContactCapabilities(hContact, CAPF_RTF))
+ {
+ if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE))
+ szClient = cliTrillian4;
+ else
+ { // workaroud for a bug in Trillian - make it receive msgs, other features will not work!
+ ClearContactCapabilities(hContact, CAPF_SRV_RELAY);
+ szClient = "Trillian v3";
+ }
+ }
+ else if (MatchCapability(caps, wLen, &capFakeHtml) || CheckContactCapabilities(hContact, CAPF_OSCAR_FILE))
+ szClient = cliTrillian4;
+ else
+ szClient = cliTrillian;
+ }
+ else if ((capId = MatchCapability(caps, wLen, &capSimOld, 0xF)) && ((*capId)[0xF] != 0x92 && (*capId)[0xF] >= 0x20 || (*capId)[0xF] == 0))
+ {
+ int hiVer = (((*capId)[0xF]) >> 6) - 1;
+ unsigned loVer = (*capId)[0xF] & 0x1F;
+
+ if ((hiVer < 0) || ((hiVer == 0) && (loVer == 0)))
+ szClient = "Kopete";
+ else
+ szClient = makeClientVersion(szClientBuf, "SIM ", (unsigned)hiVer, loVer, 0, 0);
+ }
+ else if (capId = MatchCapability(caps, wLen, &capSim, 0xC))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+ unsigned ver4 = (*capId)[0xF];
+
+ szClient = makeClientVersion(szClientBuf, "SIM ", ver1, ver2, ver3, ver4 & 0x0F);
+ if (ver4 & 0x80)
+ strcat(szClientBuf,"/Win32");
+ else if (ver4 & 0x40)
+ strcat(szClientBuf,"/MacOS X");
+ }
+ else if (capId = MatchCapability(caps, wLen, &capLicq, 0xC))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD] % 100;
+ unsigned ver3 = (*capId)[0xE];
+
+ szClient = makeClientVersion(szClientBuf, cliLicqVer, ver1, ver2, ver3, 0);
+ if ((*capId)[0xF])
+ strcat(szClientBuf,"/SSL");
+ }
+ else if (capId = MatchCapability(caps, wLen, &capKopete, 0xC))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+ unsigned ver4 = (*capId)[0xF];
+
+ szClient = makeClientVersion(szClientBuf, "Kopete ", ver1, ver2, ver3, ver4);
+ }
+ else if (capId = MatchCapability(caps, wLen, &capClimm, 0xC))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+ unsigned ver4 = (*capId)[0xF];
+
+ szClient = makeClientVersion(szClientBuf, "climm ", ver1, ver2, ver3, ver4);
+ if ((ver1 & 0x80) == 0x80)
+ strcat(szClientBuf, " alpha");
+ if (dwFT3 == 0x02000020)
+ strcat(szClientBuf, "/Win32");
+ else if (dwFT3 == 0x03000800)
+ strcat(szClientBuf, "/MacOS X");
+ }
+ else if (capId = MatchCapability(caps, wLen, &capmIcq, 0xC))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+ unsigned ver4 = (*capId)[0xF];
+
+ szClient = makeClientVersion(szClientBuf, "mICQ ", ver1, ver2, ver3, ver4);
+ if ((ver1 & 0x80) == 0x80)
+ strcat(szClientBuf, " alpha");
+ }
+ else if (MatchCapability(caps, wLen, &capIm2))
+ { // IM2 v2 provides also Aim Icon cap
+ szClient = cliIM2;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capAndRQ, 9))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xB];
+ unsigned ver3 = (*capId)[0xA];
+ unsigned ver4 = (*capId)[9];
+
+ szClient = makeClientVersion(szClientBuf, "&RQ ", ver1, ver2, ver3, ver4);
+ }
+ else if (capId = MatchCapability(caps, wLen, &capRAndQ, 9))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xB];
+ unsigned ver3 = (*capId)[0xA];
+ unsigned ver4 = (*capId)[9];
+
+ szClient = makeClientVersion(szClientBuf, "R&Q ", ver1, ver2, ver3, ver4);
+ }
+ else if (MatchCapability(caps, wLen, &capIMadering))
+ { // http://imadering.com
+ szClient = "IMadering";
+ }
+ else if (MatchCapability(caps, wLen, &capQipPDA))
+ {
+ szClient = "QIP PDA (Windows)";
+ }
+ else if (MatchCapability(caps, wLen, &capQipSymbian))
+ {
+ szClient = "QIP PDA (Symbian)";
+ }
+ else if (MatchCapability(caps, wLen, &capQipIphone))
+ {
+ szClient = "QIP Mobile (IPhone)";
+ }
+ else if (MatchCapability(caps, wLen, &capQipMobile))
+ {
+ szClient = "QIP Mobile (Java)";
+ }
+ else if (MatchCapability(caps, wLen, &capQipInfium))
+ {
+ char ver[10];
+
+ strcpy(szClientBuf, "QIP Infium");
+ if (dwFT1)
+ { // add build
+ null_snprintf(ver, 10, " (%d)", dwFT1);
+ strcat(szClientBuf, ver);
+ }
+ if (dwFT2 == 0x0B)
+ strcat(szClientBuf, " Beta");
+
+ szClient = szClientBuf;
+ }
+ else if (MatchCapability(caps, wLen, &capQip2010, 12))
+ {
+ char ver[10];
+
+ strcpy(szClientBuf, "QIP 2010");
+ if (dwFT1)
+ { // add build
+ null_snprintf(ver, 10, " (%d)", dwFT1);
+ strcat(szClientBuf, ver);
+ }
+
+ szClient = szClientBuf;
+ }
+ else if (MatchCapability(caps, wLen, &capQip2012, 12))
+ {
+ char ver[10];
+
+ strcpy(szClientBuf, "QIP 2012");
+ if (dwFT1)
+ { // add build
+ null_snprintf(ver, 10, " (%d)", dwFT1);
+ strcat(szClientBuf, ver);
+ }
+
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capQip, 0xE))
+ {
+ char ver[10];
+
+ if (dwFT3 == 0x0F)
+ strcpy(ver, "2005");
+ else
+ null_strcpy(ver, (char*)(*capId) + 11, 5);
+
+ null_snprintf(szClientBuf, 64, cliQip, ver);
+ if (dwFT1 && dwFT2 == 0x0E)
+ { // add QIP build
+ null_snprintf(ver, 10, " (%d%d%d%d)", dwFT1 >> 0x18, (dwFT1 >> 0x10) & 0xFF, (dwFT1 >> 0x08) & 0xFF, dwFT1 & 0xFF);
+ strcat(szClientBuf, ver);
+ }
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capmChat, 0xA))
+ {
+ strcpy(szClientBuf, "mChat ");
+ strncat(szClientBuf, (char*)(*capId) + 0xA, 6);
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capJimm, 5))
+ {
+ strcpy(szClientBuf, "Jimm ");
+ strncat(szClientBuf, (char*)(*capId) + 5, 11);
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capCorePager, 0xA))
+ { // http://corepager.net.ru/index/0-2
+ strcpy(szClientBuf, "CORE Pager");
+ if (dwFT2 == 0x0FFFF0011 && dwFT3 == 0x1100FFFF && (dwFT1 >> 0x18))
+ {
+ char ver[16];
+
+ null_snprintf(ver, 10, " %d.%d", dwFT1 >> 0x18, (dwFT1 >> 0x10) & 0xFF);
+ if ((dwFT1 & 0xFF) == 0x0B)
+ strcat(ver, " Beta");
+ strcat(szClientBuf, ver);
+ }
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capDiChat, 9))
+ { // http://darkjimm.ucoz.ru/
+ strcpy(szClientBuf, "D[i]Chat");
+ strncat(szClientBuf, (char*)(*capId) + 8, 8);
+ szClient = szClientBuf;
+ }
+ else if (MatchCapability(caps, wLen, &capMacIcq))
+ {
+ szClient = "ICQ for Mac";
+ }
+ else if (MatchCapability(caps, wLen, &capUim))
+ {
+ szClient = "uIM";
+ }
+ else if (MatchCapability(caps, wLen, &capAnastasia))
+ { // http://chis.nnov.ru/anastasia
+ szClient = "Anastasia";
+ }
+ else if (capId = MatchCapability(caps, wLen, &capPalmJicq, 0xC))
+ { // http://www.jsoft.ru
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+ unsigned ver4 = (*capId)[0xF];
+
+ szClient = makeClientVersion(szClientBuf, "JICQ ", ver1, ver2, ver3, ver4);
+ }
+ else if (MatchCapability(caps, wLen, &capInluxMsgr))
+ { // http://www.inlusoft.com
+ szClient = "Inlux Messenger";
+ }
+ else if (capId = MatchCapability(caps, wLen, &capMipClient, 0xC))
+ { // http://mip.rufon.net
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+ unsigned ver4 = (*capId)[0xF];
+
+ if (ver1 < 30)
+ {
+ makeClientVersion(szClientBuf, "MIP ", ver1, ver2, ver3, ver4);
+ }
+ else
+ {
+ strcpy(szClientBuf, "MIP ");
+ strncat(szClientBuf, (char*)(*capId) + 11, 5);
+ }
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capMipClient, 0x04))
+ { //http://mip.rufon.net - new signature
+ strcpy(szClientBuf, "MIP ");
+ strncat(szClientBuf, (char*)(*capId) + 4, 12);
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capVmIcq, 0x06))
+ {
+ strcpy(szClientBuf, "VmICQ");
+ strncat(szClientBuf, (char*)(*capId) + 5, 11);
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capSmapeR, 0x07))
+ { // http://www.smape.com/smaper
+ strcpy(szClientBuf, "SmapeR");
+ strncat(szClientBuf, (char*)(*capId) + 6, 10);
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capYapp, 0x04))
+ { // http://yapp.ru
+ strcpy(szClientBuf, "Yapp! v");
+ strncat(szClientBuf, (char*)(*capId) + 8, 5);
+ szClient = szClientBuf;
+ }
+ else if (MatchCapability(caps, wLen, &capDigsby, 0x06))
+ { // http://www.dibsby.com (newer builds)
+ szClient = "Digsby";
+ }
+ else if (MatchCapability(caps, wLen, &capDigsbyBeta))
+ { // http://www.digsby.com - probably by mistake (feature detection as well)
+ szClient = "Digsby";
+ }
+ else if (MatchCapability(caps, wLen, &capJapp))
+ { // http://www.japp.org.ua
+ szClient = "japp";
+ }
+ else if (MatchCapability(caps, wLen, &capPigeon, 0x07))
+ { // http://pigeon.vpro.ru
+ szClient = "PIGEON!";
+ }
+ else if (capId = MatchCapability(caps, wLen, &capQutIm, 0x05))
+ { // http://www.qutim.org
+ if ((*capId)[0x6] == 0x2E)
+ { // old qutim id
+ unsigned ver1 = (*capId)[0x5] - 0x30;
+ unsigned ver2 = (*capId)[0x7] - 0x30;
+
+ makeClientVersion(szClientBuf, "qutIM ", ver1, ver2, 0, 0);
+ }
+ else
+ { // new qutim id
+ unsigned ver1 = (*capId)[0x6];
+ unsigned ver2 = (*capId)[0x7];
+ unsigned ver3 = (*capId)[0x8];
+ unsigned ver4 = ((*capId)[0x9] << 8) || (*capId)[0xA];
+
+ makeClientVersion(szClientBuf, "qutIM ", ver1, ver2, ver3, ver4);
+
+ switch ((*capId)[0x5])
+ {
+ case 'l':
+ strcat(szClientBuf, "/Linux");
+ break;
+ case 'w':
+ strcat(szClientBuf, "/Win32");
+ break;
+ case 'm':
+ strcat(szClientBuf, "/MacOS X");
+ break;
+ }
+ }
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capBayan, 8))
+ { // http://www.barobin.com/bayanICQ.html
+ strcpy(szClientBuf, "bayanICQ ");
+ strncat(szClientBuf, (char*)(*capId) + 8, 5);
+ szClient = szClientBuf;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capJabberJIT, 0x04))
+ {
+ szClient = "Jabber ICQ Transport";
+ }
+ else if (capId = MatchCapability(caps, wLen, &capIcqKid2, 0x07))
+ { // http://sourceforge.net/projects/icqkid2
+ unsigned ver1 = (*capId)[0x7];
+ unsigned ver2 = (*capId)[0x8];
+ unsigned ver3 = (*capId)[0x9];
+ unsigned ver4 = (*capId)[0xA];
+
+ szClient = makeClientVersion(szClientBuf, "IcqKid2 v", ver1, ver2, ver3, ver4);
+ }
+ else if (capId = MatchCapability(caps, wLen, &capWebIcqPro, 0x0A))
+ { // http://intrigue.ru/workshop/webicqpro/webicqpro.html
+ szClient = "WebIcqPro";
+ }
+ else if (capId = MatchCapability(caps, wLen, &capCitron))
+ { // http://www.citron-im.com
+ szClient = "Citron IM";
+ }
+ else if (szClient == cliLibicq2k)
+ { // try to determine which client is behind libicq2000
+ if (CheckContactCapabilities(hContact, CAPF_RTF))
+ szClient = cliCentericq; // centericq added rtf capability to libicq2000
+ else if (CheckContactCapabilities(hContact, CAPF_UTF))
+ szClient = cliLibicqUTF; // IcyJuice added unicode capability to libicq2000
+ // others - like jabber transport uses unmodified library, thus cannot be detected
+ }
+ else if (szClient == NULL) // HERE ENDS THE SIGNATURE DETECTION, after this only feature default will be detected
+ {
+ if (wVersion == 8 && CheckContactCapabilities(hContact, CAPF_XTRAZ) && (MatchCapability(caps, wLen, &capIMSecKey1, 6) || MatchCapability(caps, wLen, &capIMSecKey2, 6)))
+ { // ZA mangled the version, OMG!
+ wVersion = 9;
+ }
+ if (wVersion == 8 && (MatchCapability(caps, wLen, &capComm20012) || CheckContactCapabilities(hContact, CAPF_SRV_RELAY)))
+ { // try to determine 2001-2003 versions
+ if (MatchCapability(caps, wLen, &capIs2001))
+ {
+ if (!dwFT1 && !dwFT2 && !dwFT3)
+ if (CheckContactCapabilities(hContact, CAPF_RTF))
+ szClient = "TICQClient"; // possibly also older GnomeICU
+ else
+ szClient = "ICQ for Pocket PC";
+ else
+ {
+ *bClientId = CLID_GENERIC;
+ szClient = "ICQ 2001";
+ }
+ }
+ else if (MatchCapability(caps, wLen, &capIs2002))
+ {
+ *bClientId = CLID_GENERIC;
+ szClient = "ICQ 2002";
+ }
+ else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_RTF))
+ {
+ if (!dwFT1 && !dwFT2 && !dwFT3)
+ {
+ if (!dwWebPort)
+ szClient = "GnomeICU 0.99.5+"; // no other way
+ else
+ szClient = "IC@";
+ }
+ else
+ {
+ *bClientId = CLID_GENERIC;
+ szClient = "ICQ 2002/2003a";
+ }
+ }
+ else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING | CAPF_XTRAZ) &&
+ MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimIcon) &&
+ MatchCapability(caps, wLen, &capFakeHtml))
+ { // libpurple (e.g. Pidgin 2.7.x)
+ if (MatchShortCapability(caps, wLen, &capAimDirect))
+ szClient = "libpurple";
+ else
+ szClient = "Meebo";
+ }
+ else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING))
+ {
+ if (!dwFT1 && !dwFT2 && !dwFT3)
+ {
+ szClient = "PreludeICQ";
+ }
+ }
+ }
+ else if (wVersion == 8)
+ {
+ if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect))
+ szClient = "imo.im"; //https://imo.im/ - Web IM
+ }
+ else if (wVersion == 9)
+ { // try to determine lite versions
+ if (CheckContactCapabilities(hContact, CAPF_XTRAZ))
+ {
+ *bClientId = CLID_GENERIC;
+ if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE))
+ {
+ if (MatchCapability(caps, wLen, &captZers))
+ { // capable of tZers ?
+ if (MatchCapability(caps, wLen, &capIcqLiteNew) && MatchShortCapability(caps, wLen, &capStatusTextAware) &&
+ MatchShortCapability(caps, wLen, &capAimLiveVideo) && MatchShortCapability(caps, wLen, &capAimLiveAudio))
+ {
+ strcpy(szClientBuf, "ICQ 7");
+ }
+ else if (MatchCapability(caps, wLen, &capFakeHtml))
+ {
+ if (MatchShortCapability(caps, wLen, &capAimLiveVideo) && MatchShortCapability(caps, wLen, &capAimLiveAudio))
+ {
+ strcpy(szClientBuf, "ICQ 6");
+ *bClientId = CLID_ICQ6;
+ }
+ else if (CheckContactCapabilities(hContact, CAPF_RTF) && !CheckContactCapabilities(hContact, CAPF_CONTACTS) && MatchShortCapability(caps, wLen, &capIcqDevils))
+ {
+ strcpy(szClientBuf, "Qnext v4"); // finally handles SRV_RELAY correctly
+ *bClientId = CLID_ALTERNATIVE;
+ }
+ }
+ else
+ {
+ strcpy(szClientBuf, "icq5.1");
+ }
+ }
+ else
+ {
+ strcpy(szClientBuf, "icq5");
+ }
+
+ if (MatchCapability(caps, wLen, &capRambler))
+ {
+ strcat(szClientBuf, " (Rambler)");
+ }
+ else if (MatchCapability(caps, wLen, &capAbv))
+ {
+ strcat(szClientBuf, " (Abv)");
+ }
+ else if (MatchCapability(caps, wLen, &capNetvigator))
+ {
+ strcat(szClientBuf, " (Netvigator)");
+ }
+ szClient = szClientBuf;
+ }
+ else if (!CheckContactCapabilities(hContact, CAPF_ICQDIRECT))
+ {
+ *bClientId = CLID_ALTERNATIVE;
+ if (CheckContactCapabilities(hContact, CAPF_RTF))
+ {
+ // most probably Qnext - try to make that shit at least receiving our msgs
+ ClearContactCapabilities(hContact, CAPF_SRV_RELAY);
+ NetLog_Server("Forcing simple messages (QNext client).");
+ szClient = "Qnext";
+ }
+ else if (CheckContactCapabilities(hContact, CAPF_TYPING) && MatchCapability(caps, wLen, &captZers) && MatchCapability(caps, wLen, &capFakeHtml))
+ {
+ if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF) && MatchShortCapability(caps, wLen, &capAimLiveAudio))
+ szClient = "Mail.ru Agent (PC)";
+ else
+ szClient = "Fring";
+ }
+ else
+ szClient = "pyICQ";
+ }
+ else
+ szClient = "ICQ Lite v4";
+ }
+ else if (MatchCapability(caps, wLen, &capIcqLiteNew))
+ szClient = "ICQ Lite"; // the new ICQ Lite based on ICQ6
+ else if (!CheckContactCapabilities(hContact, CAPF_ICQDIRECT))
+ {
+ if (MatchCapability(caps, wLen, &capFakeHtml) && MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimSmartCaps))
+ szClient = cliTrillian4;
+ else if (CheckContactCapabilities(hContact, CAPF_UTF) && !CheckContactCapabilities(hContact, CAPF_RTF))
+ szClient = "pyICQ";
+ }
+ }
+ else if (wVersion == 7)
+ {
+ if (CheckContactCapabilities(hContact, CAPF_RTF))
+ szClient = "GnomeICU"; // this is an exception
+ else if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY))
+ {
+ if (!dwFT1 && !dwFT2 && !dwFT3)
+ szClient = "&RQ";
+ else
+ {
+ *bClientId = CLID_GENERIC;
+ szClient = "ICQ 2000";
+ }
+ }
+ else if (CheckContactCapabilities(hContact, CAPF_UTF))
+ {
+ if (CheckContactCapabilities(hContact, CAPF_TYPING))
+ szClient = "Icq2Go! (Java)";
+ else if (wUserClass & CLASS_WIRELESS)
+ szClient = "Pocket Web 1&1";
+ else
+ szClient = "Icq2Go!";
+ }
+ }
+ else if (wVersion == 0xA)
+ {
+ if (!CheckContactCapabilities(hContact, CAPF_RTF) && !CheckContactCapabilities(hContact, CAPF_UTF))
+ { // this is bad, but we must do it - try to detect QNext
+ ClearContactCapabilities(hContact, CAPF_SRV_RELAY);
+ NetLog_Server("Forcing simple messages (QNext client).");
+ szClient = "Qnext";
+ }
+ else if (!CheckContactCapabilities(hContact, CAPF_RTF) && CheckContactCapabilities(hContact, CAPF_UTF) && !dwFT1 && !dwFT2 && !dwFT3)
+ { // not really good, but no other option
+ szClient = "NanoICQ";
+ }
+ }
+ else if (wVersion == 0xB)
+ {
+ if (CheckContactCapabilities(hContact, CAPF_XTRAZ | CAPF_SRV_RELAY | CAPF_TYPING | CAPF_UTF) && MatchShortCapability(caps, wLen, &capIcqDevils))
+ {
+ szClient = "Mail.ru Agent (Symbian)";
+ }
+ }
+ else if (wVersion == 0)
+ { // capability footprint based detection - not really reliable
+ if (!dwFT1 && !dwFT2 && !dwFT3 && !dwWebPort && !dwDirectCookie)
+ { // DC info is empty
+ if (CheckContactCapabilities(hContact, CAPF_TYPING) && MatchCapability(caps, wLen, &capIs2001) &&
+ MatchCapability(caps, wLen, &capIs2002) && MatchCapability(caps, wLen, &capComm20012))
+ szClient = cliSpamBot;
+ else if (MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect) &&
+ CheckContactCapabilities(hContact, CAPF_OSCAR_FILE | CAPF_UTF))
+ { // detect libgaim/libpurple versions
+ if (CheckContactCapabilities(hContact, CAPF_SRV_RELAY))
+ szClient = "Adium X"; // yeah, AFAIK only Adium has this fixed
+ else if (CheckContactCapabilities(hContact, CAPF_TYPING))
+ szClient = "libpurple";
+ else
+ szClient = "libgaim";
+ }
+ else if (MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect) &&
+ MatchCapability(caps, wLen, &capOscarChat) && CheckContactCapabilities(hContact, CAPF_OSCAR_FILE) && wLen == 0x40)
+ szClient = "libgaim"; // Gaim 1.5.1 most probably
+ else if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE) && MatchCapability(caps, wLen, &capOscarChat) && wLen == 0x20)
+ szClient = "Easy Message";
+ else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capOscarChat) && wLen == 0x40)
+ szClient = "Meebo";
+ else if (CheckContactCapabilities(hContact, CAPF_UTF) && MatchShortCapability(caps, wLen, &capAimIcon) && wLen == 0x20)
+ szClient = "PyICQ-t Jabber Transport";
+ else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_XTRAZ) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capXtrazVideo))
+ szClient = "PyICQ-t Jabber Transport";
+ else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_ICQDIRECT | CAPF_TYPING) && wLen == 0x40)
+ szClient = "Agile Messenger"; // Smartphone 2002
+ else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_ICQDIRECT | CAPF_OSCAR_FILE) && MatchShortCapability(caps, wLen, &capAimFileShare))
+ szClient = "Slick"; // http://lonelycatgames.com/?app=slick
+ else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_OSCAR_FILE | CAPF_CONTACTS) && MatchShortCapability(caps, wLen, &capAimFileShare) && MatchShortCapability(caps, wLen, &capAimIcon))
+ szClient = "Digsby"; // http://www.digsby.com
+ else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_SRV_RELAY | CAPF_CONTACTS) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capFakeHtml))
+ szClient = "mundu IM"; // http://messenger.mundu.com
+ else if (CheckContactCapabilities(hContact, CAPF_UTF | CAPF_OSCAR_FILE) && MatchCapability(caps, wLen, &capOscarChat))
+ szClient = "eBuddy"; //http://www.ebuddy.com
+ else if (CheckContactCapabilities(hContact, CAPF_CONTACTS | CAPF_OSCAR_FILE) && MatchShortCapability(caps, wLen, &capAimIcon) && MatchShortCapability(caps, wLen, &capAimDirect) && MatchCapability(caps, wLen, &capOscarChat))
+ szClient = "IloveIM"; //http://www.iloveim.com/
+
+ }
+ }
+ }
+ }
+ else if (!nIsICQ)
+ { // detect AIM clients
+ if (caps)
+ {
+ if (capId = MatchCapability(caps, wLen, &capAimOscar, 8))
+ { // AimOscar Signature
+ DWORD aver = (*capId)[0xC] << 0x18 | (*capId)[0xD] << 0x10 | (*capId)[0xE] << 8 | (*capId)[0xF];
+ DWORD mver = (*capId)[0x8] << 0x18 | (*capId)[0x9] << 0x10 | (*capId)[0xA] << 8 | (*capId)[0xB];
+
+ szClient = MirandaVersionToStringEx(szClientBuf, 0, "AimOscar", aver, mver);
+ bMirandaIM = TRUE;
+ }
+ else if (capId = MatchCapability(caps, wLen, &capSim, 0xC))
+ { // Sim is universal
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+
+ szClient = makeClientVersion(szClientBuf, "SIM ", ver1, ver2, ver3, 0);
+ if ((*capId)[0xF] & 0x80)
+ strcat(szClientBuf,"/Win32");
+ else if ((*capId)[0xF] & 0x40)
+ strcat(szClientBuf,"/MacOS X");
+ }
+ else if (capId = MatchCapability(caps, wLen, &capKopete, 0xC))
+ {
+ unsigned ver1 = (*capId)[0xC];
+ unsigned ver2 = (*capId)[0xD];
+ unsigned ver3 = (*capId)[0xE];
+ unsigned ver4 = (*capId)[0xF];
+
+ szClient = makeClientVersion(szClientBuf, "Kopete ", ver1, ver2, ver3, ver4);
+ }
+ else if (MatchCapability(caps, wLen, &capIm2))
+ { // IM2 extensions
+ szClient = cliIM2;
+ }
+ else if (MatchCapability(caps, wLen, &capNaim, 0x8))
+ {
+ szClient = "naim";
+ }
+ else if (MatchCapability(caps, wLen, &capDigsby, 0x06) || MatchCapability(caps, wLen, &capDigsbyBeta))
+ { // http://www.dibsby.com
+ szClient = "Digsby";
+ }
+ else if (MatchShortCapability(caps, wLen, &capAimIcon) && MatchCapability(caps, wLen, &capOscarChat) &&
+ CheckContactCapabilities(hContact, CAPF_UTF | CAPF_TYPING) && wLen == 0x40)
+ szClient = "Meebo";
+ else if (wLen == 0x90 && CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING | CAPF_XTRAZ) &&
+ MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimIcon) &&
+ MatchShortCapability(caps, wLen, &capAimDirect) && MatchCapability(caps, wLen, &capFakeHtml))
+ { // libpurple (e.g. Pidgin 2.7.x)
+ szClient = "libpurple";
+ }
+ else if (wLen == 0x70 && CheckContactCapabilities(hContact, CAPF_SRV_RELAY | CAPF_UTF | CAPF_TYPING | CAPF_XTRAZ) &&
+ MatchCapability(caps, wLen, &capOscarChat) && MatchShortCapability(caps, wLen, &capAimIcon) &&
+ MatchCapability(caps, wLen, &capFakeHtml))
+ { // libpurple - Meebo (without DirectIM and OFT)
+ szClient = "Meebo";
+ }
+ else
+ szClient = "AIM";
+ }
+ else if(wUserClass & CLASS_WIRELESS)
+ szClient = "AIM (Mobile)";
+ else
+ szClient = "AIM";
+ }
+ }
+ if (caps && bMirandaIM)
+ { // custom miranda packs
+ capstr* capId;
+
+ if (capId = MatchCapability(caps, wLen, &capMimPack, 4))
+ {
+ char szPack[16];
+
+ null_snprintf(szPack, 16, " [%.12s]", (*capId)+4);
+
+ if (szClient != szClientBuf)
+ { // make sure client string is not constant
+ strcpy(szClientBuf, szClient);
+ szClient = szClientBuf;
+ }
+
+ strcat(szClientBuf, szPack);
+ }
+ }
+
+ BOOL bClientDetected = (szClient != NULL);
+
+ if (!szClient)
+ {
+ *bClientId = CLID_GENERIC;
+
+ switch (wVersion)
+ { // client detection failed, provide default clients
+ case 6:
+ szClient = "ICQ99";
+ break;
+ case 7:
+ szClient = "ICQ 2000/Icq2Go";
+ break;
+ case 8:
+ szClient = "ICQ 2001-2003a";
+ break;
+ case 9:
+ szClient = "ICQ Lite";
+ break;
+ case 0xA:
+ szClient = "ICQ 2003b";
+ }
+ }
+
+ if (szClient)
+ {
+ char *szExtra = NULL;
+
+ if (MatchCapability(caps, wLen, &capSimpLite))
+ szExtra = " + SimpLite";
+ else if (MatchCapability(caps, wLen, &capSimpPro))
+ szExtra = " + SimpPro";
+ else if (MatchCapability(caps, wLen, &capIMsecure) || MatchCapability(caps, wLen, &capIMSecKey1, 6) || MatchCapability(caps, wLen, &capIMSecKey2, 6))
+ szExtra = " + IMsecure";
+
+ if (szExtra)
+ {
+ if (szClient != szClientBuf)
+ {
+ strcpy(szClientBuf, szClient);
+ szClient = szClientBuf;
+ }
+ strcat(szClientBuf, szExtra);
+ }
+ }
+
+ if (!szCurrentClient || strcmpnull(szCurrentClient, szClient))
+ { // Log the detection result if it has changed or contact just logged on...
+ if (bClientDetected)
+ NetLog_Server("Client identified as %s", szClient);
+ else
+ NetLog_Server("No client identification, put default ICQ client for protocol.");
+ }
+
+ return szClient;
+}
diff --git a/protocols/IcqOscarJ/src/icq_constants.h b/protocols/IcqOscarJ/src/icq_constants.h
new file mode 100644
index 0000000000..4da90f1bc0
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_constants.h
@@ -0,0 +1,652 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Global constants and default settings are defined here
+//
+// -----------------------------------------------------------------------------
+// Most of the protocol constants follow the naming conventions of the
+// Oscar documentation at http://iserverd.khstu.ru/oscar/index.html
+// BIG THANKS to Alexandr for maintaining this site and to everyone
+// in the ICQ devel community who have helped to collect the data.
+
+#ifndef __ICQ_CONSTANTS_H
+#define __ICQ_CONSTANTS_H
+
+
+/* Static icon indexes */
+#define ISI_AUTH_REQUEST 0
+#define ISI_AUTH_GRANT 1
+#define ISI_AUTH_REVOKE 2
+#define ISI_ADD_TO_SERVLIST 3
+
+/* Contact menu item indexes */
+#define ICMI_AUTH_REQUEST 0
+#define ICMI_AUTH_GRANT 1
+#define ICMI_AUTH_REVOKE 2
+#define ICMI_ADD_TO_SERVLIST 3
+#define ICMI_XSTATUS_DETAILS 4
+#define ICMI_OPEN_PROFILE 5
+
+/* Some default settings */
+#define DEFAULT_SERVER_PORT 5190
+#define DEFAULT_SERVER_PORT_SSL 443
+#define DEFAULT_SERVER_HOST "login.icq.com"
+#define DEFAULT_SERVER_HOST_SSL "slogin.icq.com"
+#define DEFAULT_SS_ENABLED 1
+#define DEFAULT_SS_ADDSERVER 1
+#define DEFAULT_SS_LOAD 0
+#define DEFAULT_SS_STORE 1
+#define DEFAULT_SS_GROUP "General"
+
+#define DEFAULT_SECURE_LOGIN 1
+#define DEFAULT_SECURE_CONNECTION 1
+#define DEFAULT_KEEPALIVE_ENABLED 1
+#define DEFAULT_AIM_ENABLED 1
+#define DEFAULT_UTF_ENABLED 2 // everything unicode is default
+#define DEFAULT_ANSI_CODEPAGE CP_ACP
+#define DEFAULT_DCMSG_ENABLED 1 // passive dc messaging is default
+#define DEFAULT_TEMPVIS_ENABLED 1 // temporary visible is enabled by default
+#define DEFAULT_MTN_ENABLED 1
+#define DEFAULT_AVATARS_ENABLED 1
+#define DEFAULT_LOAD_AVATARS 1
+#define DEFAULT_BIGGER_AVATARS 0
+#define DEFAULT_AVATARS_CHECK 1
+#define DEFAULT_XSTATUS_ENABLED 0
+#define DEFAULT_XSTATUS_AUTO 1
+#define DEFAULT_XSTATUS_RESET 0
+#define DEFAULT_MOODS_ENABLED 1
+#define DEFAULT_KILLSPAM_ENABLED 1
+
+#define DEFAULT_SLOWSEND 1
+#define DEFAULT_ONLYSERVERACKS 1
+
+#define DEFAULT_POPUPS_ENABLED 1
+#define DEFAULT_SPAM_POPUPS_ENABLED 1
+#define DEFAULT_LOG_POPUPS_ENABLED 1
+#define DEFAULT_POPUPS_SYS_ICONS 1
+#define DEFAULT_LOG0_TEXT_COLORS RGB(0,0,0) // LOG_NOTE
+#define DEFAULT_LOG0_BACK_COLORS RGB(255,255,255)
+#define DEFAULT_LOG0_TIMEOUT 0
+#define DEFAULT_LOG1_TEXT_COLORS RGB(0,0,0) // LOG_WARNING
+#define DEFAULT_LOG1_BACK_COLORS RGB(255,255,255)
+#define DEFAULT_LOG1_TIMEOUT 0
+#define DEFAULT_LOG2_TEXT_COLORS RGB(0,0,0) // LOG_ERROR
+#define DEFAULT_LOG2_BACK_COLORS RGB(255,255,255)
+#define DEFAULT_LOG2_TIMEOUT 0
+#define DEFAULT_LOG3_TEXT_COLORS RGB(0,0,0) // LOG_FATAL
+#define DEFAULT_LOG3_BACK_COLORS RGB(255,255,255)
+#define DEFAULT_LOG3_TIMEOUT 0
+#define DEFAULT_SPAM_TEXT_COLORS RGB(193,0,38)
+#define DEFAULT_SPAM_BACK_COLORS RGB(213,209,208)
+#define DEFAULT_SPAM_TIMEOUT 0
+#define DEFAULT_POPUPS_WIN_COLORS 0
+#define DEFAULT_POPUPS_DEF_COLORS (BYTE)!DEFAULT_POPUPS_WIN_COLORS
+
+/* Database setting names */
+#define DBSETTING_CAPABILITIES "caps"
+// Contact's server-list items
+#define DBSETTING_SERVLIST_ID "ServerId"
+#define DBSETTING_SERVLIST_GROUP "SrvGroupId"
+#define DBSETTING_SERVLIST_PERMIT "SrvPermitId"
+#define DBSETTING_SERVLIST_DENY "SrvDenyId"
+#define DBSETTING_SERVLIST_IGNORE "SrvIgnoreId"
+// Owner's server-list items
+#define DBSETTING_SERVLIST_PRIVACY "SrvVisibilityID"
+#define DBSETTING_SERVLIST_PHOTO "SrvPhotoID"
+#define DBSETTING_SERVLIST_AVATAR "SrvAvatarID"
+#define DBSETTING_SERVLIST_METAINFO "SrvMetaInfoID"
+#define DBSETTING_SERVLIST_UNHANDLED "SrvUnhandledIDList"
+// Contact's data from server-list
+#define DBSETTING_SERVLIST_DATA "ServerData"
+// User Details
+#define DBSETTING_METAINFO_TOKEN "MetaInfoToken"
+#define DBSETTING_METAINFO_TIME "MetaInfoTime"
+#define DBSETTING_METAINFO_SAVED "InfoTS"
+// Status Note & Mood
+#define DBSETTING_STATUS_NOTE "StatusNote"
+#define DBSETTING_STATUS_NOTE_TIME "StatusNoteTS"
+#define DBSETTING_STATUS_MOOD "StatusMood"
+// Custom Status
+#define DBSETTING_XSTATUS_ID "XStatusId"
+#define DBSETTING_XSTATUS_NAME "XStatusName"
+#define DBSETTING_XSTATUS_MSG "XStatusMsg"
+
+
+// Status FLAGS (used to determine status of other users)
+#define ICQ_STATUSF_ONLINE 0x0000
+#define ICQ_STATUSF_AWAY 0x0001
+#define ICQ_STATUSF_DND 0x0002
+#define ICQ_STATUSF_NA 0x0004
+#define ICQ_STATUSF_OCCUPIED 0x0010
+#define ICQ_STATUSF_FFC 0x0020
+#define ICQ_STATUSF_INVISIBLE 0x0100
+
+// Status values (used to set own status)
+#define ICQ_STATUS_ONLINE 0x0000
+#define ICQ_STATUS_AWAY 0x0001
+#define ICQ_STATUS_NA 0x0005
+#define ICQ_STATUS_OCCUPIED 0x0011
+#define ICQ_STATUS_DND 0x0013
+#define ICQ_STATUS_FFC 0x0020
+#define ICQ_STATUS_INVISIBLE 0x0100
+
+#define STATUS_WEBAWARE 0x0001 // Status webaware flag
+#define STATUS_SHOWIP 0x0002 // Status show ip flag
+#define STATUS_BIRTHDAY 0x0008 // User birthday flag
+#define STATUS_WEBFRONT 0x0020 // User active webfront flag
+#define STATUS_DCDISABLED 0x0100 // Direct connection not supported
+#define STATUS_DCAUTH 0x1000 // Direct connection upon authorization
+#define STATUS_DCCONT 0x2000 // DC only with contact users
+
+
+
+// Typing notification statuses
+#define MTN_FINISHED 0x0000
+#define MTN_TYPED 0x0001
+#define MTN_BEGUN 0x0002
+#define MTN_WINDOW_CLOSED 0x000F
+
+
+
+// Ascii Capability IDs
+#define CAP_RTFMSGS "{97B12751-243C-4334-AD22-D6ABF73F1492}"
+#define CAP_UTF8MSGS "{0946134E-4C7F-11D1-8222-444553540000}"
+
+// Binary Capability Sizes
+#define BINARY_CAP_SIZE 16
+#define BINARY_SHORT_CAP_SIZE 2
+
+// Binary Capability IDs
+#define CAP_SRV_RELAY 0x09, 0x46, 0x13, 0x49, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00
+#define CAP_UTF 0x09, 0x46, 0x13, 0x4e, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00
+#define CAP_RTF 0x97, 0xb1, 0x27, 0x51, 0x24, 0x3c, 0x43, 0x34, 0xad, 0x22, 0xd6, 0xab, 0xf7, 0x3f, 0x14, 0x92
+#define CAP_CONTACTS 0x09, 0x46, 0x13, 0x4b, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00
+#define CAP_TYPING 0x56, 0x3f, 0xc8, 0x09, 0x0b, 0x6f, 0x41, 0xbd, 0x9f, 0x79, 0x42, 0x26, 0x09, 0xdf, 0xa2, 0xf3
+#define CAP_ICQDIRECT 0x09, 0x46, 0x13, 0x44, 0x4c, 0x7f, 0x11, 0xd1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00
+#define CAP_XTRAZ 0x1A, 0x09, 0x3C, 0x6C, 0xD7, 0xFD, 0x4E, 0xC5, 0x9D, 0x51, 0xA6, 0x47, 0x4E, 0x34, 0xF5, 0xA0
+#define CAP_OSCAR_FILE 0x09, 0x46, 0x13, 0x43, 0x4C, 0x7F, 0x11, 0xD1, 0x82, 0x22, 0x44, 0x45, 0x53, 0x54, 0x00, 0x00
+
+// Miranda IM Capability bitmask
+#define CAPF_SRV_RELAY 0x00000001
+#define CAPF_UTF 0x00000002
+#define CAPF_RTF 0x00000004
+#define CAPF_CONTACTS 0x00000010
+#define CAPF_TYPING 0x00000020
+#define CAPF_ICQDIRECT 0x00000080
+#define CAPF_XTRAZ 0x00000100
+#define CAPF_OSCAR_FILE 0x00000400
+#define CAPF_STATUS_MESSAGES 0x10000000
+#define CAPF_STATUS_MOOD 0x40000000
+#define CAPF_XSTATUS 0x80000000
+
+
+// Message Capability IDs
+#define MCAP_SRV_RELAY_FMT_s 0x09461349, 0x4c7f11d1, 0x82224445, 0x53540000
+#define MCAP_REVERSE_DC_REQ_s 0x09461344, 0x4c7f11d1, 0x82224445, 0x53540000
+#define MCAP_FILE_TRANSFER_s 0x09461343, 0x4c7f11d1, 0x82224445, 0x53540000
+#define MCAP_CONTACTS_s 0x0946134b, 0x4c7f11d1, 0x82224445, 0x53540000
+
+// Plugin Type GUIDs
+#define PSIG_MESSAGE_s 0x00000000, 0x00000000, 0x00000000, 0x00000000
+#define PSIG_INFO_PLUGIN_s 0xa0e93f37, 0x4fe9d311, 0xbcd20004, 0xac96dd96
+#define PSIG_STATUS_PLUGIN_s 0x10cf40d1, 0x4fe9d311, 0xbcd20004, 0xac96dd96
+
+// Plugin Message GUIDs
+#define PMSG_QUERY_INFO_s 0xF002BF71, 0x4371D311, 0x8DD20010, 0x4B06462E
+#define PMSG_QUERY_STATUS_s 0x10180670, 0x5471D311, 0x8DD20010, 0x4B06462E
+
+
+
+// Message types
+#define MTYPE_PLAIN 0x01 // Plain text (simple) message
+#define MTYPE_CHAT 0x02 // Chat request message
+#define MTYPE_FILEREQ 0x03 // File request / file ok message
+#define MTYPE_URL 0x04 // URL message (0xFE formatted)
+#define MTYPE_AUTHREQ 0x06 // Authorization request message (0xFE formatted)
+#define MTYPE_AUTHDENY 0x07 // Authorization denied message (0xFE formatted)
+#define MTYPE_AUTHOK 0x08 // Authorization given message (empty)
+#define MTYPE_SERVER 0x09 // Message from OSCAR server (0xFE formatted)
+#define MTYPE_ADDED 0x0C // "You-were-added" message (0xFE formatted)
+#define MTYPE_WWP 0x0D // Web pager message (0xFE formatted)
+#define MTYPE_EEXPRESS 0x0E // Email express message (0xFE formatted)
+#define MTYPE_CONTACTS 0x13 // Contact list message
+#define MTYPE_PLUGIN 0x1A // Plugin message described by text string
+#define MTYPE_AUTOONLINE 0xE7 // Auto online message (internal only)
+#define MTYPE_AUTOAWAY 0xE8 // Auto away message
+#define MTYPE_AUTOBUSY 0xE9 // Auto occupied message
+#define MTYPE_AUTONA 0xEA // Auto not available message
+#define MTYPE_AUTODND 0xEB // Auto do not disturb message
+#define MTYPE_AUTOFFC 0xEC // Auto free for chat message
+// Internal Message types
+#define MTYPE_UNKNOWN 0x00 // Unknown message
+
+#define MTYPE_GREETINGCARD 0x101 // Greeting Card
+#define MTYPE_REQUESTCONTACTS 0x102 // Request for Contacts
+#define MTYPE_MESSAGE 0x103 // Message+
+#define MTYPE_STATUSMSGEXT 0x104 // StatusMsgExt (2003b)
+#define MTYPE_SMS_MESSAGE 0x110 // SMS message from Mobile
+#define MTYPE_SCRIPT_INVITATION 0x201 // Xtraz Invitation
+#define MTYPE_SCRIPT_DATA 0x202 // Xtraz Message
+#define MTYPE_SCRIPT_NOTIFY 0x208 // Xtraz Response
+#define MTYPE_REVERSE_REQUEST 0x401 // Reverse DC request
+
+// Message Plugin Type GUIDs
+#define MGTYPE_MESSAGE_s 0xBE6B7305, 0x0FC2104F, 0xA6DE4DB1, 0xE3564B0E
+#define MGTYPE_STATUSMSGEXT_s 0x811a18bc, 0x0e6c1847, 0xa5916f18, 0xdcc76f1a
+#define MGTYPE_FILE_s 0xF02D12D9, 0x3091D311, 0x8DD70010, 0x4B06462E
+#define MGTYPE_WEBURL_s 0x371C5872, 0xE987D411, 0xA4C100D0, 0xB759B1D9
+#define MGTYPE_CONTACTS_s 0x2A0E7D46, 0x7676D411, 0xBCE60004, 0xAC961EA6
+#define MGTYPE_GREETING_CARD_s 0x01E53B48, 0x2AE4D111, 0xB6790060, 0x97E1E294
+#define MGTYPE_CHAT_s 0xBFF720B2, 0x378ED411, 0xBD280004, 0xAC96D905
+#define MGTYPE_SMS_MESSAGE_s 0x0e28f600, 0x11e7d311, 0xbcf30004, 0xac969dc2
+#define MGTYPE_XTRAZ_SCRIPT_s 0x3b60b3ef, 0xd82a6c45, 0xa4e09c5a, 0x5e67e865
+
+// Message Plugin Sub-Type IDs
+#define MGTYPE_STANDARD_SEND 0x0000
+#define MGTYPE_CONTACTS_REQUEST 0x0002
+#define MGTYPE_SCRIPT_INVITATION 0x0001
+#define MGTYPE_SCRIPT_DATA 0x0002
+#define MGTYPE_SCRIPT_USER_REMOVE 0x0004
+#define MGTYPE_SCRIPT_NOTIFY 0x0008
+#define MGTYPE_UNDEFINED 0xFFFF
+
+
+
+/* Channels */
+#define ICQ_LOGIN_CHAN 0x01
+#define ICQ_DATA_CHAN 0x02
+#define ICQ_ERROR_CHAN 0x03
+#define ICQ_CLOSE_CHAN 0x04
+#define ICQ_PING_CHAN 0x05
+
+/* Families */
+#define ICQ_SERVICE_FAMILY 0x0001
+#define ICQ_LOCATION_FAMILY 0x0002
+#define ICQ_BUDDY_FAMILY 0x0003
+#define ICQ_MSG_FAMILY 0x0004
+#define ICQ_BOS_FAMILY 0x0009
+#define ICQ_LOOKUP_FAMILY 0x000a
+#define ICQ_STATS_FAMILY 0x000b
+#define ICQ_CHAT_NAVIGATION_FAMILY 0x000d
+#define ICQ_CHAT_FAMILY 0x000e
+#define ICQ_AVATAR_FAMILY 0x0010
+#define ICQ_LISTS_FAMILY 0x0013
+#define ICQ_EXTENSIONS_FAMILY 0x0015
+#define ICQ_AUTHORIZATION_FAMILY 0x0017
+#define ICQ_DIRECTORY_FAMILY 0x0025
+
+/* Subtypes for Service Family 0x0001 */
+#define ICQ_ERROR 0x0001
+#define ICQ_CLIENT_READY 0x0002
+#define ICQ_SERVER_READY 0x0003
+#define ICQ_CLIENT_NEW_SERVICE 0x0004
+#define ICQ_SERVER_REDIRECT_SERVICE 0x0005
+#define ICQ_CLIENT_REQ_RATE_INFO 0x0006
+#define ICQ_SERVER_RATE_INFO 0x0007
+#define ICQ_CLIENT_RATE_ACK 0x0008
+#define ICQ_SERVER_RATE_CHANGE 0x000a
+#define ICQ_SERVER_PAUSE 0x000b
+#define ICQ_CLIENT_PAUSE_ACK 0x000c
+#define ICQ_SERVER_RESUME 0x000d
+#define ICQ_CLIENT_REQINFO 0x000e
+#define ICQ_SERVER_NAME_INFO 0x000f
+#define ICQ_SERVER_EVIL_NOTICE 0x0010
+#define ICQ_CLIENT_SET_IDLE 0x0011
+#define ICQ_SERVER_MIGRATIONREQ 0x0012
+#define ICQ_SERVER_MOTD 0x0013
+#define ICQ_CLIENT_FAMILIES 0x0017
+#define ICQ_SERVER_FAMILIES2 0x0018
+#define ICQ_CLIENT_SET_STATUS 0x001e
+#define ICQ_SERVER_EXTSTATUS 0x0021
+
+/* Subtypes for Location Family 0x0002 */
+#define ICQ_LOCATION_CLI_REQ_RIGHTS 0x0002
+#define ICQ_LOCATION_RIGHTS_REPLY 0x0003
+#define ICQ_LOCATION_SET_USER_INFO 0x0004
+#define ICQ_LOCATION_REQ_USER_INFO 0x0005
+#define ICQ_LOCATION_USR_INFO_REPLY 0x0006
+#define ICQ_LOCATION_QRY_USER_INFO 0x0015
+
+/* Subtypes for Buddy Family 0x0003 */
+#define ICQ_USER_CLI_REQBUDDY 0x0002
+#define ICQ_USER_SRV_REPLYBUDDY 0x0003
+#define ICQ_USER_ADDTOLIST 0x0004 /* deprecated */
+#define ICQ_USER_REMOVEFROMLIST 0x0005 /* deprecated */
+#define ICQ_USER_NOTIFY_REJECTED 0x000a
+#define ICQ_USER_ONLINE 0x000b
+#define ICQ_USER_OFFLINE 0x000c
+#define ICQ_USER_ADDTOTEMPLIST 0x000f
+#define ICQ_USER_REMOVEFROMTEMPLIST 0x0010
+
+/* Subtypes for Message Family 0x0004 */
+#define ICQ_MSG_SRV_ERROR 0x0001
+#define ICQ_MSG_CLI_SETPARAMS 0x0002
+#define ICQ_MSG_CLI_RESETPARAMS 0x0003
+#define ICQ_MSG_CLI_REQICBM 0x0004
+#define ICQ_MSG_SRV_REPLYICBM 0x0005
+#define ICQ_MSG_SRV_SEND 0x0006
+#define ICQ_MSG_SRV_RECV 0x0007
+#define ICQ_MSG_SRV_MISSED_MESSAGE 0x000A
+#define ICQ_MSG_RESPONSE 0x000B
+#define ICQ_MSG_SRV_ACK 0x000C
+#define ICQ_MSG_CLI_REQ_OFFLINE 0x0010
+#define ICQ_MSG_MTN 0x0014
+#define ICQ_MSG_SRV_OFFLINE_REPLY 0x0017
+
+/* Subtypes for Privacy Family 0x0009 */
+#define ICQ_PRIVACY_REQ_RIGHTS 0x0002
+#define ICQ_PRIVACY_RIGHTS_REPLY 0x0003
+#define ICQ_CLI_ADDVISIBLE 0x0005
+#define ICQ_CLI_REMOVEVISIBLE 0x0006
+#define ICQ_CLI_ADDINVISIBLE 0x0007
+#define ICQ_CLI_REMOVEINVISIBLE 0x0008
+#define ICQ_PRIVACY_SERVICE_ERROR 0x0009
+#define ICQ_CLI_ADDTEMPVISIBLE 0x000A
+#define ICQ_CLI_REMOVETEMPVISIBLE 0x000B
+
+/* Subtypes for Lookup Family 0x000a */
+#define ICQ_LOOKUP_REQUEST 0x0002
+#define ICQ_LOOKUP_EMAIL_REPLY 0x0003
+
+/* Subtypes for Stats Family 0x000b */
+#define ICQ_STATS_MINREPORTINTERVAL 0x0002
+
+/* Subtypes for Avatar Family 0x0010 */
+#define ICQ_AVATAR_ERROR 0x0001
+#define ICQ_AVATAR_UPLOAD_REQUEST 0x0002
+#define ICQ_AVATAR_UPLOAD_ACK 0x0003
+#define ICQ_AVATAR_GET_REQUEST 0x0006
+#define ICQ_AVATAR_GET_REPLY 0x0007
+
+/* Subtypes for Server Lists Family 0x0013 */
+#define ICQ_LISTS_ERROR 0x0001
+#define ICQ_LISTS_CLI_REQLISTS 0x0002
+#define ICQ_LISTS_SRV_REPLYLISTS 0x0003
+#define ICQ_LISTS_CLI_REQUEST 0x0004
+#define ICQ_LISTS_CLI_CHECK 0x0005
+#define ICQ_LISTS_LIST 0x0006
+#define ICQ_LISTS_GOTLIST 0x0007
+#define ICQ_LISTS_ADDTOLIST 0x0008
+#define ICQ_LISTS_UPDATEGROUP 0x0009
+#define ICQ_LISTS_REMOVEFROMLIST 0x000A
+#define ICQ_LISTS_ACK 0x000E
+#define ICQ_LISTS_UPTODATE 0x000F
+#define ICQ_LISTS_CLI_MODIFYSTART 0x0011
+#define ICQ_LISTS_CLI_MODIFYEND 0x0012
+#define ICQ_LISTS_GRANTAUTH 0x0014
+#define ICQ_LISTS_AUTHGRANTED 0x0015
+#define ICQ_LISTS_REVOKEAUTH 0x0016
+#define ICQ_LISTS_REQUESTAUTH 0x0018
+#define ICQ_LISTS_AUTHREQUEST 0x0019
+#define ICQ_LISTS_CLI_AUTHRESPONSE 0x001A
+#define ICQ_LISTS_SRV_AUTHRESPONSE 0x001B
+#define ICQ_LISTS_YOUWEREADDED 0x001C
+
+/* Subtypes for ICQ Extensions Family 0x0015 */
+#define ICQ_META_ERROR 0x0001
+#define ICQ_META_CLI_REQUEST 0x0002
+#define ICQ_META_SRV_REPLY 0x0003
+#define ICQ_META_SRV_UPDATE 0x0004
+
+/* Subtypes for Authorization Family 0x0017 */
+#define ICQ_SIGNON_ERROR 0x0001
+#define ICQ_SIGNON_LOGIN_REQUEST 0x0002
+#define ICQ_SIGNON_LOGIN_REPLY 0x0003
+#define ICQ_SIGNON_REGISTRATION_REQ 0x0004
+#define ICQ_SIGNON_NEW_UIN 0x0005
+#define ICQ_SIGNON_AUTH_REQUEST 0x0006
+#define ICQ_SIGNON_AUTH_KEY 0x0007
+#define ICQ_SIGNON_REQUEST_IMAGE 0x000C
+#define ICQ_SIGNON_REG_AUTH_IMAGE 0x000D
+
+// Class constants
+#define CLASS_UNCONFIRMED 0x0001
+#define CLASS_ADMINISTRATOR 0x0002
+#define CLASS_AOL 0x0004
+#define CLASS_COMMERCIAL 0x0008
+#define CLASS_FREE 0x0010
+#define CLASS_AWAY 0x0020
+#define CLASS_ICQ 0x0040
+#define CLASS_WIRELESS 0x0080
+#define CLASS_FORWARDING 0x0200
+#define CLASS_BOT 0x0400
+
+// Reply types for SNAC 15/02 & 15/03
+#define CLI_DELETE_OFFLINE_MSGS_REQ 0x003E
+#define CLI_META_INFO_REQ 0x07D0
+#define SRV_META_INFO_REPLY 0x07DA
+
+// Reply subtypes for SNAC 15/02 & 15/03
+#define META_PROCESSING_ERROR 0x0001 // Meta processing error server reply
+#define META_SMS_DELIVERY_RECEIPT 0x0096 // Server SMS response (delivery receipt)
+#define META_SET_PASSWORD_ACK 0x00AA // Set user password server ack
+#define META_UNREGISTER_ACK 0x00B4 // Unregister account server ack
+#define META_BASIC_USERINFO 0x00C8 // User basic info reply
+#define META_WORK_USERINFO 0x00D2 // User work info reply
+#define META_MORE_USERINFO 0x00DC // User more info reply
+#define META_NOTES_USERINFO 0x00E6 // User notes (about) info reply
+#define META_EMAIL_USERINFO 0x00EB // User extended email info reply
+#define META_INTERESTS_USERINFO 0x00F0 // User interests info reply
+#define META_AFFILATIONS_USERINFO 0x00FA // User past/affilations info reply
+#define META_SHORT_USERINFO 0x0104 // Short user information reply
+#define META_HPAGECAT_USERINFO 0x010E // User homepage category information reply
+#define SRV_USER_FOUND 0x01A4 // Search: user found reply
+#define SRV_LAST_USER_FOUND 0x01AE // Search: last user found reply
+#define META_REGISTRATION_STATS_ACK 0x0302 // Registration stats ack
+#define SRV_RANDOM_FOUND 0x0366 // Random search server reply
+#define META_SET_PASSWORD_REQ 0x042E // Set user password request
+#define META_REQUEST_FULL_INFO 0x04B2 // Request full user info
+#define META_REQUEST_SHORT_INFO 0x04BA // Request short user info
+#define META_REQUEST_SELF_INFO 0x04D0 // Request full self user info
+#define META_SEARCH_GENERIC 0x055F // Search user by details (TLV)
+#define META_SEARCH_UIN 0x0569 // Search user by UIN (TLV)
+#define META_SEARCH_EMAIL 0x0573 // Search user by E-mail (TLV)
+#define META_DIRECTORY_QUERY 0x0FA0
+#define META_DIRECTORY_DATA 0x0FAA
+#define META_DIRECTORY_RESPONSE 0x0FB4
+#define META_DIRECTORY_UPDATE 0x0FD2
+#define META_DIRECTORY_UPDATE_ACK 0x0FDC
+
+#define META_XML_INFO 0x08A2 // Server variable requested via xml
+#define META_SET_FULLINFO_REQ 0x0C3A // Set full user info request
+#define META_SET_FULLINFO_ACK 0x0C3F // Server ack for set fullinfo command
+#define META_SPAM_REPORT_ACK 0x2012 // Server ack for user spam report
+
+// Subtypes for Directory meta requests (family 0x5b9)
+#define DIRECTORY_QUERY_INFO 0x0002
+#define DIRECTORY_SET_INFO 0x0003
+#define DIRECTORY_QUERY_MULTI_INFO 0x0006
+#define DIRECTORY_QUERY_INFO_ACK 0x0009
+#define DIRECTORY_SET_INFO_ACK 0x000A
+
+// TLV types
+
+// SECURITY flags
+#define TLV_AUTH 0x02F8 // uint8 User authorization permissions
+#define TLV_WEBAWARE 0x030C // uint8 User 'show web status' permissions
+
+
+// SEARCH only TLVs
+#define TLV_AGERANGE 0x0168 // acombo Age range to search
+#define TLV_KEYWORDS 0x0226 // sstring Whitepages search keywords string
+#define TLV_ONLINEONLY 0x0230 // uint8 Search only online users flag
+#define TLV_UIN 0x0136 // uint32 User uin
+
+// common
+#define TLV_FIRSTNAME 0x0140 // sstring User firstname
+#define TLV_LASTNAME 0x014A // sstring User lastname
+#define TLV_NICKNAME 0x0154 // sstring User nickname
+#define TLV_EMAIL 0x015E // ecombo User email
+#define TLV_GENDER 0x017C // uint8 User gender
+#define TLV_MARITAL 0x033E // uint8 User marital status
+#define TLV_LANGUAGE 0x0186 // uint16 User spoken language
+#define TLV_CITY 0x0190 // sstring User home city name
+#define TLV_STATE 0x019A // sstring User home state abbr
+#define TLV_COUNTRY 0x01A4 // uint16 User home country code
+#define TLV_COMPANY 0x01AE // sstring User work company name
+#define TLV_DEPARTMENT 0x01B8 // sstring User work department name
+#define TLV_POSITION 0x01C2 // sstring User work position (title)
+#define TLV_OCUPATION 0x01CC // uint16 User work ocupation code
+#define TLV_PASTINFO 0x01D6 // icombo User affilations node
+#define TLV_AFFILATIONS 0x01FE // icombo User past info node
+#define TLV_INTERESTS 0x01EA // icombo User interests node
+#define TLV_HOMEPAGE 0x0212 // sstring User homepage category/keywords
+
+// changeinfo
+#define TLV_AGE 0x0172 // uint16 User age
+#define TLV_URL 0x0213 // sstring User homepage url
+#define TLV_BIRTH 0x023A // bcombo User birthday info (year, month, day)
+#define TLV_ABOUT 0x0258 // sstring User notes (about) text
+#define TLV_STREET 0x0262 // sstring User home street address
+#define TLV_ZIPCODE 0x026D // sstring User home zip code
+#define TLV_PHONE 0x0276 // sstring User home phone number
+#define TLV_FAX 0x0280 // sstring User home fax number
+#define TLV_MOBILE 0x028A // sstring User home cellular phone number
+#define TLV_WORKSTREET 0x0294 // sstring User work street address
+#define TLV_WORKCITY 0x029E // sstring User work city name
+#define TLV_WORKSTATE 0x02A8 // sstring User work state name
+#define TLV_WORKCOUNTRY 0x02B2 // uint16 User work country code
+#define TLV_WORKZIPCODE 0x02BD // sstring User work zip code
+#define TLV_WORKPHONE 0x02C6 // sstring User work phone number
+#define TLV_WORKFAX 0x02D0 // sstring User work fax number
+#define TLV_WORKURL 0x02DA // sstring User work webpage url
+#define TLV_TIMEZONE 0x0316 // uint8 User GMT offset
+#define TLV_ORGCITY 0x0320 // sstring User originally from city
+#define TLV_ORGSTATE 0x032A // sstring User originally from state
+#define TLV_ORGCOUNTRY 0x0334 // uint16 User originally from country (code)
+#define TLV_ALLOWSPAM 0x0348 // uint8
+#define TLV_CODEPAGE 0x0352 // uint16 Codepage used for details
+
+
+/* Direct packet types */
+#define PEER_INIT 0xFF
+#define PEER_INIT_ACK 0x01
+#define PEER_MSG_INIT 0x03
+#define PEER_MSG 0x02
+#define PEER_FILE_INIT 0x00
+#define PEER_FILE_INIT_ACK 0x01
+#define PEER_FILE_NEXTFILE 0x02
+#define PEER_FILE_RESUME 0x03
+#define PEER_FILE_STOP 0x04
+#define PEER_FILE_SPEED 0x05
+#define PEER_FILE_DATA 0x06
+
+/* Direct command types */
+#define DIRECT_CANCEL 0x07D0 /* 2000 TCP cancel previous file/chat request */
+#define DIRECT_ACK 0x07DA /* 2010 TCP acknowledge message packet */
+#define DIRECT_MESSAGE 0x07EE /* 2030 TCP message */
+
+// DC types
+#define DC_DISABLED 0x0000 // Direct connection disabled / auth required
+#define DC_HTTPS 0x0001 // Direct connection thru firewall or https proxy
+#define DC_SOCKS 0x0002 // Direct connection thru socks4/5 proxy server
+#define DC_NORMAL 0x0004 // Normal direct connection (without proxy/firewall)
+#define DC_WEB 0x0006 // Web client - no direct connection
+
+// Message flags
+#define MFLAG_NORMAL 0x01 // Normal message
+#define MFLAG_AUTO 0x03 // Auto-message flag
+#define MFLAG_MULTI 0x80 // This is multiple recipients message
+
+// Some SSI constants
+#define SSI_ITEM_BUDDY 0x0000 // Buddy record (name: uin for ICQ and screenname for AIM)
+#define SSI_ITEM_GROUP 0x0001 // Group record
+#define SSI_ITEM_PERMIT 0x0002 // Permit record ("Allow" list in AIM, and "Visible" list in ICQ)
+#define SSI_ITEM_DENY 0x0003 // Deny record ("Block" list in AIM, and "Invisible" list in ICQ)
+#define SSI_ITEM_VISIBILITY 0x0004 // Permit/deny settings or/and bitmask of the AIM classes
+#define SSI_ITEM_PRESENCE 0x0005 // Presence info (if others can see your idle status, etc)
+#define SSI_ITEM_CLIENTDATA 0x0009 // Client specific, e.g. ICQ2k shortcut bar items
+#define SSI_ITEM_IGNORE 0x000e // Ignore list record.
+#define SSI_ITEM_LASTUPDATE 0x000f // Item that contain roster update time (name: "LastUpdateDate")
+#define SSI_ITEM_NONICQ 0x0010 // Non-ICQ contact (to send SMS). Name: 1#EXT, 2#EXT, etc
+#define SSI_ITEM_UNKNOWN2 0x0011 // Unknown.
+#define SSI_ITEM_IMPORTTIME 0x0013 // Item that contain roster import time (name: "Import time")
+#define SSI_ITEM_BUDDYICON 0x0014 // Buddy icon info. (names: "1", "8", etc. according ot the icon type)
+#define SSI_ITEM_SAVED 0x0019
+#define SSI_ITEM_PREAUTH 0x001B
+#define SSI_ITEM_METAINFO 0x0020 // Owner Details' token & last update time
+
+#define SSI_TLV_AWAITING_AUTH 0x0066 // Contact not authorized in list
+#define SSI_TLV_NOT_IN_LIST 0x006A // Always empty
+#define SSI_TLV_UNKNOWN 0x006D // WTF ?
+#define SSI_TLV_SUBITEMS 0x00C8 // List of sub-items IDs
+#define SSI_TLV_VISIBILITY 0x00CA
+#define SSI_TLV_SHORTCUT 0x00CD
+#define SSI_TLV_TIMESTAMP 0x00D4 // Import Timestamp
+#define SSI_TLV_AVATARHASH 0x00D5
+#define SSI_TLV_NAME 0x0131 // Custom contact nickname
+#define SSI_TLV_GROUP_OPENNED 0x0134
+#define SSI_TLV_EMAIL 0x0137 // Custom contact email
+#define SSI_TLV_PHONE 0x0138 // Custom contact phone number
+#define SSI_TLV_PHONE_CELLULAR 0x0139 // Custom contact cellphone number
+#define SSI_TLV_PHONE_SMS 0x013A // Custom contact SMS number
+#define SSI_TLV_COMMENT 0x013C // User comment
+#define SSI_TLV_METAINFO_TOKEN 0x015C // Privacy token for Contact's details
+#define SSI_TLV_METAINFO_TIME 0x015D // Contact's details last update time
+
+#define MAX_SSI_TLV_NAME_SIZE 0x40
+#define MAX_SSI_TLV_COMMENT_SIZE 0x50
+
+// Client ID constants (internal)
+#define CLID_GENERIC 0x00 // Generic clients (eg. older official clients)
+#define CLID_ALTERNATIVE 0x01 // Clients not using tick for MsgID (most third-party clients)
+#define CLID_MIRANDA 0x02 // Hey, that's mate!
+#define CLID_ICQ6 0x10 // Mark ICQ6 as it has some non obvious limitations!
+
+
+// Internal Constants
+#define ICQ_PROTOCOL_NAME LPGEN("ICQ")
+#define ICQ_PLUG_VERSION __VERSION_DWORD
+#define ICQ_VERSION 8 // Protocol version
+#define DC_TYPE DC_NORMAL // Used for DC settings
+#define MAX_CONTACTSSEND 15
+#define MAX_MESSAGESNACSIZE 8000
+#define CLIENTRATELIMIT 0
+#define COOKIE_TIMEOUT 3600 // One hour
+#define KEEPALIVE_INTERVAL 57000 // One minute
+#define WEBFRONTPORT 0x50
+#define CLIENTFEATURES 0x3
+#define URL_FORGOT_PASSWORD "https://www.icq.com/password/"
+#define URL_REGISTER "https://www.icq.com/register/"
+#define FLAP_MARKER 0x2a
+#define CLIENT_MD5_STRING "AOL Instant Messenger (SM)"
+#define UNIQUEIDSETTING "UIN"
+#define UINMAXLEN 11 // DWORD string max len + 1
+#define PASSWORDMAXLEN 128
+#define OSCAR_PROXY_HOST "ars.icq.com"
+#define OSCAR_PROXY_VERSION 0x044A
+
+#define CLIENT_ID_STRING "ICQ Client" // Client identification, mimic ICQ 6.5
+#define CLIENT_ID_CODE 0x010a
+#define CLIENT_VERSION_MAJOR 0x0014
+#define CLIENT_VERSION_MINOR 0x0034
+#define CLIENT_VERSION_LESSER 0x0000
+#define CLIENT_VERSION_BUILD 0x0c18
+#define CLIENT_DISTRIBUTION 0x00000611
+#define CLIENT_LANGUAGE "en"
+#define CLIENT_COUNTRY "us"
+
+#endif /* __ICQ_CONSTANTS_H */
diff --git a/protocols/IcqOscarJ/src/icq_db.cpp b/protocols/IcqOscarJ/src/icq_db.cpp
new file mode 100644
index 0000000000..f199860df8
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_db.cpp
@@ -0,0 +1,399 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Internal Database API
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::CreateResidentSetting(const char *szSetting)
+{
+ char pszSetting[2*MAX_PATH];
+
+ strcpy(pszSetting, m_szModuleName);
+ strcat(pszSetting, "/");
+ strcat(pszSetting, szSetting);
+ CallService(MS_DB_SETSETTINGRESIDENT, 1, (WPARAM)pszSetting);
+}
+
+
+int CIcqProto::getSetting(HANDLE hContact, const char *szSetting, DBVARIANT *dbv)
+{
+ return DBGetContactSettingW(hContact, m_szModuleName, szSetting, dbv);
+}
+
+
+BYTE CIcqProto::getSettingByte(HANDLE hContact, const char *szSetting, BYTE byDef)
+{
+ return DBGetContactSettingByte(hContact, m_szModuleName, szSetting, byDef);
+}
+
+
+WORD CIcqProto::getSettingWord(HANDLE hContact, const char *szSetting, WORD wDef)
+{
+ return DBGetContactSettingWord(hContact, m_szModuleName, szSetting, wDef);
+}
+
+
+DWORD CIcqProto::getSettingDword(HANDLE hContact, const char *szSetting, DWORD dwDef)
+{
+ DBVARIANT dbv = {DBVT_DELETED};
+ DWORD dwRes;
+
+ if (getSetting(hContact, szSetting, &dbv))
+ return dwDef; // not found, give default
+
+ if (dbv.type != DBVT_DWORD)
+ dwRes = dwDef; // invalid type, give default
+ else // found and valid, give result
+ dwRes = dbv.dVal;
+
+ ICQFreeVariant(&dbv);
+ return dwRes;
+}
+
+
+double CIcqProto::getSettingDouble(HANDLE hContact, const char *szSetting, double dDef)
+{
+ DBVARIANT dbv = {DBVT_DELETED};
+ double dRes;
+
+ if (getSetting(hContact, szSetting, &dbv))
+ return dDef; // not found, give default
+
+ if (dbv.type != DBVT_BLOB || dbv.cpbVal != sizeof(double))
+ dRes = dDef;
+ else
+ dRes = *(double*)dbv.pbVal;
+
+ ICQFreeVariant(&dbv);
+ return dRes;
+}
+
+
+DWORD CIcqProto::getContactUin(HANDLE hContact)
+{
+ return getSettingDword(hContact, UNIQUEIDSETTING, 0);
+}
+
+
+int CIcqProto::getContactUid(HANDLE hContact, DWORD *pdwUin, uid_str *ppszUid)
+{
+ DBVARIANT dbv = {DBVT_DELETED};
+ int iRes = 1;
+
+ *pdwUin = 0;
+ if (ppszUid) *ppszUid[0] = '\0';
+
+ if (!getSetting(hContact, UNIQUEIDSETTING, &dbv))
+ {
+ if (dbv.type == DBVT_DWORD)
+ {
+ *pdwUin = dbv.dVal;
+ iRes = 0;
+ }
+ else if (dbv.type == DBVT_ASCIIZ)
+ {
+ if (ppszUid && m_bAimEnabled)
+ {
+ strcpy(*ppszUid, dbv.pszVal);
+ iRes = 0;
+ }
+ else
+ NetLog_Server("AOL screennames not accepted");
+ }
+ ICQFreeVariant(&dbv);
+ }
+ return iRes;
+}
+
+
+int CIcqProto::getSettingString(HANDLE hContact, const char *szSetting, DBVARIANT *dbv)
+{
+ int res = DBGetContactSettingString(hContact, m_szModuleName, szSetting, dbv);
+
+ if (res)
+ ICQFreeVariant(dbv);
+
+ return res;
+}
+
+
+int CIcqProto::getSettingStringW(HANDLE hContact, const char *szSetting, DBVARIANT *dbv)
+{
+ int res = DBGetContactSettingWString(hContact, m_szModuleName, szSetting, dbv);
+
+ if (res)
+ ICQFreeVariant(dbv);
+
+ return res;
+}
+
+
+char* CIcqProto::getSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, char *szDef)
+{
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (DBGetContactSettingUTF8String(hContact, szModule, szSetting, &dbv))
+ {
+ ICQFreeVariant(&dbv); // for a setting with invalid contents/type
+ return null_strdup(szDef);
+ }
+
+ char *szRes = null_strdup(dbv.pszVal);
+ ICQFreeVariant(&dbv);
+ return szRes;
+}
+
+
+char* CIcqProto::getSettingStringUtf(HANDLE hContact, const char *szSetting, char *szDef)
+{
+ return getSettingStringUtf(hContact, m_szModuleName, szSetting, szDef);
+}
+
+
+WORD CIcqProto::getContactStatus(HANDLE hContact)
+{
+ return getSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+}
+
+
+int CIcqProto::getSettingStringStatic(HANDLE hContact, const char *szSetting, char *dest, int dest_len)
+{
+ DBVARIANT dbv = {DBVT_DELETED};
+ DBCONTACTGETSETTING sVal = {0};
+
+ dbv.pszVal = dest;
+ dbv.cchVal = dest_len;
+ dbv.type = DBVT_ASCIIZ;
+
+ sVal.pValue = &dbv;
+ sVal.szModule = m_szModuleName;
+ sVal.szSetting = szSetting;
+
+ if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC, (WPARAM)hContact, (LPARAM)&sVal) != 0)
+ { // due to MS_DB_CONTACT_GETSETTINGSTATIC setting type check, we need to request UTF8 as well
+ dbv.pszVal = dest;
+ dbv.cchVal = dest_len;
+ dbv.type = DBVT_UTF8;
+
+ if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC, (WPARAM)hContact, (LPARAM)&sVal) != 0)
+ return 1; // nothing found
+ }
+
+ return (dbv.type != DBVT_ASCIIZ);
+}
+
+
+int CIcqProto::deleteSetting(HANDLE hContact, const char *szSetting)
+{
+ return DBDeleteContactSetting(hContact, m_szModuleName, szSetting);
+}
+
+
+int CIcqProto::setSettingByte(HANDLE hContact, const char *szSetting, BYTE byValue)
+{
+ return DBWriteContactSettingByte(hContact, m_szModuleName, szSetting, byValue);
+}
+
+
+int CIcqProto::setSettingWord(HANDLE hContact, const char *szSetting, WORD wValue)
+{
+ return DBWriteContactSettingWord(hContact, m_szModuleName, szSetting, wValue);
+}
+
+
+int CIcqProto::setSettingDword(HANDLE hContact, const char *szSetting, DWORD dwValue)
+{
+ return DBWriteContactSettingDword(hContact, m_szModuleName, szSetting, dwValue);
+}
+
+
+int CIcqProto::setSettingDouble(HANDLE hContact, const char *szSetting, double dValue)
+{
+ return setSettingBlob(hContact, szSetting, (BYTE*)&dValue, sizeof(double));
+}
+
+
+int CIcqProto::setSettingString(HANDLE hContact, const char *szSetting, const char *szValue)
+{
+ return DBWriteContactSettingString(hContact, m_szModuleName, szSetting, szValue);
+}
+
+
+int CIcqProto::setSettingStringW(HANDLE hContact, const char *szSetting, const WCHAR *wszValue)
+{
+ return DBWriteContactSettingWString(hContact, m_szModuleName, szSetting, wszValue);
+}
+
+
+int CIcqProto::setSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, const char *szValue)
+{
+ return DBWriteContactSettingUTF8String(hContact, szModule, szSetting, (char*)szValue);
+}
+
+
+int CIcqProto::setSettingStringUtf(HANDLE hContact, const char *szSetting, const char *szValue)
+{
+ return setSettingStringUtf(hContact, m_szModuleName, szSetting, szValue);
+}
+
+
+int CIcqProto::setSettingBlob(HANDLE hContact, const char *szSetting, const BYTE *pValue, const int cbValue)
+{
+ return DBWriteContactSettingBlob(hContact, m_szModuleName, szSetting, (void*)pValue, cbValue);
+}
+
+
+int CIcqProto::setContactHidden(HANDLE hContact, BYTE bHidden)
+{
+ int nResult = DBWriteContactSettingByte(hContact, "CList", "Hidden", bHidden);
+
+ if (!bHidden) // clear zero setting
+ DBDeleteContactSetting(hContact, "CList", "Hidden");
+
+ return nResult;
+}
+
+void CIcqProto::setStatusMsgVar(HANDLE hContact, char* szStatusMsg, bool isAnsi)
+{
+ if (szStatusMsg && szStatusMsg[0])
+ {
+ if (isAnsi)
+ {
+ char* szStatusNote = getSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, "");
+ wchar_t* szStatusNoteW = make_unicode_string(szStatusNote);
+ int len = (int)wcslen(szStatusNoteW) * 3 + 1;
+ char* szStatusNoteAnsi = (char*)alloca(len);
+ WideCharToMultiByte(CP_ACP, WC_NO_BEST_FIT_CHARS, szStatusNoteW, -1, szStatusNoteAnsi, len, NULL, NULL);
+ bool notmatch = false;
+ for (int i=0; ;++i)
+ {
+ if (szStatusNoteAnsi[i] != szStatusMsg[i] && szStatusNoteAnsi[i] != '?' && szStatusMsg[i] != '?')
+ {
+ notmatch = true;
+ break;
+ }
+ if (!szStatusNoteAnsi[i] || !szStatusMsg[i])
+ break;
+ }
+ szStatusMsg = notmatch ? ansi_to_utf8(szStatusMsg) : szStatusNote;
+ SAFE_FREE(&szStatusNoteW);
+ if (notmatch) SAFE_FREE(&szStatusNote);
+ }
+
+ char* oldStatusMsg = NULL;
+ DBVARIANT dbv;
+ if (!DBGetContactSetting(hContact, "CList", "StatusMsg", &dbv))
+ {
+ switch (dbv.type)
+ {
+ case DBVT_UTF8:
+ oldStatusMsg = null_strdup(dbv.pszVal);
+ break;
+
+ case DBVT_WCHAR:
+ oldStatusMsg = make_utf8_string(dbv.pwszVal);
+ break;
+ }
+ ICQFreeVariant(&dbv);
+ }
+
+ if (!oldStatusMsg || strcmp(oldStatusMsg, szStatusMsg))
+ setSettingStringUtf(hContact, "CList", "StatusMsg", szStatusMsg);
+ SAFE_FREE(&oldStatusMsg);
+ if (isAnsi) SAFE_FREE(&szStatusMsg);
+ }
+ else
+ DBDeleteContactSetting(hContact, "CList", "StatusMsg");
+}
+
+int __fastcall ICQFreeVariant(DBVARIANT *dbv)
+{
+ return DBFreeVariant(dbv);
+}
+
+
+int CIcqProto::IsICQContact(HANDLE hContact)
+{
+ char* szProto = (char*)CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)hContact, 0);
+
+ return !strcmpnull(szProto, m_szModuleName);
+}
+
+
+HANDLE CIcqProto::AddEvent(HANDLE hContact, WORD wType, DWORD dwTime, DWORD flags, DWORD cbBlob, PBYTE pBlob)
+{
+ DBEVENTINFO dbei = {0};
+
+ dbei.cbSize = sizeof(dbei);
+ dbei.szModule = m_szModuleName;
+ dbei.timestamp = dwTime;
+ dbei.flags = flags;
+ dbei.eventType = wType;
+ dbei.cbBlob = cbBlob;
+ dbei.pBlob = pBlob;
+
+ return (HANDLE)CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei);
+}
+
+
+HANDLE CIcqProto::FindFirstContact()
+{
+ HANDLE hContact = db_find_first(m_szModuleName);
+
+ if (IsICQContact(hContact))
+ return hContact;
+
+ return FindNextContact(hContact);
+}
+
+
+HANDLE CIcqProto::FindNextContact(HANDLE hContact)
+{
+ hContact = db_find_next(hContact, m_szModuleName);
+ while (hContact != NULL)
+ {
+ if (IsICQContact(hContact))
+ return hContact;
+ hContact = db_find_next(hContact, m_szModuleName);
+ }
+ return hContact;
+}
+
+
+char* CIcqProto::getContactCListGroup(HANDLE hContact)
+{
+ return getSettingStringUtf(hContact, "CList", "Group", NULL);
+}
+
+
+int __stdcall ICQSetContactCListGroup(HANDLE hContact, const char *szGroup)
+{
+ /// TODO
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/icq_db.h b/protocols/IcqOscarJ/src/icq_db.h
new file mode 100644
index 0000000000..5bfc22d58b
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_db.h
@@ -0,0 +1,44 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_DB_H
+#define __ICQ_DB_H
+
+
+#ifdef _UNICODE
+ #define getSettingStringT getSettingStringW
+ #define setSettingStringT setSettingStringW
+#else
+ #define getSettingStringT getSettingString
+ #define setSettingStringT setSettingString
+#endif
+
+int __fastcall ICQFreeVariant(DBVARIANT* dbv);
+
+#endif /* __ICQ_DB_H */
diff --git a/protocols/IcqOscarJ/src/icq_direct.cpp b/protocols/IcqOscarJ/src/icq_direct.cpp
new file mode 100644
index 0000000000..7779f1bad7
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_direct.cpp
@@ -0,0 +1,1171 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+struct directthreadstartinfo
+{
+ int type; // Only valid for outgoing connections
+ int incoming; // 1=incoming, 0=outgoing
+ HANDLE hConnection; // only valid for incoming connections, handle to the connection
+ HANDLE hContact; // Only valid for outgoing connections
+ void* pvExtra; // Only valid for outgoing connections
+};
+
+static char client_check_data[] = {
+ "As part of this software beta version Mirabilis is "
+ "granting a limited access to the ICQ network, "
+ "servers, directories, listings, information and databases (\""
+ "ICQ Services and Information\"). The "
+ "ICQ Service and Information may databases (\""
+ "ICQ Services and Information\"). The "
+ "ICQ Service and Information may\0"
+};
+
+void CIcqProto::CloseContactDirectConns(HANDLE hContact)
+{
+ icq_lock l(directConnListMutex);
+
+ for ( int i = 0; i < directConns.getCount(); i++)
+ {
+ if (!hContact || directConns[i]->hContact == hContact)
+ {
+ HANDLE hConnection = directConns[i]->hConnection;
+
+ directConns[i]->hConnection = NULL; // do not allow reuse
+ NetLib_CloseConnection(&hConnection, FALSE);
+ }
+ }
+}
+
+
+directconnect* CIcqProto::FindFileTransferDC(filetransfer* ft)
+{
+ directconnect* dc = NULL;
+ icq_lock l(directConnListMutex);
+
+ for (int i = 0; i < directConns.getCount(); i++)
+ {
+ if ( directConns[i]->ft == ft )
+ {
+ dc = directConns[i];
+ break;
+ }
+ }
+
+ return dc;
+}
+
+
+filetransfer* CIcqProto::FindExpectedFileRecv(DWORD dwUin, DWORD dwTotalSize)
+{
+ filetransfer* pFt = NULL;
+ icq_lock l(expectedFileRecvMutex);
+
+ for (int i = 0; i < expectedFileRecvs.getCount(); i++)
+ {
+ if (expectedFileRecvs[i]->dwUin == dwUin && expectedFileRecvs[i]->dwTotalSize == dwTotalSize)
+ {
+ pFt = expectedFileRecvs[i];
+ expectedFileRecvs.remove( i );
+ break;
+ }
+ }
+
+ return pFt;
+}
+
+
+int CIcqProto::sendDirectPacket(directconnect* dc, icq_packet* pkt)
+{
+ int nResult = Netlib_Send(dc->hConnection, (const char*)pkt->pData, pkt->wLen + 2, 0);
+ if (nResult == SOCKET_ERROR)
+ {
+ NetLog_Direct("Direct %p socket error: %d, closing", dc->hConnection, GetLastError());
+ CloseDirectConnection(dc);
+ }
+
+ SAFE_FREE((void**)&pkt->pData);
+
+ return nResult;
+}
+
+directthreadstartinfo* CreateDTSI(HANDLE hContact, HANDLE hConnection, int type)
+{
+ directthreadstartinfo* dtsi = (directthreadstartinfo*)SAFE_MALLOC(sizeof(directthreadstartinfo));
+ dtsi->hContact = hContact;
+ dtsi->hConnection = hConnection;
+ if (type == -1)
+ dtsi->incoming = 1;
+ else
+ dtsi->type = type;
+
+ return dtsi;
+}
+
+// Check if we have an open and initialized DC with type
+// 'type' to the specified contact
+BOOL CIcqProto::IsDirectConnectionOpen(HANDLE hContact, int type, int bPassive)
+{
+ BOOL bIsOpen = FALSE, bIsCreated = FALSE;
+
+ {
+ icq_lock l(directConnListMutex);
+
+ for (int i = 0; i < directConns.getCount(); i++)
+ {
+ if (directConns[i] && (directConns[i]->type == type))
+ {
+ if (directConns[i]->hContact == hContact)
+ if (directConns[i]->initialised)
+ {
+ // Connection is OK
+ bIsOpen = TRUE;
+ // we are going to use the conn, so prevent timeout
+ directConns[i]->packetPending = 1;
+ break;
+ }
+ else
+ bIsCreated = TRUE; // we found pending connection
+ }
+ }
+ }
+
+ if (!bPassive && !bIsCreated && !bIsOpen && type == DIRECTCONN_STANDARD && m_bDCMsgEnabled == 2)
+ { // do not try to open DC to offline contact
+ if (getContactStatus(hContact) == ID_STATUS_OFFLINE) return FALSE;
+ // do not try to open DC if previous attempt was not successfull
+ if (getSettingByte(hContact, "DCStatus", 0)) return FALSE;
+
+ // Set DC status as tried
+ setSettingByte(hContact, "DCStatus", 1);
+ // Create a new connection
+ OpenDirectConnection(hContact, DIRECTCONN_STANDARD, NULL);
+ }
+
+ return bIsOpen;
+}
+
+// This function is called from the Netlib when someone is connecting to
+// one of our incomming DC ports
+void icq_newConnectionReceived(HANDLE hNewConnection, DWORD dwRemoteIP, void *pExtra)
+{
+ // Start a new thread for the incomming connection
+ CIcqProto* ppro = (CIcqProto*)pExtra;
+ ppro->ForkThread(( IcqThreadFunc )&CIcqProto::icq_directThread, CreateDTSI(NULL, hNewConnection, -1));
+}
+
+// Opens direct connection of specified type to specified contact
+void CIcqProto::OpenDirectConnection(HANDLE hContact, int type, void* pvExtra)
+{
+ // Create a new connection
+ directthreadstartinfo* dtsi = CreateDTSI(hContact, NULL, type);
+ dtsi->pvExtra = pvExtra;
+ ForkThread(( IcqThreadFunc )&CIcqProto::icq_directThread, dtsi);
+}
+
+// Safely close NetLib connection - do not corrupt direct connection list
+void CIcqProto::CloseDirectConnection(directconnect *dc)
+{
+ icq_lock l(directConnListMutex);
+
+ NetLib_CloseConnection(&dc->hConnection, FALSE);
+#ifdef _DEBUG
+ if (dc->hConnection)
+ NetLog_Direct("Direct conn closed (%p)", dc->hConnection);
+#endif
+}
+
+// Called from icq_newConnectionReceived when a new incomming dc is done
+// Called from OpenDirectConnection when a new outgoing dc is done
+// Called from SendDirectMessage when a new outgoing dc is done
+
+void __cdecl CIcqProto::icq_directThread( directthreadstartinfo *dtsi )
+{
+ directconnect dc = {0};
+ NETLIBPACKETRECVER packetRecv={0};
+ HANDLE hPacketRecver;
+ BOOL bFirstPacket = TRUE;
+ int nSkipPacketBytes = 0;
+ DWORD dwReqMsgID1;
+ DWORD dwReqMsgID2;
+
+ srand(time(NULL));
+
+ { // add to DC connection list
+ icq_lock l(directConnListMutex);
+ directConns.insert( &dc );
+ }
+
+ // Initialize DC struct
+ dc.hContact = dtsi->hContact;
+ dc.dwThreadId = GetCurrentThreadId();
+ dc.incoming = dtsi->incoming;
+ dc.hConnection = dtsi->hConnection;
+ dc.ft = NULL;
+
+ if (!dc.incoming)
+ {
+ dc.type = dtsi->type;
+ dc.dwRemoteExternalIP = getSettingDword(dtsi->hContact, "IP", 0);
+ dc.dwRemoteInternalIP = getSettingDword(dtsi->hContact, "RealIP", 0);
+ dc.dwRemotePort = getSettingWord(dtsi->hContact, "UserPort", 0);
+ dc.dwRemoteUin = getContactUin(dtsi->hContact);
+ dc.dwConnectionCookie = getSettingDword(dtsi->hContact, "DirectCookie", 0);
+ dc.wVersion = getSettingWord(dtsi->hContact, "Version", 0);
+
+ if (!dc.dwRemoteExternalIP && !dc.dwRemoteInternalIP)
+ { // we do not have any ip, do not try to connect
+ SAFE_FREE((void**)&dtsi);
+ goto LBL_Exit;
+ }
+ if (!dc.dwRemotePort)
+ { // we do not have port, do not try to connect
+ SAFE_FREE((void**)&dtsi);
+ goto LBL_Exit;
+ }
+
+ if (dc.type == DIRECTCONN_STANDARD)
+ {
+ // do nothing - some specific init for msg sessions
+ }
+ else if (dc.type == DIRECTCONN_FILE)
+ {
+ dc.ft = (filetransfer*)dtsi->pvExtra;
+ dc.dwRemotePort = dc.ft->dwRemotePort;
+ }
+ else if (dc.type == DIRECTCONN_REVERSE)
+ {
+ cookie_reverse_connect *pCookie = (cookie_reverse_connect*)dtsi->pvExtra;
+
+ dwReqMsgID1 = pCookie->dwMsgID1;
+ dwReqMsgID2 = pCookie->dwMsgID2;
+ dc.dwReqId = (DWORD)pCookie->ft;
+ SAFE_FREE((void**)&pCookie);
+ }
+ }
+ else
+ {
+ dc.type = DIRECTCONN_STANDARD;
+ }
+
+ SAFE_FREE((void**)&dtsi);
+
+ // Load local IP information
+ dc.dwLocalExternalIP = getSettingDword(NULL, "IP", 0);
+ dc.dwLocalInternalIP = getSettingDword(NULL, "RealIP", 0);
+
+ // Create outgoing DC
+ if (!dc.incoming)
+ {
+ NETLIBOPENCONNECTION nloc = {0};
+ IN_ADDR addr = {0}, addr2 = {0};
+
+ if (dc.dwRemoteExternalIP == dc.dwLocalExternalIP && dc.dwRemoteInternalIP)
+ addr.S_un.S_addr = htonl(dc.dwRemoteInternalIP);
+ else
+ {
+ addr.S_un.S_addr = htonl(dc.dwRemoteExternalIP);
+ // for different internal, try it also (for LANs with multiple external IP, VPNs, etc.)
+ if (dc.dwRemoteInternalIP != dc.dwRemoteExternalIP)
+ addr2.S_un.S_addr = htonl(dc.dwRemoteInternalIP);
+ }
+
+ // IP to connect to is empty, go away
+ if (!addr.S_un.S_addr)
+ goto LBL_Exit;
+
+ nloc.szHost = inet_ntoa(addr);
+ nloc.wPort = (WORD)dc.dwRemotePort;
+ nloc.timeout = 8; // 8 secs to connect
+ dc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, dc.type==DIRECTCONN_REVERSE?"Reverse ":NULL, &nloc);
+ if (!dc.hConnection && addr2.S_un.S_addr)
+ { // first address failed, try second one if available
+ nloc.szHost = inet_ntoa(addr2);
+ dc.hConnection = NetLib_OpenConnection(m_hDirectNetlibUser, dc.type==DIRECTCONN_REVERSE?"Reverse ":NULL, &nloc);
+ }
+ if (!dc.hConnection)
+ {
+ if (CheckContactCapabilities(dc.hContact, CAPF_ICQDIRECT))
+ { // only if the contact support ICQ DC connections
+ if (dc.type != DIRECTCONN_REVERSE)
+ { // try reverse connect
+ cookie_reverse_connect *pCookie = (cookie_reverse_connect*)SAFE_MALLOC(sizeof(cookie_reverse_connect));
+ DWORD dwCookie;
+
+ NetLog_Direct("connect() failed (%d), trying reverse.", GetLastError());
+
+ if (pCookie)
+ { // init cookie
+ InitMessageCookie(pCookie);
+ pCookie->bMessageType = MTYPE_REVERSE_REQUEST;
+ pCookie->hContact = dc.hContact;
+ pCookie->dwUin = dc.dwRemoteUin;
+ pCookie->type = dc.type;
+ pCookie->ft = dc.ft;
+ dwCookie = AllocateCookie(CKT_REVERSEDIRECT, 0, dc.hContact, pCookie);
+ icq_sendReverseReq(&dc, dwCookie, (cookie_message_data*)pCookie);
+ goto LBL_Exit;
+ }
+
+ NetLog_Direct("Reverse failed (%s)", "malloc failed");
+ }
+ }
+ else // Set DC status to failed
+ setSettingByte(dc.hContact, "DCStatus", 2);
+
+ if (dc.type == DIRECTCONN_REVERSE) // failed reverse connection
+ { // announce we failed
+ icq_sendReverseFailed(&dc, dwReqMsgID1, dwReqMsgID2, dc.dwReqId);
+ }
+ NetLog_Direct("connect() failed (%d)", GetLastError());
+ if (dc.type == DIRECTCONN_FILE)
+ {
+ BroadcastAck(dc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, dc.ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&dc.ft);
+ }
+ goto LBL_Exit;
+ }
+
+ if (dc.type == DIRECTCONN_FILE)
+ dc.ft->hConnection = dc.hConnection;
+
+ if (dc.wVersion > 6)
+ {
+ sendPeerInit_v78(&dc);
+ }
+ else
+ {
+ NetLog_Direct("Error: Unsupported direct protocol: %d, closing.", dc.wVersion);
+ CloseDirectConnection(&dc);
+ goto LBL_Exit;
+ }
+ }
+
+ hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)dc.hConnection, 8192);
+ packetRecv.cbSize = sizeof(packetRecv);
+ packetRecv.bytesUsed = 0;
+
+ // Packet receiving loop
+
+ while (dc.hConnection)
+ {
+ int recvResult;
+
+ packetRecv.dwTimeout = dc.wantIdleTime ? 0 : 600000;
+
+ recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)hPacketRecver, (LPARAM)&packetRecv);
+ if (recvResult == 0)
+ {
+ NetLog_Direct("Clean closure of direct socket (%p)", dc.hConnection);
+ break;
+ }
+
+ if (recvResult == SOCKET_ERROR)
+ {
+ if (GetLastError() == ERROR_TIMEOUT)
+ { // TODO: this will not work on some systems
+ if (dc.wantIdleTime)
+ {
+ switch (dc.type)
+ {
+ case DIRECTCONN_FILE:
+ handleFileTransferIdle(&dc);
+ break;
+ }
+ }
+ else if (dc.packetPending)
+ { // do we expect packet soon?
+ NetLog_Direct("Keeping connection, packet pending.");
+ }
+ else
+ {
+ NetLog_Direct("Connection inactive for 10 minutes, closing.");
+ break;
+ }
+ }
+ else
+ {
+ NetLog_Direct("Abortive closure of direct socket (%p) (%d)", dc.hConnection, GetLastError());
+ break;
+ }
+ }
+
+ if (dc.type == DIRECTCONN_CLOSING)
+ packetRecv.bytesUsed = packetRecv.bytesAvailable;
+ else if (packetRecv.bytesAvailable < nSkipPacketBytes)
+ { // the whole buffer needs to be skipped
+ nSkipPacketBytes -= packetRecv.bytesAvailable;
+ packetRecv.bytesUsed = packetRecv.bytesAvailable;
+ }
+ else
+ {
+ int i;
+
+ for (i = nSkipPacketBytes, nSkipPacketBytes = 0; i + 2 <= packetRecv.bytesAvailable;)
+ {
+ WORD wLen = *(WORD*)(packetRecv.buffer + i);
+
+ if (bFirstPacket)
+ {
+ if (wLen > 64)
+ { // roughly check first packet size
+ NetLog_Direct("Error: Overflowed packet, closing connection.");
+ CloseDirectConnection(&dc);
+ break;
+ }
+ bFirstPacket = FALSE;
+ }
+ else
+ {
+ if (packetRecv.bytesAvailable >= i + 2 && wLen > 8190)
+ { // check for too big packages
+ NetLog_Direct("Error: Package too big: %d bytes, skipping.");
+ nSkipPacketBytes = wLen;
+ packetRecv.bytesUsed = i + 2;
+ break;
+ }
+ }
+
+ if (wLen + 2 + i > packetRecv.bytesAvailable)
+ break;
+
+ if (dc.type == DIRECTCONN_STANDARD && wLen && packetRecv.buffer[i + 2] == 2)
+ {
+ if (!DecryptDirectPacket(&dc, packetRecv.buffer + i + 3, (WORD)(wLen - 1)))
+ {
+ NetLog_Direct("Error: Corrupted packet encryption, ignoring packet");
+ i += wLen + 2;
+ continue;
+ }
+ }
+#ifdef _DEBUG
+ NetLog_Direct("New direct package");
+#endif
+ if (dc.type == DIRECTCONN_FILE && dc.initialised)
+ handleFileTransferPacket(&dc, packetRecv.buffer + i + 2, wLen);
+ else
+ handleDirectPacket(&dc, packetRecv.buffer + i + 2, wLen);
+
+ i += wLen + 2;
+ }
+ packetRecv.bytesUsed = i;
+ }
+ }
+
+ // End of packet receiving loop
+
+ NetLib_SafeCloseHandle(&hPacketRecver);
+ CloseDirectConnection(&dc);
+
+ if (dc.ft)
+ {
+ if (dc.ft->fileId != -1)
+ {
+ _close(dc.ft->fileId);
+ BroadcastAck(dc.ft->hContact, ACKTYPE_FILE, dc.ft->dwBytesDone==dc.ft->dwTotalSize ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, dc.ft, 0);
+ }
+ else if (dc.ft->hConnection)
+ BroadcastAck(dc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, dc.ft, 0);
+
+ SafeReleaseFileTransfer((void**)&dc.ft);
+ _chdir("\\"); /* so we don't leave a subdir handle open so it can't be deleted */
+ }
+
+LBL_Exit:
+ { // remove from DC connection list
+ icq_lock l(directConnListMutex);
+ directConns.remove( &dc );
+ }
+}
+
+
+void CIcqProto::handleDirectPacket(directconnect* dc, PBYTE buf, WORD wLen)
+{
+ if (wLen < 1)
+ return;
+
+ switch (buf[0])
+ {
+ case PEER_FILE_INIT: // first packet of a file transfer
+#ifdef _DEBUG
+ NetLog_Direct("Received PEER_FILE_INIT from %u",dc->dwRemoteUin);
+#endif
+ if (dc->handshake)
+ handleFileTransferPacket(dc, buf, wLen);
+ else
+ NetLog_Direct("Received %s on uninitialised DC, ignoring.", "PEER_FILE_INIT");
+
+ break;
+
+ case PEER_INIT_ACK: // This is sent as a response to our PEER_INIT packet
+ if (wLen != 4)
+ {
+ NetLog_Direct("Error: Received malformed PEER_INITACK from %u", dc->dwRemoteUin);
+ break;
+ }
+#ifdef _DEBUG
+ NetLog_Direct("Received PEER_INITACK from %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing");
+#endif
+ if (dc->incoming) dc->handshake = 1;
+
+ if (dc->incoming && dc->type == DIRECTCONN_REVERSE)
+ {
+ cookie_reverse_connect *pCookie;
+
+ dc->incoming = 0;
+
+ if (FindCookie(dc->dwReqId, NULL, (void**)&pCookie) && pCookie)
+ { // valid reverse DC, check and init session
+ FreeCookie(dc->dwReqId);
+ if (pCookie->dwUin == dc->dwRemoteUin)
+ { // valid connection
+ dc->type = pCookie->type;
+ dc->ft = (filetransfer*)pCookie->ft;
+ dc->hContact = pCookie->hContact;
+ if (dc->type == DIRECTCONN_STANDARD)
+ { // init message session
+ sendPeerMsgInit(dc, 0);
+ }
+ else if (dc->type == DIRECTCONN_FILE)
+ { // init file session
+ sendPeerFileInit(dc);
+ dc->initialised = 1;
+ }
+ SAFE_FREE((void**)&pCookie);
+ break;
+ }
+ else
+ {
+ SAFE_FREE((void**)&pCookie);
+ NetLog_Direct("Error: Invalid connection (UINs does not match).");
+ CloseDirectConnection(dc);
+ return;
+ }
+ }
+ else
+ {
+ NetLog_Direct("Error: Received unexpected reverse DC, closing.");
+ CloseDirectConnection(dc);
+ return;
+ }
+ }
+ break;
+
+ case PEER_INIT: /* connect packet */
+#ifdef _DEBUG
+ NetLog_Direct("Received PEER_INIT");
+#endif
+ buf++;
+
+ if (wLen < 3)
+ return;
+
+ unpackLEWord(&buf, &dc->wVersion);
+
+ if (dc->wVersion > 6)
+ { // we support only versions 7 and up
+ WORD wSecondLen;
+ DWORD dwUin;
+ DWORD dwPort;
+ DWORD dwCookie;
+ HANDLE hContact;
+
+ if (wLen != 0x30)
+ {
+ NetLog_Direct("Error: Received malformed PEER_INIT");
+ return;
+ }
+
+ unpackLEWord(&buf, &wSecondLen);
+ if (wSecondLen && wSecondLen != 0x2b)
+ { // OMG? GnomeICU sets this to zero
+ NetLog_Direct("Error: Received malformed PEER_INIT");
+ return;
+ }
+
+ unpackLEDWord(&buf, &dwUin);
+ if (dwUin != m_dwLocalUIN)
+ {
+ NetLog_Direct("Error: Received PEER_INIT targeted to %u", dwUin);
+ CloseDirectConnection(dc);
+ return;
+ }
+
+ buf += 2; /* 00 00 */
+ unpackLEDWord(&buf, &dc->dwRemotePort);
+ unpackLEDWord(&buf, &dc->dwRemoteUin);
+ unpackDWord(&buf, &dc->dwRemoteExternalIP);
+ unpackDWord(&buf, &dc->dwRemoteInternalIP);
+ buf ++; /* 04: accept direct connections */
+ unpackLEDWord(&buf, &dwPort);
+ if (dwPort != dc->dwRemotePort)
+ {
+ NetLog_Direct("Error: Received malformed PEER_INIT (invalid port)");
+ return;
+ }
+ unpackLEDWord(&buf, &dwCookie);
+
+ buf += 8; // Unknown stuff
+ unpackLEDWord(&buf, &dc->dwReqId);
+
+ if (dc->dwRemoteUin || !dc->dwReqId)
+ { // OMG! Licq sends on reverse connection empty uin
+ hContact = HContactFromUIN(dc->dwRemoteUin, NULL);
+ if (hContact == INVALID_HANDLE_VALUE)
+ {
+ NetLog_Direct("Error: Received PEER_INIT from %u not on my list", dwUin);
+ CloseDirectConnection(dc);
+ return; /* don't allow direct connection with people not on my clist */
+ }
+
+ if (dc->incoming)
+ { // this is the first PEER_INIT with our cookie
+ if (dwCookie != getSettingDword(hContact, "DirectCookie", 0))
+ {
+ NetLog_Direct("Error: Received PEER_INIT with broken cookie");
+ CloseDirectConnection(dc);
+ return;
+ }
+ }
+ else
+ { // this is the second PEER_INIT with peer cookie
+ if (dwCookie != dc->dwConnectionCookie)
+ {
+ NetLog_Direct("Error: Received PEER_INIT with broken cookie");
+ CloseDirectConnection(dc);
+ return;
+ }
+ }
+ }
+
+ if (dc->incoming && dc->dwReqId)
+ { // this is reverse connection
+ dc->type = DIRECTCONN_REVERSE;
+ if (!dc->dwRemoteUin)
+ { // we need to load cookie (licq)
+ cookie_reverse_connect *pCookie;
+
+ if (FindCookie(dc->dwReqId, NULL, (void**)&pCookie) && pCookie)
+ { // valid reverse DC, check and init session
+ dc->dwRemoteUin = pCookie->dwUin;
+ dc->hContact = pCookie->hContact;
+ }
+ else
+ {
+ NetLog_Direct("Error: Received unexpected reverse DC, closing.");
+ CloseDirectConnection(dc);
+ return;
+ }
+ }
+ }
+
+ sendPeerInitAck(dc); // ack good PEER_INIT packet
+
+ if (dc->incoming)
+ { // store good IP info
+ dc->hContact = hContact;
+ dc->dwConnectionCookie = dwCookie;
+ setSettingDword(dc->hContact, "IP", dc->dwRemoteExternalIP);
+ setSettingDword(dc->hContact, "RealIP", dc->dwRemoteInternalIP);
+ sendPeerInit_v78(dc); // reply with our PEER_INIT
+ }
+ else // outgoing
+ {
+ dc->handshake = 1;
+
+ if (dc->type == DIRECTCONN_REVERSE)
+ {
+ dc->incoming = 1; // this is incoming reverse connection
+ dc->type = DIRECTCONN_STANDARD; // we still do not know type
+ }
+ else if (dc->type == DIRECTCONN_STANDARD)
+ { // send PEER_MSGINIT
+ sendPeerMsgInit(dc, 0);
+ }
+ else if (dc->type == DIRECTCONN_FILE)
+ {
+ sendPeerFileInit(dc);
+ dc->initialised = 1;
+ }
+ }
+ // Set DC Status to successful
+ setSettingByte(dc->hContact, "DCStatus", 0);
+ }
+ else
+ {
+ NetLog_Direct("Unsupported direct protocol: %d, closing connection", dc->wVersion);
+ CloseDirectConnection(dc);
+ }
+ break;
+
+ case PEER_MSG: /* messaging packets */
+#ifdef _DEBUG
+ NetLog_Direct("Received PEER_MSG from %u", dc->dwRemoteUin);
+#endif
+ if (dc->initialised)
+ handleDirectMessage(dc, buf + 1, (WORD)(wLen - 1));
+ else
+ NetLog_Direct("Received %s on uninitialised DC, ignoring.", "PEER_MSG");
+
+ break;
+
+ case PEER_MSG_INIT: /* init message connection */
+ { // it is sent by both contains GUID of message channel
+ DWORD q1,q2,q3,q4;
+
+ if (!m_bDCMsgEnabled)
+ { // DC messaging disabled, close connection
+ NetLog_Direct("Messaging DC requested, denied");
+ CloseDirectConnection(dc);
+ break;
+ }
+
+#ifdef _DEBUG
+ NetLog_Direct("Received PEER_MSG_INIT from %u",dc->dwRemoteUin);
+#endif
+ buf++;
+ if (wLen != 0x21)
+ break;
+
+ if (!dc->handshake)
+ {
+ NetLog_Direct("Received %s on unitialised DC, ignoring.", "PEER_MSG_INIT");
+ break;
+ }
+
+ buf += 4; /* always 10 */
+ buf += 4; /* some id */
+ buf += 4; /* sequence - always 0 on incoming */
+ unpackDWord(&buf, &q1); // session type GUID
+ unpackDWord(&buf, &q2);
+ if (!dc->incoming)
+ { // skip marker on sequence 1
+ buf += 4;
+ }
+ unpackDWord(&buf, &q3);
+ unpackDWord(&buf, &q4);
+ if (!CompareGUIDs(q1,q2,q3,q4,PSIG_MESSAGE))
+ { // This is not for normal messages, useless so kill.
+ if (CompareGUIDs(q1,q2,q3,q4,PSIG_STATUS_PLUGIN))
+ {
+ NetLog_Direct("Status Manager Plugin connections not supported, closing.");
+ }
+ else if (CompareGUIDs(q1,q2,q3,q4,PSIG_INFO_PLUGIN))
+ {
+ NetLog_Direct("Info Manager Plugin connection not supported, closing.");
+ }
+ else
+ {
+ NetLog_Direct("Unknown connection type init, closing.");
+ }
+ CloseDirectConnection(dc);
+ break;
+ }
+
+ if (dc->incoming)
+ { // reply with our PEER_MSG_INIT
+ sendPeerMsgInit(dc, 1);
+ }
+ else
+ { // connection initialized, ready to send message packet
+ }
+ NetLog_Direct("Direct message session ready.");
+ dc->initialised = 1;
+ }
+ break;
+
+ default:
+ NetLog_Direct("Unknown direct packet ignored.");
+ break;
+ }
+}
+
+void EncryptDirectPacket(directconnect* dc, icq_packet* p)
+{
+ unsigned long B1;
+ unsigned long M1;
+ unsigned long check;
+ unsigned int i;
+ unsigned char X1;
+ unsigned char X2;
+ unsigned char X3;
+ unsigned char* buf = (unsigned char*)(p->pData + 3);
+ unsigned char bak[6];
+ unsigned long offset;
+ unsigned long key;
+ unsigned long hex;
+ unsigned long size = p->wLen - 1;
+
+
+ if (dc->wVersion < 4)
+ return; // no encryption necessary.
+
+
+ switch (dc->wVersion)
+ {
+ case 4:
+ case 5:
+ offset = 6;
+ break;
+
+ case 6:
+ case 7:
+ case 8:
+ case 9:
+ case 10:
+ default:
+ offset = 0;
+ }
+
+ // calculate verification data
+ M1 = (rand() % ((size < 255 ? size : 255)-10))+10;
+ X1 = buf[M1] ^ 0xFF;
+ X2 = rand() % 220;
+ X3 = client_check_data[X2] ^ 0xFF;
+ if (offset)
+ {
+ memcpy(bak, buf, sizeof(bak));
+ B1 = (buf[offset+4]<<24) | (buf[offset+6]<<16) | (buf[2]<<8) | buf[0];
+ }
+ else
+ {
+ B1 = (buf[4]<<24) | (buf[6]<<16) | (buf[4]<<8) | (buf[6]);
+ }
+
+ // calculate checkcode
+ check = (M1<<24) | (X1<<16) | (X2<<8) | X3;
+ check ^= B1;
+
+ // main XOR key
+ key = 0x67657268 * size + check;
+
+ // XORing the actual data
+ for (i = 0; i<(size+3)/4; i+=4)
+ {
+ hex = key + client_check_data[i&0xFF];
+ *(PDWORD)(buf + i) ^= hex;
+ }
+
+ // in TCPv4 are the first 6 bytes unencrypted
+ // so restore them
+ if (offset)
+ memcpy(buf, bak, sizeof(bak));
+
+ // storing the checkcode
+ *(PDWORD)(buf + offset) = check;
+}
+
+int DecryptDirectPacket(directconnect* dc, PBYTE buf, WORD wLen)
+{
+ unsigned long hex;
+ unsigned long key;
+ unsigned long B1;
+ unsigned long M1;
+ unsigned long check;
+ unsigned int i;
+ unsigned char X1;
+ unsigned char X2;
+ unsigned char X3;
+ unsigned char bak[6];
+ unsigned long size = wLen;
+ unsigned long offset;
+
+
+ if (dc->wVersion < 4)
+ return 1; // no decryption necessary.
+
+ if (size < 4)
+ return 1;
+
+ if (dc->wVersion < 4)
+ return 1;
+
+ if (dc->wVersion == 4 || dc->wVersion == 5)
+ {
+ offset = 6;
+ }
+ else
+ {
+ offset = 0;
+ }
+
+ // backup the first 6 bytes
+ if (offset)
+ memcpy(bak, buf, sizeof(bak));
+
+ // retrieve checkcode
+ check = *(PDWORD)(buf+offset);
+
+ // main XOR key
+ key = 0x67657268 * size + check;
+
+ for (i=4; i<(size+3)/4; i+=4)
+ {
+ hex = key + client_check_data[i&0xFF];
+ *(PDWORD)(buf + i) ^= hex;
+ }
+
+ // retrive validate data
+ if (offset)
+ {
+ // in TCPv4 are the first 6 bytes unencrypted
+ // so restore them
+ memcpy(buf, bak, sizeof(bak));
+ B1 = (buf[offset+4]<<24) | (buf[offset+6]<<16) | (buf[2]<<8) | buf[0];
+ }
+ else
+ {
+ B1 = (buf[4]<<24) | (buf[6]<<16) | (buf[4]<<8) | (buf[6]<<0);
+ }
+
+ // special decryption
+ B1 ^= check;
+
+ // validate packet
+ M1 = (B1>>24) & 0xFF;
+ if (M1 < 10 || M1 >= size)
+ {
+ return 0;
+ }
+
+ X1 = buf[M1] ^ 0xFF;
+ if (((B1 >> 16) & 0xFF) != X1)
+ {
+ return 0;
+ }
+
+ X2 = (BYTE)((B1 >> 8) & 0xFF);
+ if (X2 < 220)
+ {
+ X3 = client_check_data[X2] ^ 0xFF;
+ if ((B1 & 0xFF) != X3)
+ {
+ return 0;
+ }
+ }
+#ifdef _DEBUG
+ { // log decrypted data
+ char szTitleLine[128];
+ char* szBuf;
+ int titleLineLen;
+ int line;
+ int col;
+ int colsInLine;
+ char* pszBuf;
+
+
+ titleLineLen = null_snprintf(szTitleLine, 128, "DECRYPTED\n");
+ szBuf = (char*)_alloca(titleLineLen + ((wLen+15)>>4) * 76 + 1);
+ CopyMemory(szBuf, szTitleLine, titleLineLen);
+ pszBuf = szBuf + titleLineLen;
+
+ for (line = 0; ; line += 16)
+ {
+ colsInLine = min(16, wLen - line);
+ pszBuf += wsprintfA(pszBuf, "%08X: ", line);
+
+ for (col = 0; col<colsInLine; col++)
+ pszBuf += wsprintfA(pszBuf, "%02X%c", buf[line+col], (col&3)==3 && col!=15?'-':' ');
+
+ for (; col<16; col++)
+ {
+ lstrcpyA(pszBuf," ");
+ pszBuf+=3;
+ }
+
+ *pszBuf++ = ' ';
+ for (col = 0; col<colsInLine; col++)
+ *pszBuf++ = buf[line+col]<' ' ? '.' : (char)buf[line+col];
+ if(wLen-line<=16) break;
+ *pszBuf++='\n';
+ }
+ *pszBuf='\0';
+
+ Netlib_Logf( NULL, szBuf );
+ }
+#endif
+
+ return 1;
+}
+
+// This should be called only if connection already exists
+int CIcqProto::SendDirectMessage(HANDLE hContact, icq_packet *pkt)
+{
+ icq_lock l(directConnListMutex);
+
+ for (int i = 0; i < directConns.getCount(); i++)
+ {
+ if (directConns[i] == NULL)
+ continue;
+
+ if (directConns[i]->hContact == hContact)
+ {
+ if (directConns[i]->initialised)
+ {
+ // This connection can be reused, send packet and exit
+ NetLog_Direct("Sending direct message");
+
+ if (pkt->pData[2] == 2)
+ EncryptDirectPacket(directConns[i], pkt);
+
+ sendDirectPacket(directConns[i], pkt);
+ directConns[i]->packetPending = 0; // packet done
+
+ return TRUE; // Success
+ }
+ break; // connection not ready, use server instead
+ }
+ }
+
+ return FALSE; // connection pending, we failed, use server instead
+}
+
+// Sends a PEER_INIT packet through a DC
+// -----------------------------------------------------------------------
+// This packet is sent during direct connection initialization between two
+// ICQ clients. It is sent by the originator of the connection to start
+// the handshake and by the receiver directly after it has sent the
+// PEER_ACK packet as a reply to the originator's PEER_INIT. The values
+// after the COOKIE field have been added for v7.
+
+void CIcqProto::sendPeerInit_v78(directconnect* dc)
+{
+ icq_packet packet;
+
+ directPacketInit(&packet, 48); // Full packet length
+ packByte(&packet, PEER_INIT); // Command
+ packLEWord(&packet, dc->wVersion); // Version
+ packLEWord(&packet, 43); // Data length
+ packLEDWord(&packet, dc->dwRemoteUin); // UIN of remote user
+ packWord(&packet, 0); // Unknown
+ packLEDWord(&packet, wListenPort); // Our port
+ packLEDWord(&packet, m_dwLocalUIN); // Our UIN
+ packDWord(&packet, dc->dwLocalExternalIP); // Our external IP
+ packDWord(&packet, dc->dwLocalInternalIP); // Our internal IP
+ packByte(&packet, DC_TYPE); // TCP connection flags
+ packLEDWord(&packet, wListenPort); // Our port
+ packLEDWord(&packet, dc->dwConnectionCookie); // DC cookie
+ packLEDWord(&packet, WEBFRONTPORT); // Unknown
+ packLEDWord(&packet, CLIENTFEATURES); // Unknown
+ if (dc->type == DIRECTCONN_REVERSE)
+ packLEDWord(&packet, dc->dwReqId); // Reverse Request Cookie
+ else
+ packDWord(&packet, 0); // Unknown
+
+ sendDirectPacket(dc, &packet);
+#ifdef _DEBUG
+ NetLog_Direct("Sent PEER_INIT to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing");
+#endif
+}
+
+// Sends a PEER_INIT packet through a DC
+// -----------------------------------------------------------------------
+// This is sent to acknowledge a PEER_INIT packet.
+
+void CIcqProto::sendPeerInitAck(directconnect* dc)
+{
+ icq_packet packet;
+
+ directPacketInit(&packet, 4); // Packet length
+ packLEDWord(&packet, PEER_INIT_ACK); //
+
+ sendDirectPacket(dc, &packet);
+#ifdef _DEBUG
+ NetLog_Direct("Sent PEER_INIT_ACK to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing");
+#endif
+}
+
+// Sends a PEER_MSG_INIT packet through a DC
+// -----------------------------------------------------------------------
+// This packet starts message session.
+
+void CIcqProto::sendPeerMsgInit(directconnect* dc, DWORD dwSeq)
+{
+ icq_packet packet;
+
+ directPacketInit(&packet, 33);
+ packByte(&packet, PEER_MSG_INIT);
+ packLEDWord(&packet, 10); // unknown
+ packLEDWord(&packet, 1); // message connection
+ packLEDWord(&packet, dwSeq); // sequence is 0,1
+ if (!dwSeq)
+ {
+ packGUID(&packet, PSIG_MESSAGE); // message type GUID
+ packLEWord(&packet, 1); // delimiter
+ packLEWord(&packet, 4);
+ }
+ else
+ {
+ packDWord(&packet, 0); // first part of Message GUID
+ packDWord(&packet, 0);
+ packLEWord(&packet, 1); // delimiter
+ packLEWord(&packet, 4);
+ packDWord(&packet, 0); // second part of Message GUID
+ packDWord(&packet, 0);
+ }
+ sendDirectPacket(dc, &packet);
+#ifdef _DEBUG
+ NetLog_Direct("Sent PEER_MSG_INIT to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing");
+#endif
+}
+
+// Sends a PEER_FILE_INIT packet through a DC
+// -----------------------------------------------------------------------
+// This packet configures file-transfer session.
+
+void CIcqProto::sendPeerFileInit(directconnect* dc)
+{
+ icq_packet packet;
+ DBVARIANT dbv;
+ char* szNick;
+ int nNickLen;
+
+ dbv.type = DBVT_DELETED;
+ if (getSettingString(NULL, "Nick", &dbv))
+ szNick = "";
+ else
+ szNick = dbv.pszVal;
+ nNickLen = strlennull(szNick);
+
+ directPacketInit(&packet, (WORD)(20 + nNickLen));
+ packByte(&packet, PEER_FILE_INIT); /* packet type */
+ packLEDWord(&packet, 0); /* unknown */
+ packLEDWord(&packet, dc->ft->dwFileCount);
+ packLEDWord(&packet, dc->ft->dwTotalSize);
+ packLEDWord(&packet, dc->ft->dwTransferSpeed);
+ packLEWord(&packet, (WORD)(nNickLen + 1));
+ packBuffer(&packet, (LPBYTE)szNick, (WORD)(nNickLen + 1));
+ sendDirectPacket(dc, &packet);
+#ifdef _DEBUG
+ NetLog_Direct("Sent PEER_FILE_INIT to %u on %s DC", dc->dwRemoteUin, dc->incoming?"incoming":"outgoing");
+#endif
+ ICQFreeVariant(&dbv);
+}
diff --git a/protocols/IcqOscarJ/src/icq_direct.h b/protocols/IcqOscarJ/src/icq_direct.h
new file mode 100644
index 0000000000..3cf91cb493
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_direct.h
@@ -0,0 +1,94 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_DIRECT_H
+#define __ICQ_DIRECT_H
+
+struct filetransfer: public basic_filetransfer
+{
+ int status;
+ int sending;
+ int iCurrentFile;
+ int currentIsDir;
+ DWORD dwCookie;
+ DWORD dwUin;
+ DWORD dwRemotePort;
+ HANDLE hContact;
+ char *szFilename;
+ char *szDescription;
+ char *szSavePath;
+ char *szThisFile;
+ char *szThisSubdir;
+ char **pszFiles;
+ DWORD dwThisFileSize;
+ DWORD dwThisFileDate;
+ DWORD dwTotalSize;
+ DWORD dwFileCount;
+ DWORD dwTransferSpeed;
+ DWORD dwBytesDone, dwFileBytesDone;
+ int fileId;
+ HANDLE hConnection;
+ DWORD dwLastNotify;
+ int nVersion; // Was this sent with a v7 or a v8 packet?
+ BOOL bDC; // Was this received over a DC or through server?
+ BOOL bEmptyDesc; // Was the description empty ?
+};
+
+#define DIRECTCONN_STANDARD 0
+#define DIRECTCONN_FILE 1
+#define DIRECTCONN_CHAT 2
+#define DIRECTCONN_REVERSE 10
+#define DIRECTCONN_CLOSING 15
+
+struct directconnect
+{
+ HANDLE hContact;
+ HANDLE hConnection;
+ DWORD dwConnectionCookie;
+ int type;
+ WORD wVersion;
+ int incoming;
+ int wantIdleTime;
+ int packetPending;
+ DWORD dwRemotePort;
+ DWORD dwRemoteUin;
+ DWORD dwRemoteExternalIP;
+ DWORD dwRemoteInternalIP;
+ DWORD dwLocalExternalIP;
+ DWORD dwLocalInternalIP;
+ int initialised;
+ int handshake;
+ DWORD dwThreadId;
+ filetransfer *ft;
+ DWORD dwReqId; // Reverse Connect request cookie
+};
+
+int DecryptDirectPacket(directconnect* dc, PBYTE buf, WORD wLen);
+
+#endif /* __ICQ_DIRECT_H */
diff --git a/protocols/IcqOscarJ/src/icq_directmsg.cpp b/protocols/IcqOscarJ/src/icq_directmsg.cpp
new file mode 100644
index 0000000000..89fbb7cea5
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_directmsg.cpp
@@ -0,0 +1,361 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void CIcqProto::handleDirectMessage(directconnect* dc, PBYTE buf, WORD wLen)
+{
+ WORD wCommand;
+ WORD wCookie;
+ BYTE bMsgType,bMsgFlags;
+ WORD wStatus;
+ WORD wFlags;
+ WORD wTextLen;
+ char* pszText = NULL;
+
+
+ // The first part of the packet should always be at least 31 bytes
+ if (wLen < 31)
+ {
+ NetLog_Direct("Error during parsing of DC packet 2 PEER_MSG (too short)");
+ return;
+ }
+
+ // Skip packet checksum
+ buf += 4;
+ wLen -= 4;
+
+ // Command:
+ // 0x07d0 = 2000 - cancel given message.
+ // 0x07da = 2010 - acknowledge message.
+ // 0x07ee = 2030 - normal message/request.
+ unpackLEWord(&buf, &wCommand);
+ wLen -= 2;
+
+ // Unknown, always 0xe (14)
+ buf += 2;
+ wLen -= 2;
+
+ // Sequence number
+ unpackLEWord(&buf, &wCookie);
+ wLen -=2;
+
+ // Unknown, always zeroes
+ buf += 12;
+ wLen -= 12;
+
+ // Peer message type
+ unpackByte(&buf, &bMsgType);
+ // Peer message flags
+ unpackByte(&buf, &bMsgFlags);
+ wLen -= 2;
+
+ // The current status of the user, or whether the message was accepted or not.
+ // 0x00 - user is online, or message was receipt, or file transfer accepted
+ // 0x01 - refused
+ // 0x04 - auto-refused, because of away
+ // 0x09 - auto-refused, because of occupied
+ // 0x0a - auto-refused, because of dnd
+ // 0x0e - auto-refused, because of na
+ unpackLEWord(&buf, &wStatus);
+ wLen -= 2;
+
+ // Flags, or priority
+ // Seen: 1 - Chat request
+ // 0 - File auto accept (type 3)
+ // 33 - priority ?
+ unpackLEWord(&buf, &wFlags);
+ wLen -= 2;
+
+ // Messagetext. This is either the status message or the actual message
+ // when this is a PEER_MSG_MSG packet
+ unpackLEWord(&buf, &wTextLen);
+ if (wTextLen > 0)
+ {
+ pszText = (char*)_alloca(wTextLen+1);
+ unpackString(&buf, pszText, wTextLen);
+ pszText[wTextLen] = '\0';
+ }
+ wLen = (wLen - 2) - wTextLen;
+
+#ifdef _DEBUG
+ NetLog_Direct("Handling PEER_MSG '%s', command %u, cookie %u, messagetype %u, messageflags %u, status %u, flags %u", pszText, wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags);
+#else
+ NetLog_Direct("Message through direct - UID: %u", dc->dwRemoteUin);
+#endif
+
+ // The remaining actual message is handled either as a status message request,
+ // a greeting message, a acknowledge or a normal (text, url, file) message
+ if (wCommand == DIRECT_MESSAGE)
+ switch (bMsgType)
+ {
+ case MTYPE_FILEREQ: // File inits
+ handleFileRequest(buf, wLen, dc->dwRemoteUin, wCookie, 0, 0, pszText, 7, TRUE);
+ break;
+
+ case MTYPE_AUTOAWAY:
+ case MTYPE_AUTOBUSY:
+ case MTYPE_AUTONA:
+ case MTYPE_AUTODND:
+ case MTYPE_AUTOFFC:
+ {
+ char **szMsg = MirandaStatusToAwayMsg(AwayMsgTypeToStatus(bMsgType));
+ if (szMsg)
+ icq_sendAwayMsgReplyDirect(dc, wCookie, bMsgType, ( const char** )szMsg);
+ }
+ break;
+
+ case MTYPE_PLUGIN: // Greeting
+ handleDirectGreetingMessage(dc, buf, wLen, wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags, pszText);
+ break;
+
+ default:
+ {
+ message_ack_params pMsgAck = {0};
+ uid_str szUID;
+
+ buf -= wTextLen;
+ wLen += wTextLen;
+
+ pMsgAck.bType = MAT_DIRECT;
+ pMsgAck.pDC = dc;
+ pMsgAck.wCookie = wCookie;
+ pMsgAck.msgType = bMsgType;
+ pMsgAck.bFlags = bMsgFlags;
+ handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, (int)bMsgType, (int)bMsgFlags, 0, (DWORD)wLen, wTextLen, (char*)buf, MTF_DIRECT, &pMsgAck);
+ break;
+ }
+ }
+ else if (wCommand == DIRECT_ACK)
+ {
+ if (bMsgFlags == 3)
+ { // this is status reply
+ uid_str szUID;
+
+ buf -= wTextLen;
+ wLen += wTextLen;
+
+ handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, (int)bMsgType, (int)bMsgFlags, 2, (DWORD)wLen, wTextLen, (char*)buf, MTF_DIRECT, NULL);
+ }
+ else
+ {
+ HANDLE hCookieContact;
+ cookie_message_data *pCookieData = NULL;
+
+ if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData))
+ {
+ NetLog_Direct("Received an unexpected direct ack");
+ }
+ else if (hCookieContact != dc->hContact)
+ {
+ NetLog_Direct("Direct Contact does not match Cookie Contact(0x%x != 0x%x)", dc->hContact, hCookieContact);
+ ReleaseCookie(wCookie); // This could be a bad idea, but I think it is safe
+ }
+ else
+ { // the ack is correct
+ int ackType = -1;
+
+ switch (bMsgType)
+ {
+ case MTYPE_PLAIN:
+ ackType = ACKTYPE_MESSAGE;
+ break;
+ case MTYPE_URL:
+ ackType = ACKTYPE_URL;
+ break;
+ case MTYPE_CONTACTS:
+ ackType = ACKTYPE_CONTACTS;
+ break;
+
+ case MTYPE_FILEREQ: // File acks
+ handleFileAck(buf, wLen, dc->dwRemoteUin, wCookie, wStatus, pszText);
+ break;
+
+ case MTYPE_PLUGIN: // Greeting
+ handleDirectGreetingMessage(dc, buf, wLen, wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags, pszText);
+ break;
+
+ default:
+ NetLog_Direct("Skipped packet from direct connection");
+ break;
+ }
+ if (ackType != -1)
+ { // was a good ack to broadcast ?
+ BroadcastAck(dc->hContact, ackType, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0);
+ ReleaseCookie(wCookie);
+ }
+ }
+ }
+ }
+ else if (wCommand == DIRECT_CANCEL)
+ {
+ NetLog_Direct("Cannot handle abort messages yet... :(");
+ }
+ else
+ NetLog_Direct("Unknown wCommand, packet skipped");
+}
+
+void CIcqProto::handleDirectGreetingMessage(directconnect* dc, PBYTE buf, WORD wLen, WORD wCommand, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, WORD wStatus, WORD wFlags, char* pszText)
+{
+ DWORD dwLengthToEnd;
+ DWORD dwDataLength;
+ char* pszFileName = NULL;
+ int typeId;
+ WORD qt;
+
+#ifdef _DEBUG
+ NetLog_Direct("Handling PEER_MSG_GREETING, command %u, cookie %u, messagetype %u, messageflags %u, status %u, flags %u", wCommand, wCookie, bMsgType, bMsgFlags, wStatus, wFlags);
+#endif
+
+ NetLog_Direct("Parsing Greeting message through direct");
+
+ if (!unpackPluginTypeId(&buf, &wLen, &typeId, &qt, TRUE)) return;
+
+ // Length of remaining data
+ unpackLEDWord(&buf, &dwLengthToEnd);
+ if (dwLengthToEnd < 4 || dwLengthToEnd > wLen)
+ {
+ NetLog_Direct("Error: Sanity checking failed (%d) in handleDirectGreetingMessage, datalen %u wLen %u", 2, dwLengthToEnd, wLen);
+ return;
+ }
+
+ // Length of message/reason
+ unpackLEDWord(&buf, &dwDataLength);
+ wLen -= 4;
+ if (dwDataLength > wLen)
+ {
+ NetLog_Direct("Error: Sanity checking failed (%d) in handleDirectGreetingMessage, datalen %u wLen %u", 3, dwDataLength, wLen);
+ return;
+ }
+
+ if (typeId == MTYPE_FILEREQ && wCommand == DIRECT_MESSAGE)
+ {
+ char* szMsg;
+
+ NetLog_Direct("This is file request");
+ szMsg = (char*)_alloca(dwDataLength+1);
+ unpackString(&buf, szMsg, (WORD)dwDataLength);
+ szMsg[dwDataLength] = '\0';
+ wLen = wLen - (WORD)dwDataLength;
+
+ handleFileRequest(buf, wLen, dc->dwRemoteUin, wCookie, 0, 0, szMsg, 8, TRUE);
+ }
+ else if (typeId == MTYPE_FILEREQ && wCommand == DIRECT_ACK)
+ {
+ char* szMsg;
+
+ NetLog_Direct("This is file ack");
+ szMsg = (char*)_alloca(dwDataLength+1);
+ unpackString(&buf, szMsg, (WORD)dwDataLength);
+ szMsg[dwDataLength] = '\0';
+ wLen = wLen - (WORD)dwDataLength;
+
+ // 50 - file request granted/refused
+ handleFileAck(buf, wLen, dc->dwRemoteUin, wCookie, wStatus, szMsg);
+ }
+ else if (typeId && wCommand == DIRECT_MESSAGE)
+ {
+ uid_str szUID;
+ message_ack_params pMsgAck = {0};
+
+ pMsgAck.bType = MAT_DIRECT;
+ pMsgAck.pDC = dc;
+ pMsgAck.wCookie = wCookie;
+ pMsgAck.msgType = typeId;
+ handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, typeId, 0, 0, dwLengthToEnd, (WORD)dwDataLength, (char*)buf, MTF_PLUGIN | MTF_DIRECT, &pMsgAck);
+ }
+ else if (typeId == MTYPE_STATUSMSGEXT && wCommand == DIRECT_ACK)
+ { // especially for icq2003b
+ NetLog_Direct("This is extended status reply");
+
+ char *szMsg = (char*)_alloca(dwDataLength+1);
+ uid_str szUID;
+ unpackString(&buf, szMsg, (WORD)dwDataLength);
+ szMsg[dwDataLength] = '\0';
+
+ handleMessageTypes(dc->dwRemoteUin, szUID, time(NULL), 0, 0, wCookie, dc->wVersion, (int)(qt + 0xE7), 3, 2, (DWORD)wLen, (WORD)dwDataLength, szMsg, MTF_PLUGIN | MTF_DIRECT, NULL);
+ }
+ else if (typeId && wCommand == DIRECT_ACK)
+ {
+ HANDLE hCookieContact;
+ cookie_message_data *pCookieData = NULL;
+
+ if (!FindCookie(wCookie, &hCookieContact, (void**)&pCookieData))
+ {
+ NetLog_Direct("Received an unexpected direct ack");
+ }
+ else if (hCookieContact != dc->hContact)
+ {
+ NetLog_Direct("Direct Contact does not match Cookie Contact(0x%x != 0x%x)", dc->hContact, hCookieContact);
+ ReleaseCookie(wCookie); // This could be a bad idea, but I think it is safe
+ }
+ else
+ {
+ int ackType = -1;
+
+ switch (typeId)
+ {
+ case MTYPE_MESSAGE:
+ ackType = ACKTYPE_MESSAGE;
+ break;
+ case MTYPE_URL:
+ ackType = ACKTYPE_URL;
+ break;
+ case MTYPE_CONTACTS:
+ ackType = ACKTYPE_CONTACTS;
+ break;
+ case MTYPE_SCRIPT_NOTIFY:
+ {
+ char *szMsg;
+
+ szMsg = (char*)_alloca(dwDataLength + 1);
+ if (dwDataLength > 0)
+ memcpy(szMsg, buf, dwDataLength);
+ szMsg[dwDataLength] = '\0';
+
+ handleXtrazNotifyResponse(dc->dwRemoteUin, dc->hContact, wCookie, szMsg, dwDataLength);
+ }
+ break;
+
+ default:
+ NetLog_Direct("Skipped packet from direct connection");
+ break;
+ }
+
+ if (ackType != -1)
+ { // was a good ack to broadcast ?
+ BroadcastAck(dc->hContact, ackType, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0);
+ }
+ // Release cookie
+ ReleaseCookie(wCookie);
+ }
+ }
+ else
+ NetLog_Direct("Unsupported plugin message type %s", typeId);
+}
diff --git a/protocols/IcqOscarJ/src/icq_fieldnames.cpp b/protocols/IcqOscarJ/src/icq_fieldnames.cpp
new file mode 100644
index 0000000000..d953a081cf
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_fieldnames.cpp
@@ -0,0 +1,595 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+const FieldNamesItem countryField[]={
+ {9999, LPGEN("Other")},
+ {93, LPGEN("Afghanistan")},
+ {355, LPGEN("Albania")},
+ {213, LPGEN("Algeria")},
+ {376, LPGEN("Andorra")},
+ {244, LPGEN("Angola")},
+ {1264, LPGEN("Anguilla")},
+ {1268, LPGEN("Antigua and Barbuda")},
+//{5902, LPGEN("Antilles")}, /* removed: it is not a country, it's a group of islands from diffrent countries (all are included in the list)*/
+ {54, LPGEN("Argentina")},
+ {374, LPGEN("Armenia")},
+ {297, LPGEN("Aruba")},
+ {247, LPGEN("Ascension Island")},
+ {61, LPGEN("Australia")},
+ {6720, LPGEN("Australia, Antarctic Territory")}, /* added country code 672(0)*/
+ {614, LPGEN("Australia, Christmas Island")}, /* rename (from Christmas Island) and change to official county code 61(4) (from 672) */
+ {61891, LPGEN("Australia, Cocos (Keeling) Islands")}, /* rename and change to official county code 61(891) (from 6102) */
+ {6723 , LPGEN("Australia, Norfolk Island")}, /* rename (from Norfolk Island) and change to official county code 672(3) (from 6722) */
+ {43, LPGEN("Austria")},
+ {994, LPGEN("Azerbaijan")},
+ {1242, LPGEN("Bahamas")},
+ {973, LPGEN("Bahrain")},
+ {880, LPGEN("Bangladesh")},
+ {1246, LPGEN("Barbados")},
+//{120, LPGEN("Barbuda")}, /* removed: it is not a country and no special island, see Antigua and Barbuda*/
+ {375, LPGEN("Belarus")},
+ {32, LPGEN("Belgium")},
+ {501, LPGEN("Belize")},
+ {229, LPGEN("Benin")},
+ {1441, LPGEN("Bermuda")},
+ {975, LPGEN("Bhutan")},
+ {591, LPGEN("Bolivia")},
+ {387, LPGEN("Bosnia and Herzegovina")},
+ {267, LPGEN("Botswana")},
+ {55, LPGEN("Brazil")},
+ {106, LPGEN("British Virgin Islands")},
+ {673, LPGEN("Brunei")},
+ {359, LPGEN("Bulgaria")},
+ {226, LPGEN("Burkina Faso")},
+ {257, LPGEN("Burundi")},
+ {855, LPGEN("Cambodia")},
+ {237, LPGEN("Cameroon")},
+ {1002, LPGEN("Canada")},
+ {178, LPGEN("Canary Islands")},
+ {238, LPGEN("Cape Verde Islands")},
+ {1345, LPGEN("Cayman Islands")},
+ {236, LPGEN("Central African Republic")},
+ {235, LPGEN("Chad")},
+ {56, LPGEN("Chile, Republic of")},
+ {86, LPGEN("China")},
+//{6101, LPGEN("Cocos (Keeling) Islands")}, /* removed (double): see Australia, Cocos (Keeling) Islands */
+ {57, LPGEN("Colombia")},
+ {269, LPGEN("Comoros")},
+ {243, LPGEN("Congo, Democratic Republic of (Zaire)")},
+ {242, LPGEN("Congo, Republic of the")},
+ {682, LPGEN("Cook Islands")},
+ {506, LPGEN("Costa Rica")},
+ {225, LPGEN("Cote d'Ivoire (Ivory Coast)")},
+ {385, LPGEN("Croatia")},
+ {53, LPGEN("Cuba")},
+ {357, LPGEN("Greek, Republic of South Cyprus")}, /* rename coz Turkey, Republic of Northern Cyprus */
+ {420, LPGEN("Czech Republic")},
+ {45, LPGEN("Denmark")},
+ {246, LPGEN("Diego Garcia")},
+ {253, LPGEN("Djibouti")},
+ {1767, LPGEN("Dominica")},
+ {1809, LPGEN("Dominican Republic")},
+ {593, LPGEN("Ecuador")},
+ {20, LPGEN("Egypt")},
+ {503, LPGEN("El Salvador")},
+ {240, LPGEN("Equatorial Guinea")},
+ {291, LPGEN("Eritrea")},
+ {372, LPGEN("Estonia")},
+ {251, LPGEN("Ethiopia")},
+ {3883,LPGEN("Europe")}, /* add county code +388 3 official European Telephony Numbering Space*/
+ {298, LPGEN("Faeroe Islands")},
+ {500, LPGEN("Falkland Islands")},
+ {679, LPGEN("Fiji")},
+ {358, LPGEN("Finland")},
+ {33, LPGEN("France")},
+ {5901, LPGEN("French Antilles")},
+ {594, LPGEN("French Guiana")},
+ {689, LPGEN("French Polynesia")},
+ {241, LPGEN("Gabon")},
+ {220, LPGEN("Gambia")},
+ {995, LPGEN("Georgia")},
+ {49, LPGEN("Germany")},
+ {233, LPGEN("Ghana")},
+ {350, LPGEN("Gibraltar")},
+ {30, LPGEN("Greece")},
+ {299, LPGEN("Greenland")},
+ {1473, LPGEN("Grenada")},
+ {590, LPGEN("Guadeloupe")},
+ {1671, LPGEN("Guam, US Territory of")},
+ {502, LPGEN("Guatemala")},
+ {224, LPGEN("Guinea")},
+ {245, LPGEN("Guinea-Bissau")},
+ {592, LPGEN("Guyana")},
+ {509, LPGEN("Haiti")},
+ {504, LPGEN("Honduras")},
+ {852, LPGEN("Hong Kong")},
+ {36, LPGEN("Hungary")},
+ {354, LPGEN("Iceland")},
+ {91, LPGEN("India")},
+ {62, LPGEN("Indonesia")},
+ {98, LPGEN("Iran (Islamic Republic of)")},
+ {964, LPGEN("Iraq")},
+ {353, LPGEN("Ireland")},
+ {972, LPGEN("Israel")},
+ {39, LPGEN("Italy")},
+ {1876, LPGEN("Jamaica")},
+ {81, LPGEN("Japan")},
+ {962, LPGEN("Jordan")},
+ {705, LPGEN("Kazakhstan")},
+ {254, LPGEN("Kenya")},
+ {686, LPGEN("Kiribati")},
+ {850, LPGEN("Korea, North")},
+ {82, LPGEN("Korea, South")},
+ {965, LPGEN("Kuwait")},
+ {996, LPGEN("Kyrgyzstan")},
+ {856, LPGEN("Laos")},
+ {371, LPGEN("Latvia")},
+ {961, LPGEN("Lebanon")},
+ {266, LPGEN("Lesotho")},
+ {231, LPGEN("Liberia")},
+ {218, LPGEN("Libyan Arab Jamahiriya")},
+ {423, LPGEN("Liechtenstein")},
+ {370, LPGEN("Lithuania")},
+ {352, LPGEN("Luxembourg")},
+ {853, LPGEN("Macau")},
+ {389, LPGEN("Macedonia, Republic of")},
+ {261, LPGEN("Madagascar")},
+ {265, LPGEN("Malawi")},
+ {60, LPGEN("Malaysia")},
+ {960, LPGEN("Maldives")},
+ {223, LPGEN("Mali")},
+ {356, LPGEN("Malta")},
+ {692, LPGEN("Marshall Islands")},
+ {596, LPGEN("Martinique")},
+ {222, LPGEN("Mauritania")},
+ {230, LPGEN("Mauritius")},
+ {262, LPGEN("Mayotte Island")},
+ {52, LPGEN("Mexico")},
+ {691, LPGEN("Micronesia, Federated States of")},
+ {373, LPGEN("Moldova, Republic of")},
+ {377, LPGEN("Monaco")},
+ {976, LPGEN("Mongolia")},
+ {1664, LPGEN("Montserrat")},
+ {212, LPGEN("Morocco")},
+ {258, LPGEN("Mozambique")},
+ {95, LPGEN("Myanmar")},
+ {264, LPGEN("Namibia")},
+ {674, LPGEN("Nauru")},
+ {977, LPGEN("Nepal")},
+ {31, LPGEN("Netherlands")},
+ {599, LPGEN("Netherlands Antilles")}, /* dissolved 2010 */
+ {5995, LPGEN("St. Maarten")}, /* add new country in 2010 (from Netherlands Antilles) */
+ {5999, LPGEN("Curacao")}, /* add new country in 2010 (from Netherlands Antilles) */
+ {5997, LPGEN("Netherlands (Bonaire Island)")}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */
+ {59946, LPGEN("Netherlands (Saba Island)")}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */
+ {59938, LPGEN("Netherlands (St. Eustatius Island)")}, /* add new Part of Netherlands in 2010 (from Netherlands Antilles) */
+//{114, LPGEN("Nevis")}, /* removed: it is not a country, it's part of Saint Kitts and Nevis*/
+ {687, LPGEN("New Caledonia")},
+ {64, LPGEN("New Zealand")},
+ {505, LPGEN("Nicaragua")},
+ {227, LPGEN("Niger")},
+ {234, LPGEN("Nigeria")},
+ {683, LPGEN("Niue")},
+ {1670, LPGEN("Northern Mariana Islands, US Territory of")}, /* added NANP */
+ {47, LPGEN("Norway")},
+ {968, LPGEN("Oman")},
+ {92, LPGEN("Pakistan")},
+ {680, LPGEN("Palau")},
+ {507, LPGEN("Panama")},
+ {675, LPGEN("Papua New Guinea")},
+ {595, LPGEN("Paraguay")},
+ {51, LPGEN("Peru")},
+ {63, LPGEN("Philippines")},
+ {48, LPGEN("Poland")},
+ {351, LPGEN("Portugal")},
+ {1939, LPGEN("Puerto Rico")},
+ {974, LPGEN("Qatar")},
+ {262, LPGEN("Reunion Island")},
+ {40, LPGEN("Romania")},
+//{6701, LPGEN("Rota Island")}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */
+ {7, LPGEN("Russia")},
+ {250, LPGEN("Rwanda")},
+ {1684, LPGEN("Samoa (USA)")}, /* rename (from American Samoa) change county code to NANP (from 684) */
+ {685, LPGEN("Samoa, Western")}, /* rename (from Western Samoa) */
+ {290, LPGEN("Saint Helena")},
+//{115, LPGEN("Saint Kitts")}, /* removed: it is not a country it is part of Saint Kitts and Nevis*/
+ {1869, LPGEN("Saint Kitts and Nevis")},
+ {1758, LPGEN("Saint Lucia")},
+ {508, LPGEN("Saint Pierre and Miquelon")},
+ {1784, LPGEN("Saint Vincent and the Grenadines")},
+//{670, LPGEN("Saipan Island")}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */
+ {378, LPGEN("San Marino")},
+ {239, LPGEN("Sao Tome and Principe")},
+ {966, LPGEN("Saudi Arabia")},
+ {442, LPGEN("Scotland")},
+ {221, LPGEN("Senegal")},
+ {248, LPGEN("Seychelles")},
+ {232, LPGEN("Sierra Leone")},
+ {65, LPGEN("Singapore")},
+ {421, LPGEN("Slovakia")},
+ {386, LPGEN("Slovenia")},
+ {677, LPGEN("Solomon Islands")},
+ {252, LPGEN("Somalia")},
+ {27, LPGEN("South Africa")},
+ {34, LPGEN("Spain")},
+ {3492, LPGEN("Spain, Canary Islands")}, /*rename and change county code to 34(92) spain + canary code*/
+ {94, LPGEN("Sri Lanka")},
+ {249, LPGEN("Sudan")},
+ {597, LPGEN("Suriname")},
+ {268, LPGEN("Swaziland")},
+ {46, LPGEN("Sweden")},
+ {41, LPGEN("Switzerland")},
+ {963, LPGEN("Syrian Arab Republic")},
+ {886, LPGEN("Taiwan")},
+ {992, LPGEN("Tajikistan")},
+ {255, LPGEN("Tanzania")},
+ {66, LPGEN("Thailand")},
+//{6702, LPGEN("Tinian Island")}, /* removed: it is not a country it is part of Northern Mariana Islands, US Territory of */
+ {670 , LPGEN("Timor, East")}, /* added (is part off Northern Mariana Islands but not US Territory*/
+ {228, LPGEN("Togo")},
+ {690, LPGEN("Tokelau")},
+ {676, LPGEN("Tonga")},
+ {1868, LPGEN("Trinidad and Tobago")},
+ {216, LPGEN("Tunisia")},
+ {90, LPGEN("Turkey")},
+ {90392, LPGEN("Turkey, Republic of Northern Cyprus")}, /* added (is diffrent from Greek part)*/
+ {993, LPGEN("Turkmenistan")},
+ {1649, LPGEN("Turks and Caicos Islands")},
+ {688, LPGEN("Tuvalu")},
+ {256, LPGEN("Uganda")},
+ {380, LPGEN("Ukraine")},
+ {971, LPGEN("United Arab Emirates")},
+ {44, LPGEN("United Kingdom")},
+ {598, LPGEN("Uruguay")},
+ {1, LPGEN("USA")},
+ {998, LPGEN("Uzbekistan")},
+ {678, LPGEN("Vanuatu")},
+ {379, LPGEN("Vatican City")},
+ {58, LPGEN("Venezuela")},
+ {84, LPGEN("Vietnam")},
+ {1284, LPGEN("Virgin Islands (UK)")}, /* change county code to NANP (from 105) - rename coz Virgin Islands (USA) */
+ {1340, LPGEN("Virgin Islands (USA)")}, /* change county code to NANP (from 123) */
+ {441, LPGEN("Wales")},
+ {681, LPGEN("Wallis and Futuna Islands")},
+ {967, LPGEN("Yemen")},
+ {38, LPGEN("Yugoslavia")},
+ {381, LPGEN("Serbia, Republic of")}, /* rename need (from Yugoslavia)*/
+ {383, LPGEN("Kosovo, Republic of")}, /*change country code (from 3811), rename need (from Yugoslavia - Serbia) */
+ {382, LPGEN("Montenegro, Republic of")}, /* rename need (from Yugoslavia - Montenegro) */
+ {260, LPGEN("Zambia")},
+ {263, LPGEN("Zimbabwe")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem interestsField[]={
+ {137, LPGEN("50's")},
+ {134, LPGEN("60's")},
+ {135, LPGEN("70's")},
+ {136, LPGEN("80's")},
+ {100, LPGEN("Art")},
+ {128, LPGEN("Astronomy")},
+ {147, LPGEN("Audio and Visual")},
+ {125, LPGEN("Business")},
+ {146, LPGEN("Business Services")},
+ {101, LPGEN("Cars")},
+ {102, LPGEN("Celebrity Fans")},
+ {130, LPGEN("Clothing")},
+ {103, LPGEN("Collections")},
+ {104, LPGEN("Computers")},
+ {105, LPGEN("Culture")},
+ {122, LPGEN("Ecology")},
+ {139, LPGEN("Entertainment")},
+ {138, LPGEN("Finance and Corporate")},
+ {106, LPGEN("Fitness")},
+ {142, LPGEN("Health and Beauty")},
+ {108, LPGEN("Hobbies")},
+ {150, LPGEN("Home Automation")},
+ {144, LPGEN("Household Products")},
+ {107, LPGEN("Games")},
+ {124, LPGEN("Government")},
+ {109, LPGEN("ICQ - Help")},
+ {110, LPGEN("Internet")},
+ {111, LPGEN("Lifestyle")},
+ {145, LPGEN("Mail Order Catalog")},
+ {143, LPGEN("Media")},
+ {112, LPGEN("Movies and TV")},
+ {113, LPGEN("Music")},
+ {126, LPGEN("Mystics")},
+ {123, LPGEN("News and Media")},
+ {114, LPGEN("Outdoors")},
+ {115, LPGEN("Parenting")},
+ {131, LPGEN("Parties")},
+ {116, LPGEN("Pets and Animals")},
+ {149, LPGEN("Publishing")},
+ {117, LPGEN("Religion")},
+ {141, LPGEN("Retail Stores")},
+ {118, LPGEN("Science")},
+ {119, LPGEN("Skills")},
+ {133, LPGEN("Social science")},
+ {129, LPGEN("Space")},
+ {148, LPGEN("Sporting and Athletic")},
+ {120, LPGEN("Sports")},
+ {127, LPGEN("Travel")},
+ {121, LPGEN("Web Design")},
+ {132, LPGEN("Women")},
+ {-1, NULL}
+};
+
+
+const FieldNamesItem languageField[]={
+ {55, LPGEN("Afrikaans")},
+ {58, LPGEN("Albanian")},
+ {1, LPGEN("Arabic")},
+ {59, LPGEN("Armenian")},
+ {68, LPGEN("Azerbaijani")},
+ {72, LPGEN("Belorussian")},
+ {2, LPGEN("Bhojpuri")},
+ {56, LPGEN("Bosnian")},
+ {3, LPGEN("Bulgarian")},
+ {4, LPGEN("Burmese")},
+ {5, LPGEN("Cantonese")},
+ {6, LPGEN("Catalan")},
+ {61, LPGEN("Chamorro")},
+ {7, LPGEN("Chinese")},
+ {8, LPGEN("Croatian")},
+ {9, LPGEN("Czech")},
+ {10, LPGEN("Danish")},
+ {11, LPGEN("Dutch")},
+ {12, LPGEN("English")},
+ {13, LPGEN("Esperanto")},
+ {14, LPGEN("Estonian")},
+ {15, LPGEN("Farsi")},
+ {16, LPGEN("Finnish")},
+ {17, LPGEN("French")},
+ {18, LPGEN("Gaelic")},
+ {19, LPGEN("German")},
+ {20, LPGEN("Greek")},
+ {70, LPGEN("Gujarati")},
+ {21, LPGEN("Hebrew")},
+ {22, LPGEN("Hindi")},
+ {23, LPGEN("Hungarian")},
+ {24, LPGEN("Icelandic")},
+ {25, LPGEN("Indonesian")},
+ {26, LPGEN("Italian")},
+ {27, LPGEN("Japanese")},
+ {28, LPGEN("Khmer")},
+ {29, LPGEN("Korean")},
+ {69, LPGEN("Kurdish")},
+ {30, LPGEN("Lao")},
+ {31, LPGEN("Latvian")},
+ {32, LPGEN("Lithuanian")},
+ {65, LPGEN("Macedonian")},
+ {33, LPGEN("Malay")},
+ {63, LPGEN("Mandarin")},
+ {62, LPGEN("Mongolian")},
+ {34, LPGEN("Norwegian")},
+ {57, LPGEN("Persian")},
+ {35, LPGEN("Polish")},
+ {36, LPGEN("Portuguese")},
+ {60, LPGEN("Punjabi")},
+ {37, LPGEN("Romanian")},
+ {38, LPGEN("Russian")},
+ {39, LPGEN("Serbian")},
+ {66, LPGEN("Sindhi")},
+ {40, LPGEN("Slovak")},
+ {41, LPGEN("Slovenian")},
+ {42, LPGEN("Somali")},
+ {43, LPGEN("Spanish")},
+ {44, LPGEN("Swahili")},
+ {45, LPGEN("Swedish")},
+ {46, LPGEN("Tagalog")},
+ {64, LPGEN("Taiwanese")},
+ {71, LPGEN("Tamil")},
+ {47, LPGEN("Tatar")},
+ {48, LPGEN("Thai")},
+ {49, LPGEN("Turkish")},
+ {50, LPGEN("Ukrainian")},
+ {51, LPGEN("Urdu")},
+ {52, LPGEN("Vietnamese")},
+ {67, LPGEN("Welsh")},
+ {53, LPGEN("Yiddish")},
+ {54, LPGEN("Yoruba")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem pastField[]={
+ {300, LPGEN("Elementary School")},
+ {301, LPGEN("High School")},
+ {302, LPGEN("College")},
+ {303, LPGEN("University")},
+ {304, LPGEN("Military")},
+ {305, LPGEN("Past Work Place")},
+ {306, LPGEN("Past Organization")},
+ {399, LPGEN("Other")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem genderField[]={
+ {'F', LPGEN("Female")},
+ {'M', LPGEN("Male")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem studyLevelField[]={
+ {4, LPGEN("Associated degree")},
+ {5, LPGEN("Bachelor's degree")},
+ {1, LPGEN("Elementary")},
+ {2, LPGEN("High-school")},
+ {6, LPGEN("Master's degree")},
+ {7, LPGEN("PhD")},
+ {8, LPGEN("Postdoctoral")},
+ {3, LPGEN("University / College")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem industryField[]={
+ {2, LPGEN("Agriculture")},
+ {3, LPGEN("Arts")},
+ {4, LPGEN("Construction")},
+ {5, LPGEN("Consumer Goods")},
+ {6, LPGEN("Corporate Services")},
+ {7, LPGEN("Education")},
+ {8, LPGEN("Finance")},
+ {9, LPGEN("Government")},
+ {10, LPGEN("High Tech")},
+ {11, LPGEN("Legal")},
+ {12, LPGEN("Manufacturing")},
+ {13, LPGEN("Media")},
+ {14, LPGEN("Medical & Health Care")},
+ {15, LPGEN("Non-Profit Organization Management")},
+ {19, LPGEN("Other")},
+ {16, LPGEN("Recreation, Travel & Entertainment")},
+ {17, LPGEN("Service Industry")},
+ {18, LPGEN("Transportation")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem occupationField[]={
+ {1, LPGEN("Academic")},
+ {2, LPGEN("Administrative")},
+ {3, LPGEN("Art/Entertainment")},
+ {4, LPGEN("College Student")},
+ {5, LPGEN("Computers")},
+ {6, LPGEN("Community & Social")},
+ {7, LPGEN("Education")},
+ {8, LPGEN("Engineering")},
+ {9, LPGEN("Financial Services")},
+ {10, LPGEN("Government")},
+ {11, LPGEN("High School Student")},
+ {12, LPGEN("Home")},
+ {13, LPGEN("ICQ - Providing Help")},
+ {14, LPGEN("Law")},
+ {15, LPGEN("Managerial")},
+ {16, LPGEN("Manufacturing")},
+ {17, LPGEN("Medical/Health")},
+ {18, LPGEN("Military")},
+ {19, LPGEN("Non-Government Organization")},
+ {20, LPGEN("Professional")},
+ {21, LPGEN("Retail")},
+ {22, LPGEN("Retired")},
+ {23, LPGEN("Science & Research")},
+ {24, LPGEN("Sports")},
+ {25, LPGEN("Technical")},
+ {26, LPGEN("University Student")},
+ {27, LPGEN("Web Building")},
+ {99, LPGEN("Other Services")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem affiliationField[]={
+ {200, LPGEN("Alumni Org.")},
+ {201, LPGEN("Charity Org.")},
+ {202, LPGEN("Club/Social Org.")},
+ {203, LPGEN("Community Org.")},
+ {204, LPGEN("Cultural Org.")},
+ {205, LPGEN("Fan Clubs")},
+ {206, LPGEN("Fraternity/Sorority")},
+ {207, LPGEN("Hobbyists Org.")},
+ {208, LPGEN("International Org.")},
+ {209, LPGEN("Nature and Environment Org.")},
+ {210, LPGEN("Professional Org.")},
+ {211, LPGEN("Scientific/Technical Org.")},
+ {212, LPGEN("Self Improvement Group")},
+ {213, LPGEN("Spiritual/Religious Org.")},
+ {214, LPGEN("Sports Org.")},
+ {215, LPGEN("Support Org.")},
+ {216, LPGEN("Trade and Business Org.")},
+ {217, LPGEN("Union")},
+ {218, LPGEN("Volunteer Org.")},
+ {299, LPGEN("Other")},
+ {0, NULL}
+};
+
+
+const FieldNamesItem agesField[]={
+ {0x0011000D, LPGEN("13-17")},
+ {0x00160012, LPGEN("18-22")},
+ {0x001D0017, LPGEN("23-29")},
+ {0x0027001E, LPGEN("30-39")},
+ {0x00310028, LPGEN("40-49")},
+ {0x003B0032, LPGEN("50-59")},
+ {0x2710003C, LPGEN("60-above")},
+ {-1, NULL}
+};
+
+
+const FieldNamesItem maritalField[]={
+ {10, LPGEN("Single")},
+ {11, LPGEN("Close relationships")},
+ {12, LPGEN("Engaged")},
+ {20, LPGEN("Married")},
+ {30, LPGEN("Divorced")},
+ {31, LPGEN("Separated")},
+ {40, LPGEN("Widowed")},
+ {50, LPGEN("Open relationship")},
+ {255, LPGEN("Other")},
+ {0, NULL}
+};
+
+
+char *LookupFieldName(const FieldNamesItem *table, int code)
+{
+ int i;
+
+ if (code != 0)
+ {
+ for(i = 0; table[i].text; i++)
+ {
+ if (table[i].code == code)
+ return table[i].text;
+ }
+
+ // Tried to get unexisting field name, you have an
+ // error in the data or in the table
+ _ASSERT(FALSE);
+ }
+
+ return NULL;
+}
+
+
+char *LookupFieldNameUtf(const FieldNamesItem *table, int code, char *str, size_t strsize)
+{
+ char *szText = LookupFieldName(table, code);
+
+ if (szText)
+ return ICQTranslateUtfStatic(szText, str, strsize);
+
+ return NULL;
+}
diff --git a/protocols/IcqOscarJ/src/icq_fieldnames.h b/protocols/IcqOscarJ/src/icq_fieldnames.h
new file mode 100644
index 0000000000..cf15475299
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_fieldnames.h
@@ -0,0 +1,49 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+struct FieldNamesItem
+{
+ int code;
+ char *text;
+};
+
+extern const FieldNamesItem countryField[];
+extern const FieldNamesItem interestsField[];
+extern const FieldNamesItem languageField[];
+extern const FieldNamesItem pastField[];
+extern const FieldNamesItem genderField[];
+extern const FieldNamesItem agesField[];
+extern const FieldNamesItem studyLevelField[];
+extern const FieldNamesItem industryField[];
+extern const FieldNamesItem occupationField[];
+extern const FieldNamesItem affiliationField[];
+extern const FieldNamesItem maritalField[];
+
+char *LookupFieldName(const FieldNamesItem *table, int code);
+char *LookupFieldNameUtf(const FieldNamesItem *table, int code, char *str, size_t strsize);
diff --git a/protocols/IcqOscarJ/src/icq_filerequests.cpp b/protocols/IcqOscarJ/src/icq_filerequests.cpp
new file mode 100644
index 0000000000..d85de72580
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_filerequests.cpp
@@ -0,0 +1,218 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+void CIcqProto::handleFileAck(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, WORD wStatus, char* pszText)
+{
+ char* pszFileName = NULL;
+ DWORD dwFileSize;
+ HANDLE hCookieContact;
+ WORD wPort;
+ WORD wFilenameLength;
+ filetransfer* ft;
+
+
+ // Find the filetransfer that belongs to this response
+ if (!FindCookie(dwCookie, &hCookieContact, (void**)&ft))
+ {
+ NetLog_Direct("Error: Received unexpected file transfer request response");
+ return;
+ }
+
+ FreeCookie(dwCookie);
+
+ if (hCookieContact != HContactFromUIN(dwUin, NULL))
+ {
+ NetLog_Direct("Error: UINs do not match in file transfer request response");
+ return;
+ }
+
+ // If status != 0, a request has been denied
+ if (wStatus != 0)
+ {
+ NetLog_Direct("File transfer denied by %u.", dwUin);
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DENIED, (HANDLE)ft, 0);
+
+ FreeCookie(dwCookie);
+
+ return;
+ }
+
+ if (wLen < 6)
+ { // sanity check
+ NetLog_Direct("Ignoring malformed file transfer request response");
+ return;
+ }
+
+ // Port to connect to
+ unpackWord(&buf, &wPort);
+ ft->dwRemotePort = wPort;
+ wLen -= 2;
+
+ // Unknown
+ buf += 2;
+ wLen -= 2;
+
+ // Filename
+ unpackLEWord(&buf, &wFilenameLength);
+ if (wFilenameLength > 0)
+ {
+ if (wFilenameLength > wLen - 2)
+ wFilenameLength = wLen - 2;
+ pszFileName = (char*)_alloca(wFilenameLength+1);
+ unpackString(&buf, pszFileName, wFilenameLength);
+ pszFileName[wFilenameLength] = '\0';
+ }
+ wLen = wLen - 2 - wFilenameLength;
+
+ if (wLen >= 4)
+ { // Total filesize
+ unpackLEDWord(&buf, &dwFileSize);
+ wLen -= 4;
+ }
+ else
+ dwFileSize = 0;
+
+ NetLog_Direct("File transfer ack from %u, port %u, name %s, size %u", dwUin, ft->dwRemotePort, pszFileName, dwFileSize);
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTING, (HANDLE)ft, 0);
+
+ OpenDirectConnection(ft->hContact, DIRECTCONN_FILE, ft);
+}
+
+filetransfer* CIcqProto::CreateFileTransfer(HANDLE hContact, DWORD dwUin, int nVersion)
+{
+ filetransfer *ft = CreateIcqFileTransfer();
+
+ ft->dwUin = dwUin;
+ ft->hContact = hContact;
+ ft->nVersion = nVersion;
+ ft->pMessage.bMessageType = MTYPE_FILEREQ;
+ InitMessageCookie(&ft->pMessage);
+
+ return ft;
+}
+
+// pszDescription points to a string with the reason
+// buf points to the first data after the string
+void CIcqProto::handleFileRequest(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, DWORD dwID1, DWORD dwID2, char* pszDescription, int nVersion, BOOL bDC)
+{
+ BOOL bEmptyDesc = FALSE;
+ if (strlennull(pszDescription) == 0) {
+ pszDescription = Translate("No description given");
+ bEmptyDesc = TRUE;
+ }
+
+ // Empty port+pad
+ buf += 4;
+ wLen -= 4;
+
+ // Filename
+ WORD wFilenameLength;
+ unpackLEWord(&buf, &wFilenameLength);
+ if (!wFilenameLength) {
+ NetLog_Direct("Ignoring malformed file send request");
+ return;
+ }
+
+ char *pszFileName = (char*)_alloca(wFilenameLength + 1);
+ unpackString(&buf, pszFileName, wFilenameLength);
+ pszFileName[wFilenameLength] = '\0';
+
+ wLen = wLen - 2 - wFilenameLength;
+
+ // Total filesize
+ DWORD dwFileSize;
+ unpackLEDWord(&buf, &dwFileSize);
+ wLen -= 4;
+
+ int bAdded;
+ HANDLE hContact = HContactFromUIN(dwUin, &bAdded);
+
+ // Initialize a filetransfer struct
+ filetransfer *ft = CreateFileTransfer(hContact, dwUin, nVersion);
+ ft->dwCookie = dwCookie;
+ ft->szFilename = mir_strdup(pszFileName);
+ ft->szDescription = 0;
+ ft->fileId = -1;
+ ft->dwTotalSize = dwFileSize;
+ ft->pMessage.dwMsgID1 = dwID1;
+ ft->pMessage.dwMsgID2 = dwID2;
+ ft->bDC = bDC;
+ ft->bEmptyDesc = bEmptyDesc;
+
+ // Send chain event
+ TCHAR* ptszFileName = mir_utf8decodeT(pszFileName);
+
+ PROTORECVFILET pre = {0};
+ pre.flags = PREF_TCHAR;
+ pre.fileCount = 1;
+ pre.timestamp = time(NULL);
+ pre.tszDescription = mir_utf8decodeT(pszDescription);
+ pre.ptszFiles = &ptszFileName;
+ pre.lParam = (LPARAM)ft;
+
+ CCSDATA ccs;
+ ccs.szProtoService = PSR_FILE;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+
+ mir_free(pre.tszDescription);
+ mir_free(ptszFileName);
+}
+
+void CIcqProto::handleDirectCancel(directconnect *dc, PBYTE buf, WORD wLen, WORD wCommand, DWORD dwCookie, WORD wMessageType, WORD wStatus, WORD wFlags, char* pszText)
+{
+ NetLog_Direct("handleDirectCancel: Unhandled cancel");
+}
+
+// *******************************************************************************
+
+void CIcqProto::icq_CancelFileTransfer(HANDLE hContact, filetransfer* ft)
+{
+ DWORD dwCookie;
+
+ if (FindCookieByData(ft, &dwCookie, NULL))
+ FreeCookie(dwCookie); /* this bit stops a send that's waiting for acceptance */
+
+ if (IsValidFileTransfer(ft))
+ { // Transfer still out there, end it
+ NetLib_CloseConnection(&ft->hConnection, FALSE);
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
+
+ if (!FindFileTransferDC(ft))
+ { // Release orphan structure only
+ SafeReleaseFileTransfer((void**)&ft);
+ }
+ }
+}
diff --git a/protocols/IcqOscarJ/src/icq_filetransfer.cpp b/protocols/IcqOscarJ/src/icq_filetransfer.cpp
new file mode 100644
index 0000000000..61484c3987
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_filetransfer.cpp
@@ -0,0 +1,515 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+static void file_buildProtoFileTransferStatus(filetransfer* ft, PROTOFILETRANSFERSTATUS* pfts)
+{
+ ZeroMemory(pfts, sizeof(PROTOFILETRANSFERSTATUS));
+ pfts->cbSize = sizeof(PROTOFILETRANSFERSTATUS);
+ pfts->hContact = ft->hContact;
+ pfts->flags = PFTS_UTF | (ft->sending ? PFTS_SENDING : PFTS_RECEIVING); /* Standard FT is Ansi only */
+ if (ft->sending)
+ pfts->pszFiles = ft->pszFiles;
+ else
+ pfts->pszFiles = NULL; /* FIXME */
+ pfts->totalFiles = ft->dwFileCount;
+ pfts->currentFileNumber = ft->iCurrentFile;
+ pfts->totalBytes = ft->dwTotalSize;
+ pfts->totalProgress = ft->dwBytesDone;
+ pfts->szWorkingDir = ft->szSavePath;
+ pfts->szCurrentFile = ft->szThisFile;
+ pfts->currentFileSize = ft->dwThisFileSize;
+ pfts->currentFileTime = ft->dwThisFileDate;
+ pfts->currentFileProgress = ft->dwFileBytesDone;
+}
+
+
+static void file_sendTransferSpeed(CIcqProto* ppro, directconnect* dc)
+{
+ icq_packet packet;
+
+ directPacketInit(&packet, 5);
+ packByte(&packet, PEER_FILE_SPEED); /* Ident */
+ packLEDWord(&packet, dc->ft->dwTransferSpeed);
+ ppro->sendDirectPacket(dc, &packet);
+}
+
+
+static void file_sendNick(CIcqProto* ppro, directconnect* dc)
+{
+ icq_packet packet;
+ char* szNick;
+ WORD wNickLen;
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (ppro->getSettingString(NULL, "Nick", &dbv))
+ szNick = "";
+ else
+ szNick = dbv.pszVal;
+
+ wNickLen = strlennull(szNick);
+
+ directPacketInit(&packet, (WORD)(8 + wNickLen));
+ packByte(&packet, PEER_FILE_INIT_ACK); /* Ident */
+ packLEDWord(&packet, dc->ft->dwTransferSpeed);
+ packLEWord(&packet, (WORD)(wNickLen + 1));
+ packBuffer(&packet, (LPBYTE)szNick, (WORD)(wNickLen + 1));
+ ppro->sendDirectPacket(dc, &packet);
+ ICQFreeVariant(&dbv);
+}
+
+
+static void file_sendNextFile(CIcqProto* ppro, directconnect* dc)
+{
+ icq_packet packet;
+ struct _stati64 statbuf;
+ char szThisSubDir[MAX_PATH];
+
+ if (dc->ft->iCurrentFile >= (int)dc->ft->dwFileCount)
+ {
+ ppro->BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dc->ft, 0);
+ ppro->CloseDirectConnection(dc);
+ dc->ft->hConnection = NULL;
+ return;
+ }
+
+ dc->ft->szThisFile = dc->ft->pszFiles[dc->ft->iCurrentFile];
+ if (FileStatUtf(dc->ft->szThisFile, &statbuf))
+ {
+ ppro->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."));
+ ppro->CloseDirectConnection(dc);
+ dc->ft->hConnection = NULL;
+ return;
+ }
+
+ char *pszThisFileName = FindFilePathContainer((LPCSTR*)dc->ft->pszFiles, dc->ft->iCurrentFile, szThisSubDir);
+
+ if (statbuf.st_mode&_S_IFDIR)
+ {
+ dc->ft->currentIsDir = 1;
+ }
+ else
+ {
+ dc->ft->currentIsDir = 0;
+ dc->ft->fileId = OpenFileUtf(dc->ft->szThisFile, _O_BINARY | _O_RDONLY, _S_IREAD);
+ if (dc->ft->fileId == -1)
+ {
+ ppro->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."));
+ ppro->CloseDirectConnection(dc);
+ dc->ft->hConnection = NULL;
+ return;
+ }
+
+ }
+ dc->ft->dwThisFileSize = statbuf.st_size;
+ dc->ft->dwThisFileDate = statbuf.st_mtime;
+ dc->ft->dwFileBytesDone = 0;
+
+ char *szThisFileNameAnsi = NULL, *szThisSubDirAnsi = NULL;
+ if (!utf8_decode(pszThisFileName, &szThisFileNameAnsi))
+ szThisFileNameAnsi = NULL;
+ if (!utf8_decode(szThisSubDir, &szThisSubDirAnsi))
+ szThisSubDirAnsi = NULL;
+ WORD wThisFileNameLen = strlennull(szThisFileNameAnsi);
+ WORD wThisSubDirLen = strlennull(szThisSubDirAnsi);
+
+ directPacketInit(&packet, (WORD)(20 + wThisFileNameLen + wThisSubDirLen));
+ packByte(&packet, PEER_FILE_NEXTFILE); /* Ident */
+ packByte(&packet, (BYTE)((statbuf.st_mode & _S_IFDIR) != 0)); // Is subdir
+ packLEWord(&packet, (WORD)(wThisFileNameLen + 1));
+ packBuffer(&packet, (LPBYTE)szThisFileNameAnsi, (WORD)(wThisFileNameLen + 1));
+ packLEWord(&packet, (WORD)(wThisSubDirLen + 1));
+ packBuffer(&packet, (LPBYTE)szThisSubDirAnsi, (WORD)(wThisSubDirLen + 1));
+ packLEDWord(&packet, dc->ft->dwThisFileSize);
+ packLEDWord(&packet, statbuf.st_mtime);
+ packLEDWord(&packet, dc->ft->dwTransferSpeed);
+ SAFE_FREE(&szThisFileNameAnsi);
+ SAFE_FREE(&szThisSubDirAnsi);
+ ppro->sendDirectPacket(dc, &packet);
+
+ ppro->BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, dc->ft, 0);
+}
+
+
+static void file_sendResume(CIcqProto* ppro, directconnect* dc)
+{
+ icq_packet packet;
+
+ directPacketInit(&packet, 17);
+ packByte(&packet, PEER_FILE_RESUME); /* Ident */
+ packLEDWord(&packet, dc->ft->dwFileBytesDone); /* file resume */
+ packLEDWord(&packet, 0); /* unknown */
+ packLEDWord(&packet, dc->ft->dwTransferSpeed);
+ packLEDWord(&packet, dc->ft->iCurrentFile + 1); /* file number */
+ ppro->sendDirectPacket(dc, &packet);
+}
+
+
+static void file_sendData(CIcqProto* ppro, directconnect* dc)
+{
+ BYTE buf[2048];
+ int bytesRead = 0;
+
+ if (!dc->ft->currentIsDir)
+ {
+ icq_packet packet;
+
+ if (dc->ft->fileId == -1)
+ return;
+ bytesRead = _read(dc->ft->fileId, buf, sizeof(buf));
+ if (bytesRead == -1)
+ return;
+
+ directPacketInit(&packet, (WORD)(1 + bytesRead));
+ packByte(&packet, PEER_FILE_DATA); /* Ident */
+ packBuffer(&packet, buf, (WORD)bytesRead);
+ ppro->sendDirectPacket(dc, &packet);
+ }
+
+ dc->ft->dwBytesDone += bytesRead;
+ dc->ft->dwFileBytesDone += bytesRead;
+
+ if (GetTickCount() > dc->ft->dwLastNotify + 500 || bytesRead == 0)
+ {
+ PROTOFILETRANSFERSTATUS pfts;
+
+ file_buildProtoFileTransferStatus(dc->ft, &pfts);
+ ppro->BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, dc->ft, (LPARAM)&pfts);
+
+ dc->ft->dwLastNotify = GetTickCount();
+ }
+
+ if (bytesRead == 0)
+ {
+ if (!dc->ft->currentIsDir) _close(dc->ft->fileId);
+ dc->ft->fileId = -1;
+ dc->wantIdleTime = 0;
+ dc->ft->iCurrentFile++;
+ file_sendNextFile(ppro, dc); /* this will close the socket if no more files */
+ }
+}
+
+
+void CIcqProto::handleFileTransferIdle(directconnect* dc)
+{
+ file_sendData(this, dc);
+}
+
+
+void CIcqProto::icq_sendFileResume(filetransfer *ft, int action, const char *szFilename)
+{
+ if (ft->hConnection == NULL)
+ return;
+
+ directconnect *dc = FindFileTransferDC(ft);
+ if (!dc) return; // something is broken...
+
+ int openFlags;
+
+ switch (action)
+ {
+ case FILERESUME_RESUME:
+ openFlags = _O_BINARY | _O_WRONLY;
+ break;
+
+ case FILERESUME_OVERWRITE:
+ openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY;
+ ft->dwFileBytesDone = 0;
+ break;
+
+ case FILERESUME_SKIP:
+ openFlags = _O_BINARY | _O_WRONLY;
+ ft->dwFileBytesDone = ft->dwThisFileSize;
+ break;
+
+ case FILERESUME_RENAME:
+ openFlags = _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY;
+ SAFE_FREE(&ft->szThisFile);
+ ft->szThisFile = null_strdup(szFilename);
+ ft->dwFileBytesDone = 0;
+ break;
+ }
+
+ ft->fileId = OpenFileUtf(ft->szThisFile, openFlags, _S_IREAD | _S_IWRITE);
+ if (ft->fileId == -1)
+ {
+ 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."));
+ NetLib_CloseConnection(&ft->hConnection, FALSE);
+ return;
+ }
+
+ if (action == FILERESUME_RESUME)
+ ft->dwFileBytesDone = _lseek(ft->fileId, 0, SEEK_END);
+ else
+ _lseek(ft->fileId, ft->dwFileBytesDone, SEEK_SET);
+
+ ft->dwBytesDone += ft->dwFileBytesDone;
+
+ file_sendResume(this, dc);
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0);
+}
+
+
+// small utility function
+void NormalizeBackslash(char* path)
+{
+ int len = strlennull(path);
+
+ if (len && path[len-1] != '\\') strcat(path, "\\");
+}
+
+/* a file transfer looks like this:
+S: 0
+R: 5
+R: 1
+S: 2
+R: 3
+S: 6 * many
+(for more files, send 2, 3, 6*many)
+*/
+
+void CIcqProto::handleFileTransferPacket(directconnect* dc, PBYTE buf, WORD wLen)
+{
+ if (wLen < 1)
+ return;
+
+ NetLog_Direct("Handling file packet");
+
+ switch (buf[0])
+ {
+ case PEER_FILE_INIT: /* first packet of a file transfer */
+ if (dc->initialised)
+ return;
+ if (wLen < 19)
+ return;
+ buf += 5; /* id, and unknown 0 */
+ dc->type = DIRECTCONN_FILE;
+ {
+ DWORD dwFileCount;
+ DWORD dwTotalSize;
+ DWORD dwTransferSpeed;
+ WORD wNickLength;
+ int bAdded;
+
+ unpackLEDWord(&buf, &dwFileCount);
+ unpackLEDWord(&buf, &dwTotalSize);
+ unpackLEDWord(&buf, &dwTransferSpeed);
+ unpackLEWord(&buf, &wNickLength);
+
+ dc->ft = FindExpectedFileRecv(dc->dwRemoteUin, dwTotalSize);
+ if (dc->ft == NULL)
+ {
+ NetLog_Direct("Unexpected file receive");
+ CloseDirectConnection(dc);
+ return;
+ }
+ dc->ft->dwFileCount = dwFileCount;
+ dc->ft->dwTransferSpeed = dwTransferSpeed;
+ dc->ft->hContact = HContactFromUIN(dc->ft->dwUin, &bAdded);
+ dc->ft->dwBytesDone = 0;
+ dc->ft->iCurrentFile = -1;
+ dc->ft->fileId = -1;
+ dc->ft->hConnection = dc->hConnection;
+ dc->ft->dwLastNotify = GetTickCount();
+
+ dc->initialised = 1;
+
+ file_sendTransferSpeed(this, dc);
+ file_sendNick(this, dc);
+ }
+ BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, dc->ft, 0);
+ break;
+
+ case PEER_FILE_INIT_ACK:
+ if (wLen < 8)
+ return;
+ buf++;
+ unpackLEDWord(&buf, &dc->ft->dwTransferSpeed);
+ /* followed by nick */
+ file_sendNextFile(this, dc);
+ break;
+
+ case PEER_FILE_NEXTFILE:
+ if (wLen < 20)
+ return;
+ buf++; /* id */
+ {
+ char *szAnsi;
+ WORD wThisFilenameLen, wSubdirLen;
+ BYTE isDirectory;
+
+ unpackByte(&buf, &isDirectory);
+ unpackLEWord(&buf, &wThisFilenameLen);
+ if (wLen < 19 + wThisFilenameLen)
+ return;
+ SAFE_FREE(&dc->ft->szThisFile);
+ szAnsi = (char *)_alloca(wThisFilenameLen + 1);
+ memcpy(szAnsi, buf, wThisFilenameLen);
+ szAnsi[wThisFilenameLen] = '\0';
+ dc->ft->szThisFile = ansi_to_utf8(szAnsi);
+ buf += wThisFilenameLen;
+
+ unpackLEWord(&buf, &wSubdirLen);
+ if (wLen < 18 + wThisFilenameLen + wSubdirLen)
+ return;
+ SAFE_FREE(&dc->ft->szThisSubdir);
+ szAnsi = (char *)_alloca(wSubdirLen + 1);
+ memcpy(szAnsi, buf, wSubdirLen);
+ szAnsi[wSubdirLen] = '\0';
+ dc->ft->szThisSubdir = ansi_to_utf8(szAnsi);
+ buf += wSubdirLen;
+
+ unpackLEDWord(&buf, &dc->ft->dwThisFileSize);
+ unpackLEDWord(&buf, &dc->ft->dwThisFileDate);
+ unpackLEDWord(&buf, &dc->ft->dwTransferSpeed);
+
+ /* no cheating with paths */
+ if (!IsValidRelativePath(dc->ft->szThisFile) || !IsValidRelativePath(dc->ft->szThisSubdir))
+ {
+ NetLog_Direct("Invalid path information");
+ break;
+ }
+
+ char *szFullPath = (char*)SAFE_MALLOC(strlennull(dc->ft->szSavePath)+strlennull(dc->ft->szThisSubdir)+strlennull(dc->ft->szThisFile)+3);
+ strcpy(szFullPath, dc->ft->szSavePath);
+ NormalizeBackslash(szFullPath);
+ strcat(szFullPath, dc->ft->szThisSubdir);
+ NormalizeBackslash(szFullPath);
+// _chdir(szFullPath); // set current dir - not very useful
+ strcat(szFullPath, dc->ft->szThisFile);
+ // we joined the full path to dest file
+ SAFE_FREE(&dc->ft->szThisFile);
+ dc->ft->szThisFile = szFullPath;
+
+ dc->ft->dwFileBytesDone = 0;
+ dc->ft->iCurrentFile++;
+
+ if (isDirectory)
+ {
+ MakeDirUtf(dc->ft->szThisFile);
+ dc->ft->fileId = -1;
+ }
+ else
+ {
+ /* file resume */
+ PROTOFILETRANSFERSTATUS pfts = {0};
+
+ file_buildProtoFileTransferStatus(dc->ft, &pfts);
+ if (BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, dc->ft, (LPARAM)&pfts))
+ break; /* UI supports resume: it will call PS_FILERESUME */
+
+ dc->ft->fileId = OpenFileUtf(dc->ft->szThisFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE);
+ if (dc->ft->fileId == -1)
+ {
+ 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."));
+ CloseDirectConnection(dc);
+ dc->ft->hConnection = NULL;
+ break;
+ }
+ }
+ }
+ file_sendResume(this, dc);
+ BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, dc->ft, 0);
+ break;
+
+ case PEER_FILE_RESUME:
+ if (dc->ft->fileId == -1 && !dc->ft->currentIsDir)
+ return;
+ if (wLen < 13)
+ return;
+ if (wLen < 17)
+ NetLog_Direct("Warning: Received short PEER_FILE_RESUME");
+ buf++;
+ {
+ DWORD dwRestartFrom;
+
+ unpackLEDWord(&buf, &dwRestartFrom);
+ if (dwRestartFrom > dc->ft->dwThisFileSize)
+ return;
+ buf += 4; /* unknown. 0 */
+ unpackLEDWord(&buf, &dc->ft->dwTransferSpeed);
+ buf += 4; /* unknown. 1 */
+ if (!dc->ft->currentIsDir)
+ _lseek(dc->ft->fileId, dwRestartFrom, 0);
+ dc->wantIdleTime = 1;
+ dc->ft->dwBytesDone += dwRestartFrom;
+ dc->ft->dwFileBytesDone += dwRestartFrom;
+ }
+ break;
+
+ case PEER_FILE_SPEED:
+ if (wLen < 5)
+ return;
+ buf++;
+ unpackLEDWord(&buf, &dc->ft->dwTransferSpeed);
+ dc->ft->dwLastNotify = GetTickCount();
+ break;
+
+ case PEER_FILE_DATA:
+ if (!dc->ft->currentIsDir)
+ {
+ if (dc->ft->fileId == -1)
+ break;
+ buf++; wLen--;
+ _write(dc->ft->fileId, buf, wLen);
+ }
+ else
+ wLen = 0;
+ dc->ft->dwBytesDone += wLen;
+ dc->ft->dwFileBytesDone += wLen;
+ if (GetTickCount() > dc->ft->dwLastNotify + 500 || wLen < 2048)
+ {
+ PROTOFILETRANSFERSTATUS pfts;
+
+ file_buildProtoFileTransferStatus(dc->ft, &pfts);
+ BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, dc->ft, (LPARAM)&pfts);
+ dc->ft->dwLastNotify = GetTickCount();
+ }
+ if (wLen < 2048)
+ {
+ /* EOF */
+ if (!dc->ft->currentIsDir)
+ _close(dc->ft->fileId);
+ dc->ft->fileId = -1;
+ if ((DWORD)dc->ft->iCurrentFile == dc->ft->dwFileCount - 1)
+ {
+ dc->type = DIRECTCONN_CLOSING; /* this guarantees that we won't accept any more data but that the sender is still free to closesocket() neatly */
+ BroadcastAck(dc->ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, dc->ft, 0);
+ }
+ }
+ break;
+
+ default:
+ NetLog_Direct("Unknown file transfer packet ignored.");
+ break;
+ }
+}
diff --git a/protocols/IcqOscarJ/src/icq_firstrun.cpp b/protocols/IcqOscarJ/src/icq_firstrun.cpp
new file mode 100644
index 0000000000..1208d6d0ec
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_firstrun.cpp
@@ -0,0 +1,124 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+static void accountLoadDetails(CIcqProto *ppro, HWND hwndDlg)
+{
+ char pszUIN[20];
+ DWORD dwUIN = ppro->getContactUin(NULL);
+ if (dwUIN)
+ {
+ null_snprintf(pszUIN, 20, "%u", dwUIN);
+ SetDlgItemTextA(hwndDlg, IDC_UIN, pszUIN);
+ }
+
+ char pszPwd[PASSWORDMAXLEN];
+ if (ppro->GetUserStoredPassword(pszPwd, PASSWORDMAXLEN))
+ SetDlgItemTextA(hwndDlg, IDC_PW, pszPwd);
+}
+
+
+INT_PTR CALLBACK icq_FirstRunDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ ppro = (CIcqProto*)lParam;
+ SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam );
+ {
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)ppro->m_hIconProtocol->GetIcon(true));
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)ppro->m_hIconProtocol->GetIcon());
+
+ SendDlgItemMessage(hwndDlg, IDC_PW, EM_LIMITTEXT, PASSWORDMAXLEN - 1, 0);
+
+ accountLoadDetails(ppro, hwndDlg);
+ }
+ return TRUE;
+
+ case WM_DESTROY:
+ ppro->m_hIconProtocol->ReleaseIcon(true);
+ ppro->m_hIconProtocol->ReleaseIcon();
+ break;
+
+ case WM_CLOSE:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDC_REGISTER:
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)URL_REGISTER);
+ break;
+
+ case IDC_UIN:
+ case IDC_PW:
+ if (HIWORD(wParam) == EN_CHANGE && (HWND)lParam == GetFocus())
+ {
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->code)
+ {
+ case PSN_APPLY:
+ {
+ char str[128];
+ GetDlgItemTextA(hwndDlg, IDC_UIN, str, sizeof(str));
+ ppro->setSettingDword(NULL, UNIQUEIDSETTING, atoi(str));
+ GetDlgItemTextA(hwndDlg, IDC_PW, str, sizeof(ppro->m_szPassword));
+ strcpy(ppro->m_szPassword, str);
+ CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(ppro->m_szPassword), (LPARAM) str);
+ ppro->setSettingString(NULL, "Password", str);
+ }
+ break;
+
+ case PSN_RESET:
+ accountLoadDetails(ppro, hwndDlg);
+ break;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+
+INT_PTR CIcqProto::OnCreateAccMgrUI(WPARAM wParam, LPARAM lParam)
+{
+ return (INT_PTR)CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_ICQACCOUNT), (HWND)lParam, icq_FirstRunDlgProc, LPARAM(this));
+}
diff --git a/protocols/IcqOscarJ/src/icq_http.cpp b/protocols/IcqOscarJ/src/icq_http.cpp
new file mode 100644
index 0000000000..3e50db43ed
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_http.cpp
@@ -0,0 +1,212 @@
+// ---------------------------------------------------------------------------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,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater
+// Copyright © 2004,2005,2006 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// HTTP Gateway Handling routines
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+int icq_httpGatewayInit(HANDLE hConn, NETLIBOPENCONNECTION *nloc, NETLIBHTTPREQUEST *nlhr)
+{
+ // initial response from ICQ http gateway
+ WORD wLen, wVersion, wType;
+ WORD wIpLen;
+ DWORD dwSid1, dwSid2, dwSid3, dwSid4;
+ BYTE *buf;
+ char szSid[33], szHttpServer[256], szHttpGetUrl[300], szHttpPostUrl[300];
+ NETLIBHTTPPROXYINFO nlhpi = {0};
+
+ if (nlhr->dataLength < 31)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return 0;
+ }
+
+ buf = (PBYTE)nlhr->pData;
+ unpackWord(&buf, &wLen);
+ unpackWord(&buf, &wVersion); /* always 0x0443 */
+ unpackWord(&buf, &wType); /* hello reply */
+ buf += 6; /* dunno */
+ unpackDWord(&buf, &dwSid1);
+ unpackDWord(&buf, &dwSid2);
+ unpackDWord(&buf, &dwSid3);
+ unpackDWord(&buf, &dwSid4);
+ null_snprintf(szSid, 33, "%08x%08x%08x%08x", dwSid1, dwSid2, dwSid3, dwSid4);
+ unpackWord(&buf, &wIpLen);
+
+ if(nlhr->dataLength < 30 + wIpLen || wIpLen == 0 || wIpLen > sizeof(szHttpServer) - 1)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return 0;
+ }
+
+ SetGatewayIndex(hConn, 1); // new master connection begins here
+
+ memcpy(szHttpServer, buf, wIpLen);
+ szHttpServer[wIpLen] = '\0';
+
+ nlhpi.cbSize = sizeof(nlhpi);
+ nlhpi.flags = NLHPIF_USEPOSTSEQUENCE;
+ nlhpi.szHttpGetUrl = szHttpGetUrl;
+ nlhpi.szHttpPostUrl = szHttpPostUrl;
+ nlhpi.firstPostSequence = 1;
+ null_snprintf(szHttpGetUrl, 300, "http://%s/monitor?sid=%s", szHttpServer, szSid);
+ null_snprintf(szHttpPostUrl, 300, "http://%s/data?sid=%s&seq=", szHttpServer, szSid);
+
+ return CallService(MS_NETLIB_SETHTTPPROXYINFO, (WPARAM)hConn, (LPARAM)&nlhpi);
+}
+
+
+
+int icq_httpGatewayBegin(HANDLE hConn, NETLIBOPENCONNECTION* nloc)
+{ // open our "virual data connection"
+ icq_packet packet;
+ size_t serverNameLen;
+
+ serverNameLen = strlennull(nloc->szHost);
+
+ packet.wLen = (WORD)(serverNameLen + 4);
+ write_httphdr(&packet, HTTP_PACKETTYPE_LOGIN, GetGatewayIndex(hConn));
+ packWord(&packet, (WORD)serverNameLen);
+ packBuffer(&packet, (LPBYTE)nloc->szHost, (WORD)serverNameLen);
+ packWord(&packet, nloc->wPort);
+ INT_PTR res = Netlib_Send(hConn, (char*)packet.pData, packet.wLen, MSG_DUMPPROXY|MSG_NOHTTPGATEWAYWRAP);
+ SAFE_FREE((void**)&packet.pData);
+
+ return res != SOCKET_ERROR;
+}
+
+
+
+int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend)
+{
+ PBYTE sendBuf = buf;
+ int sendLen = len;
+ int sendResult = 0;
+
+ while (sendLen > 0)
+ { // imitate polite behaviour of icq5.1 and split large packets
+ icq_packet packet;
+ WORD curLen;
+ int curResult;
+
+ if (sendLen > 512) curLen = 512; else curLen = (WORD)sendLen;
+ // send wrapped data
+ packet.wLen = curLen;
+ write_httphdr(&packet, HTTP_PACKETTYPE_FLAP, GetGatewayIndex(hConn));
+ packBuffer(&packet, sendBuf, (WORD)curLen);
+
+ NETLIBBUFFER nlb={ (char*)packet.pData, packet.wLen, flags };
+ curResult = pfnNetlibSend((WPARAM)hConn, (LPARAM)&nlb);
+
+ SAFE_FREE((void**)&packet.pData);
+
+ // sending failed, end loop
+ if (curResult <= 0)
+ return curResult;
+ // calculare real number of data bytes sent
+ if (curResult > 14) sendResult += curResult - 14;
+ // move on
+ sendLen -= curLen;
+ sendBuf += curLen;
+ }
+
+ return sendResult;
+}
+
+
+
+PBYTE icq_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST* nlhr, PBYTE buf, int len, int* outBufLen, void *(*NetlibRealloc)(void *, size_t))
+{
+ WORD wLen, wType;
+ DWORD dwPackSeq;
+ PBYTE tbuf;
+ int i, copyBytes;
+
+
+ tbuf = buf;
+ for(i = 0;;)
+ {
+ if (tbuf - buf + 2 > len)
+ break;
+ unpackWord(&tbuf, &wLen);
+ if (wLen < 12)
+ break;
+ if (tbuf - buf + wLen > len)
+ break;
+ tbuf += 2; /* version */
+ unpackWord(&tbuf, &wType);
+ tbuf += 4; /* flags */
+ unpackDWord(&tbuf, &dwPackSeq);
+ if (wType == HTTP_PACKETTYPE_FLAP)
+ { // it is normal data packet
+ copyBytes = wLen - 12;
+ if (copyBytes > len - i)
+ {
+ /* invalid data - do our best to get something out of it */
+ copyBytes = len - i;
+ }
+ memcpy(buf + i, tbuf, copyBytes);
+ i += copyBytes;
+ }
+ else if (wType == HTTP_PACKETTYPE_LOGINREPLY)
+ { // our "virtual connection" was established, good
+ BYTE bRes;
+
+ unpackByte(&tbuf, &bRes);
+ wLen -= 1;
+ if (!bRes)
+ Netlib_Logf( NULL, "Gateway Connection #%d Established.", dwPackSeq);
+ else
+ Netlib_Logf( NULL, "Gateway Connection #%d Failed, error: %d", dwPackSeq, bRes);
+ }
+ else if (wType == HTTP_PACKETTYPE_CLOSEREPLY)
+ { // "virtual connection" closed - only received if any other "virual connection" still active
+ Netlib_Logf( NULL, "Gateway Connection #%d Closed.", dwPackSeq);
+ }
+ tbuf += wLen - 12;
+ }
+ *outBufLen = i;
+
+ return buf;
+}
+
+
+
+int icq_httpGatewayWalkTo(HANDLE hConn, NETLIBOPENCONNECTION* nloc)
+{ // this is bad simplification - for avatars to work we need to handle
+ // two "virtual connections" at the same time
+ icq_packet packet;
+ DWORD dwGatewaySeq = GetGatewayIndex(hConn);
+
+ packet.wLen = 0;
+ write_httphdr(&packet, HTTP_PACKETTYPE_CLOSE, dwGatewaySeq);
+ Netlib_Send(hConn, (char*)packet.pData, packet.wLen, MSG_DUMPPROXY|MSG_NOHTTPGATEWAYWRAP);
+ // we closed virtual connection, open new one
+ dwGatewaySeq++;
+ SetGatewayIndex(hConn, dwGatewaySeq);
+ return icq_httpGatewayBegin(hConn, nloc);
+}
diff --git a/protocols/IcqOscarJ/src/icq_http.h b/protocols/IcqOscarJ/src/icq_http.h
new file mode 100644
index 0000000000..017a5ea5fc
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_http.h
@@ -0,0 +1,48 @@
+// ---------------------------------------------------------------------------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,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater
+// Copyright © 2004,2005 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_HTTP_H
+#define __ICQ_HTTP_H
+
+#define HTTP_PROXY_VERSION 0x0443
+
+#define HTTP_PACKETTYPE_HELLOREPLY 2
+#define HTTP_PACKETTYPE_LOGIN 3
+#define HTTP_PACKETTYPE_LOGINREPLY 4 /* contains 1 byte: 0 */
+#define HTTP_PACKETTYPE_FLAP 5
+#define HTTP_PACKETTYPE_CLOSE 6 /* contains no data */
+#define HTTP_PACKETTYPE_CLOSEREPLY 7 /* contains 1 byte: 0 */
+
+int icq_httpGatewayInit(HANDLE hConn, NETLIBOPENCONNECTION *nloc, NETLIBHTTPREQUEST *nlhr);
+int icq_httpGatewayBegin(HANDLE hConn, NETLIBOPENCONNECTION *nloc);
+int icq_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend);
+PBYTE icq_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST *nlhr, PBYTE buf, int bufLen, int *outBufLen, void *(*NetlibRealloc)(void *, size_t));
+int icq_httpGatewayWalkTo(HANDLE hConn, NETLIBOPENCONNECTION* nloc);
+
+#endif /* __ICQ_HTTP_H */
diff --git a/protocols/IcqOscarJ/src/icq_infoupdate.cpp b/protocols/IcqOscarJ/src/icq_infoupdate.cpp
new file mode 100644
index 0000000000..014df20e01
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_infoupdate.cpp
@@ -0,0 +1,401 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Background thread for automatic update of user details
+//
+// -----------------------------------------------------------------------------
+
+#include "icqoscar.h"
+
+// Retrieve users' info
+void CIcqProto::icq_InitInfoUpdate(void)
+{
+ // Create wait objects
+ hInfoQueueEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (hInfoQueueEvent) {
+ // Init mutexes
+ infoUpdateMutex = new icq_critical_section();
+
+ // Init list
+ for (int i = 0; i<LISTSIZE; i++) {
+ m_infoUpdateList[i].dwUin = 0;
+ m_infoUpdateList[i].hContact = NULL;
+ m_infoUpdateList[i].queued = 0;
+ }
+
+ CloseHandle( ForkThreadEx( &CIcqProto::InfoUpdateThread, NULL));
+ }
+
+ bInfoPendingUsers = 0;
+ dwInfoActiveRequest = 0;
+}
+
+// Returns TRUE if user was queued
+// Returns FALSE if the list was full
+BOOL CIcqProto::icq_QueueUser(HANDLE hContact)
+{
+ if ( !infoUpdateMutex )
+ return FALSE;
+
+ if (nInfoUserCount < LISTSIZE)
+ {
+ int i, nChecked = 0, nFirstFree = -1;
+ BOOL bFound = FALSE;
+
+ infoUpdateMutex->Enter();
+
+ // Check if in list
+ for (i = 0; (i<LISTSIZE && nChecked < nInfoUserCount); i++)
+ {
+ if (m_infoUpdateList[i].hContact)
+ {
+ nChecked++;
+ if (m_infoUpdateList[i].hContact == hContact)
+ {
+ bFound = TRUE;
+ break;
+ }
+ }
+ else if (nFirstFree == -1)
+ {
+ nFirstFree = i;
+ }
+ }
+ if (nFirstFree == -1)
+ nFirstFree = i;
+
+ // Add to list
+ if (!bFound)
+ {
+ DWORD dwUin = getContactUin(hContact);
+
+ if (dwUin)
+ {
+ m_infoUpdateList[nFirstFree].dwUin = dwUin;
+ m_infoUpdateList[nFirstFree].hContact = hContact;
+ m_infoUpdateList[nFirstFree].queued = time(NULL);
+ nInfoUserCount++;
+#ifdef _DEBUG
+ NetLog_Server("Queued user %u, place %u, count %u", dwUin, nFirstFree, nInfoUserCount);
+#endif
+ // Notify worker thread
+ if (hInfoQueueEvent && bInfoUpdateEnabled)
+ SetEvent(hInfoQueueEvent);
+ }
+ }
+
+ infoUpdateMutex->Leave();
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+void CIcqProto::icq_DequeueUser(DWORD dwUin)
+{
+ if (nInfoUserCount > 0)
+ {
+ int nChecked = 0;
+ // Check if in list
+ infoUpdateMutex->Enter();
+ for (int i = 0; (i < LISTSIZE && nChecked < nInfoUserCount); i++)
+ {
+ if (m_infoUpdateList[i].dwUin)
+ {
+ nChecked++;
+ // Remove from list
+ if (m_infoUpdateList[i].dwUin == dwUin)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Dequeued user %u", m_infoUpdateList[i].dwUin);
+#endif
+ m_infoUpdateList[i].dwUin = 0;
+ m_infoUpdateList[i].hContact = NULL;
+ m_infoUpdateList[i].queued = 0;
+ nInfoUserCount--;
+ break;
+ }
+ }
+ }
+ infoUpdateMutex->Leave();
+ }
+}
+
+
+void CIcqProto::icq_RescanInfoUpdate()
+{
+ bInfoPendingUsers = 0;
+ /* This is here, cause we do not want to emit large number of reuqest at once,
+ fill queue, and let thread deal with it */
+ bInfoUpdateEnabled = 0; // freeze thread
+
+ // Queue all outdated users
+ HANDLE hContact = FindFirstContact();
+ while (hContact != NULL)
+ {
+ if (IsMetaInfoChanged(hContact))
+ { // Queue user
+ if (!icq_QueueUser(hContact))
+ { // The queue is full, pause queuing contacts
+ bInfoPendingUsers = 1;
+ break;
+ }
+ }
+ hContact = FindNextContact(hContact);
+ }
+
+ bInfoUpdateEnabled = TRUE;
+}
+
+
+void CIcqProto::icq_EnableUserLookup(BOOL bEnable)
+{
+ bInfoUpdateEnabled = bEnable;
+
+ // Notify worker thread
+ if (bInfoUpdateEnabled && hInfoQueueEvent)
+ SetEvent(hInfoQueueEvent);
+}
+
+
+void __cdecl CIcqProto::InfoUpdateThread( void* )
+{
+ int i;
+ DWORD dwWait = WAIT_OBJECT_0;
+
+ NetLog_Server("%s thread starting.", "Info-Update");
+
+ bInfoUpdateRunning = TRUE;
+
+ while (bInfoUpdateRunning)
+ {
+ if (!nInfoUserCount && bInfoPendingUsers) // whole queue processed, check if more users needs updating
+ icq_RescanInfoUpdate();
+
+ if (!nInfoUserCount || !bInfoUpdateEnabled || !icqOnline())
+ {
+ dwWait = WAIT_TIMEOUT;
+ while (bInfoUpdateRunning && dwWait == WAIT_TIMEOUT)
+ { // wait for new work or until we should end
+ dwWait = ICQWaitForSingleObject(hInfoQueueEvent, 10000);
+ }
+ }
+ if (!bInfoUpdateRunning) break;
+
+ switch (dwWait) {
+ case WAIT_IO_COMPLETION:
+ // Possible shutdown in progress
+ break;
+
+ case WAIT_OBJECT_0:
+ case WAIT_TIMEOUT:
+ // Time to check for new users
+ if (!bInfoUpdateEnabled) continue; // we can't send requests now
+
+ if (nInfoUserCount && icqOnline())
+ {
+ time_t now = time(NULL);
+ BOOL bNotReady = FALSE, bTimeOuted = FALSE;
+
+ // Check the list, take only users that were there for at least 5sec
+ // wait if any user is there shorter than 5sec and not a single user is there longer than 20sec
+ infoUpdateMutex->Enter();
+ for (i = 0; i<LISTSIZE; i++)
+ {
+ if (m_infoUpdateList[i].hContact)
+ {
+ if (m_infoUpdateList[i].queued + 20 < now)
+ {
+ bTimeOuted = TRUE;
+ break;
+ }
+ else if (m_infoUpdateList[i].queued + 5 >= now)
+ bNotReady = TRUE;
+ }
+ }
+ infoUpdateMutex->Leave();
+
+ if (!bTimeOuted && bNotReady)
+ {
+ SleepEx(1000, TRUE);
+ if (!bInfoUpdateRunning)
+ { // need to end as fast as possible
+ NetLog_Server("%s thread ended.", "Info-Update");
+ goto LBL_Exit;
+ }
+ continue;
+ }
+
+ if (FindCookie(dwInfoActiveRequest, NULL, NULL))
+ { // only send another request, when the previous is completed
+#ifdef _DEBUG
+ NetLog_Server("Info-Update: Request 0x%x still in progress.", dwInfoActiveRequest);
+#endif
+ SleepEx(1000, TRUE);
+ if (!bInfoUpdateRunning)
+ { // need to end as fast as possible
+ NetLog_Server("%s thread ended.", "Info-Update");
+ goto LBL_Exit;
+ }
+ continue;
+ }
+
+#ifdef _DEBUG
+ NetLog_Server("Info-Update: Users %u in queue.", nInfoUserCount);
+#endif
+ // Either some user is waiting long enough, or all users are ready (waited at least the minimum time)
+ m_ratesMutex->Enter();
+ if (!m_rates)
+ { // we cannot send info request - icq is offline
+ m_ratesMutex->Leave();
+ break;
+ }
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST);
+ while (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_IDLE_30))
+ { // we are over rate, need to wait before sending
+ int nDelay = m_rates->getDelayToLimitLevel(wGroup, RML_IDLE_50);
+
+ m_ratesMutex->Leave();
+#ifdef _DEBUG
+ NetLog_Server("Rates: InfoUpdate delayed %dms", nDelay);
+#endif
+ SleepEx(nDelay, TRUE); // do not keep things locked during sleep
+ if (!bInfoUpdateRunning)
+ { // need to end as fast as possible
+ NetLog_Server("%s thread ended.", "Info-Update");
+ goto LBL_Exit;
+ }
+ m_ratesMutex->Enter();
+ if (!m_rates) // we lost connection when we slept, go away
+ break;
+ }
+ if (!m_rates)
+ { // we cannot send info request - icq is offline
+ m_ratesMutex->Leave();
+ break;
+ }
+ m_ratesMutex->Leave();
+
+ userinfo *hContactList[LISTSIZE];
+ int nListIndex = 0;
+ BYTE *pRequestData = NULL;
+ int nRequestSize = 0;
+
+ infoUpdateMutex->Enter();
+ for (i = 0; i<LISTSIZE; i++)
+ {
+ if (m_infoUpdateList[i].hContact)
+ {
+ // check TS again, maybe it has been updated while we slept
+ if (IsMetaInfoChanged(m_infoUpdateList[i].hContact))
+ {
+ if (m_infoUpdateList[i].queued + 5 < now)
+ {
+ BYTE *pItem = NULL;
+ int nItemSize = 0;
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (!getSetting(m_infoUpdateList[i].hContact, DBSETTING_METAINFO_TOKEN, &dbv))
+ { // retrieve user details using privacy token
+ ppackTLV(&pItem, &nItemSize, 0x96, dbv.cpbVal, dbv.pbVal);
+ ICQFreeVariant(&dbv);
+ }
+ // last updated time
+ ppackTLVDouble(&pItem, &nItemSize, 0x64, getSettingDouble(m_infoUpdateList[i].hContact, DBSETTING_METAINFO_TIME, 0));
+
+ ppackTLVUID(&pItem, &nItemSize, 0x32, m_infoUpdateList[i].dwUin, NULL);
+ ppackWord(&pRequestData, &nRequestSize, (WORD)nItemSize);
+ ppackBuffer(&pRequestData, &nRequestSize, nItemSize, pItem);
+ // take a reference
+ SAFE_FREE((void**)&pItem);
+ hContactList[nListIndex++] = &m_infoUpdateList[i];
+ }
+ }
+ else
+ {
+#ifdef _DEBUG
+ NetLog_Server("Dequeued absolete user %u", m_infoUpdateList[i].dwUin);
+#endif
+ // Dequeue user and find another one
+ m_infoUpdateList[i].dwUin = 0;
+ m_infoUpdateList[i].hContact = NULL;
+ nInfoUserCount--;
+ // continue for loop
+ }
+ }
+ }
+
+#ifdef _DEBUG
+ NetLog_Server("Request info for %u user(s).", nListIndex);
+#endif
+ if (!nListIndex)
+ { // no users to request info for
+ infoUpdateMutex->Leave();
+ break;
+ }
+ if (!(dwInfoActiveRequest = sendUserInfoMultiRequest(pRequestData, nRequestSize, nListIndex)))
+ { // sending data packet failed
+ SAFE_FREE((void**)&pRequestData);
+ infoUpdateMutex->Leave();
+ break;
+ }
+ SAFE_FREE((void**)&pRequestData);
+
+ for (i = 0; i<nListIndex; i++)
+ { // Dequeue users and go back to sleep
+ hContactList[i]->dwUin = 0;
+ hContactList[i]->hContact = NULL;
+ hContactList[i]->queued = 0;
+ nInfoUserCount--;
+ }
+ infoUpdateMutex->Leave();
+ }
+ break;
+
+ default:
+ // Something strange happened. Exit
+ bInfoUpdateRunning = FALSE;
+ break;
+ }
+ }
+
+ NetLog_Server("%s thread ended.", "Info-Update");
+
+LBL_Exit:
+ SAFE_DELETE(&infoUpdateMutex);
+ CloseHandle(hInfoQueueEvent);
+}
+
+// Clean up before exit
+void CIcqProto::icq_InfoUpdateCleanup(void)
+{
+ NetLog_Server("%s must die.", "Info-Update");
+ bInfoUpdateRunning = FALSE;
+ if (hInfoQueueEvent)
+ SetEvent(hInfoQueueEvent); // break queue loop
+}
diff --git a/protocols/IcqOscarJ/src/icq_menu.cpp b/protocols/IcqOscarJ/src/icq_menu.cpp
new file mode 100644
index 0000000000..f3dd072cde
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_menu.cpp
@@ -0,0 +1,263 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera, Bio
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+#include <m_skin.h>
+
+static HANDLE hPrebuildMenuHook;
+
+HANDLE g_hContactMenuItems[6];
+HANDLE g_hContactMenuSvc[6];
+
+static int sttCompareProtocols(const CIcqProto *p1, const CIcqProto *p2)
+{
+ return strcmp(p1->m_szModuleName, p2->m_szModuleName);
+}
+
+LIST<CIcqProto> g_Instances(1, sttCompareProtocols);
+
+static CIcqProto* IcqGetInstanceByHContact(HANDLE hContact)
+{
+ char* szProto = ( char* )CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0);
+ if (szProto == NULL)
+ return NULL;
+
+ for (int i = 0; i < g_Instances.getCount(); i++)
+ if (!strcmp(szProto, g_Instances[i]->m_szModuleName))
+ return g_Instances[i];
+
+ return NULL;
+}
+
+static INT_PTR IcqMenuHandleRequestAuth(WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam);
+ return (ppro) ? ppro->RequestAuthorization(wParam, lParam) : 0;
+}
+
+static INT_PTR IcqMenuHandleGrantAuth(WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam);
+ return (ppro) ? ppro->GrantAuthorization(wParam, lParam) : 0;
+}
+
+static INT_PTR IcqMenuHandleRevokeAuth(WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam);
+ return (ppro) ? ppro->RevokeAuthorization(wParam, lParam) : 0;
+}
+
+static INT_PTR IcqMenuHandleAddServContact(WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam);
+ return (ppro) ? ppro->AddServerContact(wParam, lParam) : 0;
+}
+
+static INT_PTR IcqMenuHandleXStatusDetails(WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam);
+ return (ppro) ? ppro->ShowXStatusDetails(wParam, lParam) : 0;
+}
+
+static INT_PTR IcqMenuHandleOpenProfile(WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam);
+ return (ppro) ? ppro->OpenWebProfile(wParam, lParam) : 0;
+}
+
+static void sttEnableMenuItem( HANDLE hMenuItem, bool bEnable )
+{
+ CLISTMENUITEM clmi = {0};
+ clmi.cbSize = sizeof(CLISTMENUITEM);
+ clmi.flags = CMIM_FLAGS;
+ if ( !bEnable )
+ clmi.flags |= CMIF_HIDDEN;
+
+ CallService( MS_CLIST_MODIFYMENUITEM, ( WPARAM )hMenuItem, ( LPARAM )&clmi );
+}
+
+static int IcqPrebuildContactMenu( WPARAM wParam, LPARAM lParam )
+{
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REQUEST], FALSE);
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_GRANT], FALSE);
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REVOKE], FALSE);
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_ADD_TO_SERVLIST], FALSE);
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_XSTATUS_DETAILS], FALSE);
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_OPEN_PROFILE], FALSE);
+
+ CIcqProto* ppro = IcqGetInstanceByHContact((HANDLE)wParam);
+ return (ppro) ? ppro->OnPreBuildContactMenu(wParam, lParam) : 0;
+}
+
+void g_MenuInit(void)
+{
+ ///////////////
+ // Contact menu
+
+ hPrebuildMenuHook = HookEvent(ME_CLIST_PREBUILDCONTACTMENU, IcqPrebuildContactMenu);
+
+ // Contact menu initialization
+
+ char str[MAXMODULELABELLENGTH], *pszDest = str + 3;
+ strcpy( str, "ICQ" );
+
+ CLISTMENUITEM mi = { 0 };
+ mi.cbSize = sizeof(CLISTMENUITEM);
+ mi.pszService = str;
+ mi.flags = CMIF_ICONFROMICOLIB;
+
+ // "Request authorization"
+ mi.pszName = LPGEN("Request authorization");
+ mi.position = 1000030000;
+ mi.icolibItem = hStaticIcons[ISI_AUTH_REQUEST]->Handle();
+ strcpy(pszDest, MS_REQ_AUTH);
+ g_hContactMenuItems[ICMI_AUTH_REQUEST] = Menu_AddContactMenuItem(&mi);
+ g_hContactMenuSvc[ICMI_AUTH_REQUEST] = CreateServiceFunction( str, IcqMenuHandleRequestAuth );
+
+ // "Grant authorization"
+ mi.pszName = LPGEN("Grant authorization");
+ mi.position = 1000029999;
+ mi.icolibItem = hStaticIcons[ISI_AUTH_GRANT]->Handle();
+ strcpy(pszDest, MS_GRANT_AUTH);
+ g_hContactMenuItems[ICMI_AUTH_GRANT] = Menu_AddContactMenuItem(&mi);
+ g_hContactMenuSvc[ICMI_AUTH_GRANT] = CreateServiceFunction(mi.pszService, IcqMenuHandleGrantAuth);
+
+ // "Revoke authorization"
+ mi.pszName = LPGEN("Revoke authorization");
+ mi.position = 1000029998;
+ mi.icolibItem = hStaticIcons[ISI_AUTH_REVOKE]->Handle();
+ strcpy(pszDest, MS_REVOKE_AUTH);
+ g_hContactMenuItems[ICMI_AUTH_REVOKE] = Menu_AddContactMenuItem(&mi);
+ g_hContactMenuSvc[ICMI_AUTH_REVOKE] = CreateServiceFunction(mi.pszService, IcqMenuHandleRevokeAuth);
+
+ // "Add to server list"
+ mi.pszName = LPGEN("Add to server list");
+ mi.position = -2049999999;
+ mi.icolibItem = hStaticIcons[ISI_ADD_TO_SERVLIST]->Handle();
+ strcpy(pszDest, MS_ICQ_ADDSERVCONTACT);
+ g_hContactMenuItems[ICMI_ADD_TO_SERVLIST] = Menu_AddContactMenuItem(&mi);
+ g_hContactMenuSvc[ICMI_ADD_TO_SERVLIST] = CreateServiceFunction(mi.pszService, IcqMenuHandleAddServContact);
+
+ // "Show custom status details"
+ mi.pszName = LPGEN("Show custom status details");
+ mi.position = -2000004999;
+ mi.flags = 0;
+ strcpy(pszDest, MS_XSTATUS_SHOWDETAILS);
+ g_hContactMenuItems[ICMI_XSTATUS_DETAILS] = Menu_AddContactMenuItem(&mi);
+ g_hContactMenuSvc[ICMI_XSTATUS_DETAILS] = CreateServiceFunction(mi.pszService, IcqMenuHandleXStatusDetails);
+
+ // "Open ICQ profile"
+ mi.pszName = LPGEN("Open ICQ profile");
+ mi.position = 1000029997;
+ strcpy(pszDest, MS_OPEN_PROFILE);
+ g_hContactMenuItems[ICMI_OPEN_PROFILE] = Menu_AddContactMenuItem(&mi);
+ g_hContactMenuSvc[ICMI_OPEN_PROFILE] = CreateServiceFunction(mi.pszService, IcqMenuHandleOpenProfile);
+}
+
+void g_MenuUninit(void)
+{
+ UnhookEvent(hPrebuildMenuHook);
+
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_AUTH_REQUEST], 0);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_AUTH_GRANT], 0);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_AUTH_REVOKE], 0);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_ADD_TO_SERVLIST], 0);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_XSTATUS_DETAILS], 0);
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_OPEN_PROFILE], 0);
+
+ DestroyServiceFunction(g_hContactMenuSvc[ICMI_AUTH_REQUEST]);
+ DestroyServiceFunction(g_hContactMenuSvc[ICMI_AUTH_GRANT]);
+ DestroyServiceFunction(g_hContactMenuSvc[ICMI_AUTH_REVOKE]);
+ DestroyServiceFunction(g_hContactMenuSvc[ICMI_ADD_TO_SERVLIST]);
+ DestroyServiceFunction(g_hContactMenuSvc[ICMI_XSTATUS_DETAILS]);
+ DestroyServiceFunction(g_hContactMenuSvc[ICMI_OPEN_PROFILE]);
+}
+
+
+INT_PTR CIcqProto::OpenWebProfile(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE)wParam;
+ DWORD dwUin = getContactUin(hContact);
+ char url[256];
+ mir_snprintf(url, sizeof(url), "http://www.icq.com/people/%d",dwUin);
+ return CallService(MS_UTILS_OPENURL, 1, (LPARAM)url);
+}
+
+
+int CIcqProto::OnPreBuildContactMenu(WPARAM wParam, LPARAM)
+{
+ HANDLE hContact = (HANDLE)wParam;
+ if (hContact == NULL)
+ return 0;
+
+ if (icqOnline())
+ {
+ BOOL bCtrlPressed = (GetKeyState(VK_CONTROL)&0x8000 ) != 0;
+
+ DWORD dwUin = getContactUin(hContact);
+
+
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REQUEST],
+ dwUin && (bCtrlPressed || (getSettingByte((HANDLE)wParam, "Auth", 0) && getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0))));
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_GRANT], dwUin && (bCtrlPressed || getSettingByte((HANDLE)wParam, "Grant", 0)));
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_AUTH_REVOKE],
+ dwUin && (bCtrlPressed || (getSettingByte(NULL, "PrivacyItems", 0) && !getSettingByte((HANDLE)wParam, "Grant", 0))));
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_ADD_TO_SERVLIST],
+ m_bSsiEnabled && !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0) &&
+ !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_IGNORE, 0) &&
+ !DBGetContactSettingByte(hContact, "CList", "NotOnList", 0));
+ }
+
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_OPEN_PROFILE],getContactUin(hContact) != 0);
+ BYTE bXStatus = getContactXStatus((HANDLE)wParam);
+
+ sttEnableMenuItem(g_hContactMenuItems[ICMI_XSTATUS_DETAILS], m_bHideXStatusUI ? 0 : bXStatus != 0);
+ if (bXStatus && !m_bHideXStatusUI) {
+ CLISTMENUITEM clmi = {0};
+
+ clmi.cbSize = sizeof(clmi);
+ clmi.flags = CMIM_ICON;
+
+ if (bXStatus > 0 && bXStatus <= XSTATUS_COUNT)
+ clmi.hIcon = getXStatusIcon(bXStatus, LR_SHARED);
+ else
+ clmi.hIcon = LoadSkinnedIcon(SKINICON_OTHER_SMALLDOT);
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)g_hContactMenuItems[ICMI_XSTATUS_DETAILS], (LPARAM)&clmi);
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnPreBuildStatusMenu event
+
+int CIcqProto::OnPreBuildStatusMenu(WPARAM wParam, LPARAM lParam)
+{
+ InitXStatusItems(TRUE);
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/icq_opts.cpp b/protocols/IcqOscarJ/src/icq_opts.cpp
new file mode 100644
index 0000000000..44c1881d14
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_opts.cpp
@@ -0,0 +1,617 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+#include <win2k.h>
+
+extern BOOL bPopUpService;
+
+static const char* szLogLevelDescr[] = {
+ LPGEN("Display all problems"),
+ LPGEN("Display problems causing possible loss of data"),
+ LPGEN("Display explanations for disconnection"),
+ LPGEN("Display problems requiring user intervention"),
+ LPGEN("Do not display any problems (not recommended)")
+};
+
+static BOOL (WINAPI *pfnEnableThemeDialogTexture)(HANDLE, DWORD) = 0;
+
+static void LoadDBCheckState(CIcqProto* ppro, HWND hwndDlg, int idCtrl, const char* szSetting, BYTE bDef)
+{
+ CheckDlgButton(hwndDlg, idCtrl, ppro->getSettingByte(NULL, szSetting, bDef));
+}
+
+static void StoreDBCheckState(CIcqProto* ppro, HWND hwndDlg, int idCtrl, const char* szSetting)
+{
+ ppro->setSettingByte(NULL, szSetting, (BYTE)IsDlgButtonChecked(hwndDlg, idCtrl));
+}
+
+static void OptDlgChanged(HWND hwndDlg)
+{
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// standalone option pages
+
+static INT_PTR CALLBACK DlgProcIcqOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ ppro = (CIcqProto*)lParam;
+ SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam );
+ {
+ DWORD dwUin = ppro->getContactUin(NULL);
+ if (dwUin)
+ SetDlgItemInt(hwndDlg, IDC_ICQNUM, dwUin, FALSE);
+ else // keep it empty when no UIN entered
+ SetDlgItemTextA(hwndDlg, IDC_ICQNUM, "");
+
+ SendDlgItemMessage(hwndDlg, IDC_PASSWORD, EM_LIMITTEXT, PASSWORDMAXLEN - 1, 0);
+
+ char pszPwd[PASSWORDMAXLEN];
+ if (ppro->GetUserStoredPassword(pszPwd, sizeof(pszPwd)))
+ {
+ //bit of a security hole here, since it's easy to extract a password from an edit box
+ SetDlgItemTextA(hwndDlg, IDC_PASSWORD, pszPwd);
+ }
+
+ LoadDBCheckState(ppro, hwndDlg, IDC_SSL, "SecureConnection", DEFAULT_SECURE_CONNECTION);
+ LoadDBCheckState(ppro, hwndDlg, IDC_MD5LOGIN, "SecureLogin", DEFAULT_SECURE_LOGIN);
+
+ char szServer[MAX_PATH];
+ if (!ppro->getSettingStringStatic(NULL, "OscarServer", szServer, MAX_PATH))
+ SetDlgItemTextA(hwndDlg, IDC_ICQSERVER, szServer);
+ else
+ SetDlgItemTextA(hwndDlg, IDC_ICQSERVER, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST);
+
+ SetDlgItemInt(hwndDlg, IDC_ICQPORT, ppro->getSettingWord(NULL, "OscarPort", IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT), FALSE);
+ LoadDBCheckState(ppro, hwndDlg, IDC_KEEPALIVE, "KeepAlive", DEFAULT_KEEPALIVE_ENABLED);
+ SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_SETRANGE, FALSE, MAKELONG(0, 4));
+ SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_SETPOS, TRUE, 4-ppro->getSettingByte(NULL, "ShowLogLevel", LOG_WARNING));
+ {
+ char buf[MAX_PATH];
+ SetDlgItemTextUtf(hwndDlg, IDC_LEVELDESCR, ICQTranslateUtfStatic(szLogLevelDescr[4-SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_GETPOS, 0, 0)], buf, MAX_PATH));
+ }
+ ShowDlgItem(hwndDlg, IDC_RECONNECTREQD, SW_HIDE);
+ LoadDBCheckState(ppro, hwndDlg, IDC_NOERRMULTI, "IgnoreMultiErrorBox", 0);
+ }
+ return TRUE;
+
+ case WM_HSCROLL:
+ {
+ char str[MAX_PATH];
+
+ SetDlgItemTextUtf(hwndDlg, IDC_LEVELDESCR, ICQTranslateUtfStatic(szLogLevelDescr[4-SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL,TBM_GETPOS, 0, 0)], str, MAX_PATH));
+ OptDlgChanged(hwndDlg);
+ }
+ break;
+
+ case WM_COMMAND:
+ {
+ switch (LOWORD(wParam)) {
+ case IDC_LOOKUPLINK:
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)URL_FORGOT_PASSWORD);
+ return TRUE;
+
+ case IDC_NEWUINLINK:
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)URL_REGISTER);
+ return TRUE;
+
+ case IDC_RESETSERVER:
+ SetDlgItemInt(hwndDlg, IDC_ICQPORT, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT, FALSE);
+
+ case IDC_SSL:
+ SetDlgItemTextA(hwndDlg, IDC_ICQSERVER, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST);
+ SetDlgItemInt(hwndDlg, IDC_ICQPORT, IsDlgButtonChecked(hwndDlg, IDC_SSL) ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT, FALSE);
+ OptDlgChanged(hwndDlg);
+ return TRUE;
+ }
+
+ if (ppro->icqOnline() && LOWORD(wParam) != IDC_NOERRMULTI)
+ {
+ char szClass[80];
+ GetClassNameA((HWND)lParam, szClass, sizeof(szClass));
+
+ if (stricmpnull(szClass, "EDIT") || HIWORD(wParam) == EN_CHANGE)
+ ShowDlgItem(hwndDlg, IDC_RECONNECTREQD, SW_SHOW);
+ }
+
+ if ((LOWORD(wParam)==IDC_ICQNUM || LOWORD(wParam)==IDC_PASSWORD || LOWORD(wParam)==IDC_ICQSERVER || LOWORD(wParam)==IDC_ICQPORT) &&
+ (HIWORD(wParam)!=EN_CHANGE || (HWND)lParam!=GetFocus()))
+ {
+ return 0;
+ }
+
+ OptDlgChanged(hwndDlg);
+ break;
+ }
+
+ case WM_NOTIFY:
+ {
+ switch (((LPNMHDR)lParam)->code)
+ {
+
+ case PSN_APPLY:
+ {
+ char str[128];
+
+ ppro->setSettingDword(NULL, UNIQUEIDSETTING, GetDlgItemInt(hwndDlg, IDC_ICQNUM, NULL, FALSE));
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, str, sizeof(ppro->m_szPassword));
+ if (strlennull(str))
+ {
+ strcpy(ppro->m_szPassword, str);
+ ppro->m_bRememberPwd = TRUE;
+ }
+ else
+ ppro->m_bRememberPwd = ppro->getSettingByte(NULL, "RememberPass", 0);
+
+ CallService(MS_DB_CRYPT_ENCODESTRING, sizeof(ppro->m_szPassword), (LPARAM)str);
+ ppro->setSettingString(NULL, "Password", str);
+ GetDlgItemTextA(hwndDlg,IDC_ICQSERVER, str, sizeof(str));
+ ppro->setSettingString(NULL, "OscarServer", str);
+ ppro->setSettingWord(NULL, "OscarPort", (WORD)GetDlgItemInt(hwndDlg, IDC_ICQPORT, NULL, FALSE));
+ StoreDBCheckState(ppro, hwndDlg, IDC_KEEPALIVE, "KeepAlive");
+ StoreDBCheckState(ppro, hwndDlg, IDC_SSL, "SecureConnection");
+ StoreDBCheckState(ppro, hwndDlg, IDC_MD5LOGIN, "SecureLogin");
+ StoreDBCheckState(ppro, hwndDlg, IDC_NOERRMULTI, "IgnoreMultiErrorBox");
+ ppro->setSettingByte(NULL, "ShowLogLevel", (BYTE)(4-SendDlgItemMessage(hwndDlg, IDC_LOGLEVEL, TBM_GETPOS, 0, 0)));
+
+ return TRUE;
+ }
+ }
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static const UINT icqPrivacyControls[] = {
+ IDC_DCALLOW_ANY, IDC_DCALLOW_CLIST, IDC_DCALLOW_AUTH, IDC_ADD_ANY, IDC_ADD_AUTH,
+ IDC_WEBAWARE, IDC_PUBLISHPRIMARY, IDC_STATIC_DC1, IDC_STATIC_DC2, IDC_STATIC_CLIST
+};
+
+static INT_PTR CALLBACK DlgProcIcqPrivacyOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ ppro = (CIcqProto*)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ {
+ int nDcType = ppro->getSettingByte(NULL, "DCType", 0);
+ int nAddAuth = ppro->getSettingByte(NULL, "Auth", 1);
+
+ if (!ppro->icqOnline())
+ {
+ icq_EnableMultipleControls(hwndDlg, icqPrivacyControls, SIZEOF(icqPrivacyControls), FALSE);
+ ShowDlgItem(hwndDlg, IDC_STATIC_NOTONLINE, SW_SHOW);
+ }
+ else
+ ShowDlgItem(hwndDlg, IDC_STATIC_NOTONLINE, SW_HIDE);
+
+ CheckDlgButton(hwndDlg, IDC_DCALLOW_ANY, (nDcType == 0));
+ CheckDlgButton(hwndDlg, IDC_DCALLOW_CLIST, (nDcType == 1));
+ CheckDlgButton(hwndDlg, IDC_DCALLOW_AUTH, (nDcType == 2));
+ CheckDlgButton(hwndDlg, IDC_ADD_ANY, (nAddAuth == 0));
+ CheckDlgButton(hwndDlg, IDC_ADD_AUTH, (nAddAuth == 1));
+ LoadDBCheckState(ppro, hwndDlg, IDC_WEBAWARE, "WebAware", 0);
+ LoadDBCheckState(ppro, hwndDlg, IDC_PUBLISHPRIMARY, "PublishPrimaryEmail", 0);
+ LoadDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_CLIST, "StatusMsgReplyCList", 0);
+ LoadDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_VISIBLE, "StatusMsgReplyVisible", 0);
+ if (!ppro->getSettingByte(NULL, "StatusMsgReplyCList", 0))
+ EnableDlgItem(hwndDlg, IDC_STATUSMSG_VISIBLE, FALSE);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_DCALLOW_ANY:
+ case IDC_DCALLOW_CLIST:
+ case IDC_DCALLOW_AUTH:
+ case IDC_ADD_ANY:
+ case IDC_ADD_AUTH:
+ case IDC_WEBAWARE:
+ case IDC_PUBLISHPRIMARY:
+ case IDC_STATUSMSG_VISIBLE:
+ if ((HWND)lParam != GetFocus()) return 0;
+ break;
+ case IDC_STATUSMSG_CLIST:
+ if (IsDlgButtonChecked(hwndDlg, IDC_STATUSMSG_CLIST))
+ {
+ EnableDlgItem(hwndDlg, IDC_STATUSMSG_VISIBLE, TRUE);
+ LoadDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_VISIBLE, "StatusMsgReplyVisible", 0);
+ }
+ else
+ {
+ EnableDlgItem(hwndDlg, IDC_STATUSMSG_VISIBLE, FALSE);
+ CheckDlgButton(hwndDlg, IDC_STATUSMSG_VISIBLE, FALSE);
+ }
+ break;
+ default:
+ return 0;
+ }
+ OptDlgChanged(hwndDlg);
+ break;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_APPLY:
+ StoreDBCheckState(ppro, hwndDlg, IDC_WEBAWARE, "WebAware");
+ StoreDBCheckState(ppro, hwndDlg, IDC_PUBLISHPRIMARY, "PublishPrimaryEmail");
+ StoreDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_CLIST, "StatusMsgReplyCList");
+ StoreDBCheckState(ppro, hwndDlg, IDC_STATUSMSG_VISIBLE, "StatusMsgReplyVisible");
+ if (IsDlgButtonChecked(hwndDlg, IDC_DCALLOW_AUTH))
+ ppro->setSettingByte(NULL, "DCType", 2);
+ else if (IsDlgButtonChecked(hwndDlg, IDC_DCALLOW_CLIST))
+ ppro->setSettingByte(NULL, "DCType", 1);
+ else
+ ppro->setSettingByte(NULL, "DCType", 0);
+ StoreDBCheckState(ppro, hwndDlg, IDC_ADD_AUTH, "Auth");
+
+ if (ppro->icqOnline())
+ {
+ PBYTE buf=NULL;
+ int buflen=0;
+
+ ppackTLVWord(&buf, &buflen, 0x19A, !ppro->getSettingByte(NULL, "Auth", 1));
+ ppackTLVByte(&buf, &buflen, 0x212, ppro->getSettingByte(NULL, "WebAware", 0));
+ ppackTLVWord(&buf, &buflen, 0x1F9, ppro->getSettingByte(NULL, "PrivacyLevel", 1));
+
+ ppro->icq_changeUserDirectoryInfoServ(buf, (WORD)buflen, DIRECTORYREQUEST_UPDATEPRIVACY);
+
+ SAFE_FREE((void**)&buf);
+
+ // Send a status packet to notify the server about the webaware setting
+ {
+ WORD wStatus = MirandaStatusToIcq(ppro->m_iStatus);
+
+ if (ppro->m_iStatus == ID_STATUS_INVISIBLE)
+ {
+ if (ppro->m_bSsiEnabled)
+ ppro->updateServVisibilityCode(3);
+ ppro->icq_setstatus(wStatus, NULL);
+ }
+ else
+ {
+ ppro->icq_setstatus(wStatus, NULL);
+ if (ppro->m_bSsiEnabled)
+ ppro->updateServVisibilityCode(4);
+ }
+ }
+ }
+ return TRUE;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static HWND hCpCombo;
+
+struct CPTABLE {
+ WORD cpId;
+ char *cpName;
+};
+
+struct CPTABLE cpTable[] = {
+ { 874, LPGEN("Thai") },
+ { 932, LPGEN("Japanese") },
+ { 936, LPGEN("Simplified Chinese") },
+ { 949, LPGEN("Korean") },
+ { 950, LPGEN("Traditional Chinese") },
+ { 1250, LPGEN("Central European") },
+ { 1251, LPGEN("Cyrillic") },
+ { 1252, LPGEN("Latin I") },
+ { 1253, LPGEN("Greek") },
+ { 1254, LPGEN("Turkish") },
+ { 1255, LPGEN("Hebrew") },
+ { 1256, LPGEN("Arabic") },
+ { 1257, LPGEN("Baltic") },
+ { 1258, LPGEN("Vietnamese") },
+ { 1361, LPGEN("Korean (Johab)") },
+ { -1, NULL}
+};
+
+static BOOL CALLBACK FillCpCombo(LPSTR str)
+{
+ int i;
+ UINT cp;
+
+ cp = atoi(str);
+ for (i=0; cpTable[i].cpName != NULL && cpTable[i].cpId!=cp; i++);
+ if (cpTable[i].cpName)
+ ComboBoxAddStringUtf(hCpCombo, cpTable[i].cpName, cpTable[i].cpId);
+
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static const UINT icqUnicodeControls[] = {IDC_UTFALL,IDC_UTFSTATIC,IDC_UTFCODEPAGE};
+static const UINT icqDCMsgControls[] = {IDC_DCPASSIVE};
+static const UINT icqXStatusControls[] = {IDC_XSTATUSAUTO};
+static const UINT icqCustomStatusControls[] = {IDC_XSTATUSRESET};
+static const UINT icqAimControls[] = {IDC_AIMENABLE};
+
+static INT_PTR CALLBACK DlgProcIcqFeaturesOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ ppro = (CIcqProto*)lParam;
+ SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam );
+ {
+ BYTE byData = ppro->getSettingByte(NULL, "UtfEnabled", DEFAULT_UTF_ENABLED);
+ CheckDlgButton(hwndDlg, IDC_UTFENABLE, byData?TRUE:FALSE);
+ CheckDlgButton(hwndDlg, IDC_UTFALL, byData==2?TRUE:FALSE);
+ icq_EnableMultipleControls(hwndDlg, icqUnicodeControls, SIZEOF(icqUnicodeControls), byData?TRUE:FALSE);
+ LoadDBCheckState(ppro, hwndDlg, IDC_TEMPVISIBLE, "TempVisListEnabled",DEFAULT_TEMPVIS_ENABLED);
+ LoadDBCheckState(ppro, hwndDlg, IDC_SLOWSEND, "SlowSend", DEFAULT_SLOWSEND);
+ LoadDBCheckState(ppro, hwndDlg, IDC_ONLYSERVERACKS, "OnlyServerAcks", DEFAULT_ONLYSERVERACKS);
+ byData = ppro->getSettingByte(NULL, "DirectMessaging", DEFAULT_DCMSG_ENABLED);
+ CheckDlgButton(hwndDlg, IDC_DCENABLE, byData?TRUE:FALSE);
+ CheckDlgButton(hwndDlg, IDC_DCPASSIVE, byData==1?TRUE:FALSE);
+ icq_EnableMultipleControls(hwndDlg, icqDCMsgControls, SIZEOF(icqDCMsgControls), byData?TRUE:FALSE);
+ BYTE byXStatusEnabled = ppro->getSettingByte(NULL, "XStatusEnabled", DEFAULT_XSTATUS_ENABLED);
+ CheckDlgButton(hwndDlg, IDC_XSTATUSENABLE, byXStatusEnabled);
+ BYTE byMoodsEnabled = ppro->getSettingByte(NULL, "MoodsEnabled", DEFAULT_MOODS_ENABLED);
+ CheckDlgButton(hwndDlg, IDC_MOODSENABLE, byMoodsEnabled);
+ icq_EnableMultipleControls(hwndDlg, icqXStatusControls, SIZEOF(icqXStatusControls), byXStatusEnabled);
+ icq_EnableMultipleControls(hwndDlg, icqCustomStatusControls, SIZEOF(icqCustomStatusControls), byXStatusEnabled || byMoodsEnabled);
+ LoadDBCheckState(ppro, hwndDlg, IDC_XSTATUSAUTO, "XStatusAuto", DEFAULT_XSTATUS_AUTO);
+ LoadDBCheckState(ppro, hwndDlg, IDC_XSTATUSRESET, "XStatusReset", DEFAULT_XSTATUS_RESET);
+ LoadDBCheckState(ppro, hwndDlg, IDC_KILLSPAMBOTS, "KillSpambots", DEFAULT_KILLSPAM_ENABLED);
+ LoadDBCheckState(ppro, hwndDlg, IDC_AIMENABLE, "AimEnabled", DEFAULT_AIM_ENABLED);
+ icq_EnableMultipleControls(hwndDlg, icqAimControls, SIZEOF(icqAimControls), ppro->icqOnline()?FALSE:TRUE);
+
+ hCpCombo = GetDlgItem(hwndDlg, IDC_UTFCODEPAGE);
+ int sCodePage = ppro->getSettingWord(NULL, "AnsiCodePage", CP_ACP);
+ ComboBoxAddStringUtf(GetDlgItem(hwndDlg, IDC_UTFCODEPAGE), LPGEN("System default codepage"), 0);
+ EnumSystemCodePagesA(FillCpCombo, CP_INSTALLED);
+ if(sCodePage == 0)
+ SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_SETCURSEL, (WPARAM)0, 0);
+ else
+ {
+ for (int i = 0; i < SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETCOUNT, 0, 0); i++)
+ {
+ if (SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETITEMDATA, (WPARAM)i, 0) == sCodePage)
+ {
+ SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_SETCURSEL, (WPARAM)i, 0);
+ break;
+ }
+ }
+ }
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_UTFENABLE:
+ icq_EnableMultipleControls(hwndDlg, icqUnicodeControls, SIZEOF(icqUnicodeControls), IsDlgButtonChecked(hwndDlg, IDC_UTFENABLE));
+ OptDlgChanged(hwndDlg);
+ break;
+ case IDC_UTFCODEPAGE:
+ if(HIWORD(wParam)==CBN_SELCHANGE)
+ OptDlgChanged(hwndDlg);
+ break;
+ case IDC_DCENABLE:
+ icq_EnableMultipleControls(hwndDlg, icqDCMsgControls, SIZEOF(icqDCMsgControls), IsDlgButtonChecked(hwndDlg, IDC_DCENABLE));
+ OptDlgChanged(hwndDlg);
+ break;
+ case IDC_XSTATUSENABLE:
+ icq_EnableMultipleControls(hwndDlg, icqXStatusControls, SIZEOF(icqXStatusControls), IsDlgButtonChecked(hwndDlg, IDC_XSTATUSENABLE));
+ case IDC_MOODSENABLE:
+ icq_EnableMultipleControls(hwndDlg, icqCustomStatusControls, SIZEOF(icqCustomStatusControls), IsDlgButtonChecked(hwndDlg, IDC_XSTATUSENABLE) || IsDlgButtonChecked(hwndDlg, IDC_MOODSENABLE));
+ default:
+ OptDlgChanged(hwndDlg);
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_APPLY:
+ if (IsDlgButtonChecked(hwndDlg, IDC_UTFENABLE))
+ ppro->m_bUtfEnabled = IsDlgButtonChecked(hwndDlg, IDC_UTFALL)?2:1;
+ else
+ ppro->m_bUtfEnabled = 0;
+ {
+ int i = SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETCURSEL, 0, 0);
+ ppro->m_wAnsiCodepage = (WORD)SendDlgItemMessage(hwndDlg, IDC_UTFCODEPAGE, CB_GETITEMDATA, (WPARAM)i, 0);
+ ppro->setSettingWord(NULL, "AnsiCodePage", ppro->m_wAnsiCodepage);
+ }
+ ppro->setSettingByte(NULL, "UtfEnabled", ppro->m_bUtfEnabled);
+ ppro->m_bTempVisListEnabled = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_TEMPVISIBLE);
+ ppro->setSettingByte(NULL, "TempVisListEnabled", ppro->m_bTempVisListEnabled);
+ StoreDBCheckState(ppro, hwndDlg, IDC_SLOWSEND, "SlowSend");
+ StoreDBCheckState(ppro, hwndDlg, IDC_ONLYSERVERACKS, "OnlyServerAcks");
+ if (IsDlgButtonChecked(hwndDlg, IDC_DCENABLE))
+ ppro->m_bDCMsgEnabled = IsDlgButtonChecked(hwndDlg, IDC_DCPASSIVE)?1:2;
+ else
+ ppro->m_bDCMsgEnabled = 0;
+ ppro->setSettingByte(NULL, "DirectMessaging", ppro->m_bDCMsgEnabled);
+ ppro->m_bXStatusEnabled = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_XSTATUSENABLE);
+ ppro->setSettingByte(NULL, "XStatusEnabled", ppro->m_bXStatusEnabled);
+ ppro->m_bMoodsEnabled = (BYTE)IsDlgButtonChecked(hwndDlg, IDC_MOODSENABLE);
+ ppro->setSettingByte(NULL, "MoodsEnabled", ppro->m_bMoodsEnabled);
+ StoreDBCheckState(ppro, hwndDlg, IDC_XSTATUSAUTO, "XStatusAuto");
+ StoreDBCheckState(ppro, hwndDlg, IDC_XSTATUSRESET, "XStatusReset");
+ StoreDBCheckState(ppro, hwndDlg, IDC_KILLSPAMBOTS , "KillSpambots");
+ StoreDBCheckState(ppro, hwndDlg, IDC_AIMENABLE, "AimEnabled");
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+static const UINT icqContactsControls[] = {IDC_ADDSERVER,IDC_LOADFROMSERVER,IDC_SAVETOSERVER,IDC_UPLOADNOW};
+static const UINT icqAvatarControls[] = {IDC_AUTOLOADAVATARS,IDC_STRICTAVATARCHECK};
+
+static INT_PTR CALLBACK DlgProcIcqContactsOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ ppro = (CIcqProto*)lParam;
+ SetWindowLongPtr( hwndDlg, GWLP_USERDATA, lParam );
+
+ LoadDBCheckState(ppro, hwndDlg, IDC_ENABLE, "UseServerCList", DEFAULT_SS_ENABLED);
+ LoadDBCheckState(ppro, hwndDlg, IDC_ADDSERVER, "ServerAddRemove", DEFAULT_SS_ADDSERVER);
+ LoadDBCheckState(ppro, hwndDlg, IDC_LOADFROMSERVER, "LoadServerDetails", DEFAULT_SS_LOAD);
+ LoadDBCheckState(ppro, hwndDlg, IDC_SAVETOSERVER, "StoreServerDetails", DEFAULT_SS_STORE);
+ LoadDBCheckState(ppro, hwndDlg, IDC_ENABLEAVATARS, "AvatarsEnabled", DEFAULT_AVATARS_ENABLED);
+ LoadDBCheckState(ppro, hwndDlg, IDC_AUTOLOADAVATARS, "AvatarsAutoLoad", DEFAULT_LOAD_AVATARS);
+ LoadDBCheckState(ppro, hwndDlg, IDC_STRICTAVATARCHECK, "StrictAvatarCheck", DEFAULT_AVATARS_CHECK);
+
+ icq_EnableMultipleControls(hwndDlg, icqContactsControls, SIZEOF(icqContactsControls),
+ ppro->getSettingByte(NULL, "UseServerCList", DEFAULT_SS_ENABLED)?TRUE:FALSE);
+ icq_EnableMultipleControls(hwndDlg, icqAvatarControls, SIZEOF(icqAvatarControls),
+ ppro->getSettingByte(NULL, "AvatarsEnabled", DEFAULT_AVATARS_ENABLED)?TRUE:FALSE);
+
+ if (ppro->icqOnline())
+ {
+ ShowDlgItem(hwndDlg, IDC_OFFLINETOENABLE, SW_SHOW);
+ EnableDlgItem(hwndDlg, IDC_ENABLE, FALSE);
+ EnableDlgItem(hwndDlg, IDC_ENABLEAVATARS, FALSE);
+ }
+ else
+ EnableDlgItem(hwndDlg, IDC_UPLOADNOW, FALSE);
+
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_UPLOADNOW:
+ ppro->ShowUploadContactsDialog();
+ return TRUE;
+ case IDC_ENABLE:
+ icq_EnableMultipleControls(hwndDlg, icqContactsControls, SIZEOF(icqContactsControls), IsDlgButtonChecked(hwndDlg, IDC_ENABLE));
+ if (ppro->icqOnline())
+ ShowDlgItem(hwndDlg, IDC_RECONNECTREQD, SW_SHOW);
+ else
+ EnableDlgItem(hwndDlg, IDC_UPLOADNOW, FALSE);
+ break;
+ case IDC_ENABLEAVATARS:
+ icq_EnableMultipleControls(hwndDlg, icqAvatarControls, SIZEOF(icqAvatarControls), IsDlgButtonChecked(hwndDlg, IDC_ENABLEAVATARS));
+ break;
+ }
+ OptDlgChanged(hwndDlg);
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->code == PSN_APPLY )
+ {
+ StoreDBCheckState(ppro, hwndDlg, IDC_ENABLE, "UseServerCList");
+ StoreDBCheckState(ppro, hwndDlg, IDC_ADDSERVER, "ServerAddRemove");
+ StoreDBCheckState(ppro, hwndDlg, IDC_LOADFROMSERVER, "LoadServerDetails");
+ StoreDBCheckState(ppro, hwndDlg, IDC_SAVETOSERVER, "StoreServerDetails");
+ StoreDBCheckState(ppro, hwndDlg, IDC_ENABLEAVATARS, "AvatarsEnabled");
+ StoreDBCheckState(ppro, hwndDlg, IDC_AUTOLOADAVATARS, "AvatarsAutoLoad");
+ StoreDBCheckState(ppro, hwndDlg, IDC_STRICTAVATARCHECK, "StrictAvatarCheck");
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR CALLBACK DlgProcIcqPopupOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+
+int CIcqProto::OnOptionsInit(WPARAM wParam, LPARAM lParam)
+{
+ if (IsWinVerXPPlus()) {
+ HMODULE hUxTheme = GetModuleHandleA("uxtheme.dll");
+ if (hUxTheme)
+ pfnEnableThemeDialogTexture = (BOOL (WINAPI *)(HANDLE, DWORD))GetProcAddress(hUxTheme, "EnableThemeDialogTexture");
+ }
+
+ OPTIONSDIALOGPAGE odp = {0};
+ odp.cbSize = sizeof(odp);
+ odp.position = -800000000;
+ odp.hInstance = hInst;
+ odp.ptszGroup = LPGENT("Network");
+ odp.dwInitParam = LPARAM(this);
+ odp.ptszTitle = m_tszUserName;
+ odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR | ODPF_DONTTRANSLATE;
+
+ odp.ptszTab = LPGENT("Account");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQ);
+ odp.pfnDlgProc = DlgProcIcqOpts;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Contacts");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQCONTACTS);
+ odp.pfnDlgProc = DlgProcIcqContactsOpts;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Features");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQFEATURES);
+ odp.pfnDlgProc = DlgProcIcqFeaturesOpts;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Privacy");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_ICQPRIVACY);
+ odp.pfnDlgProc = DlgProcIcqPrivacyOpts;
+ Options_AddPage(wParam, &odp);
+
+ if (bPopUpService) {
+ odp.position = 100000000;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_POPUPS);
+ odp.groupPosition = 900000000;
+ odp.pfnDlgProc = DlgProcIcqPopupOpts;
+ odp.ptszGroup = LPGENT("Popups");
+ odp.ptszTab = NULL;
+ Options_AddPage(wParam, &odp);
+ }
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/icq_packet.cpp b/protocols/IcqOscarJ/src/icq_packet.cpp
new file mode 100644
index 0000000000..79241cf297
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_packet.cpp
@@ -0,0 +1,898 @@
+// ---------------------------------------------------------------------------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, Bio
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+WORD generate_flap_sequence()
+{
+ DWORD n = rand(), s = 0;
+
+ for (DWORD i = n; i >>= 3; s += i);
+
+ return (((0 - s) ^ (BYTE)n) & 7 ^ n) + 2;
+}
+
+void __fastcall init_generic_packet(icq_packet *pPacket, WORD wHeaderLen)
+{
+ pPacket->wPlace = 0;
+ pPacket->wLen += wHeaderLen;
+ pPacket->pData = (BYTE*)SAFE_MALLOC(pPacket->wLen);
+}
+
+void write_httphdr(icq_packet *pPacket, WORD wType, DWORD dwSeq)
+{
+ init_generic_packet(pPacket, 14);
+
+ packWord(pPacket, (WORD)(pPacket->wLen - 2));
+ packWord(pPacket, HTTP_PROXY_VERSION);
+ packWord(pPacket, wType);
+ packDWord(pPacket, 0); // Flags?
+ packDWord(pPacket, dwSeq); // Connection sequence ?
+}
+
+void __fastcall write_flap(icq_packet *pPacket, BYTE byFlapChannel)
+{
+ init_generic_packet(pPacket, 6);
+
+ pPacket->nChannel = byFlapChannel;
+
+ packByte(pPacket, FLAP_MARKER);
+ packByte(pPacket, byFlapChannel);
+ packWord(pPacket, 0); // This is the sequence ID, it is filled in during the actual sending
+ packWord(pPacket, (WORD)(pPacket->wLen - 6)); // This counter should not include the flap header (thus the -6)
+}
+
+void __fastcall serverPacketInit(icq_packet *pPacket, WORD wSize)
+{
+ pPacket->wLen = wSize;
+ write_flap(pPacket, ICQ_DATA_CHAN);
+}
+
+void __fastcall directPacketInit(icq_packet *pPacket, DWORD dwSize)
+{
+ pPacket->wPlace = 0;
+ pPacket->wLen = (WORD)dwSize;
+ pPacket->pData = (BYTE *)SAFE_MALLOC(dwSize + 2);
+
+ packLEWord(pPacket, pPacket->wLen);
+}
+
+void __fastcall serverCookieInit(icq_packet *pPacket, BYTE *pCookie, WORD wCookieSize)
+{
+ pPacket->wLen = (WORD)(wCookieSize + 8 + sizeof(CLIENT_ID_STRING) + 66);
+
+ write_flap(pPacket, ICQ_LOGIN_CHAN);
+ packDWord(pPacket, 0x00000001);
+ packTLV(pPacket, 0x06, wCookieSize, pCookie);
+
+ // Pack client identification details.
+ packTLV(pPacket, 0x0003, (WORD)sizeof(CLIENT_ID_STRING)-1, (LPBYTE)CLIENT_ID_STRING);
+ packTLVWord(pPacket, 0x0017, CLIENT_VERSION_MAJOR);
+ packTLVWord(pPacket, 0x0018, CLIENT_VERSION_MINOR);
+ packTLVWord(pPacket, 0x0019, CLIENT_VERSION_LESSER);
+ packTLVWord(pPacket, 0x001a, CLIENT_VERSION_BUILD);
+ packTLVWord(pPacket, 0x0016, CLIENT_ID_CODE);
+ packTLVDWord(pPacket, 0x0014, CLIENT_DISTRIBUTION);
+ packTLV(pPacket, 0x000f, 0x0002, (LPBYTE)CLIENT_LANGUAGE);
+ packTLV(pPacket, 0x000e, 0x0002, (LPBYTE)CLIENT_COUNTRY);
+ packDWord(pPacket, 0x00940001); // reconnect flag
+ packByte(pPacket, 0);
+ packTLVDWord(pPacket, 0x8003, 0x00100000); // Unknown
+}
+
+void __fastcall packByte(icq_packet *pPacket, BYTE byValue)
+{
+ pPacket->pData[pPacket->wPlace++] = byValue;
+}
+
+void __fastcall packWord(icq_packet *pPacket, WORD wValue)
+{
+ pPacket->pData[pPacket->wPlace++] = ((wValue & 0xff00) >> 8);
+ pPacket->pData[pPacket->wPlace++] = (wValue & 0x00ff);
+}
+
+void __fastcall packDWord(icq_packet *pPacket, DWORD dwValue)
+{
+ pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0xff000000) >> 24);
+ pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x00ff0000) >> 16);
+ pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x0000ff00) >> 8);
+ pPacket->pData[pPacket->wPlace++] = (BYTE) (dwValue & 0x000000ff);
+}
+
+void __fastcall packQWord(icq_packet *pPacket, DWORD64 qwValue)
+{
+ packDWord(pPacket, (DWORD)(qwValue >> 32));
+ packDWord(pPacket, (DWORD)(qwValue & 0xffffffff));
+}
+
+void packTLV(icq_packet *pPacket, WORD wType, WORD wLength, const BYTE *pbyValue)
+{
+ packWord(pPacket, wType);
+ packWord(pPacket, wLength);
+ packBuffer(pPacket, pbyValue, wLength);
+}
+
+void packTLVWord(icq_packet *pPacket, WORD wType, WORD wValue)
+{
+ packWord(pPacket, wType);
+ packWord(pPacket, 0x02);
+ packWord(pPacket, wValue);
+}
+
+void packTLVDWord(icq_packet *pPacket, WORD wType, DWORD dwValue)
+{
+ packWord(pPacket, wType);
+ packWord(pPacket, 0x04);
+ packDWord(pPacket, dwValue);
+}
+
+
+void packTLVUID(icq_packet *pPacket, WORD wType, DWORD dwUin, const char *szUid)
+{
+ if (dwUin)
+ {
+ char szUin[UINMAXLEN];
+
+ _ltoa(dwUin, szUin, 10);
+
+ packTLV(pPacket, wType, getUINLen(dwUin), (BYTE*)szUin);
+ }
+ else if (szUid)
+ packTLV(pPacket, wType, strlennull(szUid), (BYTE*)szUid);
+}
+
+
+// Pack a preformatted buffer.
+// This can be used to pack strings or any type of raw data.
+void packBuffer(icq_packet *pPacket, const BYTE* pbyBuffer, WORD wLength)
+{
+ while (wLength)
+ {
+ pPacket->pData[pPacket->wPlace++] = *pbyBuffer++;
+ wLength--;
+ }
+}
+
+// Pack a buffer and prepend it with the size as a LE WORD.
+// Commented out since its not actually used anywhere right now.
+//void packLEWordSizedBuffer(icq_packet* pPacket, const BYTE* pbyBuffer, WORD wLength)
+//{
+//
+// packLEWord(pPacket, wLength);
+// packBuffer(pPacket, pbyBuffer, wLength);
+//
+//}
+
+int __fastcall getUINLen(DWORD dwUin)
+{
+ BYTE dwUinLen = 0;
+
+ while(dwUin) {
+ dwUin /= 10;
+ dwUinLen += 1;
+ }
+ return dwUinLen;
+}
+
+int __fastcall getUIDLen(DWORD dwUin, const char *szUid)
+{
+ if (dwUin)
+ return getUINLen(dwUin);
+ else
+ return strlennull(szUid);
+}
+
+void __fastcall packUIN(icq_packet *pPacket, DWORD dwUin)
+{
+ char pszUin[UINMAXLEN];
+ BYTE nUinLen = getUINLen(dwUin);
+
+ _ltoa(dwUin, pszUin, 10);
+
+ packByte(pPacket, nUinLen); // Length of user id
+ packBuffer(pPacket, (LPBYTE)pszUin, nUinLen); // Receiving user's id
+}
+
+void __fastcall packUID(icq_packet *pPacket, DWORD dwUin, const char *szUid)
+{
+ if (dwUin)
+ packUIN(pPacket, dwUin);
+ else
+ {
+ BYTE nLen = strlennull(szUid);
+ packByte(pPacket, nLen);
+ packBuffer(pPacket, (LPBYTE)szUid, nLen);
+ }
+}
+
+
+void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype)
+{
+ packFNACHeader(pPacket, wFamily, wSubtype, 0, wSubtype << 0x10);
+}
+
+
+void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence)
+{
+ WORD wSequence = (WORD)dwSequence & 0x7FFF; // this is necessary, if that bit is there we get disconnected
+
+ packWord(pPacket, wFamily); // Family type
+ packWord(pPacket, wSubtype); // Family subtype
+ packWord(pPacket, wFlags); // SNAC flags
+ packWord(pPacket, wSequence); // SNAC request id (sequence)
+ packWord(pPacket, (WORD)(dwSequence >> 0x10)); // SNAC request id (command)
+}
+
+
+void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence, WORD wVersion)
+{
+ packFNACHeader(pPacket, wFamily, wSubtype, wFlags | 0x8000, dwSequence);
+ packWord(pPacket, 0x06);
+ packTLVWord(pPacket, 0x01, wVersion);
+}
+
+
+void __fastcall packLEWord(icq_packet *pPacket, WORD wValue)
+{
+ pPacket->pData[pPacket->wPlace++] = (wValue & 0x00ff);
+ pPacket->pData[pPacket->wPlace++] = ((wValue & 0xff00) >> 8);
+}
+
+void __fastcall packLEDWord(icq_packet *pPacket, DWORD dwValue)
+{
+ pPacket->pData[pPacket->wPlace++] = (BYTE) (dwValue & 0x000000ff);
+ pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x0000ff00) >> 8);
+ pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0x00ff0000) >> 16);
+ pPacket->pData[pPacket->wPlace++] = (BYTE)((dwValue & 0xff000000) >> 24);
+}
+
+
+/* helper function to place numerics to buffer */
+static void packWord(PBYTE buf, WORD wValue)
+{
+ *(buf) = ((wValue & 0xff00) >> 8);
+ *(buf + 1) = (wValue & 0x00ff);
+}
+
+
+static void packDWord(PBYTE buf, DWORD dwValue)
+{
+ *(buf) = (BYTE)((dwValue & 0xff000000) >> 24);
+ *(buf + 1) = (BYTE)((dwValue & 0x00ff0000) >> 16);
+ *(buf + 2) = (BYTE)((dwValue & 0x0000ff00) >> 8);
+ *(buf + 3) = (BYTE) (dwValue & 0x000000ff);
+}
+
+
+static void packQWord(PBYTE buf, DWORD64 qwValue)
+{
+ packDWord(buf, (DWORD)(qwValue >> 32));
+ packDWord(buf + 4, (DWORD)(qwValue & 0xffffffff));
+}
+
+
+void ppackByte(PBYTE *buf, int *buflen, BYTE byValue)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 1 + *buflen);
+ *(*buf + *buflen) = byValue;
+ ++*buflen;
+}
+
+
+void ppackWord(PBYTE *buf, int *buflen, WORD wValue)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 2 + *buflen);
+ packWord(*buf + *buflen, wValue);
+ *buflen += 2;
+}
+
+
+void ppackLEWord(PBYTE *buf, int *buflen, WORD wValue)
+{
+ *buf=(PBYTE)SAFE_REALLOC(*buf, 2 + *buflen);
+ *(PWORD)(*buf + *buflen) = wValue;
+ *buflen+=2;
+}
+
+
+void ppackLEDWord(PBYTE *buf, int *buflen, DWORD dwValue)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 4 + *buflen);
+ *(PDWORD)(*buf + *buflen) = dwValue;
+ *buflen += 4;
+}
+
+
+void ppackLELNTS(PBYTE *buf, int *buflen, const char *str)
+{
+ WORD len = strlennull(str);
+ ppackLEWord(buf, buflen, len);
+ *buf = (PBYTE)SAFE_REALLOC(*buf, *buflen + len);
+ memcpy(*buf + *buflen, str, len);
+ *buflen += len;
+}
+
+
+void ppackBuffer(PBYTE *buf, int *buflen, WORD wLength, const BYTE *pbyValue)
+{
+ if (wLength)
+ {
+ *buf = (PBYTE)SAFE_REALLOC(*buf, wLength + *buflen);
+ memcpy(*buf + *buflen, pbyValue, wLength);
+ *buflen += wLength;
+ }
+}
+
+
+void ppackTLV(PBYTE *buf, int *buflen, WORD wType, WORD wLength, const BYTE *pbyValue)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 4 + wLength + *buflen);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, wLength);
+ if (wLength)
+ memcpy(*buf + *buflen + 4, pbyValue, wLength);
+ *buflen += 4 + wLength;
+}
+
+
+void ppackTLVByte(PBYTE *buf, int *buflen, WORD wType, BYTE byValue)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 5 + *buflen);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, 1);
+ *(*buf + *buflen + 4) = byValue;
+ *buflen += 5;
+}
+
+
+void ppackTLVWord(PBYTE *buf, int *buflen, WORD wType, WORD wValue)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, 2);
+ packWord(*buf + *buflen + 4, wValue);
+ *buflen += 6;
+}
+
+
+void ppackTLVDWord(PBYTE *buf, int *buflen, WORD wType, DWORD dwValue)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, 4);
+ packDWord(*buf + *buflen + 4, dwValue);
+ *buflen += 8;
+}
+
+
+void ppackTLVDouble(PBYTE *buf, int *buflen, WORD wType, double dValue)
+{
+ DWORD64 qwValue;
+
+ memcpy(&qwValue, &dValue, 8);
+
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 12 + *buflen);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, 8);
+ packQWord(*buf + *buflen + 4, qwValue);
+ *buflen += 12;
+}
+
+
+void ppackTLVUID(PBYTE *buf, int *buflen, WORD wType, DWORD dwUin, const char *szUid)
+{
+ if (dwUin)
+ {
+ char szUin[UINMAXLEN];
+
+ _ltoa(dwUin, szUin, 10);
+
+ ppackTLV(buf, buflen, wType, getUINLen(dwUin), (BYTE*)szUin);
+ }
+ else if (szUid)
+ ppackTLV(buf, buflen, wType, strlennull(szUid), (BYTE*)szUid);
+}
+
+
+// *** TLV based (!!! WORDs and DWORDs are LE !!!)
+void ppackLETLVByte(PBYTE *buf, int *buflen, BYTE byValue, WORD wType, BYTE always)
+{
+ if (!always && !byValue) return;
+
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 5 + *buflen);
+ *(PWORD)(*buf + *buflen) = wType;
+ *(PWORD)(*buf + *buflen + 2) = 1;
+ *(*buf + *buflen + 4) = byValue;
+ *buflen += 5;
+}
+
+
+void ppackLETLVWord(PBYTE *buf, int *buflen, WORD wValue, WORD wType, BYTE always)
+{
+ if (!always && !wValue) return;
+
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen);
+ *(PWORD)(*buf + *buflen) = wType;
+ *(PWORD)(*buf + *buflen + 2) = 2;
+ *(PWORD)(*buf + *buflen + 4) = wValue;
+ *buflen += 6;
+}
+
+
+void ppackLETLVDWord(PBYTE *buf, int *buflen, DWORD dwValue, WORD wType, BYTE always)
+{
+ if (!always && !dwValue) return;
+
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen);
+ *(PWORD)(*buf + *buflen) = wType;
+ *(PWORD)(*buf + *buflen + 2) = 4;
+ *(PDWORD)(*buf + *buflen + 4) = dwValue;
+ *buflen += 8;
+}
+
+
+void packLETLVLNTS(PBYTE *buf, int *bufpos, const char *str, WORD wType)
+{
+ int len = strlennull(str) + 1;
+
+ *(PWORD)(*buf + *bufpos) = wType;
+ *(PWORD)(*buf + *bufpos + 2) = len + 2;
+ *(PWORD)(*buf + *bufpos + 4) = len;
+ memcpy(*buf + *bufpos + 6, str, len);
+ *bufpos += len + 6;
+}
+
+
+void ppackLETLVLNTS(PBYTE *buf, int *buflen, const char *str, WORD wType, BYTE always)
+{
+ int len = strlennull(str) + 1;
+
+ if (!always && len < 2) return;
+
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen + len);
+ packLETLVLNTS(buf, buflen, str, wType);
+}
+
+
+void ppackLETLVWordLNTS(PBYTE *buf, int *buflen, WORD w, const char *str, WORD wType, BYTE always)
+{
+ int len = strlennull(str) + 1;
+
+ if (!always && len < 2 && !w) return;
+
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen + len);
+ *(PWORD)(*buf + *buflen) = wType;
+ *(PWORD)(*buf + *buflen + 2) = len + 4;
+ *(PWORD)(*buf + *buflen + 4) = w;
+ *(PWORD)(*buf + *buflen + 6) = len;
+ memcpy(*buf + *buflen + 8, str, len);
+ *buflen += len + 8;
+}
+
+
+void ppackLETLVLNTSByte(PBYTE *buf, int *buflen, const char *str, BYTE b, WORD wType)
+{
+ int len = strlennull(str) + 1;
+
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 7 + *buflen + len);
+ *(PWORD)(*buf + *buflen) = wType;
+ *(PWORD)(*buf + *buflen + 2) = len + 3;
+ *(PWORD)(*buf + *buflen + 4) = len;
+ memcpy(*buf + *buflen + 6, str, len);
+ *(*buf + *buflen + 6 + len) = b;
+ *buflen += len + 7;
+}
+
+
+void CIcqProto::ppackLETLVLNTSfromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType)
+{
+ char szTmp[1024];
+ char *str = "";
+
+ if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp)))
+ str = szTmp;
+
+ ppackLETLVLNTS(buf, buflen, str, wType, 1);
+}
+
+void CIcqProto::ppackLETLVWordLNTSfromDB(PBYTE *buf, int *buflen, WORD w, const char *szSetting, WORD wType)
+{
+ char szTmp[1024];
+ char *str = "";
+
+ if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp)))
+ str = szTmp;
+
+ ppackLETLVWordLNTS(buf, buflen, w, str, wType, 1);
+}
+
+void CIcqProto::ppackLETLVLNTSBytefromDB(PBYTE *buf, int *buflen, const char *szSetting, BYTE b, WORD wType)
+{
+ char szTmp[1024];
+ char *str = "";
+
+ if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp)))
+ str = szTmp;
+
+ ppackLETLVLNTSByte(buf, buflen, str, b, wType);
+}
+
+
+void CIcqProto::ppackTLVStringFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType)
+{
+ char szTmp[1024];
+ char *str = "";
+
+ if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp)))
+ str = szTmp;
+
+ ppackTLV(buf, buflen, wType, strlennull(str), (PBYTE)str);
+}
+
+
+void CIcqProto::ppackTLVStringUtfFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType)
+{
+ char *str = getSettingStringUtf(NULL, szSetting, NULL);
+
+ ppackTLV(buf, buflen, wType, strlennull(str), (PBYTE)str);
+
+ SAFE_FREE((void**)&str);
+}
+
+
+void CIcqProto::ppackTLVDateFromDB(PBYTE *buf, int *buflen, const char *szSettingYear, const char *szSettingMonth, const char *szSettingDay, WORD wType)
+{
+ SYSTEMTIME sTime = {0};
+ double time = 0;
+
+ sTime.wYear = getSettingWord(NULL, szSettingYear, 0);
+ sTime.wMonth = getSettingByte(NULL, szSettingMonth, 0);
+ sTime.wDay = getSettingByte(NULL, szSettingDay, 0);
+ if (sTime.wYear || sTime.wMonth || sTime.wDay)
+ {
+ SystemTimeToVariantTime(&sTime, &time);
+ time -= 2;
+ }
+
+ ppackTLVDouble(buf, buflen, wType, time);
+}
+
+
+int CIcqProto::ppackTLVWordStringItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID)
+{
+ char szTmp[1024];
+ char *str = NULL;
+
+ if (!getSettingStringStatic(NULL, szSetting, szTmp, sizeof(szTmp)))
+ str = szTmp;
+
+ if (str)
+ {
+ WORD wLen = strlennull(str);
+
+ ppackWord(buf, buflen, wLen + 0x0A);
+ ppackTLVWord(buf, buflen, wTypeID, wID);
+ ppackTLV(buf, buflen, wTypeData, wLen, (PBYTE)str);
+
+ return 1; // Success
+ }
+
+ return 0; // No data
+}
+
+
+int CIcqProto::ppackTLVWordStringUtfItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID)
+{
+ char *str = getSettingStringUtf(NULL, szSetting, NULL);
+
+ if (str)
+ {
+ WORD wLen = strlennull(str);
+
+ ppackWord(buf, buflen, wLen + 0x0A);
+ ppackTLVWord(buf, buflen, wTypeID, wID);
+ ppackTLV(buf, buflen, wTypeData, wLen, (PBYTE)str);
+
+ SAFE_FREE(&str);
+
+ return 1; // Success
+ }
+
+ return 0; // No data
+}
+
+
+void ppackTLVBlockItems(PBYTE *buf, int *buflen, WORD wType, int *nItems, PBYTE *pBlock, WORD *wLength, BOOL bSingleItem)
+{
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen + *wLength);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, (bSingleItem ? 4 : 2) + *wLength);
+ packWord(*buf + *buflen + 4, *nItems);
+ if (bSingleItem)
+ packWord(*buf + *buflen + 6, *wLength);
+ if (*wLength)
+ memcpy(*buf + *buflen + (bSingleItem ? 8 : 6), *pBlock, *wLength);
+ *buflen += (bSingleItem ? 8 : 6) + *wLength;
+
+ SAFE_FREE((void**)pBlock);
+ *wLength = 0;
+ *nItems = 0;
+}
+
+
+void ppackTLVBlockItem(PBYTE *buf, int *buflen, WORD wType, PBYTE *pItem, WORD *wLength)
+{
+ if (wLength)
+ {
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 8 + *buflen + *wLength);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, 4 + *wLength);
+ packWord(*buf + *buflen + 4, 1);
+ packWord(*buf + *buflen + 6, *wLength);
+ memcpy(*buf + *buflen + 8, *pItem, *wLength);
+ *buflen += 8 + *wLength;
+ }
+ else
+ {
+ *buf = (PBYTE)SAFE_REALLOC(*buf, 6 + *buflen);
+ packWord(*buf + *buflen, wType);
+ packWord(*buf + *buflen + 2, 0x02);
+ packWord(*buf + *buflen + 4, 0);
+ *buflen += 6;
+ }
+
+ SAFE_FREE((void**)pItem);
+ *wLength = 0;
+}
+
+
+void __fastcall unpackByte(BYTE **pSource, BYTE *byDestination)
+{
+ if (byDestination)
+ *byDestination = *(*pSource)++;
+ else
+ *pSource += 1;
+}
+
+void __fastcall unpackWord(BYTE **pSource, WORD *wDestination)
+{
+ BYTE *tmp = *pSource;
+
+ if (wDestination)
+ {
+ *wDestination = *tmp++ << 8;
+ *wDestination |= *tmp++;
+
+ *pSource = tmp;
+ }
+ else
+ *pSource += 2;
+}
+
+void __fastcall unpackDWord(BYTE **pSource, DWORD *dwDestination)
+{
+ BYTE *tmp = *pSource;
+
+ if (dwDestination)
+ {
+ *dwDestination = *tmp++ << 24;
+ *dwDestination |= *tmp++ << 16;
+ *dwDestination |= *tmp++ << 8;
+ *dwDestination |= *tmp++;
+
+ *pSource = tmp;
+ }
+ else
+ *pSource += 4;
+}
+
+void __fastcall unpackQWord(BYTE **pSource, DWORD64 *qwDestination)
+{
+ DWORD dwData;
+
+ if (qwDestination)
+ {
+ unpackDWord(pSource, &dwData);
+ *qwDestination = ((DWORD64)dwData) << 32;
+ unpackDWord(pSource, &dwData);
+ *qwDestination |= dwData;
+ }
+ else
+ *pSource += 8;
+}
+
+void __fastcall unpackLEWord(BYTE **buf, WORD *w)
+{
+ BYTE *tmp = *buf;
+
+ if (w)
+ {
+ *w = (*tmp++);
+ *w |= ((*tmp++) << 8);
+ }
+ else
+ tmp += 2;
+
+ *buf = tmp;
+}
+
+void __fastcall unpackLEDWord(BYTE **buf, DWORD *dw)
+{
+ BYTE *tmp = *buf;
+
+ if (dw)
+ {
+ *dw = (*tmp++);
+ *dw |= ((*tmp++) << 8);
+ *dw |= ((*tmp++) << 16);
+ *dw |= ((*tmp++) << 24);
+ }
+ else
+ tmp += 4;
+
+ *buf = tmp;
+}
+
+void unpackString(BYTE **buf, char *string, WORD len)
+{
+ BYTE *tmp = *buf;
+
+ if (string)
+ {
+ while (len) /* Can have 0x00 so go by len */
+ {
+ *string++ = *tmp++;
+ len--;
+ }
+ }
+ else
+ tmp += len;
+
+ *buf = tmp;
+}
+
+void unpackWideString(BYTE **buf, WCHAR *string, WORD len)
+{
+ BYTE *tmp = *buf;
+
+ while (len > 1)
+ {
+ *string = (*tmp++ << 8);
+ *string |= *tmp++;
+
+ string++;
+ len -= 2;
+ }
+
+ // We have a stray byte at the end, this means that the buffer had an odd length
+ // which indicates an error.
+ _ASSERTE(len == 0);
+ if (len != 0)
+ {
+ // We dont copy the last byte but we still need to increase the buffer pointer
+ // (we assume that 'len' was correct) since the calling function expects
+ // that it is increased 'len' bytes.
+ *tmp += len;
+ }
+
+ *buf = tmp;
+}
+
+void unpackTypedTLV(BYTE *buf, int buflen, WORD type, WORD *ttype, WORD *tlen, BYTE **ttlv)
+{
+ WORD wType, wLen;
+
+NextTLV:
+ // Unpack type and length
+ unpackWord(&buf, &wType);
+ unpackWord(&buf, &wLen);
+ buflen -= 4;
+
+ if (wType != type && buflen >= wLen + 4)
+ { // Not the right TLV, try next
+ buflen -= wLen;
+ buf += wLen;
+ goto NextTLV;
+ }
+ // Check buffer size
+ if (wLen > buflen) wLen = buflen;
+
+ // Make sure we have a good pointer
+ if (ttlv)
+ {
+ if (wLen)
+ { // Unpack and save value
+ *ttlv = (BYTE*)SAFE_MALLOC(wLen + 1); // Add 1 for \0
+ unpackString(&buf, (char*)*ttlv, wLen);
+ *(*ttlv + wLen) = '\0';
+ }
+ else
+ *ttlv = NULL;
+ }
+
+ // Save type and length
+ if (ttype)
+ *ttype = wType;
+ if (tlen)
+ *tlen = wLen;
+}
+
+
+BOOL CIcqProto::unpackUID(BYTE **ppBuf, WORD *pwLen, DWORD *pdwUIN, uid_str *ppszUID)
+{
+ BYTE nUIDLen;
+
+ // sanity check
+ if (!ppBuf || !pwLen || *pwLen < 1)
+ return FALSE;
+
+ // Sender UIN
+ unpackByte(ppBuf, &nUIDLen);
+ *pwLen -= 1;
+
+ if ((nUIDLen > *pwLen) || (nUIDLen == 0))
+ return FALSE;
+
+ if (nUIDLen <= UINMAXLEN)
+ { // it can be uin, check
+ char szUIN[UINMAXLEN+1];
+
+ unpackString(ppBuf, szUIN, nUIDLen);
+ szUIN[nUIDLen] = '\0';
+ *pwLen -= nUIDLen;
+
+ if (IsStringUIN(szUIN))
+ {
+ *pdwUIN = atoi(szUIN);
+ return TRUE;
+ }
+ else
+ { // go back
+ *ppBuf -= nUIDLen;
+ *pwLen += nUIDLen;
+ }
+ }
+ if (!m_bAimEnabled || !ppszUID || !(*ppszUID))
+ { // skip the UID data
+ *ppBuf += nUIDLen;
+ *pwLen -= nUIDLen;
+
+ NetLog_Server("Malformed UIN in packet");
+ return FALSE;
+ }
+
+ unpackString(ppBuf, *ppszUID, nUIDLen);
+ *pwLen -= nUIDLen;
+ (*ppszUID)[nUIDLen] = '\0';
+
+ *pdwUIN = 0; // this is how we determine aim contacts internally
+
+ return TRUE;
+}
diff --git a/protocols/IcqOscarJ/src/icq_packet.h b/protocols/IcqOscarJ/src/icq_packet.h
new file mode 100644
index 0000000000..ccaeb91fcc
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_packet.h
@@ -0,0 +1,120 @@
+// ---------------------------------------------------------------------------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, Bio
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_PACKET_H
+#define __ICQ_PACKET_H
+
+typedef unsigned char BYTE;
+typedef unsigned short WORD;
+typedef unsigned long DWORD;
+
+/*---------* Structures *--------------*/
+
+struct icq_packet
+{
+ WORD wPlace;
+ BYTE nChannel;
+ WORD wLen;
+ BYTE *pData;
+};
+
+/*---------* Functions *---------------*/
+
+WORD generate_flap_sequence();
+
+void __fastcall init_generic_packet(icq_packet *pPacket, WORD wHeaderLen);
+void write_httphdr(icq_packet *pPacket, WORD wType, DWORD dwSeq);
+void __fastcall write_flap(icq_packet *pPacket, BYTE byFlapChannel);
+void __fastcall serverPacketInit(icq_packet *pPacket, WORD wSize);
+void __fastcall directPacketInit(icq_packet *pPacket, DWORD dwSize);
+
+void __fastcall serverCookieInit(icq_packet *pPacket, BYTE *pCookie, WORD wCookieSize);
+
+void __fastcall packByte(icq_packet *pPacket, BYTE byValue);
+void __fastcall packWord(icq_packet *pPacket, WORD wValue);
+void __fastcall packDWord(icq_packet *pPacket, DWORD dwValue);
+void __fastcall packQWord(icq_packet *pPacket, DWORD64 qwValue);
+void packTLV(icq_packet *pPacket, WORD wType, WORD wLength, const BYTE *pbyValue);
+void packTLVWord(icq_packet *pPacket, WORD wType, WORD wData);
+void packTLVDWord(icq_packet *pPacket, WORD wType, DWORD dwData);
+void packTLVUID(icq_packet *pPacket, WORD wType, DWORD dwUin, const char *szUid);
+
+void packBuffer(icq_packet *pPacket, const BYTE *pbyBuffer, WORD wLength);
+//void packLEWordSizedBuffer(icq_packet* pPacket, const BYTE* pbyBuffer, WORD wLength);
+int __fastcall getUINLen(DWORD dwUin);
+int __fastcall getUIDLen(DWORD dwUin, const char *szUid);
+void __fastcall packUIN(icq_packet *pPacket, DWORD dwUin);
+void __fastcall packUID(icq_packet *pPacket, DWORD dwUin, const char *szUid);
+
+void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype);
+void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence);
+void packFNACHeader(icq_packet *pPacket, WORD wFamily, WORD wSubtype, WORD wFlags, DWORD dwSequence, WORD wVersion);
+
+void __fastcall packLEWord(icq_packet *pPacket, WORD wValue);
+void __fastcall packLEDWord(icq_packet *pPacket, DWORD dwValue);
+
+void packLETLVLNTS(PBYTE *buf, int *bufpos, const char *str, WORD wType);
+
+void ppackByte(PBYTE *buf, int *buflen, BYTE byValue);
+void ppackWord(PBYTE *buf, int *buflen, WORD wValue);
+void ppackLEWord(PBYTE *buf, int *buflen, WORD wValue);
+void ppackLEDWord(PBYTE *buf, int *buflen, DWORD dwValue);
+void ppackLELNTS(PBYTE *buf, int *buflen, const char *str);
+void ppackBuffer(PBYTE *buf, int *buflen, WORD wLength, const BYTE *pbyValue);
+
+void ppackTLV(PBYTE *buf, int *buflen, WORD wType, WORD wLength, const BYTE *pbyValue);
+void ppackTLVByte(PBYTE *buf, int *buflen, WORD wType, BYTE byValue);
+void ppackTLVWord(PBYTE *buf, int *buflen, WORD wType, WORD wValue);
+void ppackTLVDWord(PBYTE *buf, int *buflen, WORD wType, DWORD dwValue);
+void ppackTLVDouble(PBYTE *buf, int *buflen, WORD wType, double dValue);
+void ppackTLVUID(PBYTE *buf, int *buflen, WORD wType, DWORD dwUin, const char *szUid);
+
+void ppackLETLVByte(PBYTE *buf, int *buflen, BYTE byValue, WORD wType, BYTE always);
+void ppackLETLVWord(PBYTE *buf, int *buflen, WORD wValue, WORD wType, BYTE always);
+void ppackLETLVDWord(PBYTE *buf, int *buflen, DWORD dwValue, WORD wType, BYTE always);
+void ppackLETLVLNTS(PBYTE *buf, int *buflen, const char *str, WORD wType, BYTE always);
+void ppackLETLVWordLNTS(PBYTE *buf, int *buflen, WORD w, const char *str, WORD wType, BYTE always);
+void ppackLETLVLNTSByte(PBYTE *buf, int *buflen, const char *str, BYTE b, WORD wType);
+
+void ppackTLVBlockItems(PBYTE *buf, int *buflen, WORD wType, int *nItems, PBYTE *pBlock, WORD *wLength, BOOL bSingleItem);
+void ppackTLVBlockItem(PBYTE *buf, int *buflen, WORD wType, PBYTE *pItem, WORD *wLength);
+
+void __fastcall unpackByte(BYTE **pSource, BYTE *byDestination);
+void __fastcall unpackWord(BYTE **pSource, WORD *wDestination);
+void __fastcall unpackDWord(BYTE **pSource, DWORD *dwDestination);
+void __fastcall unpackQWord(BYTE **pSource, DWORD64 *qwDestination);
+void unpackString(BYTE **buf, char *string, WORD len);
+void unpackWideString(BYTE **buf, WCHAR *string, WORD len);
+void unpackTypedTLV(BYTE *buf, int buflen, WORD type, WORD *ttype, WORD *tlen, BYTE **ttlv);
+BOOL unpackUID(BYTE **ppBuf, WORD *pwLen, DWORD *pdwUIN, uid_str *ppszUID);
+
+void __fastcall unpackLEWord(BYTE **buf, WORD *w);
+void __fastcall unpackLEDWord(BYTE **buf, DWORD *dw);
+
+#endif /* __ICQ_PACKET_H */
diff --git a/protocols/IcqOscarJ/src/icq_popups.cpp b/protocols/IcqOscarJ/src/icq_popups.cpp
new file mode 100644
index 0000000000..21ca36348d
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_popups.cpp
@@ -0,0 +1,323 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// PopUp Plugin stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+BOOL bPopUpService = FALSE;
+
+void InitPopUps()
+{
+ if (ServiceExists(MS_POPUP_ADDPOPUPEX))
+ {
+ bPopUpService = TRUE;
+ }
+}
+
+static const UINT icqPopupsControls[] = {
+ IDC_POPUPS_LOG_ENABLED, IDC_POPUPS_SPAM_ENABLED, IDC_PREVIEW, IDC_USESYSICONS, IDC_POPUP_LOG0_TIMEOUT,
+ IDC_POPUP_LOG1_TIMEOUT, IDC_POPUP_LOG2_TIMEOUT, IDC_POPUP_LOG3_TIMEOUT, IDC_POPUP_SPAM_TIMEOUT
+};
+
+static const UINT icqPopupColorControls[] = {
+ IDC_POPUP_LOG0_TEXTCOLOR, IDC_POPUP_LOG1_TEXTCOLOR, IDC_POPUP_LOG2_TEXTCOLOR, IDC_POPUP_LOG3_TEXTCOLOR, IDC_POPUP_SPAM_TEXTCOLOR,
+ IDC_POPUP_LOG0_BACKCOLOR, IDC_POPUP_LOG1_BACKCOLOR, IDC_POPUP_LOG2_BACKCOLOR, IDC_POPUP_LOG3_BACKCOLOR, IDC_POPUP_SPAM_BACKCOLOR
+};
+
+INT_PTR CALLBACK DlgProcIcqPopupOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static bool bInitDone = true;
+ BYTE bEnabled;
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr( hwndDlg, GWLP_USERDATA );
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ bInitDone = false;
+ TranslateDialogDefault(hwndDlg);
+
+ ppro = (CIcqProto*)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ CheckDlgButton(hwndDlg, IDC_POPUPS_LOG_ENABLED, ppro->getSettingByte(NULL,"PopupsLogEnabled",DEFAULT_LOG_POPUPS_ENABLED));
+ CheckDlgButton(hwndDlg, IDC_POPUPS_SPAM_ENABLED, ppro->getSettingByte(NULL,"PopupsSpamEnabled",DEFAULT_SPAM_POPUPS_ENABLED));
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG0_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups0TextColor",DEFAULT_LOG0_TEXT_COLORS));
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG0_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups0BackColor",DEFAULT_LOG0_BACK_COLORS));
+ SetDlgItemInt(hwndDlg, IDC_POPUP_LOG0_TIMEOUT, ppro->getSettingDword(NULL,"Popups0Timeout",DEFAULT_LOG0_TIMEOUT),FALSE);
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG1_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups1TextColor",DEFAULT_LOG1_TEXT_COLORS));
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG1_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups1BackColor",DEFAULT_LOG1_BACK_COLORS));
+ SetDlgItemInt(hwndDlg, IDC_POPUP_LOG1_TIMEOUT, ppro->getSettingDword(NULL,"Popups1Timeout",DEFAULT_LOG1_TIMEOUT),FALSE);
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG2_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups2TextColor",DEFAULT_LOG2_TEXT_COLORS));
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG2_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups2BackColor",DEFAULT_LOG2_BACK_COLORS));
+ SetDlgItemInt(hwndDlg, IDC_POPUP_LOG2_TIMEOUT, ppro->getSettingDword(NULL,"Popups2Timeout",DEFAULT_LOG2_TIMEOUT),FALSE);
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG3_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups3TextColor",DEFAULT_LOG3_TEXT_COLORS));
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_LOG3_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"Popups3BackColor",DEFAULT_LOG3_BACK_COLORS));
+ SetDlgItemInt(hwndDlg, IDC_POPUP_LOG3_TIMEOUT, ppro->getSettingDword(NULL,"Popups3Timeout",DEFAULT_LOG3_TIMEOUT),FALSE);
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_SPAM_TEXTCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"PopupsSpamTextColor",DEFAULT_SPAM_TEXT_COLORS));
+ SendDlgItemMessage(hwndDlg, IDC_POPUP_SPAM_BACKCOLOR, CPM_SETCOLOUR, 0, ppro->getSettingDword(NULL,"PopupsSpamBackColor",DEFAULT_SPAM_BACK_COLORS));
+ SetDlgItemInt(hwndDlg, IDC_POPUP_SPAM_TIMEOUT, ppro->getSettingDword(NULL,"PopupsSpamTimeout",DEFAULT_SPAM_TIMEOUT),FALSE);
+ bEnabled = ppro->getSettingByte(NULL,"PopupsWinColors",DEFAULT_POPUPS_WIN_COLORS);
+ CheckDlgButton(hwndDlg, IDC_USEWINCOLORS, bEnabled);
+ bEnabled |= ppro->getSettingByte(NULL,"PopupsDefColors",DEFAULT_POPUPS_DEF_COLORS);
+ CheckDlgButton(hwndDlg, IDC_USEDEFCOLORS, bEnabled);
+ icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled);
+ CheckDlgButton(hwndDlg, IDC_USESYSICONS, ppro->getSettingByte(NULL,"PopupsSysIcons",DEFAULT_POPUPS_SYS_ICONS));
+ bEnabled = ppro->getSettingByte(NULL,"PopupsEnabled",DEFAULT_POPUPS_ENABLED);
+ CheckDlgButton(hwndDlg, IDC_POPUPS_ENABLED, bEnabled);
+ icq_EnableMultipleControls(hwndDlg, icqPopupsControls, SIZEOF(icqPopupsControls), bEnabled);
+ if (bEnabled)
+ {
+ if (IsDlgButtonChecked(hwndDlg, IDC_USEDEFCOLORS))
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), WM_ENABLE);
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), WM_ENABLE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE);
+ }
+ }
+ icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled & (!IsDlgButtonChecked(hwndDlg,IDC_USEWINCOLORS) && !IsDlgButtonChecked(hwndDlg,IDC_USEDEFCOLORS)));
+ bInitDone = true;
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_PREVIEW:
+ {
+ ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Note"), LOG_NOTE);
+ ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Warning"), LOG_WARNING);
+ ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Error"), LOG_ERROR);
+ ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Fatal"), LOG_FATAL);
+ ppro->ShowPopUpMsg(NULL, LPGEN("Popup Title"), LPGEN("Sample Spambot"), POPTYPE_SPAM);
+ }
+ return FALSE;
+
+ case IDC_POPUPS_ENABLED:
+ bEnabled = IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED);
+ if (bEnabled)
+ {
+ if (IsDlgButtonChecked(hwndDlg, IDC_USEDEFCOLORS))
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), WM_ENABLE);
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), WM_ENABLE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE);
+ }
+ }
+ else
+ {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE);
+ }
+ icq_EnableMultipleControls(hwndDlg, icqPopupsControls, SIZEOF(icqPopupsControls), bEnabled);
+
+ case IDC_USEWINCOLORS:
+ bEnabled = IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED);
+ if (bEnabled)
+ {
+ if (IsDlgButtonChecked(hwndDlg, IDC_USEWINCOLORS))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), !WM_ENABLE);
+ else
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEDEFCOLORS), WM_ENABLE);
+ }
+ icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled & !IsDlgButtonChecked(hwndDlg,IDC_USEWINCOLORS));
+
+ case IDC_USEDEFCOLORS:
+ bEnabled = IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED);
+ if (bEnabled)
+ {
+ if (IsDlgButtonChecked(hwndDlg, IDC_USEDEFCOLORS))
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), !WM_ENABLE);
+ else
+ EnableWindow(GetDlgItem(hwndDlg, IDC_USEWINCOLORS), WM_ENABLE);
+ }
+ icq_EnableMultipleControls(hwndDlg, icqPopupColorControls, SIZEOF(icqPopupColorControls), bEnabled & !IsDlgButtonChecked(hwndDlg,IDC_USEDEFCOLORS));
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ case IDC_POPUP_LOG0_TIMEOUT:
+ case IDC_POPUP_LOG1_TIMEOUT:
+ case IDC_POPUP_LOG2_TIMEOUT:
+ case IDC_POPUP_LOG3_TIMEOUT:
+ case IDC_POPUP_SPAM_TIMEOUT:
+ if ((HIWORD(wParam) == EN_CHANGE) && bInitDone)
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ default:
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_APPLY:
+ ppro->setSettingByte(NULL,"PopupsEnabled",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_POPUPS_ENABLED));
+ ppro->setSettingByte(NULL,"PopupsLogEnabled",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_POPUPS_LOG_ENABLED));
+ ppro->setSettingByte(NULL,"PopupsSpamEnabled",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_POPUPS_SPAM_ENABLED));
+ ppro->setSettingDword(NULL,"Popups0TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG0_TEXTCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups0BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG0_BACKCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups0Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG0_TIMEOUT, NULL, FALSE));
+ ppro->setSettingDword(NULL,"Popups1TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG1_TEXTCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups1BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG1_BACKCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups1Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG1_TIMEOUT, NULL, FALSE));
+ ppro->setSettingDword(NULL,"Popups2TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG2_TEXTCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups2BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG2_BACKCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups2Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG2_TIMEOUT, NULL, FALSE));
+ ppro->setSettingDword(NULL,"Popups3TextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG3_TEXTCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups3BackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_LOG3_BACKCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"Popups3Timeout",GetDlgItemInt(hwndDlg, IDC_POPUP_LOG3_TIMEOUT, NULL, FALSE));
+ ppro->setSettingDword(NULL,"PopupsSpamTextColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_SPAM_TEXTCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"PopupsSpamBackColor",SendDlgItemMessage(hwndDlg,IDC_POPUP_SPAM_BACKCOLOR,CPM_GETCOLOUR,0,0));
+ ppro->setSettingDword(NULL,"PopupsSpamTimeout",GetDlgItemInt(hwndDlg, IDC_POPUP_SPAM_TIMEOUT, NULL, FALSE));
+ ppro->setSettingByte(NULL,"PopupsWinColors",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_USEWINCOLORS));
+ ppro->setSettingByte(NULL,"PopupsDefColors",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_USEDEFCOLORS));
+ ppro->setSettingByte(NULL,"PopupsSysIcons",(BYTE)IsDlgButtonChecked(hwndDlg,IDC_USESYSICONS));
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+int CIcqProto::ShowPopUpMsg(HANDLE hContact, const char *szTitle, const char *szMsg, BYTE bType)
+{
+ if (bPopUpService && getSettingByte(NULL, "PopupsEnabled", DEFAULT_POPUPS_ENABLED))
+ {
+ POPUPDATAEX ppd = {0};
+ POPUPDATAW ppdw = {0};
+ LPCTSTR rsIcon;
+ char szPrefix[32], szSetting[32];
+
+ strcpy(szPrefix, "Popups");
+ ppd.iSeconds = 0;
+
+ switch(bType) {
+ case LOG_NOTE:
+ rsIcon = MAKEINTRESOURCE(IDI_INFORMATION);
+ ppd.colorBack = DEFAULT_LOG0_BACK_COLORS;
+ ppd.colorText = DEFAULT_LOG0_TEXT_COLORS;
+ strcat(szPrefix, "0");
+ break;
+
+ case LOG_WARNING:
+ rsIcon = MAKEINTRESOURCE(IDI_WARNING);
+ ppd.colorBack = DEFAULT_LOG1_BACK_COLORS;
+ ppd.colorText = DEFAULT_LOG1_TEXT_COLORS;
+ strcat(szPrefix, "1");
+ break;
+
+ case LOG_ERROR:
+ rsIcon = MAKEINTRESOURCE(IDI_ERROR);
+ ppd.colorBack = DEFAULT_LOG2_BACK_COLORS;
+ ppd.colorText = DEFAULT_LOG2_TEXT_COLORS;
+ strcat(szPrefix, "2");
+ break;
+
+ case LOG_FATAL:
+ rsIcon = MAKEINTRESOURCE(IDI_ERROR);
+ ppd.colorBack = DEFAULT_LOG3_BACK_COLORS;
+ ppd.colorText = DEFAULT_LOG3_TEXT_COLORS;
+ strcat(szPrefix, "3");
+ break;
+
+ case POPTYPE_SPAM:
+ rsIcon = MAKEINTRESOURCE(IDI_WARNING);
+ ppd.colorBack = DEFAULT_SPAM_BACK_COLORS;
+ ppd.colorText = DEFAULT_SPAM_TEXT_COLORS;
+ strcat(szPrefix, "Spam");
+ break;
+ default:
+ return -1;
+ }
+ if (!getSettingByte(NULL, "PopupsSysIcons", DEFAULT_POPUPS_SYS_ICONS))
+ ppd.lchIcon = m_hIconProtocol->GetIcon();
+ else
+ ppd.lchIcon = (HICON)LoadImage( NULL, rsIcon, IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_SHARED);
+ if (getSettingByte(NULL, "PopupsWinColors", DEFAULT_POPUPS_WIN_COLORS))
+ {
+ ppd.colorText = GetSysColor(COLOR_WINDOWTEXT);
+ ppd.colorBack = GetSysColor(COLOR_WINDOW);
+ }
+ else
+ {
+ if (getSettingByte(NULL, "PopupsDefColors", DEFAULT_POPUPS_DEF_COLORS))
+ {
+ ppd.colorText = NULL;
+ ppd.colorBack = NULL;
+ }
+ else
+ {
+ strcpy(szSetting, szPrefix);
+ strcat(szSetting, "TextColor");
+ ppd.colorText = getSettingDword(NULL, szSetting, ppd.colorText);
+ strcpy(szSetting, szPrefix);
+ strcat(szSetting, "BackColor");
+ ppd.colorBack = getSettingDword(NULL, szSetting, ppd.colorBack);
+ }
+ }
+ strcpy(szSetting, szPrefix);
+ strcat(szSetting, "Timeout");
+ ppd.iSeconds = getSettingDword(NULL, szSetting, ppd.iSeconds);
+
+ // call unicode popup module - only on unicode OS otherwise it will not work properly :(
+ // due to Popup Plug bug in ADDPOPUPW implementation
+ if ( ServiceExists( MS_POPUP_ADDPOPUPW ))
+ {
+ char str[4096];
+
+ make_unicode_string_static(ICQTranslateUtfStatic(szTitle, str, sizeof(str)), ppdw.lpwzContactName, MAX_CONTACTNAME);
+ make_unicode_string_static(ICQTranslateUtfStatic(szMsg, str, sizeof(str)), ppdw.lpwzText, MAX_SECONDLINE);
+ ppdw.lchContact = hContact;
+ ppdw.lchIcon = ppd.lchIcon;
+ ppdw.colorBack = ppd.colorBack;
+ ppdw.colorText = ppd.colorText;
+ ppdw.PluginWindowProc = NULL;
+ ppdw.PluginData = NULL;
+ ppdw.iSeconds = ppd.iSeconds;
+ return CallService(MS_POPUP_ADDPOPUPW, (WPARAM)&ppdw, 0);
+ }
+ else
+
+ {
+ char str[MAX_PATH];
+
+ utf8_decode_static(ICQTranslateUtfStatic(szTitle, str, MAX_PATH), ppd.lpzContactName, MAX_CONTACTNAME);
+ utf8_decode_static(ICQTranslateUtfStatic(szMsg, str, MAX_PATH), ppd.lpzText, MAX_SECONDLINE);
+ ppd.lchContact = hContact;
+ ppd.PluginWindowProc = NULL;
+ ppd.PluginData = NULL;
+
+ return CallService(MS_POPUP_ADDPOPUPEX, (WPARAM)&ppd, 0);
+ }
+ }
+ return -1; // Failure
+}
diff --git a/protocols/IcqOscarJ/src/icq_popups.h b/protocols/IcqOscarJ/src/icq_popups.h
new file mode 100644
index 0000000000..a42c230634
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_popups.h
@@ -0,0 +1,41 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Headers for PopUp Plugin support
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_POPUPS_H
+#define __ICQ_POPUPS_H
+
+
+#define POPTYPE_SPAM 254 // this is for spambots
+
+
+void InitPopUps();
+void InitPopupOpts(WPARAM wParam);
+
+
+#endif /* __ICQ_POPUPS_H */
diff --git a/protocols/IcqOscarJ/src/icq_proto.cpp b/protocols/IcqOscarJ/src/icq_proto.cpp
new file mode 100644
index 0000000000..d1490fac2e
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_proto.cpp
@@ -0,0 +1,2375 @@
+// ---------------------------------------------------------------------------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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Protocol Interface Implementation
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+#include "m_icolib.h"
+
+extern PLUGININFOEX pluginInfo;
+extern HANDLE hExtraXStatus;
+
+#pragma warning(disable:4355)
+
+static int CompareConns(const directconnect* p1, const directconnect* p2)
+{
+ if (p1 < p2)
+ return -1;
+
+ return (p1 == p2) ? 0 : 1;
+}
+
+static int CompareCookies( const icq_cookie_info* p1, const icq_cookie_info* p2 )
+{
+ if ( p1->dwCookie < p2->dwCookie )
+ return -1;
+
+ return ( p1->dwCookie == p2->dwCookie ) ? 0 : 1;
+}
+
+static int CompareFT( const filetransfer* p1, const filetransfer* p2 )
+{
+ if ( p1->dwCookie < p2->dwCookie )
+ return -1;
+
+ return ( p1->dwCookie == p2->dwCookie ) ? 0 : 1;
+}
+
+static int CompareContactsCache(const icq_contacts_cache *p1, const icq_contacts_cache *p2)
+{
+ if (p1->dwUin < p2->dwUin)
+ return -1;
+
+ if (p1->dwUin > p2->dwUin)
+ return 1;
+
+ return stricmpnull(p1->szUid, p2->szUid);
+}
+
+CIcqProto::CIcqProto( const char* aProtoName, const TCHAR* aUserName ) :
+cookies(10, CompareCookies),
+directConns(10, CompareConns),
+expectedFileRecvs(10, CompareFT),
+contactsCache(10, CompareContactsCache),
+cheekySearchId( -1 )
+{
+ m_iVersion = 2;
+ m_iStatus = ID_STATUS_OFFLINE;
+ m_tszUserName = mir_tstrdup( aUserName );
+ m_szModuleName = mir_strdup( aProtoName );
+ m_szProtoName = mir_strdup( aProtoName );
+ _strlwr( m_szProtoName );
+ m_szProtoName[0] = toupper( m_szProtoName[0] );
+ NetLog_Server( "Setting protocol/module name to '%s/%s'", m_szProtoName, m_szModuleName );
+
+ oftMutex = new icq_critical_section();
+
+ // Initialize direct connections
+ directConnListMutex = new icq_critical_section();
+ expectedFileRecvMutex = new icq_critical_section();
+
+ // Initialize server lists
+ servlistMutex = new icq_critical_section();
+ servlistQueueMutex = new icq_critical_section();
+ HookProtoEvent(ME_CLIST_GROUPCHANGE, &CIcqProto::ServListCListGroupChange);
+
+ // Initialize status message struct
+ ZeroMemory(&m_modeMsgs, sizeof(icq_mode_messages));
+ m_modeMsgsMutex = new icq_critical_section();
+ connectionHandleMutex = new icq_critical_section();
+ localSeqMutex = new icq_critical_section();
+
+ m_modeMsgsEvent = CreateProtoEvent(ME_ICQ_STATUSMSGREQ);
+ hxstatuschanged = CreateProtoEvent(ME_ICQ_CUSTOMSTATUS_CHANGED);
+ hxstatusiconchanged = CreateProtoEvent(ME_ICQ_CUSTOMSTATUS_EXTRAICON_CHANGED);
+
+ // Initialize cookies
+ cookieMutex = new icq_critical_section();
+ wCookieSeq = 2;
+
+ // Initialize rates
+ m_ratesMutex = new icq_critical_section();
+
+ // Initialize avatars
+ m_avatarsMutex = new icq_critical_section();
+
+ // Initialize temporary DB settings
+ CreateResidentSetting("Status"); // NOTE: XStatus cannot be temporary
+ CreateResidentSetting("TemporaryVisible");
+ CreateResidentSetting("TickTS");
+ CreateResidentSetting("IdleTS");
+ CreateResidentSetting("AwayTS");
+ CreateResidentSetting("LogonTS");
+ CreateResidentSetting("DCStatus");
+ CreateResidentSetting("CapBuf"); //capabilities bufer
+ CreateResidentSetting(DBSETTING_STATUS_NOTE_TIME);
+ CreateResidentSetting(DBSETTING_STATUS_MOOD);
+
+ // Setup services
+ CreateProtoService(PS_CREATEACCMGRUI, &CIcqProto::OnCreateAccMgrUI );
+ CreateProtoService(MS_ICQ_SENDSMS, &CIcqProto::SendSms);
+ CreateProtoService(PS_SET_NICKNAME, &CIcqProto::SetNickName);
+
+ CreateProtoService(PS_GETMYAWAYMSG, &CIcqProto::GetMyAwayMsg);
+
+ CreateProtoService(PS_GETINFOSETTING, &CIcqProto::GetInfoSetting);
+
+ CreateProtoService(PSS_ADDED, &CIcqProto::SendYouWereAdded);
+ // Session password API
+ CreateProtoService(PS_ICQ_SETPASSWORD, &CIcqProto::SetPassword);
+ // ChangeInfo API
+ CreateProtoService(PS_CHANGEINFOEX, &CIcqProto::ChangeInfoEx);
+ // Avatar API
+ CreateProtoService(PS_GETAVATARINFOT, &CIcqProto::GetAvatarInfo);
+ CreateProtoService(PS_GETAVATARCAPS, &CIcqProto::GetAvatarCaps);
+ CreateProtoService(PS_GETMYAVATART, &CIcqProto::GetMyAvatar);
+ CreateProtoService(PS_SETMYAVATART, &CIcqProto::SetMyAvatar);
+ // Custom Status API
+ CreateProtoService(PS_ICQ_SETCUSTOMSTATUS, &CIcqProto::SetXStatus);
+ CreateProtoService(PS_ICQ_GETCUSTOMSTATUS, &CIcqProto::GetXStatus);
+ CreateProtoService(PS_ICQ_SETCUSTOMSTATUSEX, &CIcqProto::SetXStatusEx);
+ CreateProtoService(PS_ICQ_GETCUSTOMSTATUSEX, &CIcqProto::GetXStatusEx);
+ CreateProtoService(PS_ICQ_GETCUSTOMSTATUSICON, &CIcqProto::GetXStatusIcon);
+ CreateProtoService(PS_ICQ_REQUESTCUSTOMSTATUS, &CIcqProto::RequestXStatusDetails);
+ CreateProtoService(PS_ICQ_GETADVANCEDSTATUSICON, &CIcqProto::RequestAdvStatusIconIdx);
+
+ CreateProtoService(MS_ICQ_ADDSERVCONTACT, &CIcqProto::AddServerContact);
+
+ CreateProtoService(MS_REQ_AUTH, &CIcqProto::RequestAuthorization);
+ CreateProtoService(MS_GRANT_AUTH, &CIcqProto::GrantAuthorization);
+ CreateProtoService(MS_REVOKE_AUTH, &CIcqProto::RevokeAuthorization);
+
+ CreateProtoService(MS_XSTATUS_SHOWDETAILS, &CIcqProto::ShowXStatusDetails);
+
+ // Custom caps
+ CreateProtoService(PS_ICQ_ADDCAPABILITY, &CIcqProto::IcqAddCapability);
+ CreateProtoService(PS_ICQ_CHECKCAPABILITY, &CIcqProto::IcqCheckCapability);
+
+
+ HookProtoEvent(ME_SKIN2_ICONSCHANGED, &CIcqProto::OnReloadIcons);
+
+ {
+ // Initialize IconLib icons
+ char szSectionName[MAX_PATH], *szAccountName = tchar_to_utf8(m_tszUserName);
+ null_snprintf(szSectionName, sizeof(szSectionName), "Protocols/%s/Accounts", ICQ_PROTOCOL_NAME);
+
+ TCHAR lib[MAX_PATH];
+ GetModuleFileName(hInst, lib, MAX_PATH);
+
+ m_hIconProtocol = IconLibDefine(szAccountName, szSectionName, m_szModuleName, "main", lib, -IDI_ICQ);
+ SAFE_FREE(&szAccountName);
+ }
+
+ // Reset a bunch of session specific settings
+ UpdateGlobalSettings();
+ ResetSettingsOnLoad();
+
+ // Initialize Contacts Cache
+ InitContactsCache();
+
+ // Startup Auto Info-Update thread
+ icq_InitInfoUpdate();
+
+ // Init extra statuses
+ InitXStatusIcons();
+
+ HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &CIcqProto::OnPreBuildStatusMenu);
+
+ // Register netlib users
+ NETLIBUSER nlu = {0};
+ TCHAR szBuffer[MAX_PATH + 64];
+ null_snprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s server connection"), m_tszUserName);
+ nlu.cbSize = sizeof(nlu);
+ nlu.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_TCHAR;
+ nlu.ptszDescriptiveName = szBuffer;
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.szHttpGatewayHello = "http://http.proxy.icq.com/hello";
+ nlu.szHttpGatewayUserAgent = "Mozilla/4.08 [en] (WinNT; U ;Nav)";
+ nlu.pfnHttpGatewayInit = icq_httpGatewayInit;
+ nlu.pfnHttpGatewayBegin = icq_httpGatewayBegin;
+ nlu.pfnHttpGatewayWrapSend = icq_httpGatewayWrapSend;
+ nlu.pfnHttpGatewayUnwrapRecv = icq_httpGatewayUnwrapRecv;
+ m_hServerNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);
+
+ char szP2PModuleName[MAX_PATH];
+ null_snprintf(szP2PModuleName, SIZEOF(szP2PModuleName), "%sP2P", m_szModuleName);
+ null_snprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s client-to-client connections"), m_tszUserName);
+ nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_TCHAR;
+ nlu.ptszDescriptiveName = szBuffer;
+ nlu.szSettingsModule = szP2PModuleName;
+ nlu.minIncomingPorts = 1;
+ m_hDirectNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);
+
+ // Register custom database events
+ DBEVENTTYPEDESCR eventType = {0};
+ eventType.cbSize = DBEVENTTYPEDESCR_SIZE;
+ eventType.eventType = ICQEVENTTYPE_MISSEDMESSAGE;
+ eventType.module = m_szModuleName;
+ eventType.descr = "Missed message notifications";
+ eventType.textService = ICQ_DB_GETEVENTTEXT_MISSEDMESSAGE;
+ eventType.flags = DETF_HISTORY | DETF_MSGWINDOW;
+ // for now keep default "message" icon
+ CallService(MS_DB_EVENT_REGISTERTYPE, 0, (LPARAM)&eventType);
+
+ // Protocol instance is ready
+ NetLog_Server("%s: Protocol instance '%s' created.", ICQ_PROTOCOL_NAME, m_szModuleName);
+}
+
+
+CIcqProto::~CIcqProto()
+{
+ m_bXStatusEnabled = 10; // block clist changing
+ m_bMoodsEnabled = 10;
+
+ // Serv-list update board clean-up
+ FlushServerIDs();
+ /// TODO: make sure server-list handler thread is not running
+ /// TODO: save state of server-list update board to DB
+ servlistPendingFlushOperations();
+ SAFE_FREE((void**)&servlistQueueList);
+
+ // Finalize avatars
+ /// TODO: cleanup remaining avatar requests
+ SAFE_DELETE(&m_avatarsMutex);
+
+ // NetLib clean-up
+ NetLib_SafeCloseHandle(&m_hDirectNetlibUser);
+ NetLib_SafeCloseHandle(&m_hServerNetlibUser);
+
+ // Destroy hookable events
+ if (m_modeMsgsEvent)
+ DestroyHookableEvent(m_modeMsgsEvent);
+
+ if (hxstatuschanged)
+ DestroyHookableEvent(hxstatuschanged);
+
+ if (hxstatusiconchanged)
+ DestroyHookableEvent(hxstatusiconchanged);
+
+ // Clean-up remaining protocol instance members
+ cookies.destroy();
+
+ UninitContactsCache();
+
+ CustomCapList.clear();
+
+ SAFE_DELETE(&m_ratesMutex);
+
+ SAFE_DELETE(&servlistMutex);
+ SAFE_DELETE(&servlistQueueMutex);
+
+ SAFE_DELETE(&m_modeMsgsMutex);
+ SAFE_DELETE(&localSeqMutex);
+ SAFE_DELETE(&connectionHandleMutex);
+ SAFE_DELETE(&oftMutex);
+ SAFE_DELETE(&directConnListMutex);
+ SAFE_DELETE(&expectedFileRecvMutex);
+ SAFE_DELETE(&cookieMutex);
+
+ SAFE_FREE(&m_modeMsgs.szOnline);
+ SAFE_FREE(&m_modeMsgs.szAway);
+ SAFE_FREE(&m_modeMsgs.szNa);
+ SAFE_FREE(&m_modeMsgs.szOccupied);
+ SAFE_FREE(&m_modeMsgs.szDnd);
+ SAFE_FREE(&m_modeMsgs.szFfc);
+
+ // Remove account icons
+ UninitXStatusIcons();
+
+ IconLibRemove(&m_hIconProtocol);
+
+ NetLog_Server("%s: Protocol instance '%s' destroyed.", ICQ_PROTOCOL_NAME, m_szModuleName);
+
+ mir_free( m_szProtoName );
+ mir_free( m_szModuleName );
+ mir_free( m_tszUserName );
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// OnModulesLoadedEx - performs hook registration
+
+
+int CIcqProto::OnModulesLoaded( WPARAM wParam, LPARAM lParam )
+{
+ char pszP2PName[MAX_PATH];
+ char pszGroupsName[MAX_PATH];
+ char pszSrvGroupsName[MAX_PATH];
+ char* modules[5] = {0,0,0,0,0};
+
+ null_snprintf(pszP2PName, SIZEOF(pszP2PName), "%sP2P", m_szModuleName);
+ null_snprintf(pszGroupsName, SIZEOF(pszGroupsName), "%sGroups", m_szModuleName);
+ null_snprintf(pszSrvGroupsName, SIZEOF(pszSrvGroupsName), "%sSrvGroups", m_szModuleName);
+ modules[0] = m_szModuleName;
+ modules[1] = pszP2PName;
+ modules[2] = pszGroupsName;
+ modules[3] = pszSrvGroupsName;
+ CallService("DBEditorpp/RegisterModule",(WPARAM)modules,(LPARAM)4);
+
+ HookProtoEvent(ME_OPT_INITIALISE, &CIcqProto::OnOptionsInit);
+ HookProtoEvent(ME_USERINFO_INITIALISE, &CIcqProto::OnUserInfoInit);
+ HookProtoEvent(ME_IDLE_CHANGED, &CIcqProto::OnIdleChanged);
+
+ InitAvatars();
+
+ // Init extra optional modules
+ InitPopUps();
+ InitXStatusItems(FALSE);
+
+ if (hExtraXStatus == NULL)
+ {
+ if (HookProtoEvent(ME_CLIST_EXTRA_LIST_REBUILD, &CIcqProto::CListMW_ExtraIconsRebuild))
+ { // note if the Hook was successful (e.g. clist_nicer creates them too late)
+ HookProtoEvent(ME_CLIST_EXTRA_IMAGE_APPLY, &CIcqProto::CListMW_ExtraIconsApply);
+ bXStatusExtraIconsReady = 1;
+ }
+ }
+ else
+ {
+ HANDLE hContact = FindFirstContact();
+ while (hContact != NULL)
+ {
+ DWORD bXStatus = getContactXStatus(hContact);
+ if (bXStatus > 0)
+ setContactExtraIcon(hContact, bXStatus);
+
+ hContact = FindNextContact(hContact);
+ }
+ }
+
+ return 0;
+}
+
+int CIcqProto::OnPreShutdown(WPARAM wParam,LPARAM lParam)
+{
+ // signal info update thread to stop
+ icq_InfoUpdateCleanup();
+
+ // Make sure all connections are closed
+ CloseContactDirectConns(NULL);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_AddToList - adds a contact to the contact list
+
+HANDLE CIcqProto::AddToList( int flags, PROTOSEARCHRESULT* psr )
+{
+ if (psr)
+ {
+ if (psr->cbSize == sizeof(ICQSEARCHRESULT))
+ {
+ ICQSEARCHRESULT *isr = (ICQSEARCHRESULT*)psr;
+ if (isr->uin)
+ return AddToListByUIN(isr->uin, flags);
+ else
+ { // aim contact
+ char szUid[MAX_PATH];
+
+ if (isr->hdr.flags & PSR_UNICODE)
+ unicode_to_ansi_static((WCHAR*)isr->hdr.id, szUid, MAX_PATH);
+ else
+ null_strcpy(szUid, (char*)isr->hdr.id, MAX_PATH);
+
+ if (szUid[0] == 0) return 0;
+ return AddToListByUID(szUid, flags);
+ }
+ }
+ else
+ {
+ char szUid[MAX_PATH];
+
+ if (psr->flags & PSR_UNICODE)
+ unicode_to_ansi_static((WCHAR*)psr->id, szUid, MAX_PATH);
+ else
+ null_strcpy(szUid, (char*)psr->id, MAX_PATH);
+
+ if (szUid[0] == 0) return 0;
+ if (IsStringUIN(szUid))
+ return AddToListByUIN(atoi(szUid), flags);
+ else
+ return AddToListByUID(szUid, flags);
+ }
+ }
+
+ return 0; // Failure
+}
+
+HANDLE __cdecl CIcqProto::AddToListByEvent( int flags, int iContact, HANDLE hDbEvent )
+{
+ DWORD uin = 0;
+ uid_str uid = {0};
+
+ DBEVENTINFO dbei = {0};
+ dbei.cbSize = sizeof(dbei);
+ if ((dbei.cbBlob = CallService(MS_DB_EVENT_GETBLOBSIZE, (WPARAM)hDbEvent, 0)) == -1)
+ return 0;
+
+ dbei.pBlob = (PBYTE)_alloca(dbei.cbBlob + 1);
+ dbei.pBlob[dbei.cbBlob] = '\0';
+
+ if (CallService(MS_DB_EVENT_GET, (WPARAM)hDbEvent, (LPARAM)&dbei))
+ return 0; // failed to get event
+
+ if (strcmpnull(dbei.szModule, m_szModuleName))
+ return 0; // this event is not ours
+
+ switch(dbei.eventType) {
+ case EVENTTYPE_CONTACTS:
+ {
+ char *pbOffset = (char*)dbei.pBlob;
+ char *pbEnd = pbOffset + dbei.cbBlob;
+ for (int i = 0; i <= iContact; i++) {
+ pbOffset += strlennull(pbOffset) + 1; // Nick
+ if (pbOffset >= pbEnd) break;
+ if (i == iContact)
+ { // we found the contact, get uid
+ if (IsStringUIN((char*)pbOffset))
+ uin = atoi((char*)pbOffset);
+ else
+ {
+ uin = 0;
+ strcpy(uid, (char*)pbOffset);
+ }
+ }
+ pbOffset += strlennull(pbOffset) + 1; // Uin
+ if (pbOffset >= pbEnd) break;
+ }
+ }
+ break;
+
+ case EVENTTYPE_AUTHREQUEST:
+ case EVENTTYPE_ADDED:
+ if ( getContactUid( DbGetAuthEventContact(&dbei), &uin, &uid))
+ return 0;
+
+ default:
+ return 0;
+ }
+
+ if (uin != 0)
+ return AddToListByUIN(uin, flags); // Success
+
+ // add aim contact
+ if (strlennull(uid))
+ return AddToListByUID(uid, flags); // Success
+
+ return NULL; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_AuthAllow - processes the successful authorization
+
+int CIcqProto::Authorize( HANDLE hDbEvent )
+{
+ if (icqOnline() && hDbEvent)
+ {
+ HANDLE hContact = HContactFromAuthEvent( hDbEvent );
+ if (hContact == INVALID_HANDLE_VALUE)
+ return 1;
+
+ DWORD uin;
+ uid_str uid;
+ if (getContactUid(hContact, &uin, &uid))
+ return 1;
+
+ icq_sendAuthResponseServ(uin, uid, 1, _T(""));
+
+ deleteSetting(hContact, "Grant");
+
+ return 0; // Success
+ }
+
+ return 1; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_AuthDeny - handles the unsuccessful authorization
+
+int CIcqProto::AuthDeny( HANDLE hDbEvent, const TCHAR* szReason )
+{
+ if (icqOnline() && hDbEvent)
+ {
+ HANDLE hContact = HContactFromAuthEvent(hDbEvent);
+ if (hContact == INVALID_HANDLE_VALUE)
+ return 1;
+
+ DWORD uin;
+ uid_str uid;
+ if (getContactUid(hContact, &uin, &uid))
+ return 1;
+
+ icq_sendAuthResponseServ(uin, uid, 0, szReason);
+
+ if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0))
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
+
+ return 0; // Success
+ }
+
+ return 1; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSR_AUTH
+
+int __cdecl CIcqProto::AuthRecv( HANDLE hContact, PROTORECVEVENT* pre )
+{
+ setContactHidden( hContact, 0 );
+ ICQAddRecvEvent( NULL, EVENTTYPE_AUTHREQUEST, pre, pre->lParam, (PBYTE)pre->szMessage, 0 );
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSS_AUTHREQUEST
+
+int __cdecl CIcqProto::AuthRequest( HANDLE hContact, const TCHAR* szMessage )
+{
+ if ( !icqOnline())
+ return 1;
+
+ if (hContact)
+ {
+ DWORD dwUin;
+ uid_str szUid;
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return 1; // Invalid contact
+
+ if (dwUin)
+ {
+ char *utf = tchar_to_utf8(szMessage);
+ icq_sendAuthReqServ(dwUin, szUid, utf);
+ SAFE_FREE(&utf);
+ return 0; // Success
+ }
+ }
+
+ return 1; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// ChangeInfo
+
+HANDLE __cdecl CIcqProto::ChangeInfo( int iInfoType, void* pInfoData )
+{
+ return NULL;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileAllow - starts a file transfer
+
+HANDLE __cdecl CIcqProto::FileAllow( HANDLE hContact, HANDLE hTransfer, const TCHAR* szPath )
+{
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return 0; // Invalid contact
+
+ if (icqOnline() && hContact && szPath && hTransfer)
+ { // approve old fashioned file transfer
+ basic_filetransfer *ft = (basic_filetransfer *)hTransfer;
+
+ if (!IsValidFileTransfer(ft))
+ return 0; // Invalid transfer
+
+ if (dwUin && ft->ft_magic == FT_MAGIC_ICQ)
+ {
+ filetransfer *ft = (filetransfer *)hTransfer;
+ ft->szSavePath = tchar_to_utf8(szPath);
+
+ {
+ icq_lock l(expectedFileRecvMutex);
+ expectedFileRecvs.insert(ft);
+ }
+
+ // Was request received thru DC and have we a open DC, send through that
+ if (ft->bDC && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ icq_sendFileAcceptDirect(hContact, ft);
+ else
+ icq_sendFileAcceptServ(dwUin, ft, 0);
+
+ return hTransfer; // Success
+ }
+ else if (ft->ft_magic == FT_MAGIC_OSCAR)
+ { // approve oscar file transfer
+ return oftFileAllow(hContact, hTransfer, szPath);
+ }
+ }
+
+ return 0; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileCancel - cancels a file transfer
+
+int __cdecl CIcqProto::FileCancel( HANDLE hContact, HANDLE hTransfer )
+{
+ DWORD dwUin;
+ uid_str szUid;
+ if ( getContactUid(hContact, &dwUin, &szUid))
+ return 1; // Invalid contact
+
+ if (hContact && hTransfer)
+ {
+ basic_filetransfer *ft = (basic_filetransfer *)hTransfer;
+
+ if (!IsValidFileTransfer(ft))
+ return 1; // Invalid transfer
+
+ if (dwUin && ft->ft_magic == FT_MAGIC_ICQ)
+ { // cancel old fashioned file transfer
+ filetransfer * ft = (filetransfer*)hTransfer;
+ icq_CancelFileTransfer(hContact, ft);
+ return 0; // Success
+ }
+ else if (ft->ft_magic == FT_MAGIC_OSCAR)
+ { // cancel oscar file transfer
+ return oftFileCancel(hContact, hTransfer);
+ }
+ }
+
+ return 1; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileDeny - denies a file transfer
+
+int __cdecl CIcqProto::FileDeny( HANDLE hContact, HANDLE hTransfer, const TCHAR* szReason )
+{
+ int nReturnValue = 1;
+ DWORD dwUin;
+ uid_str szUid;
+ basic_filetransfer *ft = (basic_filetransfer*)hTransfer;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return 1; // Invalid contact
+
+ if (icqOnline() && hTransfer && hContact)
+ {
+ if (!IsValidFileTransfer(hTransfer))
+ return 1; // Invalid transfer
+
+ if (dwUin && ft->ft_magic == FT_MAGIC_ICQ)
+ { // deny old fashioned file transfer
+ filetransfer *ft = (filetransfer*)hTransfer;
+ char *szReasonUtf = tchar_to_utf8(szReason);
+ // Was request received thru DC and have we a open DC, send through that
+ if (ft->bDC && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ icq_sendFileDenyDirect(hContact, ft, szReasonUtf);
+ else
+ icq_sendFileDenyServ(dwUin, ft, szReasonUtf, 0);
+ SAFE_FREE(&szReasonUtf);
+
+ nReturnValue = 0; // Success
+ }
+ else if (ft->ft_magic == FT_MAGIC_OSCAR)
+ { // deny oscar file transfer
+ return oftFileDeny(hContact, hTransfer, szReason);
+ }
+ }
+ // Release possible orphan structure
+ SafeReleaseFileTransfer((void**)&ft);
+
+ return nReturnValue;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_FileResume - processes file renaming etc
+
+int __cdecl CIcqProto::FileResume( HANDLE hTransfer, int* action, const TCHAR** szFilename )
+{
+ if (icqOnline() && hTransfer)
+ {
+ basic_filetransfer *ft = (basic_filetransfer *)hTransfer;
+
+ if (!IsValidFileTransfer(ft))
+ return 1; // Invalid transfer
+
+ if (ft->ft_magic == FT_MAGIC_ICQ)
+ {
+ char *szFileNameUtf = tchar_to_utf8(*szFilename);
+ icq_sendFileResume((filetransfer *)hTransfer, *action, szFileNameUtf);
+ SAFE_FREE(&szFileNameUtf);
+ }
+ else if (ft->ft_magic == FT_MAGIC_OSCAR)
+ {
+ oftFileResume((oscar_filetransfer *)hTransfer, *action, *szFilename);
+ }
+ else
+ return 1; // Failure
+
+ return 0; // Success
+ }
+
+ return 1; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// GetCaps - return protocol capabilities bits
+
+DWORD_PTR __cdecl CIcqProto::GetCaps( int type, HANDLE hContact )
+{
+ DWORD_PTR nReturn = 0;
+
+ switch ( type ) {
+
+ case PFLAGNUM_1:
+ nReturn = PF1_IM | PF1_URL | PF1_AUTHREQ | PF1_BASICSEARCH | PF1_ADDSEARCHRES |
+ PF1_VISLIST | PF1_INVISLIST | PF1_MODEMSG | PF1_FILE | PF1_EXTSEARCH |
+ PF1_EXTSEARCHUI | PF1_SEARCHBYEMAIL | PF1_SEARCHBYNAME |
+ PF1_ADDED | PF1_CONTACT;
+ if (!m_bAimEnabled)
+ nReturn |= PF1_NUMERICUSERID;
+ if (m_bSsiEnabled && getSettingByte(NULL, "ServerAddRemove", DEFAULT_SS_ADDSERVER))
+ nReturn |= PF1_SERVERCLIST;
+ break;
+
+ case PFLAGNUM_2:
+ nReturn = PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND |
+ PF2_FREECHAT | PF2_INVISIBLE;
+ if (m_bAimEnabled)
+ nReturn |= PF2_ONTHEPHONE;
+ break;
+
+ case PFLAGNUM_3:
+ nReturn = PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND |
+ PF2_FREECHAT | PF2_INVISIBLE;
+ break;
+
+ case PFLAGNUM_4:
+ nReturn = PF4_SUPPORTIDLE | PF4_IMSENDUTF | PF4_IMSENDOFFLINE | PF4_INFOSETTINGSVC;
+ if (m_bAvatarsEnabled)
+ nReturn |= PF4_AVATARS;
+#ifdef DBG_CAPMTN
+ nReturn |= PF4_SUPPORTTYPING;
+#endif
+ break;
+
+ case PFLAGNUM_5:
+ nReturn = PF2_FREECHAT;
+ if (m_bAimEnabled)
+ nReturn |= PF2_ONTHEPHONE;
+ break;
+
+ case PFLAG_UNIQUEIDTEXT:
+ nReturn = (DWORD_PTR)Translate("User ID");
+ break;
+
+ case PFLAG_UNIQUEIDSETTING:
+ nReturn = (DWORD_PTR)UNIQUEIDSETTING;
+ break;
+
+ case PFLAG_MAXCONTACTSPERPACKET:
+ if ( hContact )
+ { // determine per contact
+ BYTE bClientId = getSettingByte(hContact, "ClientID", CLID_GENERIC);
+
+ if (bClientId == CLID_MIRANDA)
+ {
+ if (CheckContactCapabilities(hContact, CAPF_CONTACTS) && getContactStatus(hContact) != ID_STATUS_OFFLINE)
+ nReturn = 0x100; // limited only by packet size
+ else
+ nReturn = MAX_CONTACTSSEND;
+ }
+ else if (bClientId == CLID_ICQ6)
+ {
+ if (CheckContactCapabilities(hContact, CAPF_CONTACTS))
+ nReturn = 1; // crapy ICQ6 cannot handle multiple contacts in the transfer
+ else
+ nReturn = 0; // this version does not support contacts transfer at all
+ }
+ else
+ nReturn = MAX_CONTACTSSEND;
+ }
+ else // return generic limit
+ nReturn = MAX_CONTACTSSEND;
+ break;
+
+ case PFLAG_MAXLENOFMESSAGE:
+ nReturn = MAX_MESSAGESNACSIZE-102;
+ }
+
+ return nReturn;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// GetIcon - loads an icon for the contact list
+
+HICON __cdecl CIcqProto::GetIcon( int iconIndex )
+{
+ if (LOWORD(iconIndex) == PLI_PROTOCOL)
+ {
+ if (iconIndex & PLIF_ICOLIBHANDLE)
+ return (HICON)m_hIconProtocol->Handle();
+
+ bool big = (iconIndex & PLIF_SMALL) == 0;
+ HICON hIcon = m_hIconProtocol->GetIcon(big);
+
+ if (iconIndex & PLIF_ICOLIB)
+ return hIcon;
+
+ hIcon = CopyIcon(hIcon);
+ m_hIconProtocol->ReleaseIcon(big);
+ return hIcon;
+
+ }
+ return NULL;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// GetInfo - retrieves a contact info
+
+int __cdecl CIcqProto::GetInfo(HANDLE hContact, int infoType)
+{
+ if (icqOnline())
+ {
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return 1; // Invalid contact
+
+ DWORD dwCookie;
+ if (dwUin)
+ dwCookie = icq_sendGetInfoServ(hContact, dwUin, (infoType & SGIF_ONOPEN) != 0);
+ else // TODO: this needs something better
+ dwCookie = icq_sendGetAimProfileServ(hContact, szUid);
+
+ return (dwCookie) ? 0 : 1;
+ }
+
+ return 1; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SearchBasic - searches the contact by UID
+
+void CIcqProto::CheekySearchThread( void* )
+{
+ char szUin[UINMAXLEN];
+ ICQSEARCHRESULT isr = {0};
+ isr.hdr.cbSize = sizeof(isr);
+
+ if (cheekySearchUin)
+ {
+ _itoa(cheekySearchUin, szUin, 10);
+ isr.hdr.id = (FNAMECHAR*)szUin;
+ }
+ else
+ {
+ isr.hdr.id = (FNAMECHAR*)cheekySearchUid;
+ }
+ isr.uin = cheekySearchUin;
+
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)cheekySearchId, (LPARAM)&isr);
+ BroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)cheekySearchId, 0);
+ cheekySearchId = -1;
+}
+
+
+HANDLE __cdecl CIcqProto::SearchBasic( const PROTOCHAR *pszSearch )
+{
+ if (strlennull(pszSearch) == 0)
+ return 0;
+
+ char pszUIN[255];
+ int nHandle = 0;
+ int i, j;
+
+ if (!m_bAimEnabled)
+ {
+ for (i=j=0; (i<strlennull(pszSearch)) && (j<255); i++)
+ { // we take only numbers
+ if ((pszSearch[i]>=0x30) && (pszSearch[i]<=0x39))
+ {
+ pszUIN[j] = pszSearch[i];
+ j++;
+ }
+ }
+ }
+ else
+ {
+ for (i=j=0; (i<strlennull(pszSearch)) && (j<255); i++)
+ { // we remove spaces and slashes
+ if ((pszSearch[i]!=0x20) && (pszSearch[i]!='-'))
+ {
+ if (pszSearch[i] >= 0x80) continue;
+ pszUIN[j] = pszSearch[i];
+ j++;
+ }
+ }
+ }
+ pszUIN[j] = 0;
+
+ if (strlennull(pszUIN))
+ {
+ DWORD dwUin;
+ if (IsStringUIN(pszUIN))
+ dwUin = atoi(pszUIN);
+ else
+ dwUin = 0;
+
+ // Cheeky instant UIN search
+ if (!dwUin || GetKeyState(VK_CONTROL)&0x8000)
+ {
+ cheekySearchId = GenerateCookie(0);
+ cheekySearchUin = dwUin;
+ cheekySearchUid = null_strdup(pszUIN);
+ ForkThread(&CIcqProto::CheekySearchThread, 0); // The caller needs to get this return value before the results
+ nHandle = cheekySearchId;
+ }
+ else if (icqOnline())
+ {
+ nHandle = SearchByUin(dwUin);
+ }
+
+ // Success
+ return (HANDLE)nHandle;
+ }
+
+ // Failure
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SearchByEmail - searches the contact by its e-mail
+
+HANDLE __cdecl CIcqProto::SearchByEmail( const PROTOCHAR *email )
+{
+ if (email && icqOnline() && strlennull(email) > 0)
+ {
+ DWORD dwSearchId, dwSecId;
+ char *szEmail = tchar_to_ansi(email);
+
+ // Success
+ dwSearchId = SearchByMail(szEmail);
+ if (m_bAimEnabled)
+ dwSecId = icq_searchAimByEmail(szEmail, dwSearchId);
+ else
+ dwSecId = 0;
+
+ SAFE_FREE(&szEmail);
+
+ if (dwSearchId)
+ return ( HANDLE )dwSearchId;
+ else
+ return ( HANDLE )dwSecId;
+ }
+
+ return 0; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SearchByName - searches the contact by its first or last name, or by a nickname
+
+HANDLE __cdecl CIcqProto::SearchByName(const PROTOCHAR *nick, const PROTOCHAR *firstName, const PROTOCHAR *lastName)
+{
+ if (icqOnline())
+ {
+ if (nick || firstName || lastName)
+ {
+ char *nickUtf = tchar_to_utf8(nick);
+ char *firstNameUtf = tchar_to_utf8(firstName);
+ char *lastNameUtf = tchar_to_utf8(lastName);
+
+ // Success
+ HANDLE dwCookie = (HANDLE)SearchByNames(nickUtf, firstNameUtf, lastNameUtf, 0);
+
+ SAFE_FREE(&nickUtf);
+ SAFE_FREE(&firstNameUtf);
+ SAFE_FREE(&lastNameUtf);
+
+ return dwCookie;
+ }
+ }
+
+ return 0; // Failure
+}
+
+
+HWND __cdecl CIcqProto::CreateExtendedSearchUI( HWND parent )
+{
+ if (parent && hInst)
+ return CreateDialog(hInst, MAKEINTRESOURCE(IDD_ICQADVANCEDSEARCH), parent, AdvancedSearchDlgProc);
+
+ return NULL; // Failure
+}
+
+HWND __cdecl CIcqProto::SearchAdvanced( HWND hwndDlg )
+{
+ if (icqOnline() && IsWindow(hwndDlg))
+ {
+ int nDataLen;
+ BYTE* bySearchData;
+
+ if (bySearchData = createAdvancedSearchStructure(hwndDlg, &nDataLen))
+ {
+ int result = icq_sendAdvancedSearchServ(bySearchData, nDataLen);
+ SAFE_FREE((void**)&bySearchData);
+ return ( HWND )result; // Success
+ }
+ }
+
+ return NULL; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// RecvContacts
+
+int __cdecl CIcqProto::RecvContacts( HANDLE hContact, PROTORECVEVENT* pre )
+{
+ ICQSEARCHRESULT **isrList = (ICQSEARCHRESULT**)pre->szMessage;
+ int i;
+ DWORD cbBlob = 0;
+ DWORD flags = 0;
+
+ if (pre->flags & PREF_UTF || pre->flags & PREF_UNICODE)
+ flags |= DBEF_UTF;
+
+ for (i = 0; i < pre->lParam; i++)
+ {
+ if (pre->flags & PREF_UNICODE)
+ cbBlob += get_utf8_size((WCHAR*)isrList[i]->hdr.nick) + 2;
+ else
+ cbBlob += strlennull((char*)isrList[i]->hdr.nick) + 2; // both trailing zeros
+ if (isrList[i]->uin)
+ cbBlob += getUINLen(isrList[i]->uin);
+ else if (pre->flags & PREF_UNICODE)
+ cbBlob += strlennull((WCHAR*)isrList[i]->hdr.id);
+ else
+ cbBlob += strlennull((char*)isrList[i]->hdr.id);
+ }
+ PBYTE pBlob = (PBYTE)_alloca(cbBlob), pCurBlob;
+ for (i = 0, pCurBlob = pBlob; i < pre->lParam; i++)
+ {
+ if (pre->flags & PREF_UNICODE)
+ make_utf8_string_static((WCHAR*)isrList[i]->hdr.nick, (char*)pCurBlob, cbBlob - (pCurBlob - pBlob));
+ else
+ strcpy((char*)pCurBlob, (char*)isrList[i]->hdr.nick);
+ pCurBlob += strlennull((char*)pCurBlob) + 1;
+ if (isrList[i]->uin)
+ {
+ char szUin[UINMAXLEN];
+ _itoa(isrList[i]->uin, szUin, 10);
+ strcpy((char*)pCurBlob, szUin);
+ }
+ else
+ { // aim contact
+ if (pre->flags & PREF_UNICODE)
+ unicode_to_ansi_static((WCHAR*)isrList[i]->hdr.id, (char*)pCurBlob, cbBlob - (pCurBlob - pBlob));
+ else
+ strcpy((char*)pCurBlob, (char*)isrList[i]->hdr.id);
+ }
+ pCurBlob += strlennull((char*)pCurBlob) + 1;
+ }
+
+ ICQAddRecvEvent(hContact, EVENTTYPE_CONTACTS, pre, cbBlob, pBlob, flags);
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// RecvFile
+
+int __cdecl CIcqProto::RecvFile( HANDLE hContact, PROTORECVFILET* evt )
+{
+ return Proto_RecvFile(hContact, evt);
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// RecvMsg
+
+int __cdecl CIcqProto::RecvMsg( HANDLE hContact, PROTORECVEVENT* pre )
+{
+ DWORD cbBlob;
+ DWORD flags = 0;
+
+ cbBlob = strlennull(pre->szMessage) + 1;
+ // process utf-8 encoded messages
+ if ((pre->flags & PREF_UTF) && !IsUSASCII(pre->szMessage, strlennull(pre->szMessage)))
+ flags |= DBEF_UTF;
+ // process unicode ucs-2 messages
+ if ((pre->flags & PREF_UNICODE) && !IsUnicodeAscii((WCHAR*)(pre->szMessage+cbBlob), strlennull((WCHAR*)(pre->szMessage+cbBlob))))
+ cbBlob *= (sizeof(WCHAR)+1);
+
+ ICQAddRecvEvent(hContact, EVENTTYPE_MESSAGE, pre, cbBlob, (PBYTE)pre->szMessage, flags);
+
+ // stop contact from typing - some clients do not sent stop notify
+ if (CheckContactCapabilities(hContact, CAPF_TYPING))
+ CallService(MS_PROTO_CONTACTISTYPING, (WPARAM)hContact, PROTOTYPE_CONTACTTYPING_OFF);
+
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// RecvUrl
+
+int __cdecl CIcqProto::RecvUrl( HANDLE hContact, PROTORECVEVENT* )
+{
+ return 1;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendContacts
+
+int __cdecl CIcqProto::SendContacts( HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList )
+{
+ if (hContact && hContactsList)
+ {
+ int i;
+ DWORD dwUin;
+ uid_str szUid;
+ WORD wRecipientStatus;
+ DWORD dwCookie;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ { // Invalid contact
+ return ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The receiver has an invalid user ID.");
+ }
+
+ wRecipientStatus = getContactStatus(hContact);
+
+ // Failures
+ if (!icqOnline())
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "You cannot send messages when you are offline.");
+ }
+ else if (!hContactsList || (nContacts < 1) || (nContacts > MAX_CONTACTSSEND))
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "Bad data (internal error #1)");
+ }
+ // OK
+ else
+ {
+ if (CheckContactCapabilities(hContact, CAPF_CONTACTS) && wRecipientStatus != ID_STATUS_OFFLINE)
+ { // Use the new format if possible
+ int nDataLen, nNamesLen;
+ struct icq_contactsend_s* contacts = NULL;
+
+ // Format the data part and the names part
+ // This is kinda messy, but there is no simple way to do it. First
+ // we need to calculate the length of the packet.
+ contacts = (struct icq_contactsend_s*)_alloca(sizeof(struct icq_contactsend_s)*nContacts);
+ ZeroMemory(contacts, sizeof(struct icq_contactsend_s)*nContacts);
+ {
+ nDataLen = 0; nNamesLen = 0;
+ for (i = 0; i < nContacts; i++)
+ {
+ uid_str szContactUid;
+
+ if (!IsICQContact(hContactsList[i]))
+ break; // Abort if a non icq contact is found
+ if (getContactUid(hContactsList[i], &contacts[i].uin, &szContactUid))
+ break; // Abort if invalid contact
+ contacts[i].uid = contacts[i].uin?NULL:null_strdup(szContactUid);
+ contacts[i].szNick = NickFromHandleUtf(hContactsList[i]);
+ nDataLen += getUIDLen(contacts[i].uin, contacts[i].uid) + 4;
+ nNamesLen += strlennull(contacts[i].szNick) + 8;
+ }
+
+ if (i == nContacts)
+ {
+ icq_packet mData, mNames;
+
+#ifdef _DEBUG
+ NetLog_Server("Sending contacts to %s.", strUID(dwUin, szUid));
+#endif
+ // Do not calculate the exact size of the data packet - only the maximal size (easier)
+ // Sumarize size of group information
+ // - we do not utilize the full power of the protocol and send all contacts with group "General"
+ // just like ICQ6 does
+ nDataLen += 9;
+ nNamesLen += 9;
+
+ // Create data structures
+ mData.wPlace = 0;
+ mData.pData = (LPBYTE)SAFE_MALLOC(nDataLen);
+ mData.wLen = nDataLen;
+ mNames.wPlace = 0;
+ mNames.pData = (LPBYTE)SAFE_MALLOC(nNamesLen);
+
+ // pack Group Name
+ packWord(&mData, 7);
+ packBuffer(&mData, (LPBYTE)"General", 7);
+ packWord(&mNames, 7);
+ packBuffer(&mNames, (LPBYTE)"General", 7);
+
+ // all contacts in one group
+ packWord(&mData, (WORD)nContacts);
+ packWord(&mNames, (WORD)nContacts);
+ for (i = 0; i < nContacts; i++)
+ {
+ uid_str szContactUid;
+ WORD wLen;
+
+ if (contacts[i].uin)
+ strUID(contacts[i].uin, szContactUid);
+ else
+ strcpy(szContactUid, contacts[i].uid);
+
+ // prepare UID
+ wLen = strlennull(szContactUid);
+ packWord(&mData, wLen);
+ packBuffer(&mData, (LPBYTE)szContactUid, wLen);
+
+ // prepare Nick
+ wLen = strlennull(contacts[i].szNick);
+ packWord(&mNames, (WORD)(wLen + 4));
+ packTLV(&mNames, 0x01, wLen, (LPBYTE)contacts[i].szNick);
+ }
+
+ // Cleanup temporary list
+ for(i = 0; i < nContacts; i++)
+ {
+ SAFE_FREE(&contacts[i].szNick);
+ SAFE_FREE(&contacts[i].uid);
+ }
+
+ // Rate check
+ if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT))
+ { // rate is too high, the message will not go thru...
+ SAFE_FREE((void**)&mData.pData);
+ SAFE_FREE((void**)&mNames.pData);
+
+ return ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The message could not be delivered. You are sending too fast. Wait a while and try again.");
+ }
+
+ // Set up the ack type
+ cookie_message_data *pCookieData = CreateMessageCookieData(MTYPE_CONTACTS, hContact, dwUin, FALSE);
+
+ // AIM clients do not send acknowledgement
+ if (!dwUin && pCookieData->nAckType == ACKTYPE_CLIENT)
+ pCookieData->nAckType = ACKTYPE_SERVER;
+ // Send the message
+ dwCookie = icq_SendChannel2Contacts(dwUin, szUid, hContact, (char*)mData.pData, mData.wPlace, (char*)mNames.pData, mNames.wPlace, pCookieData);
+
+ // This will stop the message dialog from waiting for the real message delivery ack
+ if (pCookieData->nAckType == ACKTYPE_NONE)
+ {
+ SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_CONTACTS, NULL);
+ // We need to free this here since we will never see the real ack
+ // The actual cookie value will still have to be returned to the message dialog though
+ ReleaseCookie(dwCookie);
+ }
+ // Release our buffers
+ SAFE_FREE((void**)&mData.pData);
+ SAFE_FREE((void**)&mNames.pData);
+ }
+ else
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "Bad data (internal error #2)");
+ }
+
+ for(i = 0; i < nContacts; i++)
+ {
+ SAFE_FREE(&contacts[i].szNick);
+ SAFE_FREE(&contacts[i].uid);
+ }
+ }
+ }
+ else if (dwUin)
+ { // old format is only understood by ICQ clients
+ int nBodyLength;
+ char szContactUin[UINMAXLEN];
+ char szCount[17];
+ struct icq_contactsend_s* contacts = NULL;
+ uid_str szContactUid;
+
+
+ // Format the body
+ // This is kinda messy, but there is no simple way to do it. First
+ // we need to calculate the length of the packet.
+ contacts = (struct icq_contactsend_s*)_alloca(sizeof(struct icq_contactsend_s)*nContacts);
+ ZeroMemory(contacts, sizeof(struct icq_contactsend_s)*nContacts);
+ {
+ nBodyLength = 0;
+ for (i = 0; i < nContacts; i++)
+ {
+ if (!IsICQContact(hContactsList[i]))
+ break; // Abort if a non icq contact is found
+ if (getContactUid(hContactsList[i], &contacts[i].uin, &szContactUid))
+ break; // Abort if invalid contact
+ contacts[i].uid = contacts[i].uin?NULL:null_strdup(szContactUid);
+ contacts[i].szNick = NickFromHandle(hContactsList[i]);
+ // Compute this contact's length
+ nBodyLength += getUIDLen(contacts[i].uin, contacts[i].uid) + 1;
+ nBodyLength += strlennull(contacts[i].szNick) + 1;
+ }
+
+ if (i == nContacts)
+ {
+ char* pBody;
+ char* pBuffer;
+
+#ifdef _DEBUG
+ NetLog_Server("Sending contacts to %d.", dwUin);
+#endif
+ // Compute count record's length
+ _itoa(nContacts, szCount, 10);
+ nBodyLength += strlennull(szCount) + 1;
+
+ // Finally we need to copy the contact data into the packet body
+ pBuffer = pBody = (char *)SAFE_MALLOC(nBodyLength);
+ null_strcpy(pBuffer, szCount, nBodyLength - 1);
+ pBuffer += strlennull(pBuffer);
+ *pBuffer++ = (char)0xFE;
+ for (i = 0; i < nContacts; i++)
+ {
+ if (contacts[i].uin)
+ {
+ _itoa(contacts[i].uin, szContactUin, 10);
+ strcpy(pBuffer, szContactUin);
+ }
+ else
+ strcpy(pBuffer, contacts[i].uid);
+ pBuffer += strlennull(pBuffer);
+ *pBuffer++ = (char)0xFE;
+ strcpy(pBuffer, contacts[i].szNick);
+ pBuffer += strlennull(pBuffer);
+ *pBuffer++ = (char)0xFE;
+ }
+
+ for (i = 0; i < nContacts; i++)
+ { // release memory
+ SAFE_FREE(&contacts[i].szNick);
+ SAFE_FREE(&contacts[i].uid);
+ }
+
+ // Set up the ack type
+ cookie_message_data *pCookieData = CreateMessageCookieData(MTYPE_CONTACTS, hContact, dwUin, TRUE);
+
+ if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ {
+ int iRes = icq_SendDirectMessage(hContact, pBody, nBodyLength, 1, pCookieData, NULL);
+
+ if (iRes)
+ {
+ SAFE_FREE((void**)&pBody);
+
+ return iRes; // we succeded, return
+ }
+ }
+
+ // Rate check
+ if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT))
+ { // rate is too high, the message will not go thru...
+ SAFE_FREE((void**)&pCookieData);
+ SAFE_FREE((void**)&pBody);
+
+ return ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The message could not be delivered. You are sending too fast. Wait a while and try again.");
+ }
+ // Select channel and send
+/*
+ if (!CheckContactCapabilities(hContact, CAPF_SRV_RELAY) || wRecipientStatus == ID_STATUS_OFFLINE)
+ {
+ dwCookie = icq_SendChannel4Message(dwUin, hContact, MTYPE_CONTACTS, (WORD)nBodyLength, pBody, pCookieData);
+ }
+ else
+*/
+ {
+ WORD wPriority;
+
+ if (wRecipientStatus == ID_STATUS_ONLINE || wRecipientStatus == ID_STATUS_FREECHAT)
+ wPriority = 0x0001;
+ else
+ wPriority = 0x0021;
+
+ dwCookie = icq_SendChannel2Message(dwUin, hContact, pBody, nBodyLength, wPriority, pCookieData, NULL);
+ }
+
+ // This will stop the message dialog from waiting for the real message delivery ack
+ if (pCookieData->nAckType == ACKTYPE_NONE)
+ {
+ SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_CONTACTS, NULL);
+ // We need to free this here since we will never see the real ack
+ // The actual cookie value will still have to be returned to the message dialog though
+ ReleaseCookie(dwCookie);
+ }
+ SAFE_FREE((void**)&pBody);
+ }
+ else
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "Bad data (internal error #2)");
+ }
+ }
+ }
+ else
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_CONTACTS, "The reciever does not support receiving of contacts.");
+ }
+ }
+ return dwCookie;
+ }
+
+ // Exit with Failure
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendFile - sends a file
+
+HANDLE __cdecl CIcqProto::SendFile( HANDLE hContact, const TCHAR* szDescription, TCHAR** ppszFiles )
+{
+ if ( !icqOnline())
+ return 0;
+
+ if (hContact && szDescription && ppszFiles)
+ {
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return 0; // Invalid contact
+
+ if (getContactStatus(hContact) != ID_STATUS_OFFLINE)
+ {
+ if (CheckContactCapabilities(hContact, CAPF_OSCAR_FILE))
+ return oftInitTransfer(hContact, dwUin, szUid, (LPCTSTR*)ppszFiles, szDescription);
+
+ if (dwUin)
+ {
+ WORD wClientVersion = getSettingWord(hContact, "Version", 7);
+
+ if (wClientVersion < 7)
+ NetLog_Server("IcqSendFile() can't send to version %u", wClientVersion);
+ else
+ {
+ int i;
+ filetransfer* ft;
+ struct _stat statbuf;
+
+ // Initialize filetransfer struct
+ ft = CreateFileTransfer(hContact, dwUin, (wClientVersion == 7) ? 7: 8);
+
+ for (ft->dwFileCount = 0; ppszFiles[ft->dwFileCount]; ft->dwFileCount++);
+ ft->pszFiles = (char **)SAFE_MALLOC(sizeof(char *) * ft->dwFileCount);
+ ft->dwTotalSize = 0;
+ for (i = 0; i < (int)ft->dwFileCount; i++)
+ {
+ ft->pszFiles[i] = (ppszFiles[i]) ? tchar_to_utf8(ppszFiles[i]) : NULL;
+
+ if (_tstat(ppszFiles[i], &statbuf))
+ NetLog_Server("IcqSendFile() was passed invalid filename(s)");
+ else
+ ft->dwTotalSize += statbuf.st_size;
+ }
+ ft->szDescription = tchar_to_utf8(szDescription);
+ ft->dwTransferSpeed = 100;
+ ft->sending = 1;
+ ft->fileId = -1;
+ ft->iCurrentFile = 0;
+ ft->dwCookie = AllocateCookie(CKT_FILE, 0, hContact, ft);
+ ft->hConnection = NULL;
+
+ // Send file transfer request
+ {
+ char szFiles[64], tmp[64];
+ char *pszFiles;
+
+
+ NetLog_Server("Init file send");
+
+ if (ft->dwFileCount == 1)
+ {
+ pszFiles = strchr(ft->pszFiles[0], '\\');
+ if (pszFiles)
+ pszFiles++;
+ else
+ pszFiles = ft->pszFiles[0];
+ }
+ else
+ {
+ null_snprintf(szFiles, SIZEOF(szFiles), ICQTranslateUtfStatic("%d Files", tmp, SIZEOF(tmp)), ft->dwFileCount);
+ pszFiles = szFiles;
+ }
+
+ // Send packet
+ {
+ if (ft->nVersion == 7)
+ {
+ if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ {
+ int iRes = icq_sendFileSendDirectv7(ft, pszFiles);
+ if (iRes) return ft; // Success
+ }
+ NetLog_Server("Sending v%u file transfer request through server", 7);
+ icq_sendFileSendServv7(ft, pszFiles);
+ }
+ else
+ {
+ if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ {
+ int iRes = icq_sendFileSendDirectv8(ft, pszFiles);
+ if (iRes) return ft; // Success
+ }
+ NetLog_Server("Sending v%u file transfer request through server", 8);
+ icq_sendFileSendServv8(ft, pszFiles, ACKTYPE_NONE);
+ }
+ }
+ }
+
+ return ft; // Success
+ }
+ }
+ }
+ }
+
+ return 0; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SendMessage - sends a message
+
+int __cdecl CIcqProto::SendMsg( HANDLE hContact, int flags, const char* pszSrc )
+{
+ if (hContact && pszSrc)
+ {
+ DWORD dwCookie;
+ char* puszText = NULL;
+ int bNeedFreeU = 0;
+ cookie_message_data *pCookieData = NULL;
+
+ // Invalid contact
+ DWORD dwUin;
+ uid_str szUID;
+ if (getContactUid(hContact, &dwUin, &szUID))
+ return ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The receiver has an invalid user ID.");
+
+ if (flags & PREF_UNICODE)
+ {
+ puszText = make_utf8_string((WCHAR*)(pszSrc + strlennull(pszSrc) + 1)); // get the UTF-16 part
+ bNeedFreeU = 1;
+ }
+ else if (flags & PREF_UTF)
+ puszText = (char*)pszSrc;
+ else
+ {
+ puszText = (char*)ansi_to_utf8(pszSrc);
+ bNeedFreeU = 1;
+ }
+
+ WORD wRecipientStatus = getContactStatus(hContact);
+
+ BOOL plain_ascii = IsUSASCII(puszText, strlennull(puszText));
+
+ BOOL oldAnsi = plain_ascii || !m_bUtfEnabled ||
+ (!(flags & (PREF_UTF | PREF_UNICODE)) && m_bUtfEnabled == 1) ||
+ !CheckContactCapabilities(hContact, CAPF_UTF) ||
+ !getSettingByte(hContact, "UnicodeSend", 1);
+
+ if (m_bTempVisListEnabled && m_iStatus == ID_STATUS_INVISIBLE)
+ makeContactTemporaryVisible(hContact); // make us temporarily visible to contact
+
+ // Failure scenarios
+ if (!icqOnline())
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "You cannot send messages when you are offline.");
+ }
+ else if ((wRecipientStatus == ID_STATUS_OFFLINE) && (strlennull(puszText) > 4096))
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "Messages to offline contacts must be shorter than 4096 characters.");
+ }
+ // Looks OK
+ else
+ {
+#ifdef _DEBUG
+ NetLog_Server("Send %smessage - Message cap is %u", puszText ? "unicode " : "", CheckContactCapabilities(hContact, CAPF_SRV_RELAY));
+ NetLog_Server("Send %smessage - Contact status is %u", puszText ? "unicode " : "", wRecipientStatus);
+#endif
+ if (dwUin && m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ { // send thru direct
+ char *dc_msg = puszText;
+ char *dc_cap = plain_ascii ? NULL : CAP_UTF8MSGS;
+ char *szUserAnsi = NULL;
+
+ if (!plain_ascii && oldAnsi)
+ {
+ szUserAnsi = ConvertMsgToUserSpecificAnsi(hContact, puszText);
+ if (szUserAnsi)
+ {
+ dc_msg = szUserAnsi;
+ dc_cap = NULL;
+ }
+ }
+
+ // Set up the ack type
+ pCookieData = CreateMessageCookieData(MTYPE_PLAIN, hContact, dwUin, TRUE);
+ pCookieData->nAckType = ACKTYPE_CLIENT;
+ dwCookie = icq_SendDirectMessage(hContact, dc_msg, strlennull(dc_msg), 1, pCookieData, dc_cap);
+
+ SAFE_FREE(&szUserAnsi);
+ if (dwCookie)
+ { // free the buffers if alloced
+ if (bNeedFreeU) SAFE_FREE(&puszText);
+
+ return dwCookie; // we succeded, return
+ }
+ // on failure, fallback to send thru server
+ }
+
+ if (!dwUin || !CheckContactCapabilities(hContact, CAPF_SRV_RELAY) ||
+ wRecipientStatus == ID_STATUS_OFFLINE || wRecipientStatus == ID_STATUS_INVISIBLE ||
+ getSettingByte(hContact, "OnlyServerAcks", getSettingByte(NULL, "OnlyServerAcks", DEFAULT_ONLYSERVERACKS)) ||
+ !getSettingByte(hContact, "SlowSend", getSettingByte(NULL, "SlowSend", DEFAULT_SLOWSEND)))
+ {
+ /// TODO: add support for RTL & user customizable font
+ {
+ char *mng = MangleXml(puszText, strlennull(puszText));
+ int len = strlennull(mng);
+ mng = (char*)SAFE_REALLOC(mng, len + 28);
+ memmove(mng + 12, mng, len + 1);
+ memcpy(mng, "<HTML><BODY>", 12);
+ strcat(mng, "</BODY></HTML>");
+ if (bNeedFreeU) SAFE_FREE(&puszText);
+ puszText = mng;
+ bNeedFreeU = 1;
+ }
+
+ WCHAR *pwszText = plain_ascii ? NULL : make_unicode_string(puszText);
+ if ((plain_ascii ? strlennull(puszText) : strlennull(pwszText) * sizeof(WCHAR)) > MAX_MESSAGESNACSIZE)
+ { // max length check // TLV(2) is currently limited to 0xA00 bytes in online mode
+ // only limit to not get disconnected, all other will be handled by error 0x0A
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered, it is too long.");
+
+ // free the buffers if alloced
+ SAFE_FREE((void**)&pwszText);
+ if (bNeedFreeU) SAFE_FREE(&puszText);
+
+ return dwCookie;
+ }
+ // Rate check
+ if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT))
+ { // rate is too high, the message will not go thru...
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered. You are sending too fast. Wait a while and try again.");
+
+ // free the buffers if alloced
+ SAFE_FREE((void**)&pwszText);
+ if (bNeedFreeU) SAFE_FREE(&puszText);
+
+ return dwCookie;
+ }
+
+ pCookieData = CreateMessageCookieData(MTYPE_PLAIN, hContact, dwUin, FALSE);
+
+ if (plain_ascii)
+ dwCookie = icq_SendChannel1Message(dwUin, szUID, hContact, puszText, pCookieData);
+ else
+ dwCookie = icq_SendChannel1MessageW(dwUin, szUID, hContact, pwszText, pCookieData);
+ // free the unicode message
+ SAFE_FREE((void**)&pwszText);
+ }
+ else
+ {
+ WORD wPriority;
+
+ char *srv_msg = puszText;
+ char *srv_cap = plain_ascii ? NULL : CAP_UTF8MSGS;
+ char *szUserAnsi = NULL;
+
+ if (!plain_ascii && oldAnsi)
+ {
+ szUserAnsi = ConvertMsgToUserSpecificAnsi(hContact, puszText);
+ if (szUserAnsi)
+ {
+ srv_msg = szUserAnsi;
+ srv_cap = NULL;
+ }
+ }
+
+ if (wRecipientStatus == ID_STATUS_ONLINE || wRecipientStatus == ID_STATUS_FREECHAT)
+ wPriority = 0x0001;
+ else
+ wPriority = 0x0021;
+
+ if (strlennull(srv_msg) + (!oldAnsi ? 144 : 102) > MAX_MESSAGESNACSIZE)
+ { // max length check
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered, it is too long.");
+
+ SAFE_FREE(&szUserAnsi);
+ // free the buffers if alloced
+ if (bNeedFreeU) SAFE_FREE(&puszText);
+
+ return dwCookie;
+ }
+ // Rate check
+ if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT))
+ { // rate is too high, the message will not go thru...
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_MESSAGE, "The message could not be delivered. You are sending too fast. Wait a while and try again.");
+
+ SAFE_FREE(&szUserAnsi);
+ // free the buffers if alloced
+ if (bNeedFreeU) SAFE_FREE(&puszText);
+
+ return dwCookie;
+ }
+
+ pCookieData = CreateMessageCookieData(MTYPE_PLAIN, hContact, dwUin, TRUE);
+ dwCookie = icq_SendChannel2Message(dwUin, hContact, srv_msg, strlennull(srv_msg), wPriority, pCookieData, srv_cap);
+ SAFE_FREE(&szUserAnsi);
+ }
+
+ // This will stop the message dialog from waiting for the real message delivery ack
+ if (pCookieData && pCookieData->nAckType == ACKTYPE_NONE)
+ {
+ SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_MESSAGE, NULL);
+ // We need to free this here since we will never see the real ack
+ // The actual cookie value will still have to be returned to the message dialog though
+ ReleaseCookie(dwCookie);
+ }
+ }
+ // free the buffers if alloced
+ if (bNeedFreeU) SAFE_FREE(&puszText);
+
+ return dwCookie; // Success
+ }
+
+ return 0; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendUrl
+
+int __cdecl CIcqProto::SendUrl( HANDLE hContact, int flags, const char* url )
+{
+ if (hContact && url)
+ {
+ DWORD dwCookie;
+ WORD wRecipientStatus;
+ DWORD dwUin;
+
+ if (getContactUid(hContact, &dwUin, NULL))
+ { // Invalid contact
+ return ReportGenericSendError(hContact, ACKTYPE_URL, "The receiver has an invalid user ID.");
+ }
+
+ wRecipientStatus = getContactStatus(hContact);
+
+ // Failure
+ if (!icqOnline())
+ {
+ dwCookie = ReportGenericSendError(hContact, ACKTYPE_URL, "You cannot send messages when you are offline.");
+ }
+ // Looks OK
+ else
+ {
+ char* szDesc;
+ char* szBody;
+ int nBodyLen;
+ int nDescLen;
+ int nUrlLen;
+
+
+ // Set up the ack type
+ cookie_message_data *pCookieData = CreateMessageCookieData(MTYPE_URL, hContact, dwUin, TRUE);
+
+ // Format the body
+ nUrlLen = strlennull(url);
+ szDesc = (char *)url + nUrlLen + 1;
+ nDescLen = strlennull(szDesc);
+ nBodyLen = nUrlLen + nDescLen + 2;
+ szBody = (char *)_alloca(nBodyLen);
+ strcpy(szBody, szDesc);
+ szBody[nDescLen] = (char)0xFE; // Separator
+ strcpy(szBody + nDescLen + 1, url);
+
+ if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ {
+ int iRes = icq_SendDirectMessage(hContact, szBody, nBodyLen, 1, pCookieData, NULL);
+ if (iRes) return iRes; // we succeded, return
+ }
+
+ // Rate check
+ if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_LIMIT))
+ { // rate is too high, the message will not go thru...
+ SAFE_FREE((void**)&pCookieData);
+
+ return ReportGenericSendError(hContact, ACKTYPE_URL, "The message could not be delivered. You are sending too fast. Wait a while and try again.");
+ }
+ // Select channel and send
+/*
+ if (!CheckContactCapabilities(hContact, CAPF_SRV_RELAY) ||
+ wRecipientStatus == ID_STATUS_OFFLINE)
+ {
+ dwCookie = icq_SendChannel4Message(dwUin, hContact, MTYPE_URL,
+ (WORD)nBodyLen, szBody, pCookieData);
+ }
+ else
+*/
+ {
+ WORD wPriority;
+
+ if (wRecipientStatus == ID_STATUS_ONLINE || wRecipientStatus == ID_STATUS_FREECHAT)
+ wPriority = 0x0001;
+ else
+ wPriority = 0x0021;
+
+ dwCookie = icq_SendChannel2Message(dwUin, hContact, szBody, nBodyLen, wPriority, pCookieData, NULL);
+ }
+
+ // This will stop the message dialog from waiting for the real message delivery ack
+ if (pCookieData->nAckType == ACKTYPE_NONE)
+ {
+ SendProtoAck(hContact, dwCookie, ACKRESULT_SUCCESS, ACKTYPE_URL, NULL);
+ // We need to free this here since we will never see the real ack
+ // The actual cookie value will still have to be returned to the message dialog though
+ ReleaseCookie(dwCookie);
+ }
+ }
+
+ return dwCookie; // Success
+ }
+
+ return 0; // Failure
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SetApparentMode - sets the visibility status
+
+int __cdecl CIcqProto::SetApparentMode( HANDLE hContact, int mode )
+{
+ DWORD uin;
+ uid_str uid;
+
+ if (getContactUid(hContact, &uin, &uid))
+ return 1; // Invalid contact
+
+ if (hContact)
+ {
+ // Only 3 modes are supported
+ if (mode == 0 || mode == ID_STATUS_ONLINE || mode == ID_STATUS_OFFLINE)
+ {
+ int oldMode = getSettingWord(hContact, "ApparentMode", 0);
+
+ // Don't send redundant updates
+ if (mode != oldMode)
+ {
+ setSettingWord(hContact, "ApparentMode", (WORD)mode);
+
+ // Not being online is only an error when in SS mode. This is not handled
+ // yet so we just ignore this for now.
+ if (icqOnline())
+ {
+ if (oldMode != 0)
+ { // Remove from old list
+ if (oldMode == ID_STATUS_OFFLINE && getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0))
+ { // Need to remove Ignore item as well
+ icq_removeServerPrivacyItem(hContact, uin, uid, getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0), SSI_ITEM_IGNORE);
+
+ setSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0);
+ }
+ icq_sendChangeVisInvis(hContact, uin, uid, oldMode==ID_STATUS_OFFLINE, 0);
+ }
+ if (mode != 0)
+ { // Add to new list
+ if (mode==ID_STATUS_OFFLINE && getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0))
+ return 0; // Success: offline by ignore item
+
+ icq_sendChangeVisInvis(hContact, uin, uid, mode==ID_STATUS_OFFLINE, 1);
+ }
+ }
+
+ return 0; // Success
+ }
+ }
+ }
+
+ return 1; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PrepareStatusNote - returns correct status note for given status
+
+char* CIcqProto::PrepareStatusNote(int nStatus)
+{
+ char *szStatusNote = NULL;
+ BYTE bXStatus = getContactXStatus(NULL);
+
+ // use custom status message as status note
+ if (bXStatus)
+ szStatusNote = getSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, "");
+
+ if (!szStatusNote || !szStatusNote[0])
+ { // get standard status message (no custom status defined)
+ icq_lock l(m_modeMsgsMutex);
+
+ char **pszStatusNote = MirandaStatusToAwayMsg(nStatus);
+ if (pszStatusNote)
+ szStatusNote = null_strdup(*pszStatusNote);
+ }
+
+ if (!szStatusNote)
+ // nothing available set empty status note
+ szStatusNote = null_strdup("");
+
+ return szStatusNote;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SetStatus - sets the protocol status
+
+int __cdecl CIcqProto::SetStatus(int iNewStatus)
+{
+ int nNewStatus = MirandaStatusToSupported(iNewStatus);
+
+ // check if netlib handles are ready
+ if (!m_hServerNetlibUser)
+ return 0;
+
+ if (m_bTempVisListEnabled && icqOnline()) // remove temporary visible users
+ sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_REMOVETEMPVISIBLE, BUL_TEMPVISIBLE);
+
+ if (nNewStatus == m_iStatus)
+ return 0;
+
+ // clear custom status on status change
+ if (getSettingByte(NULL, "XStatusReset", DEFAULT_XSTATUS_RESET))
+ setXStatusEx(0, 0);
+
+ // New status is OFFLINE
+ if (nNewStatus == ID_STATUS_OFFLINE)
+ { // for quick logoff
+ if (icqOnline())
+ { // set offline status note (otherwise the old will remain)
+ char *szOfflineNote = PrepareStatusNote(nNewStatus);
+
+ // Create unnamed event to wait until the status note change process is completed
+ m_hNotifyNameInfoEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ int bNoteChanged = SetStatusNote(szOfflineNote, 0, FALSE);
+
+ SAFE_FREE(&szOfflineNote);
+
+ // Note was changed, wait until the process is over
+ if (bNoteChanged)
+ ICQWaitForSingleObject(m_hNotifyNameInfoEvent, 4000, TRUE);
+
+ // Release the event
+ CloseHandle(m_hNotifyNameInfoEvent);
+ m_hNotifyNameInfoEvent = NULL;
+ }
+
+ m_iDesiredStatus = nNewStatus;
+
+ if (hServerConn)
+ { // Connected, Send disconnect packet
+ icq_sendCloseConnection();
+
+ icq_serverDisconnect(FALSE);
+
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+
+ NetLog_Server("Logged off.");
+ }
+ }
+ else
+ {
+ switch (m_iStatus) {
+
+ // We are offline and need to connect
+ case ID_STATUS_OFFLINE:
+ {
+ // Update user connection settings
+ UpdateGlobalSettings();
+
+ // Read UIN from database
+ m_dwLocalUIN = getContactUin(NULL);
+ if (m_dwLocalUIN == 0)
+ {
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ BroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_BADUSERID);
+ icq_LogMessage(LOG_FATAL, LPGEN("You have not entered a ICQ number.\nConfigure this in Options->Network->ICQ and try again."));
+ return 0;
+ }
+
+ // Set status to 'Connecting'
+ m_iDesiredStatus = nNewStatus;
+ SetCurrentStatus(ID_STATUS_CONNECTING);
+
+ // Read password from database
+ char *pszPwd = GetUserPassword(FALSE);
+
+ if (pszPwd)
+ icq_login(pszPwd);
+ else
+ RequestPassword();
+
+ break;
+ }
+
+ // We are connecting... We only need to change the going online status
+ case ID_STATUS_CONNECTING:
+ m_iDesiredStatus = nNewStatus;
+ break;
+
+ // We are already connected so we should just change status
+ default:
+ SetCurrentStatus(nNewStatus);
+
+ char *szStatusNote = PrepareStatusNote(nNewStatus);
+
+ //! This is a bit tricky, we do trigger status note change thread and then
+ // change the status note right away (this spares one packet) - so SetStatusNote()
+ // will only change User Details Directory
+ SetStatusNote(szStatusNote, 6000, FALSE);
+
+ if (m_iStatus == ID_STATUS_INVISIBLE)
+ {
+ if (m_bSsiEnabled)
+ updateServVisibilityCode(3);
+ icq_setstatus(MirandaStatusToIcq(m_iStatus), szStatusNote);
+ }
+ else
+ {
+ icq_setstatus(MirandaStatusToIcq(m_iStatus), szStatusNote);
+ if (m_bSsiEnabled)
+ updateServVisibilityCode(4);
+ }
+ SAFE_FREE(&szStatusNote);
+
+ if (m_bAimEnabled)
+ {
+ icq_lock l(m_modeMsgsMutex);
+
+ char ** pszStatusNote = MirandaStatusToAwayMsg(m_iStatus);
+
+ if (pszStatusNote)
+ icq_sendSetAimAwayMsgServ(*pszStatusNote);
+ else // clear the away message
+ icq_sendSetAimAwayMsgServ(NULL);
+ }
+ }
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// GetAwayMsgThread - return a contact's status message
+
+struct status_message_thread_data
+{
+ HANDLE hContact;
+ char *szMessage;
+ HANDLE hProcess;
+};
+
+void __cdecl CIcqProto::GetAwayMsgThread( void *pStatusData )
+{
+ status_message_thread_data *pThreadData = (status_message_thread_data*)pStatusData;
+ if (pThreadData) {
+ // wait a little
+ Sleep(100);
+
+ setStatusMsgVar(pThreadData->hContact, pThreadData->szMessage, false);
+
+ TCHAR *tszMsg = mir_utf8decodeT(pThreadData->szMessage);
+ BroadcastAck(pThreadData->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, pThreadData->hProcess, (LPARAM)tszMsg);
+ mir_free(tszMsg);
+
+ SAFE_FREE(&pThreadData->szMessage);
+ SAFE_FREE((void**)&pThreadData);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_GetAwayMsg - returns a contact's away message
+
+HANDLE __cdecl CIcqProto::GetAwayMsg( HANDLE hContact )
+{
+ DWORD dwUin;
+ uid_str szUID;
+
+ if (getContactUid(hContact, &dwUin, &szUID))
+ return 0; // Invalid contact
+
+ if (!dwUin || !CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES))
+ { // No individual status messages, check if the contact has Status Note, if yes give it
+ char *szStatusNote = getSettingStringUtf(hContact, DBSETTING_STATUS_NOTE, NULL);
+
+ if (strlennull(szStatusNote) > 0)
+ { // Give Status Note
+ status_message_thread_data *pThreadData = (status_message_thread_data*)SAFE_MALLOC(sizeof(status_message_thread_data));
+
+ pThreadData->hContact = hContact;
+ pThreadData->szMessage = szStatusNote;
+ pThreadData->hProcess = (HANDLE)GenerateCookie(0);
+ ForkThread(&CIcqProto::GetAwayMsgThread, pThreadData);
+
+ return pThreadData->hProcess;
+ }
+ SAFE_FREE(&szStatusNote);
+ }
+
+ if (!icqOnline())
+ return 0;
+
+ WORD wStatus = getContactStatus(hContact);
+
+ if (dwUin)
+ {
+ int wMessageType = 0;
+
+ switch(wStatus)
+ {
+ case ID_STATUS_ONLINE:
+ if (CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES))
+ wMessageType = MTYPE_AUTOONLINE;
+ break;
+
+ case ID_STATUS_AWAY:
+ wMessageType = MTYPE_AUTOAWAY;
+ break;
+
+ case ID_STATUS_NA:
+ wMessageType = MTYPE_AUTONA;
+ break;
+
+ case ID_STATUS_OCCUPIED:
+ wMessageType = MTYPE_AUTOBUSY;
+ break;
+
+ case ID_STATUS_DND:
+ wMessageType = MTYPE_AUTODND;
+ break;
+
+ case ID_STATUS_FREECHAT:
+ wMessageType = MTYPE_AUTOFFC;
+ break;
+
+ default:
+ break;
+ }
+
+ if (wMessageType)
+ {
+ if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ {
+ int iRes = icq_sendGetAwayMsgDirect(hContact, wMessageType);
+ if (iRes) return (HANDLE)iRes; // we succeded, return
+ }
+ if (CheckContactCapabilities(hContact, CAPF_STATUS_MESSAGES))
+ return (HANDLE)icq_sendGetAwayMsgServExt(hContact, dwUin, szUID, wMessageType,
+ (WORD)(getSettingWord(hContact, "Version", 0)==9?9:ICQ_VERSION)); // Success
+ else
+ return (HANDLE)icq_sendGetAwayMsgServ(hContact, dwUin, wMessageType,
+ (WORD)(getSettingWord(hContact, "Version", 0)==9?9:ICQ_VERSION)); // Success
+ }
+ }
+ else
+ { // AIM contact
+ if (wStatus == ID_STATUS_AWAY)
+ return (HANDLE)icq_sendGetAimAwayMsgServ(hContact, szUID, MTYPE_AUTOAWAY);
+ }
+
+ return 0; // Failure
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSR_AWAYMSG - processes received status mode message
+
+int __cdecl CIcqProto::RecvAwayMsg( HANDLE hContact, int statusMode, PROTORECVEVENT* evt )
+{
+ if (evt->flags & PREF_UTF) {
+ setStatusMsgVar(hContact, evt->szMessage, false);
+
+ TCHAR* pszMsg = mir_utf8decodeT(evt->szMessage);
+ BroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)evt->lParam, (LPARAM)pszMsg);
+ mir_free(pszMsg);
+ }
+ else {
+ setStatusMsgVar(hContact, evt->szMessage, true);
+ BroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)evt->lParam, (LPARAM)(TCHAR*)_A2T(evt->szMessage));
+ }
+ return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSS_AWAYMSG - send status mode message (individual mode)
+
+int __cdecl CIcqProto::SendAwayMsg( HANDLE hContact, HANDLE hProcess, const char* msg )
+{
+ return 1;
+}
+
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PS_SetAwayMsg - sets the away status message
+
+int __cdecl CIcqProto::SetAwayMsg(int status, const TCHAR* msg)
+{
+ icq_lock l(m_modeMsgsMutex);
+
+ char **ppszMsg = MirandaStatusToAwayMsg(MirandaStatusToSupported(status));
+ if (!ppszMsg)
+ return 1; // Failure
+
+ // Prepare UTF-8 status message
+ char *szNewUtf = tchar_to_utf8(msg);
+
+ if (strcmpnull(szNewUtf, *ppszMsg))
+ {
+ // Free old message
+ SAFE_FREE(ppszMsg);
+
+ // Set new message
+ *ppszMsg = szNewUtf;
+ szNewUtf = NULL;
+
+ if ((m_iStatus == status) && icqOnline())
+ { // update current status note
+ char *szNote = *ppszMsg ? *ppszMsg : "";
+
+ BYTE bXStatus = getContactXStatus(NULL);
+ if (!bXStatus)
+ SetStatusNote(szNote, 1000, FALSE);
+
+ if (m_bAimEnabled)
+ icq_sendSetAimAwayMsgServ(*ppszMsg);
+ }
+ }
+ SAFE_FREE(&szNewUtf);
+
+ return 0; // Success
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// GetMyAwayMsg - obtain the current away message
+
+INT_PTR CIcqProto::GetMyAwayMsg(WPARAM wParam, LPARAM lParam)
+{
+ icq_lock l(m_modeMsgsMutex);
+
+ char **ppszMsg = MirandaStatusToAwayMsg(wParam ? wParam : m_iStatus);
+
+ if (!ppszMsg || !*ppszMsg)
+ return 0;
+
+ int nMsgLen = strlennull(*ppszMsg) + 1;
+
+ if (lParam & SGMA_UNICODE)
+ {
+ WCHAR *szMsg = (WCHAR*)_alloca(nMsgLen * sizeof(WCHAR));
+
+ make_unicode_string_static(*ppszMsg, szMsg, nMsgLen);
+ return (INT_PTR)mir_wstrdup(szMsg);
+ }
+ else
+ { // convert to ansi
+ char *szMsg = (char*)_alloca(nMsgLen);
+
+ if (utf8_decode_static(*ppszMsg, szMsg, nMsgLen))
+ return (INT_PTR)mir_strdup(szMsg);
+ }
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// PS_UserIsTyping - sends a UTN notification
+
+int __cdecl CIcqProto::UserIsTyping( HANDLE hContact, int type )
+{
+ if (hContact && icqOnline())
+ {
+ if (CheckContactCapabilities(hContact, CAPF_TYPING))
+ {
+ switch (type) {
+ case PROTOTYPE_SELFTYPING_ON:
+ sendTypingNotification(hContact, MTN_BEGUN);
+ return 0;
+
+ case PROTOTYPE_SELFTYPING_OFF:
+ sendTypingNotification(hContact, MTN_FINISHED);
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnEvent - maintain protocol events
+
+int __cdecl CIcqProto::OnEvent(PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam)
+{
+ switch( eventType ) {
+ case EV_PROTO_ONLOAD:
+ return OnModulesLoaded(0, 0);
+
+ case EV_PROTO_ONEXIT:
+ return OnPreShutdown(0, 0);
+
+ case EV_PROTO_ONOPTIONS:
+ return OnOptionsInit(wParam, lParam);
+
+ case EV_PROTO_ONERASE:
+ {
+ char szDbSetting[MAX_PATH];
+
+ null_snprintf(szDbSetting, sizeof(szDbSetting), "%sP2P", m_szModuleName);
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbSetting);
+ null_snprintf(szDbSetting, sizeof(szDbSetting), "%sSrvGroups", m_szModuleName);
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbSetting);
+ null_snprintf(szDbSetting, sizeof(szDbSetting), "%sGroups", m_szModuleName);
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbSetting);
+ break;
+ }
+
+ case EV_PROTO_ONCONTACTDELETED:
+ return ServListDbContactDeleted(wParam, lParam);
+
+ case EV_PROTO_DBSETTINGSCHANGED:
+ return ServListDbSettingChanged(wParam, lParam);
+ }
+ return 1;
+}
diff --git a/protocols/IcqOscarJ/src/icq_proto.h b/protocols/IcqOscarJ/src/icq_proto.h
new file mode 100644
index 0000000000..aa903689ad
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_proto.h
@@ -0,0 +1,984 @@
+// ---------------------------------------------------------------------------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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Protocol Interface declarations
+//
+// -----------------------------------------------------------------------------
+#ifndef _ICQ_PROTO_H_
+#define _ICQ_PROTO_H_
+
+#include "m_system_cpp.h"
+#include "m_protoint.h"
+
+#define LISTSIZE 100
+
+#define XSTATUS_COUNT 86
+
+struct CIcqProto;
+typedef void ( __cdecl CIcqProto::*IcqThreadFunc )( void* );
+typedef int ( __cdecl CIcqProto::*IcqEventFunc )( WPARAM, LPARAM );
+typedef INT_PTR ( __cdecl CIcqProto::*IcqServiceFunc )( WPARAM, LPARAM );
+typedef INT_PTR ( __cdecl CIcqProto::*IcqServiceFuncParam )( WPARAM, LPARAM, LPARAM );
+
+// for InfoUpdate
+struct userinfo
+{
+ DWORD dwUin;
+ HANDLE hContact;
+ time_t queued;
+};
+
+struct CIcqProto : public PROTO_INTERFACE, public MZeroedObject
+{
+ CIcqProto( const char*, const TCHAR* );
+ ~CIcqProto();
+
+ //====================================================================================
+ // PROTO_INTERFACE
+ //====================================================================================
+
+ virtual HANDLE __cdecl AddToList( int flags, PROTOSEARCHRESULT* psr );
+ virtual HANDLE __cdecl AddToListByEvent( int flags, int iContact, HANDLE hDbEvent );
+
+ virtual int __cdecl Authorize( HANDLE hContact );
+ virtual int __cdecl AuthDeny( HANDLE hContact, const TCHAR* szReason );
+ virtual int __cdecl AuthRecv( HANDLE hContact, PROTORECVEVENT* );
+ virtual int __cdecl AuthRequest( HANDLE hContact, const TCHAR* szMessage );
+
+ virtual HANDLE __cdecl ChangeInfo( int iInfoType, void* pInfoData );
+
+ virtual HANDLE __cdecl FileAllow( HANDLE hContact, HANDLE hTransfer, const TCHAR* szPath );
+ virtual int __cdecl FileCancel( HANDLE hContact, HANDLE hTransfer );
+ virtual int __cdecl FileDeny( HANDLE hContact, HANDLE hTransfer, const TCHAR* szReason );
+ virtual int __cdecl FileResume( HANDLE hTransfer, int* action, const TCHAR** szFilename );
+
+ virtual DWORD_PTR __cdecl GetCaps( int type, HANDLE hContact = NULL );
+ virtual HICON __cdecl GetIcon( int iconIndex );
+ virtual int __cdecl GetInfo( HANDLE hContact, int infoType );
+
+ virtual HANDLE __cdecl SearchBasic( const PROTOCHAR *id );
+ virtual HANDLE __cdecl SearchByEmail( const PROTOCHAR *email );
+ virtual HANDLE __cdecl SearchByName(const PROTOCHAR *nick, const PROTOCHAR *firstName, const PROTOCHAR *lastName);
+ virtual HWND __cdecl SearchAdvanced( HWND owner );
+ virtual HWND __cdecl CreateExtendedSearchUI( HWND owner );
+
+ virtual int __cdecl RecvContacts( HANDLE hContact, PROTORECVEVENT* );
+ virtual int __cdecl RecvFile( HANDLE hContact, PROTORECVFILET* );
+ virtual int __cdecl RecvMsg( HANDLE hContact, PROTORECVEVENT* );
+ virtual int __cdecl RecvUrl( HANDLE hContact, PROTORECVEVENT* );
+
+ virtual int __cdecl SendContacts( HANDLE hContact, int flags, int nContacts, HANDLE* hContactsList );
+ virtual HANDLE __cdecl SendFile( HANDLE hContact, const TCHAR* szDescription, TCHAR** ppszFiles );
+ virtual int __cdecl SendMsg( HANDLE hContact, int flags, const char* msg );
+ virtual int __cdecl SendUrl( HANDLE hContact, int flags, const char* url );
+
+ virtual int __cdecl SetApparentMode( HANDLE hContact, int mode );
+ virtual int __cdecl SetStatus( int iNewStatus );
+
+ virtual HANDLE __cdecl GetAwayMsg( HANDLE hContact );
+ virtual int __cdecl RecvAwayMsg( HANDLE hContact, int mode, PROTORECVEVENT* evt );
+ virtual int __cdecl SendAwayMsg( HANDLE hContact, HANDLE hProcess, const char* msg );
+ virtual int __cdecl SetAwayMsg( int m_iStatus, const TCHAR* msg );
+
+ virtual int __cdecl UserIsTyping( HANDLE hContact, int type );
+
+ virtual int __cdecl OnEvent( PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam );
+
+ //====| Services |====================================================================
+ INT_PTR __cdecl AddServerContact(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetInfoSetting(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl ChangeInfoEx(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetAvatarCaps(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetAvatarInfo(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetMyAvatar(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetMyAwayMsg(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetXStatus(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetXStatusEx(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetXStatusIcon(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GrantAuthorization(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl menuXStatus(WPARAM wParam,LPARAM lParam,LPARAM fParam);
+ INT_PTR __cdecl OpenWebProfile(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl RequestAdvStatusIconIdx(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl RequestAuthorization(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl RequestXStatusDetails(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl RevokeAuthorization(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SendSms(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SendYouWereAdded(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetMyAvatar(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetNickName(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetPassword(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetXStatus(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetXStatusEx(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl ShowXStatusDetails(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl OnCreateAccMgrUI(WPARAM, LPARAM);
+
+ //====| Events |======================================================================
+ void __cdecl OnAddContactForever( DBCONTACTWRITESETTING* cws, HANDLE hContact );
+ int __cdecl OnIdleChanged( WPARAM, LPARAM );
+ int __cdecl OnModernOptInit( WPARAM, LPARAM );
+ int __cdecl OnModulesLoaded( WPARAM, LPARAM );
+ int __cdecl OnOptionsInit( WPARAM, LPARAM );
+ int __cdecl OnPreShutdown( WPARAM, LPARAM );
+ int __cdecl OnPreBuildContactMenu( WPARAM, LPARAM );
+ int __cdecl OnMsgUserTyping( WPARAM, LPARAM );
+ int __cdecl OnProcessSrmmIconClick( WPARAM, LPARAM );
+ int __cdecl OnProcessSrmmEvent( WPARAM, LPARAM );
+ int __cdecl OnReloadIcons( WPARAM, LPARAM );
+ void __cdecl OnRenameContact( DBCONTACTWRITESETTING* cws, HANDLE hContact );
+ void __cdecl OnRenameGroup( DBCONTACTWRITESETTING* cws, HANDLE hContact );
+ int __cdecl OnUserInfoInit( WPARAM, LPARAM );
+
+ int __cdecl CListMW_ExtraIconsRebuild( WPARAM, LPARAM );
+ int __cdecl CListMW_ExtraIconsApply( WPARAM, LPARAM );
+ int __cdecl OnPreBuildStatusMenu( WPARAM, LPARAM );
+
+ //====| Data |========================================================================
+ IcqIconHandle m_hIconProtocol;
+ HANDLE m_hServerNetlibUser, m_hDirectNetlibUser;
+ HANDLE hxstatuschanged, hxstatusiconchanged;
+
+ BYTE m_bGatewayMode;
+ BYTE m_bSecureLogin;
+ BYTE m_bSecureConnection;
+ BYTE m_bAimEnabled;
+ BYTE m_bUtfEnabled;
+ WORD m_wAnsiCodepage;
+ BYTE m_bDCMsgEnabled;
+ BYTE m_bTempVisListEnabled;
+ BYTE m_bSsiEnabled;
+ BYTE m_bSsiSimpleGroups;
+ BYTE m_bAvatarsEnabled;
+ BYTE m_bXStatusEnabled;
+ BYTE m_bMoodsEnabled;
+
+ icq_critical_section *localSeqMutex;
+ icq_critical_section *connectionHandleMutex;
+
+ int m_bIdleAllow;
+ DWORD m_dwLocalUIN;
+ BYTE m_bConnectionLost;
+
+ char m_szPassword[PASSWORDMAXLEN];
+ BYTE m_bRememberPwd;
+
+ int cheekySearchId;
+ DWORD cheekySearchUin;
+ char* cheekySearchUid;
+
+ /*******************************************************************
+ * Function declarations
+ *******************************************************************/
+
+ //----| capabilities.cpp |------------------------------------------------------------
+ // Deletes all oscar capabilities for a given contact.
+ void ClearAllContactCapabilities(HANDLE hContact);
+
+ // Deletes one or many oscar capabilities for a given contact.
+ void ClearContactCapabilities(HANDLE hContact, DWORD fdwCapabilities);
+
+ // Sets one or many oscar capabilities for a given contact.
+ void SetContactCapabilities(HANDLE hContact, DWORD fdwCapabilities);
+
+ // Returns true if the given contact supports the requested capabilites.
+ BOOL CheckContactCapabilities(HANDLE hContact, DWORD fdwCapabilities);
+
+ // Scans a binary buffer for oscar capabilities and adds them to the contact.
+ void AddCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength);
+
+ // Scans a binary buffer for oscar capabilities and sets them to the contact.
+ void SetCapabilitiesFromBuffer(HANDLE hContact, BYTE *pBuffer, int nLength, BOOL bReset);
+
+ //----| chan_01login.cpp |------------------------------------------------------------
+ void handleLoginChannel(BYTE *buf, WORD datalen, serverthread_info *info);
+
+ //----| chan_02data.cpp |-------------------------------------------------------------
+ void handleDataChannel(BYTE *buf, WORD wLen, serverthread_info *info);
+
+ void LogFamilyError(WORD wFamily, WORD wError);
+
+ //----| chan_03error.cpp |------------------------------------------------------------
+ void handleErrorChannel(unsigned char *buf, WORD datalen);
+
+ //----| chan_04close.cpp |------------------------------------------------------------
+ void handleCloseChannel(BYTE *buf, WORD datalen, serverthread_info *info);
+ void handleLoginReply(BYTE *buf, WORD datalen, serverthread_info *info);
+ void handleMigration(serverthread_info *info);
+ void handleSignonError(WORD wError);
+
+ int connectNewServer(serverthread_info *info);
+
+ //----| chan_05ping.cpp |-------------------------------------------------------------
+ void handlePingChannel(BYTE *buf, WORD wLen);
+
+ void __cdecl KeepAliveThread(void *arg);
+
+ void StartKeepAlive(serverthread_info *info);
+ void StopKeepAlive(serverthread_info *info);
+
+ //----| cookies.cpp |-----------------------------------------------------------------
+ icq_critical_section *cookieMutex; // we want this in avatar thread, used as queue lock
+ LIST<icq_cookie_info> cookies;
+ WORD wCookieSeq;
+
+ DWORD AllocateCookie(BYTE bType, WORD wIdent, HANDLE hContact, void *pvExtra);
+ void FreeCookie(DWORD dwCookie);
+ void FreeCookieByData(BYTE bType, void *pvExtra);
+ void ReleaseCookie(DWORD dwCookie);
+ DWORD GenerateCookie(WORD wIdent);
+
+ int GetCookieType(DWORD dwCookie);
+
+ int FindCookie(DWORD wCookie, HANDLE *phContact, void **ppvExtra);
+ int FindCookieByData(void *pvExtra, DWORD *pdwCookie, HANDLE *phContact);
+ int FindCookieByType(BYTE bType, DWORD *pdwCookie, HANDLE *phContact, void **ppvExtra);
+ int FindMessageCookie(DWORD dwMsgID1, DWORD dwMsgID2, DWORD *pdwCookie, HANDLE *phContact, cookie_message_data **ppvExtra);
+
+ void InitMessageCookie(cookie_message_data *pCookie);
+ cookie_message_data* CreateMessageCookie(WORD bMsgType, BYTE bAckType);
+ cookie_message_data* CreateMessageCookieData(BYTE bMsgType, HANDLE hContact, DWORD dwUin, int bUseSrvRelay);
+
+ void RemoveExpiredCookies(void);
+
+ //----| directpackets.cpp |-----------------------------------------------------------
+ void icq_sendDirectMsgAck(directconnect* dc, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, char* szCap);
+ DWORD icq_sendGetAwayMsgDirect(HANDLE hContact, int type);
+ void icq_sendAwayMsgReplyDirect(directconnect *dc, WORD wCookie, BYTE msgType, const char** szMsg);
+ void icq_sendFileAcceptDirect(HANDLE hContact, filetransfer *ft);
+ void icq_sendFileDenyDirect(HANDLE hContact, filetransfer *ft, const char *szReason);
+ int icq_sendFileSendDirectv7(filetransfer *ft, const char *pszFiles);
+ int icq_sendFileSendDirectv8(filetransfer *ft, const char *pszFiles);
+ DWORD icq_SendDirectMessage(HANDLE hContact, const char *szMessage, int nBodyLength, WORD wPriority, cookie_message_data *pCookieData, char *szCap);
+ void icq_sendXtrazRequestDirect(HANDLE hContact, DWORD dwCookie, char* szBody, int nBodyLen, WORD wType);
+ void icq_sendXtrazResponseDirect(HANDLE hContact, WORD wCookie, char* szBody, int nBodyLen, WORD wType);
+
+ //----| fam_01service.cpp |-----------------------------------------------------------
+ HANDLE m_hNotifyNameInfoEvent;
+
+ void handleServiceFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info);
+ char* buildUinList(int subtype, WORD wMaxLen, HANDLE *hContactResume);
+ void sendEntireListServ(WORD wFamily, WORD wSubtype, int listType);
+ void setUserInfo(void);
+ void handleServUINSettings(int nPort, serverthread_info *info);
+
+ //----| fam_02location.cpp |----------------------------------------------------------
+ void handleLocationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader);
+ void handleLocationUserInfoReply(BYTE* buf, WORD wLen, DWORD dwCookie);
+
+ //----| fam_03buddy.cpp |-------------------------------------------------------------
+ void handleBuddyFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info);
+ void handleReplyBuddy(BYTE *buf, WORD wPackLen);
+ void handleUserOffline(BYTE *buf, WORD wPackLen);
+ void handleUserOnline(BYTE *buf, WORD wPackLen, serverthread_info *info);
+ void parseStatusNote(DWORD dwUin, char *szUid, HANDLE hContact, oscar_tlv_chain *pChain);
+ void handleNotifyRejected(BYTE *buf, WORD wPackLen);
+
+ //----| fam_04message.cpp |-----------------------------------------------------------
+ icq_mode_messages m_modeMsgs;
+ icq_critical_section *m_modeMsgsMutex;
+ HANDLE m_modeMsgsEvent;
+
+ void handleMsgFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader);
+
+ void handleReplyICBM(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleRecvServMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleRecvServMsgType1(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef);
+ void handleRecvServMsgType2(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef);
+ void handleRecvServMsgType4(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwRef);
+ void handleRecvServMsgError(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleRecvMsgResponse(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleServerAck(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleStatusMsgReply(const char *szPrefix, HANDLE hContact, DWORD dwUin, WORD wVersion, int bMsgType, WORD wCookie, const char *szMsg, int nMsgFlags);
+ void handleTypingNotification(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleMissedMsg(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleOffineMessagesReply(BYTE *buf, WORD wLen, WORD wFlags, DWORD dwRef);
+ void handleRecvServMsgContacts(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand);
+ void handleRuntimeError(WORD wError);
+
+ void parseServRelayData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType);
+ void parseServRelayPluginData(BYTE *pDataBuf, WORD wLen, HANDLE hContact, DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wAckType, BYTE bFlags, WORD wStatus, WORD wCookie, WORD wVersion);
+
+ HANDLE handleMessageAck(DWORD dwUin, char *szUID, WORD wCookie, WORD wVersion, int type, WORD wMsgLen, PBYTE buf, BYTE bFlags, int nMsgFlags);
+ void handleMessageTypes(DWORD dwUin, char *szUID, DWORD dwTimestamp, DWORD dwMsgID, DWORD dwMsgID2, WORD wCookie, WORD wVersion, int type, int flags, WORD wAckType, DWORD dwDataLen, WORD wMsgLen, char *pMsg, int nMsgFlags, message_ack_params *pAckParams);
+ void sendMessageTypesAck(HANDLE hContact, int bUnicode, message_ack_params *pArgs);
+ void sendTypingNotification(HANDLE hContact, WORD wMTNCode);
+
+ int unpackPluginTypeId(BYTE **pBuffer, WORD *pwLen, int *pTypeId, WORD *pFunctionId, BOOL bThruDC);
+
+ char* convertMsgToUserSpecificUtf(HANDLE hContact, const char *szMsg);
+
+ //----| fam_09bos.cpp |---------------------------------------------------------------
+ void handleBosFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader);
+ void handlePrivacyRightsReply(unsigned char *pBuffer, WORD wBufferLength);
+ void makeContactTemporaryVisible(HANDLE hContact);
+
+ //----| fam_0alookup.cpp |------------------------------------------------------------
+ void handleLookupFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader);
+
+ void handleLookupEmailReply(BYTE* buf, WORD wLen, DWORD dwCookie);
+ void ReleaseLookupCookie(DWORD dwCookie, cookie_search *pCookie);
+
+ //----| fam_0bstatus.cpp |------------------------------------------------------------
+ void handleStatusFam(unsigned char *pBuffer, WORD wBufferLength, snac_header* pSnacHeader);
+
+ //----| fam_13servclist.cpp |---------------------------------------------------------
+ BOOL bIsSyncingCL;
+
+ WORD m_wServerListLimits[0x20];
+ WORD m_wServerListGroupMaxContacts;
+ WORD m_wServerListRecordNameMaxLength;
+
+ void handleServCListFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader, serverthread_info *info);
+ void handleServerCListRightsReply(BYTE *buf, WORD wLen);
+ void handleServerCListAck(cookie_servlist_action* sc, WORD wError);
+ void handleServerCListReply(BYTE *buf, WORD wLen, WORD wFlags, serverthread_info *info);
+ void handleServerCListItemAdd(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData);
+ void handleServerCListItemUpdate(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData);
+ void handleServerCListItemDelete(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData);
+ void handleRecvAuthRequest(BYTE *buf, WORD wLen);
+ void handleRecvAuthResponse(BYTE *buf, WORD wLen);
+ void handleRecvAdded(BYTE *buf, WORD wLen);
+
+ HANDLE HContactFromRecordName(const char *szRecordName, int *bAdded);
+
+ void processCListReply(const char *szRecordName, WORD wGroupId, WORD wItemId, WORD wItemType, oscar_tlv_chain *pItemData);
+
+ void icq_sendServerBeginOperation(int bImport);
+ void icq_sendServerEndOperation();
+ void sendRosterAck(void);
+
+ int getServerDataFromItemTLV(oscar_tlv_chain* pChain, unsigned char *buf);
+ DWORD updateServerGroupData(WORD wGroupId, void *groupData, int groupSize, DWORD dwOperationFlags);
+ void updateServAvatarHash(BYTE *pHash, int size);
+ void updateServVisibilityCode(BYTE bCode);
+
+ //----| fam_15icqserver.cpp |---------------------------------------------------------
+ void handleIcqExtensionsFam(BYTE *pBuffer, WORD wBufferLength, snac_header* pSnacHeader);
+
+ void handleExtensionError(BYTE *buf, WORD wPackLen);
+ void handleExtensionServerInfo(BYTE *buf, WORD wPackLen, WORD wFlags);
+ void handleExtensionMetaResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wFlags);
+
+ int parseUserInfoRecord(HANDLE hContact, oscar_tlv *pData, UserInfoRecordItem pRecordDef[], int nRecordDef, int nMaxRecords);
+
+ void handleDirectoryQueryResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, WORD wFlags);
+ void handleDirectoryUpdateResponse(BYTE *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype);
+
+ void parseDirectoryUserDetailsData(HANDLE hContact, oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType);
+ void parseDirectorySearchData(oscar_tlv_chain *cDetails, DWORD dwCookie, cookie_directory_data *pCookieData, WORD wReplySubType);
+
+ void parseSearchReplies(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode);
+ void parseUserInfoUpdateAck(unsigned char *databuf, WORD wPacketLen, WORD wCookie, WORD wReplySubtype, BYTE bResultCode);
+
+ void ReleaseSearchCookie(DWORD dwCookie, cookie_search *pCookie);
+
+ //----| fam_17signon.cpp |------------------------------------------------------------
+ void handleAuthorizationFam(BYTE *pBuffer, WORD wBufferLength, snac_header *pSnacHeader, serverthread_info *info);
+ void handleAuthKeyResponse(BYTE *buf, WORD wPacketLen, serverthread_info *info);
+
+ void sendClientAuth(const char *szKey, WORD wKeyLen, BOOL bSecure);
+
+ //----| icq_avatars.cpp |-------------------------------------------------------------
+ icq_critical_section *m_avatarsMutex;
+ avatars_request *m_avatarsQueue;
+
+ BOOL m_avatarsConnectionPending;
+ avatars_server_connection *m_avatarsConnection;
+
+ int bAvatarsFolderInited;
+ HANDLE hAvatarsFolder;
+
+ void requestAvatarConnection();
+ void __cdecl AvatarThread(avatars_server_connection *pInfo);
+
+ void handleAvatarOwnerHash(WORD wItemID, BYTE bFlags, BYTE *pData, BYTE nDataLen);
+ void handleAvatarContactHash(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *pHash, int nHashLen, WORD wOldStatus);
+
+ void InitAvatars();
+ avatars_request *ReleaseAvatarRequestInQueue(avatars_request *request);
+
+ TCHAR* GetOwnAvatarFileName();
+ void GetFullAvatarFileName(int dwUin, const char *szUid, int dwFormat, TCHAR *pszDest, int cbLen);
+ void GetAvatarFileName(int dwUin, const char *szUid, TCHAR *pszDest, int cbLen);
+ int IsAvatarChanged(HANDLE hContact, const BYTE *pHash, int nHashLen);
+
+ int GetAvatarData(HANDLE hContact, DWORD dwUin, const char *szUid, const BYTE *hash, unsigned int hashlen, const TCHAR *file);
+ int SetAvatarData(HANDLE hContact, WORD wRef, const BYTE *data, unsigned int datalen);
+
+ void StartAvatarThread(HANDLE hConn, char* cookie, WORD cookieLen);
+ void StopAvatarThread();
+
+ //----| icq_clients.cpp |-------------------------------------------------------------
+ const char* detectUserClient(HANDLE hContact, int nIsICQ, WORD wUserClass, DWORD dwOnlineSince, const char *szCurrentClient, WORD wVersion, DWORD dwFT1, DWORD dwFT2, DWORD dwFT3, BYTE bDirectFlag, DWORD dwDirectCookie, DWORD dwWebPort, BYTE *caps, WORD wLen, BYTE *bClientId, char *szClientBuf);
+
+ //----| icq_db.cpp |------------------------------------------------------------------
+ HANDLE AddEvent(HANDLE hContact, WORD wType, DWORD dwTime, DWORD flags, DWORD cbBlob, PBYTE pBlob);
+ void CreateResidentSetting(const char* szSetting);
+ HANDLE FindFirstContact();
+ HANDLE FindNextContact(HANDLE hContact);
+ int IsICQContact(HANDLE hContact);
+
+ int getSetting(HANDLE hContact, const char *szSetting, DBVARIANT *dbv);
+ BYTE getSettingByte(HANDLE hContact, const char *szSetting, BYTE byDef);
+ WORD getSettingWord(HANDLE hContact, const char *szSetting, WORD wDef);
+ DWORD getSettingDword(HANDLE hContact, const char *szSetting, DWORD dwDef);
+ double getSettingDouble(HANDLE hContact, const char *szSetting, double dDef);
+ int getSettingString(HANDLE hContact, const char *szSetting, DBVARIANT *dbv);
+ int getSettingStringW(HANDLE hContact, const char *szSetting, DBVARIANT *dbv);
+ int getSettingStringStatic(HANDLE hContact, const char *szSetting, char *dest, int dest_len);
+ char* getSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, char *szDef);
+ char* getSettingStringUtf(HANDLE hContact, const char *szSetting, char *szDef);
+ int getContactUid(HANDLE hContact, DWORD *pdwUin, uid_str *ppszUid);
+ DWORD getContactUin(HANDLE hContact);
+ WORD getContactStatus(HANDLE hContact);
+ char* getContactCListGroup(HANDLE hContact);
+
+ int deleteSetting(HANDLE hContact, const char *szSetting);
+
+ int setSettingByte(HANDLE hContact, const char *szSetting, BYTE byValue);
+ int setSettingWord(HANDLE hContact, const char *szSetting, WORD wValue);
+ int setSettingDword(HANDLE hContact, const char *szSetting, DWORD dwValue);
+ int setSettingDouble(HANDLE hContact, const char *szSetting, double dValue);
+ int setSettingString(HANDLE hContact, const char *szSetting, const char *szValue);
+ int setSettingStringW(HANDLE hContact, const char *szSetting, const WCHAR *wszValue);
+ int setSettingStringUtf(HANDLE hContact, const char *szModule, const char *szSetting, const char *szValue);
+ int setSettingStringUtf(HANDLE hContact, const char *szSetting, const char *szValue);
+ int setSettingBlob(HANDLE hContact, const char *szSetting, const BYTE *pValue, const int cbValue);
+ int setContactHidden(HANDLE hContact, BYTE bHidden);
+ void setStatusMsgVar(HANDLE hContact, char* szStatusMsg, bool isAnsi);
+
+ //----| icq_direct.cpp |--------------------------------------------------------------
+ icq_critical_section *directConnListMutex;
+ LIST<directconnect> directConns;
+
+ icq_critical_section *expectedFileRecvMutex;
+ LIST<filetransfer> expectedFileRecvs;
+
+ void __cdecl icq_directThread(struct directthreadstartinfo* dtsi);
+
+ void handleDirectPacket(directconnect* dc, PBYTE buf, WORD wLen);
+ void sendPeerInit_v78(directconnect* dc);
+ void sendPeerInitAck(directconnect* dc);
+ void sendPeerMsgInit(directconnect* dc, DWORD dwSeq);
+ void sendPeerFileInit(directconnect* dc);
+ int sendDirectPacket(directconnect* dc, icq_packet* pkt);
+
+ void CloseContactDirectConns(HANDLE hContact);
+ directconnect* FindFileTransferDC(filetransfer* ft);
+ filetransfer* FindExpectedFileRecv(DWORD dwUin, DWORD dwTotalSize);
+ BOOL IsDirectConnectionOpen(HANDLE hContact, int type, int bPassive);
+ void OpenDirectConnection(HANDLE hContact, int type, void* pvExtra);
+ void CloseDirectConnection(directconnect *dc);
+ int SendDirectMessage(HANDLE hContact, icq_packet *pkt);
+
+ //----| icq_directmsg.cpp |-----------------------------------------------------------
+ void handleDirectMessage(directconnect* dc, PBYTE buf, WORD wLen);
+ void handleDirectGreetingMessage(directconnect* dc, PBYTE buf, WORD wLen, WORD wCommand, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags, WORD wStatus, WORD wFlags, char* pszText);
+
+ //----| icq_filerequests.cpp |--------------------------------------------------------
+ filetransfer* CreateFileTransfer(HANDLE hContact, DWORD dwUin, int nVersion);
+
+ void handleFileAck(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, WORD wStatus, char* pszText);
+ void handleFileRequest(PBYTE buf, WORD wLen, DWORD dwUin, DWORD dwCookie, DWORD dwID1, DWORD dwID2, char* pszDescription, int nVersion, BOOL bDC);
+ void handleDirectCancel(directconnect *dc, PBYTE buf, WORD wLen, WORD wCommand, DWORD dwCookie, WORD wMessageType, WORD wStatus, WORD wFlags, char* pszText);
+
+ void icq_CancelFileTransfer(HANDLE hContact, filetransfer* ft);
+
+ //----| icq_filetransfer.cpp |--------------------------------------------------------
+ void icq_AcceptFileTransfer(HANDLE hContact, filetransfer *ft);
+ void icq_sendFileResume(filetransfer *ft, int action, const char *szFilename);
+ void icq_InitFileSend(filetransfer *ft);
+
+ void handleFileTransferPacket(directconnect *dc, PBYTE buf, WORD wLen);
+ void handleFileTransferIdle(directconnect *dc);
+
+ //----| icq_infoupdate.cpp |----------------------------------------------------------
+ icq_critical_section *infoUpdateMutex;
+ HANDLE hInfoQueueEvent;
+ int nInfoUserCount;
+ int bInfoPendingUsers;
+ BOOL bInfoUpdateEnabled;
+ BOOL bInfoUpdateRunning;
+ HANDLE hInfoThread;
+ DWORD dwInfoActiveRequest;
+ userinfo m_infoUpdateList[LISTSIZE];
+
+ void __cdecl InfoUpdateThread(void*);
+
+ void icq_InitInfoUpdate(void); // Queues all outdated users
+ BOOL icq_QueueUser(HANDLE hContact); // Queue one UIN to the list for updating
+ void icq_DequeueUser(DWORD dwUin); // Remove one UIN from the list
+ void icq_RescanInfoUpdate(); // Add all outdated contacts to the list
+ void icq_InfoUpdateCleanup(void); // Clean up on exit
+ void icq_EnableUserLookup(BOOL bEnable); // Enable/disable user info lookups
+
+ //----| log.cpp |-----------------------------------------------------------------
+ BOOL bErrorBoxVisible;
+
+ void __cdecl icq_LogMessageThread(void* arg);
+
+ void icq_LogMessage(int level, const char *szMsg);
+ void icq_LogUsingErrorCode(int level, DWORD dwError, const char *szMsg); //szMsg is optional
+ void icq_LogFatalParam(const char *szMsg, WORD wError);
+
+ //----| icq_packet.cpp |--------------------------------------------------------------
+ void ppackLETLVLNTSfromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType);
+ void ppackLETLVWordLNTSfromDB(PBYTE *buf, int *buflen, WORD w, const char *szSetting, WORD wType);
+ void ppackLETLVLNTSBytefromDB(PBYTE *buf, int *buflen, const char *szSetting, BYTE b, WORD wType);
+
+ void ppackTLVStringFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType);
+ void ppackTLVStringUtfFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wType);
+ void ppackTLVDateFromDB(PBYTE *buf, int *buflen, const char *szSettingYear, const char *szSettingMonth, const char *szSettingDay, WORD wType);
+
+ int ppackTLVWordStringItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID);
+ int ppackTLVWordStringUtfItemFromDB(PBYTE *buf, int *buflen, const char *szSetting, WORD wTypeID, WORD wTypeData, WORD wID);
+
+ BOOL unpackUID(BYTE **ppBuf, WORD *pwLen, DWORD *pdwUIN, uid_str *ppszUID);
+
+ //----| icq_popups.cpp |--------------------------------------------------------------
+ int ShowPopUpMsg(HANDLE hContact, const char *szTitle, const char *szMsg, BYTE bType);
+
+ //----| icq_proto.cpp |--------------------------------------------------------------
+ void __cdecl CheekySearchThread( void* );
+
+ void __cdecl GetAwayMsgThread( void *pStatusData );
+
+ char* PrepareStatusNote(int nStatus);
+
+ //----| icq_rates.cpp |---------------------------------------------------------------
+ icq_critical_section *m_ratesMutex;
+ rates *m_rates;
+
+ rates_queue *m_ratesQueue_Request; // rate queue for xtraz requests
+ rates_queue *m_ratesQueue_Response; // rate queue for msg responses
+
+ int handleRateItem(rates_queue_item *item, int nQueueType = RQT_DEFAULT, int nMinDelay = 0, BOOL bAllowDelay = TRUE);
+
+ void __cdecl rateDelayThread(struct rate_delay_args *pArgs);
+
+ //----| icq_server.cpp |--------------------------------------------------------------
+ HANDLE hServerConn;
+ WORD wListenPort;
+ WORD wLocalSequence;
+ UINT serverThreadId;
+ HANDLE serverThreadHandle;
+
+ __inline bool icqOnline() const
+ { return (m_iStatus != ID_STATUS_OFFLINE && m_iStatus != ID_STATUS_CONNECTING);
+ }
+
+ void __cdecl SendPacketAsyncThread(icq_packet* pArgs);
+ void __cdecl ServerThread(serverthread_start_info *infoParam);
+
+ void icq_serverDisconnect(BOOL bBlock);
+ void icq_login(const char* szPassword);
+
+ int handleServerPackets(BYTE *buf, int len, serverthread_info *info);
+ void sendServPacket(icq_packet *pPacket);
+ void sendServPacketAsync(icq_packet *pPacket);
+
+ int IsServerOverRate(WORD wFamily, WORD wCommand, int nLevel);
+
+ //----| icq_servlist.cpp |------------------------------------------------------------
+ HANDLE hHookSettingChanged;
+ HANDLE hHookContactDeleted;
+ HANDLE hHookCListGroupChange;
+ icq_critical_section *servlistMutex;
+
+ DWORD* pdwServerIDList;
+ int nServerIDListCount;
+ int nServerIDListSize;
+
+ // server-list update board
+ icq_critical_section *servlistQueueMutex;
+ int servlistQueueCount;
+ int servlistQueueSize;
+ ssiqueueditems **servlistQueueList;
+ int servlistQueueState;
+ HANDLE servlistQueueThreadHandle;
+ int servlistEditCount;
+
+ void servlistBeginOperation(int operationCount, int bImport);
+ void servlistEndOperation(int operationCount);
+
+ void __cdecl servlistQueueThread(void* queueState);
+
+ void servlistQueueAddGroupItem(servlistgroupitem* pGroupItem, int dwTimeout);
+ int servlistHandlePrimitives(DWORD dwOperation);
+ void servlistProcessLogin();
+
+ void servlistPostPacket(icq_packet* packet, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout);
+ void servlistPostPacketDouble(icq_packet* packet1, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout, icq_packet* packet2, WORD wAction2);
+
+ // server-list pending queue
+ int servlistPendingCount;
+ int servlistPendingSize;
+ servlistpendingitem** servlistPendingList;
+
+ int servlistPendingFindItem(int nType, HANDLE hContact, const char *pszGroup);
+ void servlistPendingAddItem(servlistpendingitem* pItem);
+ servlistpendingitem* servlistPendingRemoveItem(int nType, HANDLE hContact, const char *pszGroup);
+
+ void servlistPendingAddContactOperation(HANDLE hContact, LPARAM param, PENDING_CONTACT_CALLBACK callback, DWORD flags);
+ void servlistPendingAddGroupOperation(const char *pszGroup, LPARAM param, PENDING_GROUP_CALLBACK callback, DWORD flags);
+ int servlistPendingAddContact(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM param, PENDING_CONTACT_CALLBACK callback, int bDoInline, LPARAM operationParam = 0, PENDING_CONTACT_CALLBACK operationCallback = NULL);
+ int servlistPendingAddGroup(const char *pszGroup, WORD wGroupID, LPARAM param, PENDING_GROUP_CALLBACK callback, int bDoInline, LPARAM operationParam = 0, PENDING_GROUP_CALLBACK operationCallback = NULL);
+ void servlistPendingRemoveContact(HANDLE hContact, WORD wContactID, WORD wGroupID, int nResult);
+ void servlistPendingRemoveGroup(const char *pszGroup, WORD wGroupID, int nResult);
+ void servlistPendingFlushOperations();
+
+ // server-list support functions
+ int nJustAddedCount;
+ int nJustAddedSize;
+ HANDLE* pdwJustAddedList;
+
+ void AddJustAddedContact(HANDLE hContact);
+ BOOL IsContactJustAdded(HANDLE hContact);
+ void FlushJustAddedContacts();
+
+ WORD GenerateServerID(int bGroupType, int bFlags, int wCount = 0);
+ void ReserveServerID(WORD wID, int bGroupType, int bFlags);
+ void FreeServerID(WORD wID, int bGroupType);
+ BOOL CheckServerID(WORD wID, unsigned int wCount);
+ void FlushServerIDs();
+ void LoadServerIDs();
+ void StoreServerIDs();
+
+ void* collectGroups(int *count);
+ void* collectBuddyGroup(WORD wGroupID, int *count);
+ char* getServListGroupName(WORD wGroupID);
+ void setServListGroupName(WORD wGroupID, const char *szGroupName);
+ WORD getServListGroupLinkID(const char *szPath);
+ void setServListGroupLinkID(const char *szPath, WORD wGroupID);
+ int IsServerGroupsDefined();
+ char* getServListGroupCListPath(WORD wGroupId);
+ char* getServListUniqueGroupName(const char *szGroupName, int bAlloced);
+
+ int __cdecl servlistCreateGroup_gotParentGroup(const char *szGroup, WORD wGroupID, LPARAM param, int nResult);
+ int __cdecl servlistCreateGroup_Ready(const char *szGroup, WORD groupID, LPARAM param, int nResult);
+ void servlistCreateGroup(const char *szGroupPath, LPARAM param, PENDING_GROUP_CALLBACK callback);
+
+ int __cdecl servlistAddContact_gotGroup(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult);
+ int __cdecl servlistAddContact_Ready(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM lParam, int nResult);
+ void servlistAddContact(HANDLE hContact, const char *pszGroup);
+
+ int __cdecl servlistRemoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult);
+ void servlistRemoveContact(HANDLE hContact);
+
+ int __cdecl servlistMoveContact_gotTargetGroup(const char *szGroup, WORD wNewGroupID, LPARAM lParam, int nResult);
+ int __cdecl servlistMoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult);
+ void servlistMoveContact(HANDLE hContact, const char *pszNewGroup);
+
+ int __cdecl servlistUpdateContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult);
+ void servlistUpdateContact(HANDLE hContact);
+
+ int __cdecl servlistRenameGroup_Ready(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult);
+ void servlistRenameGroup(char *szGroup, WORD wGroupId, char *szNewGroup);
+
+ int __cdecl servlistRemoveGroup_Ready(const char *szGroup, WORD groupID, LPARAM lParam, int nResult);
+ void servlistRemoveGroup(const char *szGroup, WORD wGroupId);
+
+ void removeGroupPathLinks(WORD wGroupID);
+ int getServListGroupLevel(WORD wGroupId);
+
+ void resetServContactAuthState(HANDLE hContact, DWORD dwUin);
+
+ void FlushSrvGroupsCache();
+ int getCListGroupHandle(const char *szGroup);
+ int getCListGroupExists(const char *szGroup);
+ int moveContactToCListGroup(HANDLE hContact, const char *szGroup); /// TODO: this should be DB function
+
+ DWORD icq_sendServerItem(DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wItemId, const char *szName, BYTE *pTLVs, int nTlvLength, WORD wItemType, DWORD dwOperation, DWORD dwTimeout, void **doubleObject);
+ DWORD icq_sendServerContact(HANDLE hContact, DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wContactId, DWORD dwOperation, DWORD dwTimeout, void **doubleObject);
+ DWORD icq_sendSimpleItem(DWORD dwCookie, WORD wAction, DWORD dwUin, char* szUID, WORD wGroupId, WORD wItemId, WORD wItemType, DWORD dwOperation, DWORD dwTimeout);
+ DWORD icq_sendServerGroup(DWORD dwCookie, WORD wAction, WORD wGroupId, const char *szName, void *pContent, int cbContent, DWORD dwOperationFlags);
+
+ DWORD icq_modifyServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wAction, DWORD dwOperation, WORD wItemId, WORD wType);
+ DWORD icq_removeServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType);
+ DWORD icq_addServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType);
+
+ int __cdecl ServListDbSettingChanged(WPARAM wParam, LPARAM lParam);
+ int __cdecl ServListDbContactDeleted(WPARAM wParam, LPARAM lParam);
+ int __cdecl ServListCListGroupChange(WPARAM wParam, LPARAM lParam);
+
+ //----| stdpackets.cpp |----------------------------------------------------------
+ void icq_sendCloseConnection();
+
+ void icq_requestnewfamily(WORD wFamily, void (CIcqProto::*familyhandler)(HANDLE hConn, char* cookie, WORD cookieLen));
+
+ void icq_setidle(int bAllow);
+ void icq_setstatus(WORD wStatus, const char *szStatusNote = NULL);
+ DWORD icq_sendGetInfoServ(HANDLE, DWORD, int);
+ DWORD icq_sendGetAimProfileServ(HANDLE hContact, char *szUid);
+ DWORD icq_sendGetAwayMsgServ(HANDLE, DWORD, int, WORD);
+ DWORD icq_sendGetAwayMsgServExt(HANDLE hContact, DWORD dwUin, char *szUID, int type, WORD wVersion);
+ DWORD icq_sendGetAimAwayMsgServ(HANDLE hContact, char *szUID, int type);
+ void icq_sendSetAimAwayMsgServ(const char *szMsg);
+
+ void icq_sendFileSendServv7(filetransfer* ft, const char *szFiles);
+ void icq_sendFileSendServv8(filetransfer* ft, const char *szFiles, int nAckType);
+
+ void icq_sendFileAcceptServ(DWORD dwUin, filetransfer *ft, int nAckType);
+ void icq_sendFileAcceptServv7(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char *szFiles, const char *szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType);
+ void icq_sendFileAcceptServv8(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char *szFiles, const char *szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType);
+
+ void icq_sendFileDenyServ(DWORD dwUin, filetransfer *ft, const char *szReason, int nAckType);
+
+ DWORD icq_sendAdvancedSearchServ(BYTE *fieldsBuffer,int bufferLen);
+ DWORD icq_changeUserPasswordServ(const char *szPassword);
+ DWORD icq_changeUserDirectoryInfoServ(const BYTE *pData, WORD wDataLen, BYTE bRequestType);
+ void icq_sendGenericContact(DWORD dwUin, const char *szUid, WORD wFamily, WORD wSubType);
+ void icq_sendNewContact(DWORD dwUin, const char *szUid);
+ void icq_sendRemoveContact(DWORD dwUin, const char *szUid);
+ void icq_sendChangeVisInvis(HANDLE hContact, DWORD dwUin, char* szUID, int list, int add);
+ void icq_sendEntireVisInvisList(int);
+ void icq_sendAwayMsgReplyServ(DWORD, DWORD, DWORD, WORD, WORD, BYTE, char **);
+ void icq_sendAwayMsgReplyServExt(DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char **szMsg);
+
+ DWORD icq_sendSMSServ(const char *szPhoneNumber, const char *szMsg);
+ void icq_sendMessageCapsServ(DWORD dwUin);
+ void icq_sendRevokeAuthServ(DWORD dwUin, char *szUid);
+ void icq_sendGrantAuthServ(DWORD dwUin, const char *szUid, const char *szMsg);
+ void icq_sendAuthReqServ(DWORD dwUin, char* szUid, const char *szMsg);
+ void icq_sendAuthResponseServ(DWORD dwUin, char* szUid,int auth,const TCHAR *szReason);
+ void icq_sendYouWereAddedServ(DWORD,DWORD);
+
+ DWORD sendDirectorySearchPacket(const BYTE *pSearchData, WORD wDataLen, WORD wPage, BOOL bOnlineUsersOnly);
+ DWORD sendTLVSearchPacket(BYTE bType, char* pSearchDataBuf, WORD wSearchType, WORD wInfoLen, BOOL bOnlineUsersOnly);
+ void sendOwnerInfoRequest(void);
+ DWORD sendUserInfoMultiRequest(BYTE *pRequestData, WORD wDataLen, int nItems);
+
+ DWORD icq_SendChannel1Message(DWORD dwUin, char *szUID, HANDLE hContact, char *pszText, cookie_message_data *pCookieData);
+ DWORD icq_SendChannel1MessageW(DWORD dwUin, char *szUID, HANDLE hContact, WCHAR *pszText, cookie_message_data *pCookieData); // UTF-16
+ DWORD icq_SendChannel2Message(DWORD dwUin, HANDLE hContact, const char *szMessage, int nBodyLength, WORD wPriority, cookie_message_data *pCookieData, char *szCap);
+ DWORD icq_SendChannel2Contacts(DWORD dwUin, char *szUid, HANDLE hContact, const char *pData, WORD wDataLen, const char *pNames, WORD wNamesLen, cookie_message_data *pCookieData);
+ DWORD icq_SendChannel4Message(DWORD dwUin, HANDLE hContact, BYTE bMsgType, WORD wMsgLen, const char *szMsg, cookie_message_data *pCookieData);
+
+ void icq_sendAdvancedMsgAck(DWORD, DWORD, DWORD, WORD, BYTE, BYTE);
+ void icq_sendContactsAck(DWORD dwUin, char *szUid, DWORD dwMsgID1, DWORD dwMsgID2);
+
+ void icq_sendReverseReq(directconnect *dc, DWORD dwCookie, cookie_message_data *pCookie);
+ void icq_sendReverseFailed(directconnect* dc, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwCookie);
+
+ void icq_sendXtrazRequestServ(DWORD dwUin, DWORD dwCookie, char* szBody, int nBodyLen, cookie_message_data *pCookieData);
+ void icq_sendXtrazResponseServ(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szBody, int nBodyLen, int nType);
+
+ DWORD SearchByUin(DWORD dwUin);
+ DWORD SearchByNames(const char *pszNick, const char *pszFirstName, const char *pszLastName, WORD wPage);
+ DWORD SearchByMail(const char *pszEmail);
+
+ DWORD icq_searchAimByEmail(const char* pszEmail, DWORD dwSearchId);
+
+ void oft_sendFileRequest(DWORD dwUin, char *szUid, oscar_filetransfer *ft, const char *pszFiles, DWORD dwLocalInternalIP);
+ void oft_sendFileAccept(DWORD dwUin, char *szUid, oscar_filetransfer *ft);
+ void oft_sendFileDeny(DWORD dwUin, char *szUid, oscar_filetransfer *ft);
+ void oft_sendFileCancel(DWORD dwUin, char *szUid, oscar_filetransfer *ft);
+ void oft_sendFileResponse(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResponse);
+ void oft_sendFileRedirect(DWORD dwUin, char *szUid, oscar_filetransfer *ft, DWORD dwIP, WORD wPort, int bProxy);
+
+ //---- | icq_svcs.cpp |----------------------------------------------------------------
+ HANDLE AddToListByUIN(DWORD dwUin, DWORD dwFlags);
+ HANDLE AddToListByUID(const char *szUID, DWORD dwFlags);
+
+ void ICQAddRecvEvent(HANDLE hContact, WORD wType, PROTORECVEVENT* pre, DWORD cbBlob, PBYTE pBlob, DWORD flags);
+ INT_PTR __cdecl IcqAddCapability(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl IcqCheckCapability(WPARAM wParam, LPARAM lParam);
+
+ std::list<ICQ_CUSTOMCAP*> CustomCapList;
+
+ //----| icq_uploadui.cpp |------------------------------------------------------------
+ void ShowUploadContactsDialog(void);
+
+ //----| icq_xstatus.cpp |-------------------------------------------------------------
+ int m_bHideXStatusUI;
+ int m_bHideXStatusMenu;
+ int bXStatusExtraIconsReady;
+ HANDLE hHookExtraIconsRebuild;
+ HANDLE hHookStatusBuild;
+ HANDLE hHookExtraIconsApply;
+ HANDLE hXStatusExtraIcons[XSTATUS_COUNT];
+ IcqIconHandle hXStatusIcons[XSTATUS_COUNT];
+ HANDLE hXStatusItems[XSTATUS_COUNT + 1];
+
+ int hXStatusCListIcons[XSTATUS_COUNT];
+ BOOL bXStatusCListIconsValid[XSTATUS_COUNT];
+
+ void InitXStatusItems(BOOL bAllowStatus);
+ BYTE getContactXStatus(HANDLE hContact);
+ DWORD sendXStatusDetailsRequest(HANDLE hContact, int bForced);
+ DWORD requestXStatusDetails(HANDLE hContact, BOOL bAllowDelay);
+ HICON getXStatusIcon(int bStatus, UINT flags);
+ void releaseXStatusIcon(int bStatus, UINT flags);
+ void setXStatusEx(BYTE bXStatus, BYTE bQuiet);
+ void setContactExtraIcon(HANDLE hContact, int xstatus);
+ void handleXStatusCaps(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *caps, int capsize, char *moods, int moodsize);
+ void updateServerCustomStatus(int fullUpdate);
+
+ void InitXStatusIcons();
+ void UninitXStatusIcons();
+
+ //----| icq_xtraz.cpp |---------------------------------------------------------------
+ void handleXtrazNotify(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC);
+ void handleXtrazNotifyResponse(DWORD dwUin, HANDLE hContact, WORD wCookie, char* szMsg, int nMsgLen);
+
+ void handleXtrazInvitation(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC);
+ void handleXtrazData(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC);
+
+ DWORD SendXtrazNotifyRequest(HANDLE hContact, char* szQuery, char* szNotify, int bForced);
+ void SendXtrazNotifyResponse(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szResponse, int nResponseLen, BOOL bThruDC);
+
+ //----| init.cpp |--------------------------------------------------------------------
+ void UpdateGlobalSettings();
+
+ //----| loginpassword.cpp |-----------------------------------------------------------
+ void RequestPassword();
+
+ //----| oscar_filetransfer.cpp |------------------------------------------------------
+ icq_critical_section *oftMutex;
+ int fileTransferCount;
+ basic_filetransfer** fileTransferList;
+
+ oscar_filetransfer* CreateOscarTransfer();
+ filetransfer *CreateIcqFileTransfer();
+ void ReleaseFileTransfer(void *ft);
+ void SafeReleaseFileTransfer(void **ft);
+ oscar_filetransfer* FindOscarTransfer(HANDLE hContact, DWORD dwID1, DWORD dwID2);
+
+ oscar_listener* CreateOscarListener(oscar_filetransfer *ft, NETLIBNEWCONNECTIONPROC_V2 handler);
+ void ReleaseOscarListener(oscar_listener **pListener);
+
+ void OpenOscarConnection(HANDLE hContact, oscar_filetransfer *ft, int type);
+ void CloseOscarConnection(oscar_connection *oc);
+ int CreateOscarProxyConnection(oscar_connection *oc);
+
+ int getFileTransferIndex(void *ft);
+ int IsValidFileTransfer(void *ft);
+ int IsValidOscarTransfer(void *ft);
+
+ void handleRecvServMsgOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand);
+ void handleRecvServResponseOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, void* ft);
+
+ HANDLE oftInitTransfer(HANDLE hContact, DWORD dwUin, char *szUid, const TCHAR **pszFiles, const TCHAR *szDescription);
+ HANDLE oftFileAllow(HANDLE hContact, HANDLE hTransfer, const TCHAR *szPath);
+ DWORD oftFileDeny(HANDLE hContact, HANDLE hTransfer, const TCHAR *szReason);
+ DWORD oftFileCancel(HANDLE hContact, HANDLE hTransfer);
+ void oftFileResume(oscar_filetransfer *ft, int action, const TCHAR *szFilename);
+
+ void sendOscarPacket(oscar_connection *oc, icq_packet *packet);
+ void handleOFT2FramePacket(oscar_connection *oc, WORD datatype, BYTE *pBuffer, WORD wLen);
+ void sendOFT2FramePacket(oscar_connection *oc, WORD datatype);
+
+ void proxy_sendInitTunnel(oscar_connection *oc);
+ void proxy_sendJoinTunnel(oscar_connection *oc, WORD wPort);
+
+ //----| stdpackets.cpp |--------------------------------------------------------------
+ void __cdecl oft_connectionThread(struct oscarthreadstartinfo *otsi);
+
+ int oft_handlePackets(oscar_connection *oc, BYTE *buf, int len);
+ int oft_handleFileData(oscar_connection *oc, BYTE *buf, int len);
+ int oft_handleProxyData(oscar_connection *oc, BYTE *buf, int len);
+ void oft_sendFileData(oscar_connection *oc);
+ void oft_sendPeerInit(oscar_connection *oc);
+ void oft_sendFileReply(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResult);
+
+ //----| upload.cpp |------------------------------------------------------------------
+ int StringToListItemId(const char *szSetting,int def);
+
+ //----| utilities.cpp |---------------------------------------------------------------
+ int BroadcastAck(HANDLE hContact,int type,int result,HANDLE hProcess,LPARAM lParam);
+ char* ConvertMsgToUserSpecificAnsi(HANDLE hContact, const char* szMsg);
+
+ char* GetUserStoredPassword(char *szBuffer, int cbSize);
+ char* GetUserPassword(BOOL bAlways);
+ WORD GetMyStatusFlags();
+
+ DWORD ReportGenericSendError(HANDLE hContact, int nType, const char* szErrorMsg);
+ void SetCurrentStatus(int nStatus);
+
+ void ForkThread( IcqThreadFunc pFunc, void* arg );
+ HANDLE ForkThreadEx( IcqThreadFunc pFunc, void* arg, UINT* threadID = NULL );
+
+ void __cdecl ProtocolAckThread(icq_ack_args* pArguments);
+ void SendProtoAck(HANDLE hContact, DWORD dwCookie, int nAckResult, int nAckType, char* pszMessage);
+
+ HANDLE CreateProtoEvent(const char* szEvent);
+ void CreateProtoService(const char* szService, IcqServiceFunc serviceProc);
+ void CreateProtoServiceParam(const char* szService, IcqServiceFuncParam serviceProc, LPARAM lParam);
+ HANDLE HookProtoEvent(const char* szEvent, IcqEventFunc pFunc);
+
+ int NetLog_Server(const char *fmt,...);
+ int NetLog_Direct(const char *fmt,...);
+ int NetLog_Uni(BOOL bDC, const char *fmt,...);
+
+ icq_critical_section *contactsCacheMutex;
+ LIST<icq_contacts_cache> contactsCache;
+
+ void AddToContactsCache(HANDLE hContact, DWORD dwUin, const char *szUid);
+ void DeleteFromContactsCache(HANDLE hContact);
+ void InitContactsCache();
+ void UninitContactsCache();
+
+ void AddToSpammerList(DWORD dwUIN);
+ BOOL IsOnSpammerList(DWORD dwUIN);
+
+ HANDLE NetLib_BindPort(NETLIBNEWCONNECTIONPROC_V2 pFunc, void* lParam, WORD* pwPort, DWORD* pdwIntIP);
+
+ HANDLE HandleFromCacheByUid(DWORD dwUin, const char *szUid);
+ HANDLE HContactFromUIN(DWORD dwUin, int *Added);
+ HANDLE HContactFromUID(DWORD dwUin, const char *szUid, int *Added);
+ HANDLE HContactFromAuthEvent(HANDLE hEvent);
+
+ void ResetSettingsOnListReload();
+ void ResetSettingsOnConnect();
+ void ResetSettingsOnLoad();
+
+ int IsMetaInfoChanged(HANDLE hContact);
+
+ char *setStatusNoteText, *setStatusMoodData;
+ void __cdecl SetStatusNoteThread(void *pArguments);
+ int SetStatusNote(const char *szStatusNote, DWORD dwDelay, int bForced);
+ int SetStatusMood(const char *szMoodData, DWORD dwDelay);
+
+ BOOL writeDbInfoSettingString(HANDLE hContact, const char* szSetting, char** buf, WORD* pwLength);
+ BOOL writeDbInfoSettingWord(HANDLE hContact, const char *szSetting, char **buf, WORD* pwLength);
+ BOOL writeDbInfoSettingWordWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength);
+ BOOL writeDbInfoSettingByte(HANDLE hContact, const char *pszSetting, char **buf, WORD* pwLength);
+ BOOL writeDbInfoSettingByteWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength);
+
+ void writeDbInfoSettingTLVStringUtf(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv);
+ void writeDbInfoSettingTLVString(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv);
+ void writeDbInfoSettingTLVWord(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv);
+ void writeDbInfoSettingTLVByte(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv);
+ void writeDbInfoSettingTLVDouble(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv);
+ void writeDbInfoSettingTLVDate(HANDLE hContact, const char *szSettingYear, const char *szSettingMonth, const char *szSettingDay, oscar_tlv_chain *chain, WORD wTlv);
+ void writeDbInfoSettingTLVBlob(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv);
+
+ char** MirandaStatusToAwayMsg(int nStatus);
+
+ BOOL validateStatusMessageRequest(HANDLE hContact, WORD byMessageType);
+};
+
+#endif
diff --git a/protocols/IcqOscarJ/src/icq_rates.cpp b/protocols/IcqOscarJ/src/icq_rates.cpp
new file mode 100644
index 0000000000..e7513ec148
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_rates.cpp
@@ -0,0 +1,529 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Rate Management stuff
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+//
+// Rate Level 1 Management
+/////////////////////////////
+
+rates::rates(CIcqProto *ppro, BYTE *pBuffer, WORD wLen)
+{
+ nGroups = 0;
+ memset(&groups, 0, MAX_RATES_GROUP_COUNT * sizeof(rates_group));
+ this->ppro = ppro;
+
+ // Parse Rate Data Block
+ WORD wCount;
+ unpackWord(&pBuffer, &wCount);
+ wLen -= 2;
+
+ if (wCount > MAX_RATES_GROUP_COUNT)
+ { // just sanity check
+ ppro->NetLog_Server("Rates: Error: Data packet contains too many rate groups!");
+ wCount = MAX_RATES_GROUP_COUNT;
+ }
+
+ nGroups = wCount;
+ // Parse Group details
+ int i;
+ for (i=0; i<wCount; i++)
+ {
+ rates_group *pGroup = &groups[i];
+
+ if (wLen >= 35)
+ {
+ pBuffer += 2; // Group ID
+ unpackDWord(&pBuffer, &pGroup->dwWindowSize);
+ unpackDWord(&pBuffer, &pGroup->dwClearLevel);
+ unpackDWord(&pBuffer, &pGroup->dwAlertLevel);
+ unpackDWord(&pBuffer, &pGroup->dwLimitLevel);
+ pBuffer += 8;
+ unpackDWord(&pBuffer, &pGroup->dwMaxLevel);
+ pBuffer += 5;
+ wLen -= 35;
+ }
+ else
+ { // packet broken, put some basic defaults
+ pGroup->dwWindowSize = 10;
+ pGroup->dwMaxLevel = 5000;
+ }
+ pGroup->rCurrentLevel = pGroup->dwMaxLevel;
+ }
+ // Parse Group associated pairs
+ for (i=0; i<wCount; i++)
+ {
+ rates_group *pGroup = &groups[i];
+ WORD wNum;
+
+ if (wLen<4) break;
+ pBuffer += 2; // Group ID
+ unpackWord(&pBuffer, &wNum);
+ wLen -= 4;
+ if (wLen < wNum*4) break;
+ pGroup->nPairs = wNum;
+ pGroup->pPairs = (WORD*)SAFE_MALLOC(wNum*4);
+ for (int n=0; n<wNum*2; n++)
+ {
+ WORD wItem;
+
+ unpackWord(&pBuffer, &wItem);
+ pGroup->pPairs[n] = wItem;
+ }
+#ifdef _DEBUG
+ ppro->NetLog_Server("Rates: %d# %d pairs.", i+1, wNum);
+#endif
+ wLen -= wNum*4;
+ }
+}
+
+
+rates::~rates()
+{
+ for (int i = 0; i < nGroups; i++)
+ SAFE_FREE((void**)&groups[i].pPairs);
+
+ nGroups = 0;
+}
+
+
+WORD rates::getGroupFromSNAC(WORD wFamily, WORD wCommand)
+{
+ if (this)
+ {
+ for (int i = 0; i < nGroups; i++)
+ {
+ rates_group* pGroup = &groups[i];
+
+ for (int j = 0; j < 2 * pGroup->nPairs; j += 2)
+ {
+ if (pGroup->pPairs[j] == wFamily && pGroup->pPairs[j + 1] == wCommand)
+ { // we found the group
+ return (WORD)(i + 1);
+ }
+ }
+ }
+ _ASSERTE(0);
+ }
+
+ return 0; // Failure
+}
+
+
+WORD rates::getGroupFromPacket(icq_packet *pPacket)
+{
+ if (this)
+ {
+ if (pPacket->nChannel == ICQ_DATA_CHAN && pPacket->wLen >= 0x10)
+ {
+ WORD wFamily, wCommand;
+ BYTE *pBuf = pPacket->pData + 6;
+
+ unpackWord(&pBuf, &wFamily);
+ unpackWord(&pBuf, &wCommand);
+
+ return getGroupFromSNAC(wFamily, wCommand);
+ }
+ }
+ return 0;
+}
+
+
+rates_group* rates::getGroup(WORD wGroup)
+{
+ if (this && wGroup && wGroup <= nGroups)
+ return &groups[wGroup - 1];
+
+ return NULL;
+}
+
+
+int rates::getNextRateLevel(WORD wGroup)
+{
+ rates_group *pGroup = getGroup(wGroup);
+
+ if (pGroup)
+ {
+ int nLevel = pGroup->rCurrentLevel*(pGroup->dwWindowSize-1)/pGroup->dwWindowSize + (GetTickCount() - pGroup->tCurrentLevel)/pGroup->dwWindowSize;
+
+ return nLevel < (int)pGroup->dwMaxLevel ? nLevel : pGroup->dwMaxLevel;
+ }
+ return -1; // Failure
+}
+
+
+int rates::getDelayToLimitLevel(WORD wGroup, int nLevel)
+{
+ rates_group *pGroup = getGroup(wGroup);
+
+ if (pGroup)
+ return (getLimitLevel(wGroup, nLevel) - pGroup->rCurrentLevel)*pGroup->dwWindowSize + pGroup->rCurrentLevel;
+
+ return 0; // Failure
+}
+
+
+void rates::packetSent(icq_packet *pPacket)
+{
+ if (this)
+ {
+ WORD wGroup = getGroupFromPacket(pPacket);
+
+ if (wGroup)
+ updateLevel(wGroup, getNextRateLevel(wGroup));
+ }
+}
+
+
+void rates::updateLevel(WORD wGroup, int nLevel)
+{
+ rates_group *pGroup = getGroup(wGroup);
+
+ if (pGroup)
+ {
+ pGroup->rCurrentLevel = nLevel;
+ pGroup->tCurrentLevel = GetTickCount();
+#ifdef _DEBUG
+ ppro->NetLog_Server("Rates: New level %d for #%d", nLevel, wGroup);
+#endif
+ }
+}
+
+
+int rates::getLimitLevel(WORD wGroup, int nLevel)
+{
+ rates_group *pGroup = getGroup(wGroup);
+
+ if (pGroup)
+ {
+ switch(nLevel)
+ {
+ case RML_CLEAR:
+ return pGroup->dwClearLevel;
+
+ case RML_ALERT:
+ return pGroup->dwAlertLevel;
+
+ case RML_LIMIT:
+ return pGroup->dwLimitLevel;
+
+ case RML_IDLE_10:
+ return pGroup->dwClearLevel + ((pGroup->dwMaxLevel - pGroup->dwClearLevel)/10);
+
+ case RML_IDLE_30:
+ return pGroup->dwClearLevel + (3*(pGroup->dwMaxLevel - pGroup->dwClearLevel)/10);
+
+ case RML_IDLE_50:
+ return pGroup->dwClearLevel + ((pGroup->dwMaxLevel - pGroup->dwClearLevel)/2);
+
+ case RML_IDLE_70:
+ return pGroup->dwClearLevel + (7*(pGroup->dwMaxLevel - pGroup->dwClearLevel)/10);
+ }
+ }
+ return 9999; // some high number - without rates we allow anything
+}
+
+
+void rates::initAckPacket(icq_packet *pPacket)
+{
+ serverPacketInit(pPacket, 10 + nGroups * (int)sizeof(WORD));
+ packFNACHeader(pPacket, ICQ_SERVICE_FAMILY, ICQ_CLIENT_RATE_ACK);
+ for (WORD wGroup = 1; wGroup <= nGroups; wGroup++)
+ packWord(pPacket, wGroup);
+}
+
+
+
+//
+// Rate Level 2 Management
+/////////////////////////////
+
+
+rates_queue_item::rates_queue_item(CIcqProto *ppro, WORD wGroup) : bCreated(FALSE), dwUin(0), szUid(NULL)
+{
+ this->ppro = ppro;
+ this->wGroup = wGroup;
+}
+
+rates_queue_item::~rates_queue_item()
+{
+ if (bCreated)
+ {
+ SAFE_FREE(&szUid);
+ bCreated = FALSE;
+ }
+}
+
+
+BOOL rates_queue_item::isEqual(rates_queue_item *pItem)
+{ // the same event (equal address of _vftable) for the same contact
+ return (pItem->hContact == this->hContact) && (*(void**)pItem == *(void**)this);
+}
+
+
+rates_queue_item* rates_queue_item::copyItem(rates_queue_item *pDest)
+{
+ if (!pDest)
+ pDest = new rates_queue_item(ppro, wGroup);
+
+ pDest->hContact = hContact;
+ pDest->dwUin = dwUin;
+ pDest->szUid = dwUin ? null_strdup(szUid) : NULL;
+ pDest->bCreated = TRUE;
+
+ return pDest;
+}
+
+
+void rates_queue_item::execute()
+{
+#ifdef _DEBUG
+ ppro->NetLog_Server("Rates: Error executing abstract event.");
+#endif
+}
+
+
+BOOL rates_queue_item::isOverRate(int nLevel)
+{
+ icq_lock l(ppro->m_ratesMutex);
+
+ if (ppro->m_rates)
+ return ppro->m_rates->getNextRateLevel(wGroup) < ppro->m_rates->getLimitLevel(wGroup, nLevel);
+
+ return FALSE;
+}
+
+
+rates_queue::rates_queue(CIcqProto *ppro, const char *szDescr, int nLimitLevel, int nWaitLevel, int nDuplicates)
+{
+ this->listsMutex = new icq_critical_section();
+ this->ppro = ppro;
+ this->szDescr = szDescr;
+ limitLevel = nLimitLevel;
+ waitLevel = nWaitLevel;
+ duplicates = nDuplicates;
+}
+
+
+rates_queue::~rates_queue()
+{
+ cleanup();
+ delete listsMutex;
+}
+
+
+// links to functions that are under Rate Control
+struct rate_delay_args
+{
+ int nDelay;
+ rates_queue *queue;
+ IcqRateFunc delaycode;
+};
+
+void __cdecl CIcqProto::rateDelayThread(rate_delay_args *pArgs)
+{
+ SleepEx(pArgs->nDelay, TRUE);
+ (pArgs->queue->*pArgs->delaycode)();
+ SAFE_FREE((void**)&pArgs);
+}
+
+
+void rates_queue::initDelay(int nDelay, IcqRateFunc delaycode)
+{
+#ifdef _DEBUG
+ ppro->NetLog_Server("Rates: Delay %dms", nDelay);
+#endif
+
+ rate_delay_args *pArgs = (rate_delay_args*)SAFE_MALLOC(sizeof(rate_delay_args)); // This will be freed in the new thread
+ pArgs->queue = this;
+ pArgs->nDelay = nDelay;
+ pArgs->delaycode = delaycode;
+
+ ppro->ForkThread((IcqThreadFunc)&CIcqProto::rateDelayThread, pArgs);
+}
+
+
+void rates_queue::cleanup()
+{
+ icq_lock l(listsMutex);
+
+ if (pendingListSize)
+ ppro->NetLog_Server("Rates: Purging %d %s(s).", pendingListSize, szDescr);
+
+ for (int i=0; i < pendingListSize; i++)
+ delete pendingList[i];
+ SAFE_FREE((void**)&pendingList);
+ pendingListSize = 0;
+}
+
+
+void rates_queue::processQueue()
+{
+ if (!pendingList)
+ return;
+
+ if (!ppro->icqOnline())
+ {
+ cleanup();
+ return;
+ }
+ // take from queue, execute
+ rates_queue_item *item = pendingList[0];
+
+ ppro->m_ratesMutex->Enter();
+ listsMutex->Enter();
+ if (item->isOverRate(limitLevel))
+ { // the rate is higher, keep sleeping
+ int nDelay = ppro->m_rates->getDelayToLimitLevel(item->wGroup, ppro->m_rates->getLimitLevel(item->wGroup, waitLevel));
+
+ listsMutex->Leave();
+ ppro->m_ratesMutex->Leave();
+ if (nDelay < 10) nDelay = 10;
+ initDelay(nDelay, &rates_queue::processQueue);
+ return;
+ }
+ ppro->m_ratesMutex->Leave();
+
+ if (pendingListSize > 1)
+ { // we need to keep order
+ memmove(&pendingList[0], &pendingList[1], (pendingListSize - 1) * sizeof(rates_queue_item*));
+ }
+ else
+ SAFE_FREE((void**)&pendingList);
+
+ int bSetupTimer = --pendingListSize != 0;
+
+ listsMutex->Leave();
+
+ if (ppro->icqOnline())
+ {
+ ppro->NetLog_Server("Rates: Resuming %s.", szDescr);
+ item->execute();
+ }
+ else
+ ppro->NetLog_Server("Rates: Discarding %s.", szDescr);
+
+ if (bSetupTimer)
+ {
+ // in queue remained some items, setup timer
+ ppro->m_ratesMutex->Enter();
+ int nDelay = ppro->m_rates->getDelayToLimitLevel(item->wGroup, waitLevel);
+ ppro->m_ratesMutex->Leave();
+
+ if (nDelay < 10) nDelay = 10;
+ initDelay(nDelay, &rates_queue::processQueue);
+ }
+ delete item;
+}
+
+
+void rates_queue::putItem(rates_queue_item *pItem, int nMinDelay)
+{
+ int bFound = FALSE;
+
+ if (!ppro->icqOnline())
+ return;
+
+ ppro->NetLog_Server("Rates: Delaying %s.", szDescr);
+
+ listsMutex->Enter();
+ if (pendingListSize)
+ {
+ for (int i = 0; i < pendingListSize; i++)
+ {
+ if (pendingList[i]->isEqual(pItem))
+ {
+ if (duplicates == -1)
+ { // discard existing, append new item
+ delete pendingList[i];
+ memcpy(&pendingList[i], &pendingList[i + 1], (pendingListSize - i - 1) * sizeof(rates_queue_item*));
+ bFound = TRUE;
+ }
+ else if (duplicates == 1)
+ { // keep existing, ignore new
+ listsMutex->Leave();
+ return;
+ }
+ // otherwise keep existing and append new
+ }
+ }
+ }
+ if (!bFound)
+ { // not found, enlarge the queue
+ pendingListSize++;
+ pendingList = (rates_queue_item**)SAFE_REALLOC(pendingList, pendingListSize * sizeof(rates_queue_item*));
+ }
+ pendingList[pendingListSize - 1] = pItem->copyItem();
+
+ if (pendingListSize == 1)
+ { // queue was empty setup timer
+ listsMutex->Leave();
+ ppro->m_ratesMutex->Enter();
+ int nDelay = ppro->m_rates->getDelayToLimitLevel(pItem->wGroup, waitLevel);
+ ppro->m_ratesMutex->Leave();
+
+ if (nDelay < 10) nDelay = 10;
+ if (nDelay < nMinDelay) nDelay = nMinDelay;
+ initDelay(nDelay, &rates_queue::processQueue);
+ }
+ else
+ listsMutex->Leave();
+}
+
+
+int CIcqProto::handleRateItem(rates_queue_item *item, int nQueueType, int nMinDelay, BOOL bAllowDelay)
+{
+ rates_queue *pQueue = NULL;
+
+ m_ratesMutex->Enter();
+ switch (nQueueType)
+ {
+ case RQT_REQUEST:
+ pQueue = m_ratesQueue_Request;
+ break;
+ case RQT_RESPONSE:
+ pQueue = m_ratesQueue_Response;
+ break;
+ }
+
+ if (pQueue)
+ {
+ if (bAllowDelay && (item->isOverRate(pQueue->waitLevel) || nMinDelay))
+ { // limit reached or min delay configured, add to queue
+ pQueue->putItem(item, nMinDelay);
+
+ m_ratesMutex->Leave();
+ return 1;
+ }
+ }
+ m_ratesMutex->Leave();
+
+ item->execute();
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/icq_rates.h b/protocols/IcqOscarJ/src/icq_rates.h
new file mode 100644
index 0000000000..ab16e74007
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_rates.h
@@ -0,0 +1,146 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Rate management
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_RATES_H
+#define __ICQ_RATES_H
+
+#define MAX_RATES_GROUP_COUNT 5
+
+struct rates_group
+{
+ DWORD dwWindowSize;
+ DWORD dwClearLevel;
+ DWORD dwAlertLevel;
+ DWORD dwLimitLevel;
+ DWORD dwMaxLevel;
+ // current level
+ int rCurrentLevel;
+ int tCurrentLevel;
+ // links
+ WORD *pPairs;
+ int nPairs;
+};
+
+struct rates : public MZeroedObject
+{
+private:
+ CIcqProto *ppro;
+ int nGroups;
+ rates_group groups[MAX_RATES_GROUP_COUNT];
+
+ rates_group *getGroup(WORD wGroup);
+public:
+ rates(CIcqProto *ppro, BYTE *pBuffer, WORD wLen);
+ ~rates();
+
+ WORD getGroupFromSNAC(WORD wFamily, WORD wCommand);
+ WORD getGroupFromPacket(icq_packet *pPacket);
+
+ int getLimitLevel(WORD wGroup, int nLevel);
+ int getDelayToLimitLevel(WORD wGroup, int nLevel);
+ int getNextRateLevel(WORD wGroup);
+
+ void packetSent(icq_packet *pPacket);
+ void updateLevel(WORD wGroup, int nLevel);
+
+ void initAckPacket(icq_packet *pPacket);
+};
+
+#define RML_CLEAR 0x01
+#define RML_ALERT 0x02
+#define RML_LIMIT 0x03
+#define RML_IDLE_10 0x10
+#define RML_IDLE_30 0x11
+#define RML_IDLE_50 0x12
+#define RML_IDLE_70 0x13
+
+// Rates - Level 2
+
+// queue types
+#define RQT_DEFAULT 0 // standard - pushes all items without much delay
+#define RQT_REQUEST 1 // request - pushes only first item on duplicity
+#define RQT_RESPONSE 2 // response - pushes only last item on duplicity
+
+//
+// generic queue item
+//
+struct rates_queue_item : public MZeroedObject
+{
+ friend struct rates_queue;
+protected:
+ CIcqProto *ppro;
+ BOOL bCreated;
+ WORD wGroup;
+
+ virtual BOOL isEqual(rates_queue_item *pItem);
+ virtual rates_queue_item* copyItem(rates_queue_item *pDest = NULL);
+public:
+ rates_queue_item(CIcqProto *ppro, WORD wGroup);
+ virtual ~rates_queue_item();
+
+ BOOL isOverRate(int nLevel);
+
+ virtual void execute();
+
+ HANDLE hContact;
+ DWORD dwUin;
+ char *szUid;
+};
+
+struct rates_queue;
+typedef void (rates_queue::*IcqRateFunc)(void);
+
+//
+// generic item queue (FIFO)
+//
+struct rates_queue : public MZeroedObject
+{
+private:
+ CIcqProto *ppro;
+ const char *szDescr;
+ icq_critical_section *listsMutex; // we need to be thread safe
+ int pendingListSize;
+ rates_queue_item **pendingList;
+ int duplicates;
+protected:
+ void cleanup();
+ void processQueue();
+ void initDelay(int nDelay, IcqRateFunc delaycode);
+public:
+ rates_queue(CIcqProto *ppro, const char *szDescr, int nLimitLevel, int nWaitLevel, int nDuplicates = 0);
+ ~rates_queue();
+
+ void putItem(rates_queue_item *pItem, int nMinDelay);
+
+ int limitLevel; // RML_*
+ int waitLevel;
+};
+
+
+#endif /* __ICQ_RATES_H */
diff --git a/protocols/IcqOscarJ/src/icq_server.cpp b/protocols/IcqOscarJ/src/icq_server.cpp
new file mode 100644
index 0000000000..63a5d63d84
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_server.cpp
@@ -0,0 +1,444 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Manages main server connection, low-level communication
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void icq_newConnectionReceived(HANDLE hNewConnection, DWORD dwRemoteIP, void *pExtra);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ICQ Server thread
+
+void __cdecl CIcqProto::ServerThread(serverthread_start_info *infoParam)
+{
+ serverthread_info info = {0};
+
+ info.isLoginServer = 1;
+ info.wAuthKeyLen = infoParam->wPassLen;
+ null_strcpy((char*)info.szAuthKey, infoParam->szPass, info.wAuthKeyLen);
+ // store server port
+ info.wServerPort = infoParam->nloc.wPort;
+
+ srand(time(NULL));
+
+ ResetSettingsOnConnect();
+
+ // Connect to the login server
+ NetLog_Server("Authenticating to server");
+ {
+ NETLIBOPENCONNECTION nloc = infoParam->nloc;
+ nloc.timeout = 6;
+ if (m_bGatewayMode)
+ nloc.flags |= NLOCF_HTTPGATEWAY;
+
+ hServerConn = NetLib_OpenConnection(m_hServerNetlibUser, NULL, &nloc);
+
+ SAFE_FREE((void**)&nloc.szHost);
+ SAFE_FREE((void**)&infoParam);
+
+ if (hServerConn && m_bSecureConnection)
+ {
+ if (!CallService(MS_NETLIB_STARTSSL, (WPARAM)hServerConn, 0))
+ {
+ icq_LogMessage(LOG_ERROR, LPGEN("Unable to connect to ICQ login server, SSL could not be negotiated"));
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ NetLib_CloseConnection(&hServerConn, TRUE);
+ return;
+ }
+ }
+ }
+
+ // Login error
+ if (hServerConn == NULL)
+ {
+ DWORD dwError = GetLastError();
+
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+
+ icq_LogUsingErrorCode(LOG_ERROR, dwError, LPGEN("Unable to connect to ICQ login server"));
+ return;
+ }
+
+ // Initialize direct connection ports
+ {
+ DWORD dwInternalIP;
+ BYTE bConstInternalIP = getSettingByte(NULL, "ConstRealIP", 0);
+
+ info.hDirectBoundPort = NetLib_BindPort(icq_newConnectionReceived, this, &wListenPort, &dwInternalIP);
+ if (!info.hDirectBoundPort)
+ {
+ icq_LogUsingErrorCode(LOG_WARNING, GetLastError(), LPGEN("Miranda was unable to allocate a port to listen for direct peer-to-peer connections between clients. You will be able to use most of the ICQ network without problems but you may be unable to send or receive files.\n\nIf you have a firewall this may be blocking Miranda, in which case you should configure your firewall to leave some ports open and tell Miranda which ports to use in M->Options->ICQ->Network."));
+ wListenPort = 0;
+ if (!bConstInternalIP) deleteSetting(NULL, "RealIP");
+ }
+ else if (!bConstInternalIP)
+ setSettingDword(NULL, "RealIP", dwInternalIP);
+ }
+
+ // Initialize rate limiting queues
+ {
+ icq_lock l(m_ratesMutex);
+
+ m_ratesQueue_Request = new rates_queue(this, "request", RML_IDLE_30, RML_IDLE_50, 1);
+ m_ratesQueue_Response = new rates_queue(this, "response", RML_IDLE_10, RML_IDLE_30, -1);
+ }
+
+ // This is the "infinite" loop that receives the packets from the ICQ server
+ {
+ int recvResult;
+ NETLIBPACKETRECVER packetRecv = {0};
+
+ info.hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)hServerConn, 0x2400);
+ packetRecv.cbSize = sizeof(packetRecv);
+ packetRecv.dwTimeout = INFINITE;
+ while (serverThreadHandle)
+ {
+ if (info.bReinitRecver)
+ { // we reconnected, reinit struct
+ info.bReinitRecver = 0;
+ ZeroMemory(&packetRecv, sizeof(packetRecv));
+ packetRecv.cbSize = sizeof(packetRecv);
+ packetRecv.dwTimeout = INFINITE;
+ }
+
+ recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)info.hPacketRecver, (LPARAM)&packetRecv);
+
+ if (recvResult == 0)
+ {
+ NetLog_Server("Clean closure of server socket");
+ break;
+ }
+
+ if (recvResult == SOCKET_ERROR)
+ {
+ NetLog_Server("Abortive closure of server socket, error: %d", GetLastError());
+ break;
+ }
+
+ if (m_iDesiredStatus == ID_STATUS_OFFLINE)
+ { // Disconnect requested, send disconnect packet
+ icq_sendCloseConnection();
+
+ // disconnected upon request
+ m_bConnectionLost = FALSE;
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+
+ NetLog_Server("Logged off.");
+ break;
+ }
+
+ // Deal with the packet
+ packetRecv.bytesUsed = handleServerPackets(packetRecv.buffer, packetRecv.bytesAvailable, &info);
+ }
+ serverThreadHandle = NULL;
+
+ // Time to shutdown
+ NetLib_CloseConnection(&hServerConn, TRUE);
+
+ // Close the packet receiver (connection may still be open)
+ NetLib_SafeCloseHandle(&info.hPacketRecver);
+
+ // Close DC port
+ NetLib_SafeCloseHandle(&info.hDirectBoundPort);
+ }
+
+ // disable auto info-update thread
+ icq_EnableUserLookup(FALSE);
+
+ if (m_iStatus != ID_STATUS_OFFLINE && m_iDesiredStatus != ID_STATUS_OFFLINE)
+ {
+ if (!info.bLoggedIn)
+ icq_LogMessage(LOG_FATAL, LPGEN("Connection failed.\nLogin sequence failed for unknown reason.\nTry again later."));
+
+ // set flag indicating we were kicked out
+ m_bConnectionLost = TRUE;
+
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ }
+
+ // signal keep-alive thread to stop
+ StopKeepAlive(&info);
+
+ // Close all open DC connections
+ CloseContactDirectConns(NULL);
+
+ // Close avatar connection if any
+ StopAvatarThread();
+
+ // Offline all contacts
+ HANDLE hContact = FindFirstContact();
+ while (hContact)
+ {
+ DWORD dwUIN;
+ uid_str szUID;
+
+ if (!getContactUid(hContact, &dwUIN, &szUID))
+ {
+ if (getContactStatus(hContact) != ID_STATUS_OFFLINE)
+ {
+ char tmp = 0;
+
+ setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+
+ handleXStatusCaps(dwUIN, szUID, hContact, (BYTE*)&tmp, 0, &tmp, 0);
+ }
+ }
+
+ hContact = FindNextContact(hContact);
+ }
+
+ setSettingDword(NULL, "LogonTS", 0); // clear logon time
+
+ servlistPendingFlushOperations(); // clear pending operations list
+
+ // release rates queues
+ {
+ icq_lock l(m_ratesMutex);
+
+ SAFE_DELETE((MZeroedObject**)&m_ratesQueue_Request);
+ SAFE_DELETE((MZeroedObject**)&m_ratesQueue_Response);
+ SAFE_DELETE((MZeroedObject**)&m_rates);
+ }
+
+ FlushServerIDs(); // clear server IDs list
+
+ NetLog_Server("%s thread ended.", "Server");
+}
+
+
+void CIcqProto::icq_serverDisconnect(BOOL bBlock)
+{
+ if ( !hServerConn)
+ return;
+
+ NetLog_Server("Server shutdown requested");
+ Netlib_Shutdown(hServerConn);
+
+ if (serverThreadHandle) {
+ // Not called from network thread?
+ if (bBlock && GetCurrentThreadId() != serverThreadId)
+ while (ICQWaitForSingleObject(serverThreadHandle, INFINITE) != WAIT_OBJECT_0);
+
+ CloseHandle(serverThreadHandle);
+ serverThreadHandle = NULL;
+ }
+}
+
+
+int CIcqProto::handleServerPackets(BYTE *buf, int len, serverthread_info *info)
+{
+ BYTE channel;
+ WORD sequence;
+ WORD datalen;
+ int bytesUsed = 0;
+
+ while (len > 0)
+ {
+ if (info->bReinitRecver)
+ break;
+
+ // All FLAPS begin with 0x2a
+ if (*buf++ != FLAP_MARKER)
+ break;
+
+ if (len < 6)
+ break;
+
+ unpackByte(&buf, &channel);
+ unpackWord(&buf, &sequence);
+ unpackWord(&buf, &datalen);
+
+ if (len < 6 + datalen)
+ break;
+
+
+#ifdef _DEBUG
+ NetLog_Server("Server FLAP: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen);
+#endif
+
+ switch (channel) {
+ case ICQ_LOGIN_CHAN:
+ handleLoginChannel(buf, datalen, info);
+ break;
+
+ case ICQ_DATA_CHAN:
+ handleDataChannel(buf, datalen, info);
+ break;
+
+ case ICQ_ERROR_CHAN:
+ handleErrorChannel(buf, datalen);
+ break;
+
+ case ICQ_CLOSE_CHAN:
+ handleCloseChannel(buf, datalen, info);
+ break; // we need this for walking thru proxy
+
+ case ICQ_PING_CHAN:
+ handlePingChannel(buf, datalen);
+ break;
+
+ default:
+ NetLog_Server("Warning: Unhandled Server FLAP Channel: Channel %u, Seq %u, Length %u bytes", channel, sequence, datalen);
+ break;
+ }
+
+ /* Increase pointers so we can check for more FLAPs */
+ buf += datalen;
+ len -= (datalen + 6);
+ bytesUsed += (datalen + 6);
+ }
+
+ return bytesUsed;
+}
+
+
+void CIcqProto::sendServPacket(icq_packet *pPacket)
+{
+ // make sure to have the connection handle
+ connectionHandleMutex->Enter();
+
+ if (hServerConn)
+ {
+ int nSendResult;
+
+ // This critsec makes sure that the sequence order doesn't get screwed up
+ localSeqMutex->Enter();
+
+ // :IMPORTANT:
+ // The FLAP sequence must be a WORD. When it reaches 0xFFFF it should wrap to
+ // 0x0000, otherwise we'll get kicked by server.
+ wLocalSequence++;
+
+ // Pack sequence number
+ pPacket->pData[2] = ((wLocalSequence & 0xff00) >> 8);
+ pPacket->pData[3] = (wLocalSequence & 0x00ff);
+
+ nSendResult = Netlib_Send(hServerConn, (const char *)pPacket->pData, pPacket->wLen, 0);
+
+ localSeqMutex->Leave();
+ connectionHandleMutex->Leave();
+
+ // Send error
+ if (nSendResult == SOCKET_ERROR)
+ {
+ icq_LogUsingErrorCode(LOG_ERROR, GetLastError(), LPGEN("Your connection with the ICQ server was abortively closed"));
+ icq_serverDisconnect(FALSE);
+
+ if (m_iStatus != ID_STATUS_OFFLINE)
+ {
+ SetCurrentStatus(ID_STATUS_OFFLINE);
+ }
+ }
+ else
+ { // Rates management
+ icq_lock l(m_ratesMutex);
+ m_rates->packetSent(pPacket);
+ }
+
+ }
+ else
+ {
+ connectionHandleMutex->Leave();
+ NetLog_Server("Error: Failed to send packet (no connection)");
+ }
+
+ SAFE_FREE((void**)&pPacket->pData);
+}
+
+
+void __cdecl CIcqProto::SendPacketAsyncThread(icq_packet* pkt)
+{
+ sendServPacket( pkt );
+ SAFE_FREE((void**)&pkt);
+}
+
+
+void CIcqProto::sendServPacketAsync(icq_packet *packet)
+{
+ icq_packet *pPacket;
+
+ pPacket = (icq_packet*)SAFE_MALLOC(sizeof(icq_packet)); // This will be freed in the new thread
+ memcpy(pPacket, packet, sizeof(icq_packet));
+
+ ForkThread(( IcqThreadFunc )&CIcqProto::SendPacketAsyncThread, pPacket);
+}
+
+
+int CIcqProto::IsServerOverRate(WORD wFamily, WORD wCommand, int nLevel)
+{
+ icq_lock l(m_ratesMutex);
+
+ if (m_rates)
+ {
+ WORD wGroup = m_rates->getGroupFromSNAC(wFamily, wCommand);
+
+ // check if the rate is not over specified level
+ if (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, nLevel))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ICQ Server thread
+
+void CIcqProto::icq_login(const char* szPassword)
+{
+ DWORD dwUin = getContactUin(NULL);
+ serverthread_start_info* stsi = (serverthread_start_info*)SAFE_MALLOC(sizeof(serverthread_start_info));
+
+ // Server host name
+ char szServer[MAX_PATH];
+ if (getSettingStringStatic(NULL, "OscarServer", szServer, MAX_PATH))
+ stsi->nloc.szHost = null_strdup(m_bSecureConnection ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST);
+ else
+ stsi->nloc.szHost = null_strdup(szServer);
+
+ // Server port
+ stsi->nloc.wPort = getSettingWord(NULL, "OscarPort", m_bSecureConnection ? DEFAULT_SERVER_PORT_SSL : DEFAULT_SERVER_PORT);
+ if (stsi->nloc.wPort == 0)
+ stsi->nloc.wPort = RandRange(1024, 65535);
+
+ // User password
+ stsi->wPassLen = strlennull(szPassword);
+ if (stsi->wPassLen > 8) stsi->wPassLen = 8;
+ null_strcpy(stsi->szPass, szPassword, stsi->wPassLen);
+
+ // Randomize sequence
+ wLocalSequence = generate_flap_sequence();
+
+ m_dwLocalUIN = dwUin;
+
+ // Initialize members
+ m_avatarsConnectionPending = TRUE;
+
+ serverThreadHandle = ForkThreadEx(( IcqThreadFunc )&CIcqProto::ServerThread, stsi, &serverThreadId);
+}
diff --git a/protocols/IcqOscarJ/src/icq_server.h b/protocols/IcqOscarJ/src/icq_server.h
new file mode 100644
index 0000000000..eba3ecfa42
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_server.h
@@ -0,0 +1,71 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Declarations for server thread
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_SERVER_H
+#define __ICQ_SERVER_H
+
+struct serverthread_start_info
+{
+ NETLIBOPENCONNECTION nloc;
+ WORD wPassLen;
+ char szPass[128];
+};
+
+struct serverthread_info
+{
+ struct CIcqProto *ppro;
+ int bLoggedIn;
+ int isLoginServer;
+ BYTE szAuthKey[20];
+ WORD wAuthKeyLen;
+ WORD wServerPort;
+ char *newServer;
+ BYTE *cookieData;
+ int cookieDataLen;
+ int newServerSSL;
+ int newServerReady;
+ int isMigrating;
+ HANDLE hPacketRecver;
+ int bReinitRecver;
+ int bMyAvatarInited;
+//
+ HANDLE hDirectBoundPort;
+//
+ HANDLE hKeepAliveEvent;
+};
+
+/*---------* Functions *---------------*/
+
+void icq_serverDisconnect(BOOL bBlock);
+void icq_login(const char *szPassword);
+
+int IsServerOverRate(WORD wFamily, WORD wCommand, int nLevel);
+
+
+#endif /* __ICQ_SERVER_H */
diff --git a/protocols/IcqOscarJ/src/icq_servlist.cpp b/protocols/IcqOscarJ/src/icq_servlist.cpp
new file mode 100644
index 0000000000..b2adc4831c
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_servlist.cpp
@@ -0,0 +1,2829 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Functions that handles list of used server IDs, sends low-level packets for SSI information
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+// SERVER-LIST UPDATE BOARD
+//
+
+void CIcqProto::servlistBeginOperation(int operationCount, int bImport)
+{
+ if (operationCount)
+ { // check if we should send operation begin packet
+ if (!servlistEditCount)
+ icq_sendServerBeginOperation(bImport);
+ // update count of active operations
+ servlistEditCount += operationCount;
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Begin operation processed (%d operations active)", servlistEditCount);
+#endif
+ }
+}
+
+void CIcqProto::servlistEndOperation(int operationCount)
+{
+ if (operationCount)
+ {
+ if (operationCount > servlistEditCount)
+ { // sanity check
+ NetLog_Server("Error: Server-List End operation is not paired!");
+ operationCount = servlistEditCount;
+ }
+ // update count of active operations
+ servlistEditCount -= operationCount;
+ // check if we should send operation end packet
+ if (!servlistEditCount)
+ icq_sendServerEndOperation();
+#ifdef _DEBUG
+ NetLog_Server("Server-List: End operation processed (%d operations active)", servlistEditCount);
+#endif
+ }
+}
+
+void __cdecl CIcqProto::servlistQueueThread(void *param)
+{
+ int* queueState = ( int* )param;
+
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Starting Update board.");
+#endif
+
+ SleepEx(50, FALSE);
+ // handle server-list requests queue
+ servlistQueueMutex->Enter();
+ while (servlistQueueCount)
+ {
+ ssiqueueditems* pItem = NULL;
+ int bItemDouble;
+ WORD wItemAction;
+ icq_packet groupPacket = {0};
+ icq_packet groupPacket2 = {0};
+ cookie_servlist_action* pGroupCookie = NULL;
+ int nEndOperations;
+
+ // first check if the state is calm
+ while (*queueState)
+ {
+ int i;
+ time_t tNow = time(NULL);
+ int bFound = FALSE;
+
+ for (i = 0; i < servlistQueueCount; i++)
+ { // check if we do not have some expired items to handle, otherwise keep sleeping
+ if ((servlistQueueList[i]->tAdded + servlistQueueList[i]->dwTimeout) < tNow)
+ { // got expired item, stop sleep even when changes goes on
+ bFound = TRUE;
+ break;
+ }
+ }
+ if (bFound) break;
+ // reset queue state, keep sleeping
+ *queueState = FALSE;
+ servlistQueueMutex->Leave();
+ SleepEx(100, TRUE);
+ servlistQueueMutex->Enter();
+ }
+ if (!icqOnline())
+ { // do not try to send packets if offline
+ servlistQueueMutex->Leave();
+ SleepEx(100, TRUE);
+ servlistQueueMutex->Enter();
+ continue;
+ }
+#ifdef _DEBUG
+ NetLog_Server("Server-List: %d items in queue.", servlistQueueCount);
+#endif
+ // take the oldest item (keep the board FIFO)
+ pItem = servlistQueueList[0]; // take first (queue contains at least one item here)
+ wItemAction = (WORD)(pItem->pItems[0]->dwOperation & SSOF_ACTIONMASK);
+ bItemDouble = pItem->pItems[0]->dwOperation & SSOG_DOUBLE;
+ // check item rate - too high -> sleep
+ m_ratesMutex->Enter();
+ {
+ WORD wRateGroup = m_rates->getGroupFromSNAC(ICQ_LISTS_FAMILY, wItemAction);
+ int nRateLevel = bItemDouble ? RML_IDLE_30 : RML_IDLE_10;
+
+ while (m_rates->getNextRateLevel(wRateGroup) < m_rates->getLimitLevel(wRateGroup, nRateLevel))
+ { // the rate is higher, keep sleeping
+ int nDelay = m_rates->getDelayToLimitLevel(wRateGroup, nRateLevel);
+
+ m_ratesMutex->Leave();
+ // do not keep the queue frozen
+ servlistQueueMutex->Leave();
+ if (nDelay < 10) nDelay = 10;
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Delaying %dms [Rates]", nDelay);
+#endif
+ SleepEx(nDelay, FALSE);
+ // check if the rate is now ok
+ servlistQueueMutex->Enter();
+ m_ratesMutex->Enter();
+ }
+ }
+ m_ratesMutex->Leave();
+ { // setup group packet(s) & cookie
+ int totalSize = 0;
+ int i;
+ cookie_servlist_action *pGroupCookie;
+ DWORD dwGroupCookie;
+ // determine the total size of the packet
+ for(i = 0; i < pItem->nItems; i++)
+ totalSize += pItem->pItems[i]->packet.wLen - 0x10;
+
+ // process begin & end operation flags
+ {
+ int bImportOperation = FALSE;
+ int nBeginOperations = 0;
+
+ nEndOperations = 0;
+ for(i = 0; i < pItem->nItems; i++)
+ { // collect begin & end operation flags
+ if (pItem->pItems[i]->dwOperation & SSOF_BEGIN_OPERATION)
+ nBeginOperations++;
+ if (pItem->pItems[i]->dwOperation & SSOF_END_OPERATION)
+ nEndOperations++;
+ // check if the operation is import
+ if (pItem->pItems[i]->dwOperation & SSOF_IMPORT_OPERATION)
+ bImportOperation = TRUE;
+ }
+ // really begin operation if requested
+ if (nBeginOperations)
+ servlistBeginOperation(nBeginOperations, bImportOperation);
+ }
+
+ if (pItem->nItems > 1)
+ { // pack all packet's data, create group cookie
+ pGroupCookie = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ pGroupCookie->dwAction = SSA_ACTION_GROUP;
+ pGroupCookie->dwGroupCount = pItem->nItems;
+ pGroupCookie->pGroupItems = (cookie_servlist_action**)SAFE_MALLOC(pItem->nItems * sizeof(cookie_servlist_action*));
+ for (i = 0; i < pItem->nItems; i++)
+ { // build group cookie data - assign cookies datas
+ pGroupCookie->pGroupItems[i] = pItem->pItems[i]->cookie;
+ // release the separate cookie id
+ FreeCookieByData(CKT_SERVERLIST, pItem->pItems[i]->cookie);
+ }
+ // allocate cookie id
+ dwGroupCookie = AllocateCookie(CKT_SERVERLIST, wItemAction, 0, pGroupCookie);
+ // prepare packet data
+ serverPacketInit(&groupPacket, (WORD)(totalSize + 0x0A)); // FLAP size added inside
+ packFNACHeader(&groupPacket, ICQ_LISTS_FAMILY, wItemAction, 0, dwGroupCookie);
+ for (i = 0; i < pItem->nItems; i++)
+ packBuffer(&groupPacket, pItem->pItems[i]->packet.pData + 0x10, (WORD)(pItem->pItems[i]->packet.wLen - 0x10));
+
+ if (bItemDouble)
+ { // prepare second packet
+ wItemAction = ((servlistgroupitemdouble*)(pItem->pItems[0]))->wAction2;
+ totalSize = 0;
+ // determine the total size of the packet
+ for(i = 0; i < pItem->nItems; i++)
+ totalSize += ((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.wLen - 0x10;
+
+ pGroupCookie = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ pGroupCookie->dwAction = SSA_ACTION_GROUP;
+ pGroupCookie->dwGroupCount = pItem->nItems;
+ pGroupCookie->pGroupItems = (cookie_servlist_action**)SAFE_MALLOC(pItem->nItems * sizeof(cookie_servlist_action*));
+ for (i = 0; i < pItem->nItems; i++)
+ pGroupCookie->pGroupItems[i] = pItem->pItems[i]->cookie;
+ // allocate cookie id
+ dwGroupCookie = AllocateCookie(CKT_SERVERLIST, wItemAction, 0, pGroupCookie);
+ // prepare packet data
+ serverPacketInit(&groupPacket2, (WORD)(totalSize + 0x0A)); // FLAP size added inside
+ packFNACHeader(&groupPacket2, ICQ_LISTS_FAMILY, wItemAction, 0, dwGroupCookie);
+ for (i = 0; i < pItem->nItems; i++)
+ packBuffer(&groupPacket2, ((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.pData + 0x10, (WORD)(((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.wLen - 0x10));
+ }
+ }
+ else
+ { // just send the one packet, do not create action group
+ pGroupCookie = pItem->pItems[0]->cookie;
+ memcpy(&groupPacket, &pItem->pItems[0]->packet, sizeof(icq_packet));
+ if (bItemDouble)
+ memcpy(&groupPacket2, &((servlistgroupitemdouble*)(pItem->pItems[0]))->packet2, sizeof(icq_packet));
+ }
+
+ { // remove grouped item from queue & release grouped item
+ servlistQueueCount--;
+ servlistQueueList[0] = servlistQueueList[servlistQueueCount];
+
+ for (i = 0; i < pItem->nItems; i++)
+ { // release memory
+ if (pItem->nItems > 1)
+ { // free the packet only if we created the group packet
+ SAFE_FREE((void**)&pItem->pItems[i]->packet.pData);
+ if (pItem->pItems[i]->dwOperation & SSOG_DOUBLE)
+ SAFE_FREE((void**)&((servlistgroupitemdouble*)(pItem->pItems[i]))->packet2.pData);
+ }
+ SAFE_FREE((void**)&pItem->pItems[i]);
+ break;
+ }
+ SAFE_FREE((void**)&pItem);
+ // resize the queue
+ if (servlistQueueSize > servlistQueueCount + 6)
+ {
+ servlistQueueSize -= 4;
+ servlistQueueList = (ssiqueueditems**)SAFE_REALLOC(servlistQueueList, servlistQueueSize * sizeof(ssiqueueditems*));
+ }
+ }
+ }
+ servlistQueueMutex->Leave();
+ // send group packet
+ sendServPacket(&groupPacket);
+ // send second group packet (if present)
+ if (bItemDouble)
+ sendServPacket(&groupPacket2);
+ // process end operation marks
+ if (nEndOperations)
+ servlistEndOperation(nEndOperations);
+ // loose the loop a bit
+ SleepEx(100, TRUE);
+ servlistQueueMutex->Enter();
+ }
+ // clean-up thread
+ CloseHandle(servlistQueueThreadHandle);
+ servlistQueueThreadHandle = NULL;
+ servlistQueueMutex->Leave();
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Update Board ending.");
+#endif
+}
+
+void CIcqProto::servlistQueueAddGroupItem(servlistgroupitem* pGroupItem, int dwTimeout)
+{
+ icq_lock l(servlistQueueMutex);
+
+ { // add the packet to queue
+ DWORD dwMark = pGroupItem->dwOperation & SSOF_GROUPINGMASK;
+ ssiqueueditems* pItem = NULL;
+
+ // try to find compatible item
+ for (int i = 0; i < servlistQueueCount; i++)
+ {
+ if ((servlistQueueList[i]->pItems[0]->dwOperation & SSOF_GROUPINGMASK) == dwMark && servlistQueueList[i]->nItems < MAX_SERVLIST_PACKET_ITEMS)
+ { // found compatible item, check if it does not contain operation for the same server-list item
+ pItem = servlistQueueList[i];
+
+ for (int j = 0; j < pItem->nItems; j++)
+ if (pItem->pItems[j]->cookie->wContactId == pGroupItem->cookie->wContactId &&
+ pItem->pItems[j]->cookie->wGroupId == pGroupItem->cookie->wGroupId)
+ {
+ pItem = NULL;
+ break;
+ }
+ // cannot send two operations for the same server-list record in one packet, look for another
+ if (!pItem) continue;
+
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Adding packet to item #%d with operation %x.", i, servlistQueueList[i]->pItems[0]->dwOperation);
+#endif
+ break;
+ }
+ }
+ if (!pItem)
+ { // compatible item was not found, create new one, add to queue
+ pItem = (ssiqueueditems*)SAFE_MALLOC(sizeof(ssiqueueditems));
+ pItem->tAdded = time(NULL);
+ pItem->dwTimeout = dwTimeout;
+
+ if (servlistQueueCount == servlistQueueSize)
+ { // resize the queue - it is too small
+ servlistQueueSize += 4;
+ servlistQueueList = (ssiqueueditems**)SAFE_REALLOC(servlistQueueList, servlistQueueSize * sizeof(ssiqueueditems*));
+ }
+ // really add to queue
+ servlistQueueList[servlistQueueCount++] = pItem;
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Adding new item to queue.");
+#endif
+ }
+ else if (pItem->dwTimeout > dwTimeout)
+ { // if the timeout of currently added packet is shorter, update the previous one
+ pItem->dwTimeout = dwTimeout;
+ }
+ // add GroupItem to queueditems (pItem)
+ pItem->pItems[pItem->nItems++] = pGroupItem;
+ }
+ // wake up board thread (keep sleeping or start new one)
+ if (!servlistQueueThreadHandle)
+ {
+ // create new board thread
+ servlistQueueThreadHandle = ForkThreadEx( &CIcqProto::servlistQueueThread, &servlistQueueState );
+ }
+ else // signal thread, that queue was changed during sleep
+ servlistQueueState = TRUE;
+}
+
+int CIcqProto::servlistHandlePrimitives(DWORD dwOperation)
+{
+ if (dwOperation & SSO_BEGIN_OPERATION)
+ { // operation starting, no action ready yet
+ servlistBeginOperation(1, dwOperation & SSOF_IMPORT_OPERATION);
+ return TRUE;
+ }
+ else if (dwOperation & SSO_END_OPERATION)
+ { // operation ending without action
+ servlistEndOperation(1);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void CIcqProto::servlistPostPacket(icq_packet* packet, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout)
+{
+ cookie_servlist_action* pCookie;
+
+ if (servlistHandlePrimitives(dwOperation))
+ return;
+
+ if (!FindCookie(dwCookie, NULL, (void**)&pCookie))
+ return; // invalid cookie
+
+ if (dwOperation & SSOF_SEND_DIRECTLY)
+ { // send directly - this is for some special cases
+ // begin operation if requested
+ if (dwOperation & SSOF_BEGIN_OPERATION)
+ servlistBeginOperation(1, dwOperation & SSOF_IMPORT_OPERATION);
+
+ // send the packet
+ sendServPacket(packet);
+
+ // end operation if requested
+ if (dwOperation & SSOF_END_OPERATION)
+ servlistEndOperation(1);
+ }
+ else
+ { // add to server-list update board
+ servlistgroupitem* pGroupItem;
+
+ // prepare group item
+ pGroupItem = (servlistgroupitem*)SAFE_MALLOC(sizeof(servlistgroupitem));
+ pGroupItem->dwOperation = dwOperation;
+ pGroupItem->cookie = pCookie;
+ // packet data are alloced, keep them until they are sent
+ memcpy(&pGroupItem->packet, packet, sizeof(icq_packet));
+
+ servlistQueueAddGroupItem(pGroupItem, dwTimeout);
+ }
+}
+
+void CIcqProto::servlistPostPacketDouble(icq_packet* packet1, DWORD dwCookie, DWORD dwOperation, DWORD dwTimeout, icq_packet* packet2, WORD wAction2)
+{
+ cookie_servlist_action* pCookie;
+
+ if (servlistHandlePrimitives(dwOperation))
+ return;
+
+ if (!FindCookie(dwCookie, NULL, (void**)&pCookie))
+ return; // invalid cookie
+
+ if (dwOperation & SSOF_SEND_DIRECTLY)
+ { // send directly - this is for some special cases
+ // begin operation if requested
+ if (dwOperation & SSOF_BEGIN_OPERATION)
+ servlistBeginOperation(1, dwOperation & SSOF_IMPORT_OPERATION);
+
+ // send the packets
+ sendServPacket(packet1);
+ sendServPacket(packet2);
+
+ // end operation if requested
+ if (dwOperation & SSOF_END_OPERATION)
+ servlistEndOperation(1);
+ }
+ else
+ { // add to server-list update board
+ servlistgroupitemdouble* pGroupItem;
+
+ // prepare group item
+ pGroupItem = (servlistgroupitemdouble*)SAFE_MALLOC(sizeof(servlistgroupitemdouble));
+ pGroupItem->dwOperation = dwOperation;
+ pGroupItem->cookie = pCookie;
+ pGroupItem->wAction2 = wAction2;
+ // packets data are alloced, keep them until they are sent
+ memcpy(&pGroupItem->packet, packet1, sizeof(icq_packet));
+ memcpy(&pGroupItem->packet2, packet2, sizeof(icq_packet));
+
+ servlistQueueAddGroupItem((servlistgroupitem*)pGroupItem, dwTimeout);
+ }
+}
+
+void CIcqProto::servlistProcessLogin()
+{
+ // reset edit state counter
+ servlistEditCount = 0;
+
+ /// TODO: preserve queue state in DB! restore here!
+
+ // if the server-list queue contains items and thread is not running, start it
+ if (servlistQueueCount && !servlistQueueThreadHandle)
+ servlistQueueThreadHandle = ForkThreadEx( &CIcqProto::servlistQueueThread, &servlistQueueState );
+}
+
+// HERE ENDS SERVER-LIST UPDATE BOARD IMPLEMENTATION //
+///////////////////////////////////////////////////////
+//===================================================//
+
+// PENDING SERVER-LIST OPERATIONS
+//
+#define ITEM_PENDING_CONTACT 0x01
+#define ITEM_PENDING_GROUP 0x02
+
+#define CALLBACK_RESULT_CONTINUE 0x00
+#define CALLBACK_RESULT_POSTPONE 0x0D
+#define CALLBACK_RESULT_PURGE 0x10
+
+
+#define SPOF_AUTO_CREATE_ITEM 0x01
+
+int CIcqProto::servlistPendingFindItem(int nType, HANDLE hContact, const char *pszGroup)
+{
+ if (servlistPendingList)
+ for (int i = 0; i < servlistPendingCount; i++)
+ if (servlistPendingList[i]->nType == nType)
+ {
+ if (((nType == ITEM_PENDING_CONTACT) && (servlistPendingList[i]->hContact == hContact)) ||
+ ((nType == ITEM_PENDING_GROUP) && (!strcmpnull(servlistPendingList[i]->szGroup, pszGroup))))
+ return i;
+ }
+ return -1;
+}
+
+
+void CIcqProto::servlistPendingAddItem(servlistpendingitem *pItem)
+{
+ if (servlistPendingCount >= servlistPendingSize) // add new
+ {
+ servlistPendingSize += 10;
+ servlistPendingList = (servlistpendingitem**)SAFE_REALLOC(servlistPendingList, servlistPendingSize * sizeof(servlistpendingitem*));
+ }
+
+ servlistPendingList[servlistPendingCount++] = pItem;
+}
+
+
+servlistpendingitem* CIcqProto::servlistPendingRemoveItem(int nType, HANDLE hContact, const char *pszGroup)
+{ // unregister pending item, trigger pending operations
+ int iItem;
+ servlistpendingitem *pItem = NULL;
+
+ icq_lock l(servlistMutex);
+
+ if ((iItem = servlistPendingFindItem(nType, hContact, pszGroup)) != -1)
+ { // found, remove from the pending list
+ pItem = servlistPendingList[iItem];
+
+ servlistPendingList[iItem] = servlistPendingList[--servlistPendingCount];
+
+ if (servlistPendingCount + 10 < servlistPendingSize)
+ {
+ servlistPendingSize -= 5;
+ servlistPendingList = (servlistpendingitem**)SAFE_REALLOC(servlistPendingList, servlistPendingSize * sizeof(servlistpendingitem*));
+ }
+ // was the first operation was created automatically to postpone ItemAdd?
+ if (pItem->operations && pItem->operations[0].flags & SPOF_AUTO_CREATE_ITEM)
+ { // yes, add new item
+ servlistpendingitem *pNewItem = (servlistpendingitem*)SAFE_MALLOC(sizeof(servlistpendingitem));
+
+ if (pNewItem)
+ { // move the remaining operations
+#ifdef _DEBUG
+ if (pItem->nType == ITEM_PENDING_CONTACT)
+ NetLog_Server("Server-List: Resuming contact %x operation.", pItem->hContact);
+ else
+ NetLog_Server("Server-List: Resuming group \"%s\" operation.", pItem->szGroup);
+#endif
+
+ pNewItem->nType = pItem->nType;
+ pNewItem->hContact = pItem->hContact;
+ pNewItem->szGroup = null_strdup(pItem->szGroup);
+ pNewItem->wContactID = pItem->wContactID;
+ pNewItem->wGroupID = pItem->wGroupID;
+ pNewItem->operationsCount = pItem->operationsCount - 1;
+ pNewItem->operations = (servlistpendingoperation*)SAFE_MALLOC(pNewItem->operationsCount * sizeof(servlistpendingoperation));
+ memcpy(pNewItem->operations, pItem->operations + 1, pNewItem->operationsCount * sizeof(servlistpendingoperation));
+ pItem->operationsCount = 1;
+
+ servlistPendingAddItem(pNewItem);
+ // clear the flag
+ pItem->operations[0].flags &= ~SPOF_AUTO_CREATE_ITEM;
+ }
+ }
+ }
+#ifdef _DEBUG
+ else
+ NetLog_Server("Server-List Error: Trying to remove non-existing pending %s.", nType == ITEM_PENDING_CONTACT ? "contact" : "group");
+#endif
+
+ return pItem;
+}
+
+
+void CIcqProto::servlistPendingAddContactOperation(HANDLE hContact, LPARAM param, PENDING_CONTACT_CALLBACK callback, DWORD flags)
+{ // add postponed operation (add contact, update contact, regroup resume, etc.)
+ // - after contact is added
+ int iItem;
+ servlistpendingitem *pItem = NULL;
+
+ icq_lock l(servlistMutex);
+
+ if ((iItem = servlistPendingFindItem(ITEM_PENDING_CONTACT, hContact, NULL)) != -1)
+ pItem = servlistPendingList[iItem];
+
+ if (pItem)
+ {
+ int iOperation = pItem->operationsCount++;
+
+ pItem->operations = (servlistpendingoperation*)SAFE_REALLOC(pItem->operations, pItem->operationsCount * sizeof(servlistpendingoperation));
+ pItem->operations[iOperation].param = param;
+ pItem->operations[iOperation].callback = (PENDING_GROUP_CALLBACK)callback;
+ pItem->operations[iOperation].flags = flags;
+ }
+ else
+ {
+ NetLog_Server("Server-List Error: Trying to add pending operation to a non existing contact.");
+ }
+}
+
+
+void CIcqProto::servlistPendingAddGroupOperation(const char *pszGroup, LPARAM param, PENDING_GROUP_CALLBACK callback, DWORD flags)
+{ // add postponed operation - after group is added
+ int iItem;
+ servlistpendingitem *pItem = NULL;
+
+ icq_lock l(servlistMutex);
+
+ if ((iItem = servlistPendingFindItem(ITEM_PENDING_GROUP, NULL, pszGroup)) != -1)
+ pItem = servlistPendingList[iItem];
+
+ if (pItem)
+ {
+ int iOperation = pItem->operationsCount++;
+
+ pItem->operations = (servlistpendingoperation*)SAFE_REALLOC(pItem->operations, pItem->operationsCount * sizeof(servlistpendingoperation));
+ pItem->operations[iOperation].param = param;
+ pItem->operations[iOperation].callback = callback;
+ pItem->operations[iOperation].flags = flags;
+ }
+ else
+ {
+ NetLog_Server("Server-List Error: Trying to add pending operation to a non existing group.");
+ }
+}
+
+
+int CIcqProto::servlistPendingAddContact(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM param, PENDING_CONTACT_CALLBACK callback, int bDoInline, LPARAM operationParam, PENDING_CONTACT_CALLBACK operationCallback)
+{
+ int iItem;
+ servlistpendingitem *pItem = NULL;
+
+ servlistMutex->Enter();
+
+ if ((iItem = servlistPendingFindItem(ITEM_PENDING_CONTACT, hContact, NULL)) != -1)
+ pItem = servlistPendingList[iItem];
+
+ if (pItem)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Pending contact %x already in list; adding as operation.", hContact);
+#endif
+ servlistPendingAddContactOperation(hContact, param, callback, SPOF_AUTO_CREATE_ITEM);
+
+ if (operationCallback)
+ servlistPendingAddContactOperation(hContact, operationParam, operationCallback, 0);
+
+ servlistMutex->Leave();
+
+ return 0; // Pending
+ }
+
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Starting contact %x operation.", hContact);
+#endif
+
+ pItem = (servlistpendingitem *)SAFE_MALLOC(sizeof(servlistpendingitem));
+ pItem->nType = ITEM_PENDING_CONTACT;
+ pItem->hContact = hContact;
+ pItem->wContactID = wContactID;
+ pItem->wGroupID = wGroupID;
+
+ servlistPendingAddItem(pItem);
+
+ if (operationCallback)
+ servlistPendingAddContactOperation(hContact, operationParam, operationCallback, 0);
+
+ servlistMutex->Leave();
+
+ if (bDoInline)
+ { // not postponed, called directly if requested
+ (this->*callback)(hContact, wContactID, wGroupID, param, PENDING_RESULT_INLINE);
+ }
+
+ return 1; // Ready
+}
+
+
+int CIcqProto::servlistPendingAddGroup(const char *pszGroup, WORD wGroupID, LPARAM param, PENDING_GROUP_CALLBACK callback, int bDoInline, LPARAM operationParam, PENDING_GROUP_CALLBACK operationCallback)
+{
+ int iItem;
+ servlistpendingitem *pItem = NULL;
+
+ servlistMutex->Enter();
+
+ if ((iItem = servlistPendingFindItem(ITEM_PENDING_GROUP, NULL, pszGroup)) != -1)
+ pItem = servlistPendingList[iItem];
+
+ if (pItem)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Pending group \"%s\" already in list; adding as operation.", pszGroup);
+#endif
+ servlistPendingAddGroupOperation(pszGroup, param, callback, SPOF_AUTO_CREATE_ITEM);
+
+ if (operationCallback)
+ servlistPendingAddGroupOperation(pszGroup, operationParam, operationCallback, 0);
+
+ servlistMutex->Leave();
+
+ return 0; // Pending
+ }
+
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Starting group \"%s\" operation.", pszGroup);
+#endif
+
+ pItem = (servlistpendingitem *)SAFE_MALLOC(sizeof(servlistpendingitem));
+ pItem->nType = ITEM_PENDING_GROUP;
+ pItem->szGroup = null_strdup(pszGroup);
+ pItem->wGroupID = wGroupID;
+
+ servlistPendingAddItem(pItem);
+
+ if (operationCallback)
+ servlistPendingAddGroupOperation(pszGroup, operationParam, operationCallback, 0);
+
+ servlistMutex->Leave();
+
+ if (bDoInline)
+ { // not postponed, called directly if requested
+ (this->*callback)(pszGroup, wGroupID, param, PENDING_RESULT_INLINE);
+ }
+
+ return 1; // Ready
+}
+
+
+void CIcqProto::servlistPendingRemoveContact(HANDLE hContact, WORD wContactID, WORD wGroupID, int nResult)
+{
+#ifdef _DEBUG
+ NetLog_Server("Server-List: %s contact %x operation.", (nResult != PENDING_RESULT_PURGE) ? "Ending" : "Purging", hContact);
+#endif
+
+ servlistpendingitem *pItem = servlistPendingRemoveItem(ITEM_PENDING_CONTACT, hContact, NULL);
+
+ if (pItem)
+ { // process pending operations
+ if (pItem->operations)
+ {
+ for (int i = 0; i < pItem->operationsCount; i++)
+ {
+ int nCallbackState = (this->*(PENDING_CONTACT_CALLBACK)(pItem->operations[i].callback))(hContact, wContactID, wGroupID, pItem->operations[i].param, nResult);
+
+ if (nResult != PENDING_RESULT_PURGE && nCallbackState == CALLBACK_RESULT_POSTPONE)
+ { // any following pending operations cannot be processed now, move them to the new pending contact
+ for (int j = i + 1; j < pItem->operationsCount; j++)
+ servlistPendingAddContactOperation(hContact, pItem->operations[j].param, (PENDING_CONTACT_CALLBACK)(pItem->operations[j].callback), pItem->operations[j].flags);
+ break;
+ }
+ else if (nCallbackState == CALLBACK_RESULT_PURGE)
+ { // purge all following operations - fatal failure occured
+ nResult = PENDING_RESULT_PURGE;
+ }
+ }
+ SAFE_FREE((void**)&pItem->operations);
+ }
+ // release item's memory
+ SAFE_FREE((void**)&pItem);
+ }
+ else
+ NetLog_Server("Server-List Error: Trying to remove a non existing pending contact.");
+}
+
+
+void CIcqProto::servlistPendingRemoveGroup(const char *pszGroup, WORD wGroupID, int nResult)
+{
+#ifdef _DEBUG
+ NetLog_Server("Server-List: %s group \"%s\" operation.", (nResult != PENDING_RESULT_PURGE) ? "Ending" : "Purging", pszGroup);
+#endif
+
+ servlistpendingitem *pItem = servlistPendingRemoveItem(ITEM_PENDING_GROUP, NULL, pszGroup);
+
+ if (pItem)
+ { // process pending operations
+ if (pItem->operations)
+ {
+ for (int i = 0; i < pItem->operationsCount; i++)
+ {
+ int nCallbackState = (this->*pItem->operations[i].callback)(pItem->szGroup, wGroupID, pItem->operations[i].param, nResult);
+
+ if (nResult != PENDING_RESULT_PURGE && nCallbackState == CALLBACK_RESULT_POSTPONE)
+ { // any following pending operations cannot be processed now, move them to the new pending group
+ for (int j = i + 1; j < pItem->operationsCount; j++)
+ servlistPendingAddGroupOperation(pItem->szGroup, pItem->operations[j].param, pItem->operations[j].callback, pItem->operations[j].flags);
+ break;
+ }
+ else if (nCallbackState == CALLBACK_RESULT_PURGE)
+ { // purge all following operations - fatal failure occured
+ nResult = PENDING_RESULT_PURGE;
+ }
+ }
+ SAFE_FREE((void**)&pItem->operations);
+ }
+ // release item's memory
+ SAFE_FREE((void**)&pItem->szGroup);
+ SAFE_FREE((void**)&pItem);
+ }
+ else
+ NetLog_Server("Server-List Error: Trying to remove a non existing pending group.");
+}
+
+
+// Remove All pending operations
+void CIcqProto::servlistPendingFlushOperations()
+{
+ icq_lock l(servlistMutex);
+
+ for (int i = servlistPendingCount; i; i--)
+ { // purge all items
+ servlistpendingitem *pItem = servlistPendingList[i - 1];
+
+ if (pItem->nType == ITEM_PENDING_CONTACT)
+ servlistPendingRemoveContact(pItem->hContact, 0, 0, PENDING_RESULT_PURGE);
+ else if (pItem->nType == ITEM_PENDING_GROUP)
+ servlistPendingRemoveGroup(pItem->szGroup, 0, PENDING_RESULT_PURGE);
+ }
+ // release the list completely
+ SAFE_FREE((void**)&servlistPendingList);
+ servlistPendingCount = 0;
+ servlistPendingSize = 0;
+}
+
+// END OF SERVER-LIST PENDING OPERATIONS
+////
+
+
+// used for adding new contacts to list - sync with visible items
+void CIcqProto::AddJustAddedContact(HANDLE hContact)
+{
+ icq_lock l(servlistMutex);
+
+ if (nJustAddedCount >= nJustAddedSize)
+ {
+ nJustAddedSize += 10;
+ pdwJustAddedList = (HANDLE*)SAFE_REALLOC(pdwJustAddedList, nJustAddedSize * sizeof(HANDLE));
+ }
+
+ pdwJustAddedList[nJustAddedCount] = hContact;
+ nJustAddedCount++;
+}
+
+
+// was the contact added during this serv-list load
+BOOL CIcqProto::IsContactJustAdded(HANDLE hContact)
+{
+ icq_lock l(servlistMutex);
+
+ if (pdwJustAddedList)
+ {
+ for (int i = 0; i<nJustAddedCount; i++)
+ {
+ if (pdwJustAddedList[i] == hContact)
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+
+void CIcqProto::FlushJustAddedContacts()
+{
+ icq_lock l(servlistMutex);
+
+ SAFE_FREE((void**)&pdwJustAddedList);
+ nJustAddedSize = 0;
+ nJustAddedCount = 0;
+}
+
+
+// Add a server ID to the list of reserved IDs.
+// To speed up the process, no checks is done, if
+// you try to reserve an ID twice, it will be added again.
+// You should call CheckServerID before reserving an ID.
+void CIcqProto::ReserveServerID(WORD wID, int bGroupType, int bFlags)
+{
+ servlistMutex->Enter();
+ if (nServerIDListCount >= nServerIDListSize)
+ {
+ nServerIDListSize += 100;
+ pdwServerIDList = (DWORD*)SAFE_REALLOC(pdwServerIDList, nServerIDListSize * sizeof(DWORD));
+ }
+
+ pdwServerIDList[nServerIDListCount] = wID | (bGroupType & 0x00FF0000) | (bFlags & 0xFF000000);
+ nServerIDListCount++;
+ servlistMutex->Leave();
+
+ if (!bIsSyncingCL)
+ StoreServerIDs();
+}
+
+
+// Remove a server ID from the list of reserved IDs.
+// Used for deleting contacts and other modifications.
+void CIcqProto::FreeServerID(WORD wID, int bGroupType)
+{
+ DWORD dwId = wID | (bGroupType & 0x00FF0000);
+
+ icq_lock l(servlistMutex);
+
+ if (pdwServerIDList)
+ {
+ for (int i = 0; i<nServerIDListCount; i++)
+ {
+ if ((pdwServerIDList[i] & 0x00FFFFFF) == dwId)
+ { // we found it, so remove
+ for (int j = i+1; j<nServerIDListCount; j++)
+ pdwServerIDList[j-1] = pdwServerIDList[j];
+
+ nServerIDListCount--;
+ }
+ }
+ }
+}
+
+
+// Returns true if dwID is reserved
+BOOL CIcqProto::CheckServerID(WORD wID, unsigned int wCount)
+{
+ icq_lock l(servlistMutex);
+
+ if (pdwServerIDList)
+ {
+ for (int i = 0; i<nServerIDListCount; i++)
+ {
+ if (((pdwServerIDList[i] & 0xFFFF) >= wID) && ((pdwServerIDList[i] & 0xFFFF) <= wID + wCount))
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+void CIcqProto::FlushServerIDs()
+{
+ icq_lock l(servlistMutex);
+
+ SAFE_FREE((void**)&pdwServerIDList);
+ nServerIDListCount = 0;
+ nServerIDListSize = 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+struct GroupReserveIdsEnumParam
+{
+ CIcqProto *ppro;
+ char *szModule;
+};
+
+static int GroupReserveIdsEnumProc(const char *szSetting,LPARAM lParam)
+{
+ if (szSetting && strlennull(szSetting)<5)
+ {
+ // it is probably server group
+ GroupReserveIdsEnumParam *param = (GroupReserveIdsEnumParam*)lParam;
+ char val[MAX_PATH+2]; // dummy
+
+ DBVARIANT dbv = {DBVT_DELETED};
+ dbv.type = DBVT_ASCIIZ;
+ dbv.pszVal = val;
+ dbv.cchVal = MAX_PATH;
+
+ DBCONTACTGETSETTING cgs;
+ cgs.szModule = param->szModule;
+ cgs.szSetting = szSetting;
+ cgs.pValue = &dbv;
+ if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs))
+ { // we failed to read setting, try also utf8 - DB bug
+ dbv.type = DBVT_UTF8;
+ dbv.pszVal = val;
+ dbv.cchVal = MAX_PATH;
+ if (CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs))
+ return 0; // we failed also, invalid setting
+ }
+ if (dbv.type != DBVT_ASCIIZ)
+ { // it is not a cached server-group name
+ return 0;
+ }
+ param->ppro->ReserveServerID((WORD)strtoul(szSetting, NULL, 0x10), SSIT_GROUP, 0);
+#ifdef _DEBUG
+ param->ppro->NetLog_Server("Loaded group %u:'%s'", strtoul(szSetting, NULL, 0x10), val);
+#endif
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Load all known server IDs from DB to list
+void CIcqProto::LoadServerIDs()
+{
+ WORD wSrvID;
+ int nGroups = 0, nContacts = 0, nPermits = 0, nDenys = 0, nIgnores = 0, nUnhandled = 0;
+
+ servlistMutex->Enter();
+ if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0))
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_PHOTO, 0))
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, 0))
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ if (wSrvID = getSettingWord(NULL, DBSETTING_SERVLIST_METAINFO, 0))
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ if (wSrvID = getSettingWord(NULL, "SrvImportID", 0))
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+
+ DBCONTACTENUMSETTINGS dbces;
+ int nStart = nServerIDListCount;
+
+ char szModule[MAX_PATH];
+ null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName);
+
+ GroupReserveIdsEnumParam param = { this, szModule };
+ dbces.pfnEnumProc = &GroupReserveIdsEnumProc;
+ dbces.szModule = szModule;
+ dbces.lParam = (LPARAM)&param;
+ CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces);
+
+ nGroups = nServerIDListCount - nStart;
+
+ HANDLE hContact = FindFirstContact();
+
+ while (hContact)
+ { // search all our contacts, reserve their server IDs
+ if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0))
+ {
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ nContacts++;
+ }
+ if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_DENY, 0))
+ {
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ nDenys++;
+ }
+ if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, 0))
+ {
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ nPermits++;
+ }
+ if (wSrvID = getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0))
+ {
+ ReserveServerID(wSrvID, SSIT_ITEM, 0);
+ nIgnores++;
+ }
+
+ hContact = FindNextContact(hContact);
+ }
+ servlistMutex->Leave();
+
+ DBVARIANT dbv = {0};
+ if (!getSetting(NULL, DBSETTING_SERVLIST_UNHANDLED, &dbv))
+ {
+ int dataLen = dbv.cpbVal;
+ BYTE *pData = dbv.pbVal;
+
+ while (dataLen >= 4)
+ {
+ BYTE bGroupType;
+ BYTE bFlags;
+
+ unpackLEWord(&pData, &wSrvID);
+ unpackByte(&pData, &bGroupType);
+ unpackByte(&pData, &bFlags);
+
+ ReserveServerID(wSrvID, bGroupType, bFlags);
+ dataLen -= 4;
+ nUnhandled++;
+ }
+
+ ICQFreeVariant(&dbv);
+ }
+
+ NetLog_Server("Loaded SSI: %d contacts, %d groups, %d permit, %d deny, %d ignore, %d unknown items.", nContacts, nGroups, nPermits, nDenys, nIgnores, nUnhandled);
+}
+
+
+void CIcqProto::StoreServerIDs() /// TODO: allow delayed
+{
+ BYTE *pUnhandled = NULL;
+ int cbUnhandled = 0;
+
+ servlistMutex->Enter();
+ if (pdwServerIDList)
+ for (int i = 0; i<nServerIDListCount; i++)
+ if ((pdwServerIDList[i] & 0xFF000000) == SSIF_UNHANDLED)
+ {
+ ppackLEWord(&pUnhandled, &cbUnhandled, pdwServerIDList[i] & 0xFFFF);
+ ppackByte(&pUnhandled, &cbUnhandled, (pdwServerIDList[i] & 0x00FF0000) >> 0x10);
+ ppackByte(&pUnhandled, &cbUnhandled, (pdwServerIDList[i] & 0xFF000000) >> 0x18);
+ }
+ servlistMutex->Leave();
+
+ if (pUnhandled)
+ setSettingBlob(NULL, DBSETTING_SERVLIST_UNHANDLED, pUnhandled, cbUnhandled);
+ else
+ deleteSetting(NULL, DBSETTING_SERVLIST_UNHANDLED);
+
+ SAFE_FREE((void**)&pUnhandled);
+}
+
+
+// Generate server ID with wCount IDs free after it, for sub-groups.
+WORD CIcqProto::GenerateServerID(int bGroupType, int bFlags, int wCount)
+{
+ WORD wId;
+
+ while (TRUE)
+ {
+ // Randomize a new ID
+ // Max value is probably 0x7FFF, lowest value is probably 0x0001 (generated by Icq2Go)
+ // We use range 0x1000-0x7FFF.
+ wId = (WORD)RandRange(0x1000, 0x7FFF);
+
+ if (!CheckServerID(wId, wCount))
+ break;
+ }
+
+ ReserveServerID(wId, bGroupType, bFlags);
+
+ return wId;
+}
+
+
+/***********************************************
+*
+* --- Low-level packet sending functions ---
+*
+*/
+
+struct doubleServerItemObject
+{
+ WORD wAction;
+ icq_packet packet;
+};
+
+DWORD CIcqProto::icq_sendServerItem(DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wItemId, const char *szName, BYTE *pTLVs, int nTlvLength, WORD wItemType, DWORD dwOperation, DWORD dwTimeout, void **doubleObject)
+{ // generic packet
+ icq_packet packet;
+ int nNameLen;
+ WORD wTLVlen = (WORD)nTlvLength;
+
+ // Prepare item name length
+ nNameLen = strlennull(szName);
+
+ // Build the packet
+ serverPacketInit(&packet, (WORD)(nNameLen + 20 + wTLVlen));
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, wAction, 0, dwCookie);
+ packWord(&packet, (WORD)nNameLen);
+ if (nNameLen)
+ packBuffer(&packet, (LPBYTE)szName, (WORD)nNameLen);
+ packWord(&packet, wGroupId);
+ packWord(&packet, wItemId);
+ packWord(&packet, wItemType);
+ packWord(&packet, wTLVlen);
+ if (wTLVlen)
+ packBuffer(&packet, pTLVs, wTLVlen);
+
+ if (!doubleObject)
+ { // Send the packet and return the cookie
+ servlistPostPacket(&packet, dwCookie, dwOperation | wAction, dwTimeout);
+ }
+ else
+ {
+ if (*doubleObject)
+ { // Send both packets and return the cookie
+ doubleServerItemObject* helper = (doubleServerItemObject*)*doubleObject;
+
+ servlistPostPacketDouble(&helper->packet, dwCookie, dwOperation | helper->wAction, dwTimeout, &packet, wAction);
+ SAFE_FREE(doubleObject);
+ }
+ else
+ { // Create helper object, return the cookie
+ doubleServerItemObject* helper = (doubleServerItemObject*)SAFE_MALLOC(sizeof(doubleServerItemObject));
+
+ if (helper)
+ {
+ helper->wAction = wAction;
+ memcpy(&helper->packet, &packet, sizeof(icq_packet));
+ *doubleObject = helper;
+ }
+ else // memory alloc failed
+ return 0;
+ }
+ }
+
+ // Force reload of server-list after change
+ setSettingWord(NULL, "SrvRecordCount", 0);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendServerContact(HANDLE hContact, DWORD dwCookie, WORD wAction, WORD wGroupId, WORD wContactId, DWORD dwOperation, DWORD dwTimeout, void **doubleObject)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ icq_packet pBuffer;
+ char *szNick = NULL, *szNote = NULL;
+ BYTE *pData = NULL, *pMetaToken = NULL, *pMetaTime = NULL;
+ int nNickLen, nNoteLen, nDataLen = 0, nMetaTokenLen = 0, nMetaTimeLen = 0;
+ WORD wTLVlen;
+ BYTE bAuth;
+ int bDataTooLong = FALSE;
+
+ // Prepare UID
+ if (getContactUid(hContact, &dwUin, &szUid))
+ {
+ NetLog_Server("Buddy upload failed (UID missing).");
+ return 0;
+ }
+
+ bAuth = getSettingByte(hContact, "Auth", 0);
+ szNick = getSettingStringUtf(hContact, "CList", "MyHandle", NULL);
+ szNote = getSettingStringUtf(hContact, "UserInfo", "MyNotes", NULL);
+
+ DBVARIANT dbv;
+
+ if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &dbv))
+ {
+ nMetaTokenLen = dbv.cpbVal;
+ pMetaToken = (BYTE*)_alloca(dbv.cpbVal);
+ memcpy(pMetaToken, dbv.pbVal, dbv.cpbVal);
+
+ ICQFreeVariant(&dbv);
+ }
+ if (!getSetting(hContact, DBSETTING_METAINFO_TIME, &dbv))
+ {
+ nMetaTimeLen = dbv.cpbVal;
+ pMetaTime = (BYTE*)_alloca(dbv.cpbVal);
+ for (int i = 0; i < dbv.cpbVal; i++)
+ pMetaTime[i] = dbv.pbVal[dbv.cpbVal - i - 1];
+
+ ICQFreeVariant(&dbv);
+ }
+
+ if (!getSetting(hContact, DBSETTING_SERVLIST_DATA, &dbv))
+ { // read additional server item data
+ nDataLen = dbv.cpbVal;
+ pData = (BYTE*)_alloca(nDataLen);
+ memcpy(pData, dbv.pbVal, nDataLen);
+
+ ICQFreeVariant(&dbv);
+ }
+
+ nNickLen = strlennull(szNick);
+ nNoteLen = strlennull(szNote);
+
+ // Limit the strings
+ if (nNickLen > MAX_SSI_TLV_NAME_SIZE)
+ {
+ bDataTooLong = TRUE;
+ nNickLen = null_strcut(szNick, MAX_SSI_TLV_NAME_SIZE);
+ }
+ if (nNoteLen > MAX_SSI_TLV_COMMENT_SIZE)
+ {
+ bDataTooLong = TRUE;
+ nNoteLen = null_strcut(szNote, MAX_SSI_TLV_COMMENT_SIZE);
+ }
+ if (bDataTooLong)
+ { // Inform the user
+ /// TODO: do something with this for Manage Server-List dialog.
+ if (wAction != ICQ_LISTS_REMOVEFROMLIST) // do not report this when removing from list
+ icq_LogMessage(LOG_WARNING, LPGEN("The contact's information was too big and was truncated."));
+ }
+
+ // Build the packet
+ wTLVlen = (nNickLen?4+nNickLen:0) + (nNoteLen?4+nNoteLen:0) + (bAuth?4:0) + nDataLen + (nMetaTokenLen?4+nMetaTokenLen:0) + (nMetaTimeLen?4+nMetaTimeLen:0);
+
+ // Initialize our handy data buffer
+ pBuffer.wPlace = 0;
+ pBuffer.pData = (BYTE *)_alloca(wTLVlen);
+ pBuffer.wLen = wTLVlen;
+
+ if (nNickLen)
+ packTLV(&pBuffer, SSI_TLV_NAME, (WORD)nNickLen, (LPBYTE)szNick); // Nickname TLV
+
+ if (nNoteLen)
+ packTLV(&pBuffer, SSI_TLV_COMMENT, (WORD)nNoteLen, (LPBYTE)szNote); // Comment TLV
+
+ if (nMetaTokenLen)
+ packTLV(&pBuffer, SSI_TLV_METAINFO_TOKEN, (WORD)nMetaTokenLen, pMetaToken);
+
+ if (nMetaTimeLen)
+ packTLV(&pBuffer, SSI_TLV_METAINFO_TIME, (WORD)nMetaTimeLen, pMetaTime);
+
+ if (pData)
+ packBuffer(&pBuffer, pData, (WORD)nDataLen);
+
+ if (bAuth) // icq5 gives this as last TLV
+ packDWord(&pBuffer, 0x00660000); // "Still waiting for auth" TLV
+
+ SAFE_FREE((void**)&szNick);
+ SAFE_FREE((void**)&szNote);
+
+ return icq_sendServerItem(dwCookie, wAction, wGroupId, wContactId, strUID(dwUin, szUid), pBuffer.pData, wTLVlen, SSI_ITEM_BUDDY, dwOperation, dwTimeout, doubleObject);
+}
+
+
+DWORD CIcqProto::icq_sendSimpleItem(DWORD dwCookie, WORD wAction, DWORD dwUin, char* szUID, WORD wGroupId, WORD wItemId, WORD wItemType, DWORD dwOperation, DWORD dwTimeout)
+{ // for privacy items
+ return icq_sendServerItem(dwCookie, wAction, wGroupId, wItemId, strUID(dwUin, szUID), NULL, 0, wItemType, dwOperation, dwTimeout, NULL);
+}
+
+
+DWORD CIcqProto::icq_sendServerGroup(DWORD dwCookie, WORD wAction, WORD wGroupId, const char *szName, void *pContent, int cbContent, DWORD dwOperationFlags)
+{
+ WORD wTLVlen;
+ icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer
+ // I should be ashamed! ;)
+
+ if (strlennull(szName) == 0 && wGroupId != 0)
+ {
+ NetLog_Server("Group upload failed (GroupName missing).");
+ return 0; // without name we could not change the group
+ }
+
+ // Calculate buffer size
+ wTLVlen = (cbContent?4+cbContent:0);
+
+ // Initialize our handy data buffer
+ pBuffer.wPlace = 0;
+ pBuffer.pData = (BYTE *)_alloca(wTLVlen);
+ pBuffer.wLen = wTLVlen;
+
+ if (wTLVlen)
+ packTLV(&pBuffer, SSI_TLV_SUBITEMS, (WORD)cbContent, (LPBYTE)pContent); // Groups TLV
+
+ return icq_sendServerItem(dwCookie, wAction, wGroupId, 0, szName, pBuffer.pData, wTLVlen, SSI_ITEM_GROUP, SSOP_GROUP_ACTION | dwOperationFlags, 400, NULL);
+}
+
+
+DWORD CIcqProto::icq_modifyServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wAction, DWORD dwOperation, WORD wItemId, WORD wType)
+{
+ cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ DWORD dwCookie;
+
+ if (ack)
+ {
+ ack->dwAction = dwOperation; // remove privacy item
+ ack->hContact = hContact;
+ ack->wContactId = wItemId;
+
+ dwCookie = AllocateCookie(CKT_SERVERLIST, wAction, hContact, ack);
+ }
+ else // cookie failed
+ return 0;
+
+ return icq_sendSimpleItem(dwCookie, wAction, dwUin, szUid, 0, wItemId, wType, SSOP_ITEM_ACTION, 400);
+}
+
+
+DWORD CIcqProto::icq_removeServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType)
+{
+ return icq_modifyServerPrivacyItem(hContact, dwUin, szUid, ICQ_LISTS_REMOVEFROMLIST, SSA_PRIVACY_REMOVE, wItemId, wType);
+}
+
+
+DWORD CIcqProto::icq_addServerPrivacyItem(HANDLE hContact, DWORD dwUin, char *szUid, WORD wItemId, WORD wType)
+{
+ return icq_modifyServerPrivacyItem(hContact, dwUin, szUid, ICQ_LISTS_ADDTOLIST, SSA_PRIVACY_ADD, wItemId, wType);
+}
+
+/*****************************************
+*
+* --- Contact DB Utilities ---
+*
+*/
+
+
+/// TODO: do not check by plugin version, check by ServListStructures version!
+int CIcqProto::IsServerGroupsDefined()
+{
+ int iRes = 1;
+
+ if (getSettingDword(NULL, "Version", 0) < 0x00030608)
+ { // group cache & linking data too old, flush, reload from server
+ char szModule[MAX_PATH];
+
+ // flush obsolete linking data
+ null_snprintf(szModule, SIZEOF(szModule), "%sGroups", m_szModuleName);
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szModule);
+
+ iRes = 0; // no groups defined, or older version
+ }
+ // store our current version
+ setSettingDword(NULL, "Version", ICQ_PLUG_VERSION & 0x00FFFFFF);
+
+ return iRes;
+}
+
+
+void CIcqProto::FlushSrvGroupsCache()
+{
+ char szModule[MAX_PATH];
+
+ null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName);
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szModule);
+}
+
+
+// Look thru DB and collect all ContactIDs from a group
+void* CIcqProto::collectBuddyGroup(WORD wGroupID, int *count)
+{
+ WORD* buf = NULL;
+ int cnt = 0;
+ HANDLE hContact;
+ WORD wItemID;
+
+ hContact = FindFirstContact();
+
+ while (hContact)
+ { // search all contacts
+ if (wGroupID == getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0))
+ { // add only buddys from specified group
+ wItemID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0);
+
+ if (wItemID)
+ { // valid ID, add
+ cnt++;
+ buf = (WORD*)SAFE_REALLOC(buf, cnt*sizeof(WORD));
+ buf[cnt-1] = wItemID;
+ if (!count) break;
+ }
+ }
+
+ hContact = FindNextContact(hContact);
+ }
+
+ if (count)
+ *count = cnt<<1; // we return size in bytes
+ return buf;
+}
+
+
+// Look thru DB and collect all GroupIDs
+void* CIcqProto::collectGroups(int *count)
+{
+ WORD* buf = NULL;
+ int cnt = 0;
+ int i;
+ HANDLE hContact;
+ WORD wGroupID;
+
+ hContact = FindFirstContact();
+
+ while (hContact)
+ { // search all contacts
+ if (wGroupID = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0))
+ { // add only valid IDs
+ for (i = 0; i<cnt; i++)
+ { // check for already added ids
+ if (buf[i] == wGroupID) break;
+ }
+
+ if (i == cnt)
+ { // not preset, add
+ cnt++;
+ buf = (WORD*)SAFE_REALLOC(buf, cnt*sizeof(WORD));
+ buf[i] = wGroupID;
+ }
+ }
+
+ hContact = FindNextContact(hContact);
+ }
+
+ *count = cnt<<1;
+ return buf;
+}
+
+
+static int GroupLinksEnumProc(const char *szSetting,LPARAM lParam)
+{
+ // check link target, add if match
+ if (DBGetContactSettingWord(NULL, ((char**)lParam)[2], szSetting, 0) == (WORD)((char**)lParam)[1])
+ {
+ char** block = (char**)SAFE_MALLOC(2*sizeof(char*));
+ block[1] = null_strdup(szSetting);
+ block[0] = ((char**)lParam)[0];
+ ((char**)lParam)[0] = (char*)block;
+ }
+ return 0;
+}
+
+void CIcqProto::removeGroupPathLinks(WORD wGroupID)
+{ // remove miranda grouppath links targeting to this groupid
+ DBCONTACTENUMSETTINGS dbces;
+ char szModule[MAX_PATH];
+ char* pars[3];
+
+ null_snprintf(szModule, SIZEOF(szModule), "%sGroups", m_szModuleName);
+
+ pars[0] = NULL;
+ pars[1] = (char*)wGroupID;
+ pars[2] = szModule;
+
+ dbces.pfnEnumProc = &GroupLinksEnumProc;
+ dbces.szModule = szModule;
+ dbces.lParam = (LPARAM)pars;
+
+ if (!CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces))
+ { // we found some links, remove them
+ char** list = (char**)pars[0];
+ while (list)
+ {
+ void* bet;
+
+ DBDeleteContactSetting(NULL, szModule, list[1]);
+ SAFE_FREE((void**)&list[1]);
+ bet = list;
+ list = (char**)list[0];
+ SAFE_FREE((void**)&bet);
+ }
+ }
+}
+
+
+char *CIcqProto::getServListGroupName(WORD wGroupID)
+{
+ char szModule[MAX_PATH];
+ char szGroup[16];
+
+ if (!wGroupID)
+ {
+ NetLog_Server("Warning: Cannot get group name (Group ID missing)!");
+ return NULL;
+ }
+
+ null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName);
+ _itoa(wGroupID, szGroup, 0x10);
+
+ if (!CheckServerID(wGroupID, 0))
+ { // check if valid id, if not give empty and remove
+ NetLog_Server("Removing group %u from cache...", wGroupID);
+ DBDeleteContactSetting(NULL, szModule, szGroup);
+ return NULL;
+ }
+
+ return getSettingStringUtf(NULL, szModule, szGroup, NULL);
+}
+
+
+void CIcqProto::setServListGroupName(WORD wGroupID, const char *szGroupName)
+{
+ char szModule[MAX_PATH];
+ char szGroup[16];
+
+ if (!wGroupID)
+ {
+ NetLog_Server("Warning: Cannot set group name (Group ID missing)!");
+ return;
+ }
+
+ null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName);
+ _itoa(wGroupID, szGroup, 0x10);
+
+ if (szGroupName)
+ setSettingStringUtf(NULL, szModule, szGroup, szGroupName);
+ else
+ {
+ DBDeleteContactSetting(NULL, szModule, szGroup);
+ removeGroupPathLinks(wGroupID);
+ }
+ return;
+}
+
+
+WORD CIcqProto::getServListGroupLinkID(const char *szPath)
+{
+ char szModule[MAX_PATH];
+ WORD wGroupId;
+
+ null_snprintf(szModule, SIZEOF(szModule), "%sGroups", m_szModuleName);
+
+ wGroupId = DBGetContactSettingWord(NULL, szModule, szPath, 0);
+
+ if (wGroupId && !CheckServerID(wGroupId, 0))
+ { // known, check if still valid, if not remove
+ NetLog_Server("Removing group \"%s\" from cache...", szPath);
+ DBDeleteContactSetting(NULL, szModule, szPath);
+ wGroupId = 0;
+ }
+
+ return wGroupId;
+}
+
+
+void CIcqProto::setServListGroupLinkID(const char *szPath, WORD wGroupID)
+{
+ char szModule[MAX_PATH];
+
+ null_snprintf(szModule, SIZEOF(szModule), "%sGroups", m_szModuleName);
+
+ if (wGroupID)
+ DBWriteContactSettingWord(NULL, szModule, szPath, wGroupID);
+ else
+ DBDeleteContactSetting(NULL, szModule, szPath);
+}
+
+
+// this function takes all backslashes in szGroup as group-level separators
+int CIcqProto::getCListGroupHandle(const char *szGroup)
+{
+ char *pszGroup = (char*)szGroup;
+ int hParentGroup = 0, hGroup = 0;
+
+ if (!strlennull(szGroup)) return 0; // no group
+
+ if (strrchr(szGroup, '\\'))
+ { // create parent group
+ char *szSeparator = (char*)strrchr(szGroup, '\\');
+
+ *szSeparator = '\0';
+ hParentGroup = getCListGroupHandle(szGroup);
+ *szSeparator = '\\';
+ // take only sub-group name
+ pszGroup = ++szSeparator;
+ }
+
+ int size = strlennull(szGroup) + 2;
+ TCHAR *tszGroup = (TCHAR*)_alloca(size * sizeof(TCHAR));
+
+ if (utf8_to_tchar_static(pszGroup, tszGroup, size))
+ hGroup = CallService(MS_CLIST_GROUPCREATE, hParentGroup, (LPARAM)tszGroup); // 0.7+
+
+#ifdef _DEBUG
+ NetLog_Server("Obtained CList group \"%s\" handle %x", szGroup, hGroup);
+#endif
+
+ return hGroup;
+}
+
+
+// determine if the specified clist group path exists
+//!! this function is not thread-safe due to the use of cli->pfnGetGroupName()
+int CIcqProto::getCListGroupExists(const char *szGroup)
+{
+ int hGroup = 0;
+ CLIST_INTERFACE *clint = NULL;
+
+ if (!szGroup) return 0;
+
+ if (ServiceExists(MS_CLIST_RETRIEVE_INTERFACE))
+ clint = (CLIST_INTERFACE*)CallService(MS_CLIST_RETRIEVE_INTERFACE, 0, 0);
+
+ if (clint && clint->version >= 1)
+ { // we've got clist interface, use it
+ int size = strlennull(szGroup) + 2;
+ TCHAR *tszGroup = (TCHAR*)_alloca(size * sizeof(TCHAR));
+
+ if (utf8_to_tchar_static(szGroup, tszGroup, size))
+ for (int i = 1; TRUE; i++)
+ {
+ TCHAR *tszGroupName = (TCHAR*)clint->pfnGetGroupName(i, NULL);
+
+ if (!tszGroupName) break;
+
+ if (!_tcscmp(tszGroup, tszGroupName))
+ { // we have found the group
+ hGroup = i;
+ break;
+ }
+ }
+ }
+ else
+ { // old ansi version - no other way
+ int size = strlennull(szGroup) + 2;
+ char *aszGroup = (char*)_alloca(size);
+
+ utf8_decode_static(szGroup, aszGroup, size);
+
+ for (int i = 1; TRUE; i++)
+ {
+ char *paszGroup = (char*)CallService(MS_CLIST_GROUPGETNAME, i, 0);
+
+ if (!paszGroup) break;
+
+ if (!strcmpnull(aszGroup, paszGroup))
+ { // we found the group
+ hGroup = i;
+ break;
+ }
+ }
+ }
+
+ return hGroup;
+}
+
+
+int CIcqProto::moveContactToCListGroup(HANDLE hContact, const char *szGroup)
+{
+ int hGroup = getCListGroupHandle(szGroup);
+
+ if (ServiceExists(MS_CLIST_CONTACTCHANGEGROUP))
+ return CallService(MS_CLIST_CONTACTCHANGEGROUP, (WPARAM)hContact, hGroup);
+ else /// TODO: is this neccessary ?
+ return setSettingStringUtf(hContact, "CList", "Group", szGroup);
+}
+
+
+// utility function which counts > on start of a server group name
+static int countGroupNameLevel(const char *szGroupName)
+{
+ int nNameLen = strlennull(szGroupName);
+ int i = 0;
+
+ while (i<nNameLen)
+ {
+ if (szGroupName[i] != '>')
+ return i;
+
+ i++;
+ }
+ return -1;
+}
+
+static int countCListGroupLevel(const char *szClistName)
+{
+ int nNameLen = strlennull(szClistName);
+ int i, level = 0;
+
+ for (i = 0; i < nNameLen; i++)
+ if (szClistName[i] == '\\') level++;
+
+ return level;
+}
+
+int CIcqProto::getServListGroupLevel(WORD wGroupId)
+{
+ char *szGroupName = getServListGroupName(wGroupId);
+ int cnt = -1;
+
+ if (szGroupName)
+ { // groupid is valid count group name level
+ if (m_bSsiSimpleGroups)
+ cnt = countCListGroupLevel(szGroupName);
+ else
+ cnt = countGroupNameLevel(szGroupName);
+
+ SAFE_FREE((void**)&szGroupName);
+ }
+
+ return cnt;
+}
+
+
+// demangle group path
+char *CIcqProto::getServListGroupCListPath(WORD wGroupId)
+{
+ char *szGroup = NULL;
+
+ if (szGroup = getServListGroupName(wGroupId))
+ { // this groupid is valid
+ if (!m_bSsiSimpleGroups)
+ while (strstrnull(szGroup, "\\")) *strstrnull(szGroup, "\\") = '_'; // remove invalid char
+
+ if (getServListGroupLinkID(szGroup) == wGroupId)
+ { // this grouppath is known and is for this group, set user group
+ return szGroup;
+ }
+ else if (m_bSsiSimpleGroups)
+ { // with simple groups it is mapped 1:1, give real serv-list group name
+ setServListGroupLinkID(szGroup, wGroupId);
+ return szGroup;
+ }
+ else
+ { // advanced groups, determine group level
+ int nGroupLevel = getServListGroupLevel(wGroupId);
+
+ if (nGroupLevel > 0)
+ { // it is probably a sub-group locate parent group
+ WORD wParentGroupId = wGroupId;
+ int nParentGroupLevel;
+
+ do
+ { // we look for parent group at the correct level
+ wParentGroupId--;
+ nParentGroupLevel = getServListGroupLevel(wParentGroupId);
+ } while ((nParentGroupLevel >= nGroupLevel) && (nParentGroupLevel != -1));
+
+ if (nParentGroupLevel == -1)
+ { // that was not a sub-group, it was just a group starting with >
+ setServListGroupLinkID(szGroup, wGroupId);
+ return szGroup;
+ }
+
+ { // recursively determine parent group clist path
+ char *szParentGroup = getServListGroupCListPath(wParentGroupId);
+
+ /// FIXME: properly handle ~N suffixes
+ szParentGroup = (char*)SAFE_REALLOC(szParentGroup, strlennull(szGroup) + strlennull(szParentGroup) + 2);
+ strcat(szParentGroup, "\\");
+ strcat(szParentGroup, (char*)szGroup + nGroupLevel);
+ /* if (strstrnull(szGroup, "~"))
+ { // check if the ~ was not added to obtain unique servlist item name
+ char *szUniqueMark = strrchr(szParentGroup, '~');
+
+ *szUniqueMark = '\0';
+ // not the same group without ~, return it
+ if (getServListGroupLinkID(szParentGroup) != wGroupId)
+ *szUniqueMark = '~';
+ } */ /// FIXME: this is necessary, but needs group loading changes
+ SAFE_FREE((void**)&szGroup);
+ szGroup = szParentGroup;
+
+
+ if (getServListGroupLinkID(szGroup) == wGroupId)
+ { // known path, give
+ return szGroup;
+ }
+ else
+ { // unknown path, setup a link
+ setServListGroupLinkID(szGroup, wGroupId);
+ return szGroup;
+ }
+ }
+ }
+ else
+ { // normal group, setup a link
+ setServListGroupLinkID(szGroup, wGroupId);
+ return szGroup;
+ }
+ }
+ }
+ return NULL;
+}
+
+
+static int SrvGroupNamesEnumProc(const char *szSetting, LPARAM lParam)
+{ // check server-group cache item
+ const char **params = (const char**)lParam;
+ CIcqProto *ppro = (CIcqProto*)params[0];
+ char *szGroupName = ppro->getSettingStringUtf(NULL, params[3], szSetting, NULL);
+
+ if (!strcmpnull(szGroupName, params[2]))
+ params[1] = szSetting; // do not need the real value, just arbitrary non-NULL
+
+ SAFE_FREE(&szGroupName);
+ return 0;
+}
+
+char* CIcqProto::getServListUniqueGroupName(const char *szGroupName, int bAlloced)
+{ // enum ICQSrvGroups and create unique name if neccessary
+ DBCONTACTENUMSETTINGS dbces;
+ char szModule[MAX_PATH];
+ char *pars[4];
+ int uniqueID = 1;
+ char *szGroupNameBase = (char*)szGroupName;
+ char *szNewGroupName = NULL;
+
+ if (!bAlloced)
+ szGroupNameBase = null_strdup(szGroupName);
+ null_strcut(szGroupNameBase, m_wServerListRecordNameMaxLength);
+
+ null_snprintf(szModule, SIZEOF(szModule), "%sSrvGroups", m_szModuleName);
+
+ do {
+ pars[0] = (char*)this;
+ pars[1] = NULL;
+ pars[2] = szNewGroupName ? szNewGroupName : szGroupNameBase;
+ pars[3] = szModule;
+
+ dbces.pfnEnumProc = &SrvGroupNamesEnumProc;
+ dbces.szModule = szModule;
+ dbces.lParam = (LPARAM)pars;
+
+ CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces);
+
+ if (pars[1])
+ { // the groupname already exists, create another
+ SAFE_FREE((void**)&szNewGroupName);
+
+ char szUnique[10];
+ _itoa(uniqueID++, szUnique, 10);
+ null_strcut(szGroupNameBase, m_wServerListRecordNameMaxLength - strlennull(szUnique) - 1);
+ szNewGroupName = (char*)SAFE_MALLOC(strlennull(szUnique) + strlennull(szGroupNameBase) + 2);
+ if (szNewGroupName)
+ {
+ strcpy(szNewGroupName, szGroupNameBase);
+ strcat(szNewGroupName, "~");
+ strcat(szNewGroupName, szUnique);
+ }
+ }
+ } while (pars[1] && szNewGroupName);
+
+ if (szNewGroupName)
+ {
+ SAFE_FREE(&szGroupNameBase);
+ return szNewGroupName;
+ }
+ if (szGroupName != szGroupNameBase)
+ {
+ SAFE_FREE(&szGroupNameBase);
+ return (char*)szGroupName;
+ }
+ else
+ return szGroupNameBase;
+}
+
+
+// this is the second part of recursive event-driven procedure
+int CIcqProto::servlistCreateGroup_gotParentGroup(const char *szGroup, WORD wGroupID, LPARAM param, int nResult)
+{
+ cookie_servlist_action* clue = (cookie_servlist_action*)param;
+ char *szSubGroupName = clue->szGroupName;
+ char *szSubGroup;
+ int wSubGroupLevel = -1;
+ WORD wSubGroupID;
+
+ SAFE_FREE((void**)&clue);
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // only cleanup
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ szSubGroup = (char*)SAFE_MALLOC(strlennull(szGroup) + strlennull(szSubGroupName) + 2);
+ if (szSubGroup)
+ {
+ strcpy(szSubGroup, szGroup);
+ strcat(szSubGroup, "\\");
+ strcat(szSubGroup, szSubGroupName);
+ }
+
+ if (nResult == PENDING_RESULT_SUCCESS) // if we got an id count level
+ wSubGroupLevel = getServListGroupLevel(wGroupID);
+
+ if (wSubGroupLevel == -1)
+ { // something went wrong, give the id and go away
+ servlistPendingRemoveGroup(szSubGroup, wGroupID, PENDING_RESULT_FAILED);
+
+ SAFE_FREE((void**)&szSubGroupName);
+ SAFE_FREE((void**)&szSubGroup);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+ wSubGroupLevel++; // we are a sub-group
+ wSubGroupID = wGroupID + 1;
+
+ // check if on that id is not group of the same or greater level, if yes, try next
+ while (CheckServerID(wSubGroupID, 0) && (getServListGroupLevel(wSubGroupID) >= wSubGroupLevel))
+ {
+ wSubGroupID++;
+ }
+
+ if (!CheckServerID(wSubGroupID, 0))
+ { // the next id is free, so create our group with that id
+ cookie_servlist_action *ack;
+ DWORD dwCookie;
+ char *szSubGroupItem = (char*)SAFE_MALLOC(strlennull(szSubGroupName) + wSubGroupLevel + 1);
+
+ if (szSubGroupItem)
+ {
+ int i;
+
+ for (i=0; i < wSubGroupLevel; i++)
+ szSubGroupItem[i] = '>';
+
+ strcpy(szSubGroupItem + wSubGroupLevel, szSubGroupName);
+ szSubGroupItem[strlennull(szSubGroupName) + wSubGroupLevel] = '\0';
+ SAFE_FREE((void**)&szSubGroupName);
+ // check and create unique group name (Miranda does allow more subgroups with the same name!)
+ szSubGroupItem = getServListUniqueGroupName(szSubGroupItem, TRUE);
+
+ if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)))
+ { // we have cookie good, go on
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Creating sub-group \"%s\", parent group \"%s\".", szSubGroupItem, szGroup);
+#endif
+ ReserveServerID(wSubGroupID, SSIT_GROUP, 0);
+
+ ack->wGroupId = wSubGroupID;
+ ack->szGroupName = szSubGroupItem; // we need that name
+ ack->szGroup = szSubGroup;
+ ack->dwAction = SSA_GROUP_ADD;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack);
+
+ icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, ack->wGroupId, szSubGroupItem, NULL, 0, SSOF_BEGIN_OPERATION);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+ SAFE_FREE((void**)&szSubGroupItem);
+ }
+ }
+ // we failed to create sub-group give parent groupid
+ icq_LogMessage(LOG_ERROR, LPGEN("Failed to create the correct sub-group, the using closest parent group."));
+
+ servlistPendingRemoveGroup(szSubGroup, wGroupID, PENDING_RESULT_FAILED);
+
+ SAFE_FREE((void**)&szSubGroupName);
+ SAFE_FREE((void**)&szSubGroup);
+ return CALLBACK_RESULT_CONTINUE;
+}
+
+
+int CIcqProto::servlistCreateGroup_Ready(const char *szGroup, WORD groupID, LPARAM param, int nResult)
+{
+ WORD wGroupID = 0;
+
+ if (nResult == PENDING_RESULT_PURGE)
+ return CALLBACK_RESULT_CONTINUE;
+
+ if (wGroupID = getServListGroupLinkID(szGroup))
+ { // the path is known, continue the process
+ servlistPendingRemoveGroup(szGroup, wGroupID, PENDING_RESULT_SUCCESS);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ if (!strstrnull(szGroup, "\\") || m_bSsiSimpleGroups)
+ { // a root group can be simply created without problems; simple groups are mapped directly
+ cookie_servlist_action* ack;
+ DWORD dwCookie;
+
+ if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)))
+ { // we have cookie good, go on
+#ifdef _DEBUG
+ NetLog_Server("Server-List: Creating root group \"%s\".", szGroup);
+#endif
+ ack->wGroupId = GenerateServerID(SSIT_GROUP, 0);
+ ack->szGroup = null_strdup(szGroup); // we need that name
+ // check if the groupname is unique - just to be sure, Miranda should handle that!
+ ack->szGroupName = getServListUniqueGroupName(ack->szGroup, FALSE);
+ ack->dwAction = SSA_GROUP_ADD;
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, 0, ack);
+
+ icq_sendServerGroup(dwCookie, ICQ_LISTS_ADDTOLIST, ack->wGroupId, ack->szGroup, NULL, 0, SSOF_BEGIN_OPERATION);
+
+ return CALLBACK_RESULT_POSTPONE;
+ }
+ }
+ else
+ { // this is a sub-group
+ char* szSub = null_strdup(szGroup); // create subgroup, recursive, event-driven, possibly relocate
+ cookie_servlist_action* ack;
+ char *szLast;
+
+ if (strstrnull(szSub, "\\"))
+ { // determine parent group
+ szLast = strrchr(szSub, '\\') + 1;
+
+ szLast[-1] = '\0';
+ }
+ // make parent group id
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (ack)
+ {
+ ack->szGroupName = null_strdup(szLast); // groupname
+ servlistCreateGroup(szSub, (LPARAM)ack, &CIcqProto::servlistCreateGroup_gotParentGroup);
+ SAFE_FREE((void**)&szSub);
+
+ return CALLBACK_RESULT_POSTPONE;
+ }
+
+ SAFE_FREE((void**)&szSub);
+ }
+ servlistPendingRemoveGroup(szGroup, groupID, PENDING_RESULT_FAILED);
+
+ return CALLBACK_RESULT_CONTINUE;
+}
+
+
+// create group with this path, a bit complex task
+// this supposes that all server groups are known
+void CIcqProto::servlistCreateGroup(const char *szGroupPath, LPARAM param, PENDING_GROUP_CALLBACK callback)
+{
+ char *szGroup = (char*)szGroupPath;
+
+ if (!strlennull(szGroup)) szGroup = DEFAULT_SS_GROUP;
+
+ servlistPendingAddGroup(szGroup, 0, 0, &CIcqProto::servlistCreateGroup_Ready, TRUE, param, callback);
+}
+
+
+/*****************************************
+*
+* --- Server-List Operations ---
+*
+*/
+
+int CIcqProto::servlistAddContact_gotGroup(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult)
+{
+ cookie_servlist_action* ack = (cookie_servlist_action*)lParam;
+
+ if (ack) SAFE_FREE(&ack->szGroup);
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // only cleanup
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ if (!ack || !wGroupID) // something went wrong
+ {
+ if (ack) servlistPendingRemoveContact(ack->hContact, 0, wGroupID, PENDING_RESULT_FAILED);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0);
+
+ if (wItemID) /// TODO: redundant ???
+ { // Only add the contact if it doesnt already have an ID
+ servlistPendingRemoveContact(ack->hContact, wItemID, wGroupID, PENDING_RESULT_SUCCESS);
+ NetLog_Server("Failed to add contact to server side list (%s)", "already there");
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ wItemID = GenerateServerID(SSIT_ITEM, 0);
+
+ ack->dwAction = SSA_CONTACT_ADD;
+ ack->wGroupId = wGroupID;
+ ack->wContactId = wItemID;
+
+ DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, ack->hContact, ack);
+
+ icq_sendServerContact(ack->hContact, dwCookie, ICQ_LISTS_ADDTOLIST, wGroupID, wItemID, SSOP_ITEM_ACTION | SSOF_CONTACT | SSOF_BEGIN_OPERATION, 400, NULL);
+
+ return CALLBACK_RESULT_CONTINUE;
+}
+
+
+// Need to be called when Pending Contact is active
+int CIcqProto::servlistAddContact_Ready(HANDLE hContact, WORD wContactID, WORD wGroupID, LPARAM lParam, int nResult)
+{
+ cookie_servlist_action* ack = (cookie_servlist_action*)lParam;
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // removing obsolete items, just free the memory
+ SAFE_FREE((void**)&ack->szGroup);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0);
+
+ if (wItemID)
+ { // Only add the contact if it doesn't already have an ID
+ servlistPendingRemoveContact(ack->hContact, wItemID, getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0), PENDING_RESULT_SUCCESS);
+ NetLog_Server("Failed to add contact to server side list (%s)", "already there");
+ SAFE_FREE((void**)&ack->szGroup);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ // obtain a correct groupid first
+ servlistCreateGroup(ack->szGroup, lParam, &CIcqProto::servlistAddContact_gotGroup);
+
+ return CALLBACK_RESULT_POSTPONE;
+}
+
+
+// Called when contact should be added to server list, if group does not exist, create one
+void CIcqProto::servlistAddContact(HANDLE hContact, const char *pszGroup)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ cookie_servlist_action* ack;
+
+ // Get UID
+ if (getContactUid(hContact, &dwUin, &szUid))
+ { // Could not do anything without uid
+ NetLog_Server("Failed to add contact to server side list (%s)", "no UID");
+ return;
+ }
+
+ if (!(ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))))
+ { // Could not do anything without cookie
+ NetLog_Server("Failed to add contact to server side list (%s)", "malloc failed");
+ return;
+ }
+ else
+ {
+ ack->hContact = hContact;
+ ack->szGroup = null_strdup(pszGroup);
+ // call thru pending operations - makes sure the contact is ready to be added
+ servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistAddContact_Ready, TRUE);
+ return;
+ }
+}
+
+
+int CIcqProto::servlistRemoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult)
+{
+ WORD wGroupID;
+ WORD wItemID;
+ cookie_servlist_action* ack = (cookie_servlist_action*)lParam;
+ DWORD dwCookie;
+
+ if (nResult == PENDING_RESULT_PURGE)
+ {
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ // Get the contact's group ID
+ if (!(wGroupID = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0)))
+ { // Could not find a usable group ID
+ servlistPendingRemoveContact(hContact, contactID, groupID, PENDING_RESULT_FAILED);
+
+ NetLog_Server("Failed to remove contact from server side list (%s)", "no group ID");
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ // Get the contact's item ID
+ if (!(wItemID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0)))
+ { // Could not find usable item ID
+ servlistPendingRemoveContact(hContact, contactID, wGroupID, PENDING_RESULT_FAILED);
+
+ NetLog_Server("Failed to remove contact from server side list (%s)", "no item ID");
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ ack->dwAction = SSA_CONTACT_REMOVE;
+ ack->hContact = hContact;
+ ack->wGroupId = wGroupID;
+ ack->wContactId = wItemID;
+
+ dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, hContact, ack);
+
+ icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupID, wItemID, SSOP_ITEM_ACTION | SSOF_CONTACT | SSOF_BEGIN_OPERATION, 400, NULL);
+
+ return CALLBACK_RESULT_POSTPONE;
+}
+
+
+// Called when contact should be removed from server list, remove group if it remain empty
+void CIcqProto::servlistRemoveContact(HANDLE hContact)
+{
+ DWORD dwUin;
+ uid_str szUid;
+ cookie_servlist_action* ack;
+
+ // Get UID
+ if (getContactUid(hContact, &dwUin, &szUid))
+ {
+ // Could not do anything without uid
+ NetLog_Server("Failed to remove contact from server side list (%s)", "no UID");
+ return;
+ }
+
+ if (!(ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action))))
+ { // Could not do anything without cookie
+ NetLog_Server("Failed to remove contact from server side list (%s)", "malloc failed");
+ return;
+ }
+ else
+ {
+ ack->hContact = hContact;
+ // call thru pending operations - makes sure the contact is ready to be removed
+ servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistRemoveContact_Ready, TRUE);
+ return;
+ }
+}
+
+
+int CIcqProto::servlistMoveContact_gotTargetGroup(const char *szGroup, WORD wNewGroupID, LPARAM lParam, int nResult)
+{
+ cookie_servlist_action *ack = (cookie_servlist_action*)lParam;
+
+ if (ack) SAFE_FREE(&ack->szGroup);
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // removing obsolete items, just free the memory
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ if (!ack || !wNewGroupID || !ack->hContact) // something went wrong
+ {
+ if (ack) servlistPendingRemoveContact(ack->hContact, 0, 0, PENDING_RESULT_FAILED);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0);
+ WORD wGroupID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_GROUP, 0);
+
+ if (!wItemID)
+ { // We have no ID, so try to simply add the contact to serv-list
+ NetLog_Server("Unable to move contact (no ItemID) -> trying to add");
+ // we know the GroupID, so directly call add
+ return servlistAddContact_gotGroup(szGroup, wNewGroupID, lParam, nResult);
+ }
+
+ if (wGroupID == wNewGroupID)
+ { // Only move the contact if it had different GroupID
+ servlistPendingRemoveContact(ack->hContact, wItemID, wNewGroupID, PENDING_RESULT_SUCCESS);
+ NetLog_Server("Contact not moved to group on server side list (same Group)");
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ ack->szGroupName = NULL;
+ ack->dwAction = SSA_CONTACT_SET_GROUP;
+ ack->wGroupId = wGroupID;
+ ack->wContactId = wItemID;
+ ack->wNewContactId = GenerateServerID(SSIT_ITEM, 0); // icq5 recreates also this, imitate
+ ack->wNewGroupId = wNewGroupID;
+ ack->lParam = 0; // we use this as a sign
+
+ DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, ack->hContact, ack);
+ DWORD dwCookie2 = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_ADDTOLIST, ack->hContact, ack);
+
+ { // imitate icq5, previously here was different order, but AOL changed and it ceased to work
+ void *doubleObject = NULL;
+
+ icq_sendServerContact(ack->hContact, dwCookie2, ICQ_LISTS_ADDTOLIST, wNewGroupID, ack->wNewContactId, SSO_CONTACT_SETGROUP | SSOF_BEGIN_OPERATION, 500, &doubleObject);
+ icq_sendServerContact(ack->hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupID, wItemID, SSO_CONTACT_SETGROUP | SSOF_BEGIN_OPERATION, 500, &doubleObject);
+ }
+ return CALLBACK_RESULT_CONTINUE;
+}
+
+
+int CIcqProto::servlistMoveContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult)
+{
+ cookie_servlist_action *ack = (cookie_servlist_action*)lParam;
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // removing obsolete items, just free the memory
+ SAFE_FREE(&ack->szGroup);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ WORD wItemID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_ID, 0);
+ WORD wGroupID = getSettingWord(ack->hContact, DBSETTING_SERVLIST_GROUP, 0);
+
+ if (!wGroupID && wItemID)
+ { // Only move the contact if it had an GroupID
+ servlistPendingRemoveContact(ack->hContact, contactID, groupID, PENDING_RESULT_FAILED);
+
+ NetLog_Server("Failed to move contact to group on server side list (%s)", "no Group");
+ SAFE_FREE(&ack->szGroup);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ // obtain a correct target groupid first
+ servlistCreateGroup(ack->szGroup, lParam, &CIcqProto::servlistMoveContact_gotTargetGroup);
+
+ return CALLBACK_RESULT_POSTPONE;
+}
+
+
+// Called when contact should be moved from one group to another, create new, remove empty
+void CIcqProto::servlistMoveContact(HANDLE hContact, const char *pszNewGroup)
+{
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (!hContact) return; // we do not move us, caused our uin was wrongly added to list
+
+ // Get UID
+ if (getContactUid(hContact, &dwUin, &szUid))
+ { // Could not do anything without uin
+ NetLog_Server("Failed to move contact to group on server side list (%s)", "no UID");
+ return;
+ }
+
+ if ((pszNewGroup != NULL) && (pszNewGroup[0]!='\0') && !getCListGroupExists(pszNewGroup))
+ { // the contact moved to non existing group, do not do anything: MetaContact hack
+ NetLog_Server("Contact not moved - probably hiding by MetaContacts.");
+ return;
+ }
+
+ if (!getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0)) /// FIXME:::: this should be in _ready
+ { // the contact is not stored on the server, check if we should try to add
+ if (!getSettingByte(NULL, "ServerAddRemove", DEFAULT_SS_ADDSERVER) ||
+ DBGetContactSettingByte(hContact, "CList", "Hidden", 0))
+ return;
+ }
+ cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+
+ if (!ack)
+ { // Could not do anything without cookie
+ NetLog_Server("Failed to add contact to server side list (%s)", "malloc failed");
+ return;
+ }
+ else
+ {
+ ack->hContact = hContact;
+ ack->szGroup = null_strdup(pszNewGroup);
+ // call thru pending operations - makes sure the contact is ready to be moved
+ servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistMoveContact_Ready, TRUE);
+ return;
+ }
+}
+
+
+int CIcqProto::servlistUpdateContact_Ready(HANDLE hContact, WORD contactID, WORD groupID, LPARAM lParam, int nResult)
+{
+ cookie_servlist_action *ack = (cookie_servlist_action*)lParam;
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // removing obsolete items, just free the memory
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+ WORD wItemID;
+ WORD wGroupID;
+
+ // Get the contact's group ID
+ if (!(wGroupID = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0)))
+ {
+ servlistPendingRemoveContact(hContact, contactID, groupID, PENDING_RESULT_FAILED);
+ // Could not find a usable group ID
+ NetLog_Server("Failed to update contact's details on server side list (%s)", "no group ID");
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ // Get the contact's item ID
+ if (!(wItemID = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0)))
+ {
+ servlistPendingRemoveContact(hContact, contactID, wGroupID, PENDING_RESULT_FAILED);
+ // Could not find usable item ID
+ NetLog_Server("Failed to update contact's details on server side list (%s)", "no item ID");
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ ack->dwAction = SSA_CONTACT_UPDATE;
+ ack->wContactId = wItemID;
+ ack->wGroupId = wGroupID;
+ ack->hContact = hContact;
+
+ DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, hContact, ack);
+
+ // There is no need to send ICQ_LISTS_CLI_MODIFYSTART or
+ // ICQ_LISTS_CLI_MODIFYEND when just changing nick name
+ icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_UPDATEGROUP, wGroupID, wItemID, SSOP_ITEM_ACTION | SSOF_CONTACT, 400, NULL);
+
+ return CALLBACK_RESULT_POSTPONE;
+}
+
+
+// Is called when a contact' details has been changed locally to update
+// the server side details.
+void CIcqProto::servlistUpdateContact(HANDLE hContact)
+{
+ DWORD dwUin;
+ uid_str szUid;
+
+ // Get UID
+ if (getContactUid(hContact, &dwUin, &szUid))
+ {
+ // Could not set nickname on server without uid
+ NetLog_Server("Failed to update contact's details on server side list (%s)", "no UID");
+ return;
+ }
+ cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+
+ if (!ack)
+ {
+ // Could not allocate cookie - use old fake
+ NetLog_Server("Failed to update contact's details on server side list (%s)", "malloc failed");
+ return;
+ }
+ else
+ {
+ ack->hContact = hContact;
+ // call thru pending operations - makes sure the contact is ready to be updated
+ servlistPendingAddContact(hContact, 0, 0, (LPARAM)ack, &CIcqProto::servlistUpdateContact_Ready, TRUE);
+ return;
+ }
+}
+
+
+int CIcqProto::servlistRenameGroup_Ready(const char *szGroup, WORD wGroupID, LPARAM lParam, int nResult)
+{
+ cookie_servlist_action *ack = (cookie_servlist_action*)lParam;
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // only cleanup
+ if (ack) SAFE_FREE(&ack->szGroupName);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ if (!ack || !wGroupID) // something went wrong
+ {
+ servlistPendingRemoveGroup(szGroup, wGroupID, PENDING_RESULT_FAILED);
+
+ if (ack) SAFE_FREE(&ack->szGroupName);
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+ void *groupData;
+ int groupSize;
+
+ if (groupData = collectBuddyGroup(wGroupID, &groupSize))
+ {
+ ack->dwAction = SSA_GROUP_RENAME;
+ ack->wGroupId = wGroupID;
+ ack->szGroup = null_strdup(szGroup); // we need this name
+ // check if the new name is unique, create unique groupname if necessary
+ ack->szGroupName = getServListUniqueGroupName(ack->szGroupName, TRUE);
+
+ DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack);
+
+ icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, wGroupID, ack->szGroupName, groupData, groupSize, 0);
+ SAFE_FREE(&groupData);
+ }
+ return CALLBACK_RESULT_POSTPONE;
+}
+
+
+void CIcqProto::servlistRenameGroup(char *szGroup, WORD wGroupId, char *szNewGroup)
+{
+ char *szNewGroupName;
+ int nGroupLevel = getServListGroupLevel(wGroupId);
+
+ if (nGroupLevel == -1) return; // we failed to prepare group
+
+ if (!m_bSsiSimpleGroups)
+ {
+ char *szGroupName = szGroup;
+ int i = nGroupLevel;
+ while (i)
+ { // find correct part of grouppath
+ szGroupName = strstrnull(szGroupName, "\\");
+ if (!szGroupName) return; // failed to get correct part of the grouppath
+ szGroupName++;
+ i--;
+ }
+ szNewGroupName = szNewGroup;
+ i = nGroupLevel;
+ while (i)
+ { // find correct part of new grouppath
+ szNewGroupName = strstrnull(szNewGroupName, "\\");
+ if (!szNewGroupName) return; // failed to get correct part of the new grouppath
+ szNewGroupName++;
+ i--;
+ }
+ // truncate possible sub-groups
+ char *szLast = strstrnull(szGroupName, "\\");
+ if (szLast)
+ szLast[0] = '\0';
+ szLast = strstrnull(szNewGroupName, "\\");
+ if (szLast)
+ szLast[0] = '\0';
+
+ // this group was not changed, nothing to rename
+ if (!strcmpnull(szGroupName, szNewGroupName)) return;
+
+ szGroupName = szNewGroupName;
+ szNewGroupName = (char*)SAFE_MALLOC(strlennull(szGroupName) + 1 + nGroupLevel);
+ if (!szNewGroupName) return; // Failure
+
+ for (i = 0; i < nGroupLevel; i++)
+ { // create level prefix
+ szNewGroupName[i] = '>';
+ }
+ strcat(szNewGroupName, szGroupName);
+ }
+ else // simple groups do not require any conversion
+ szNewGroupName = null_strdup(szNewGroup);
+
+ cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (!ack)
+ { // cookie failed
+ NetLog_Server("Error: Failed to allocate cookie");
+
+ SAFE_FREE(&szNewGroupName);
+ return;
+ }
+ // store new group name for future use
+ ack->szGroupName = szNewGroupName;
+ // call thru pending operations - makes sure the group is ready for rename
+ servlistPendingAddGroup(szGroup, wGroupId, (LPARAM)ack, &CIcqProto::servlistRenameGroup_Ready, TRUE);
+}
+
+
+int CIcqProto::servlistRemoveGroup_Ready(const char *szGroup, WORD groupID, LPARAM lParam, int nResult)
+{
+ cookie_servlist_action *ack = (cookie_servlist_action*)lParam;
+
+ if (nResult == PENDING_RESULT_PURGE)
+ { // only cleanup
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+ WORD wGroupID = getServListGroupLinkID(szGroup);
+ char *szGroupName;
+
+ if (wGroupID && (szGroupName = getServListGroupName(wGroupID)))
+ { // the group is valid, check if it is empty
+ void *groupData = collectBuddyGroup(wGroupID, NULL);
+
+ if (groupData)
+ { // the group is not empty, cannot delete
+ SAFE_FREE(&groupData);
+ SAFE_FREE(&szGroupName);
+ // end operation
+ servlistPendingRemoveGroup(szGroup, wGroupID, PENDING_RESULT_SUCCESS);
+ // cleanup
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+ }
+
+ if (!CheckServerID((WORD)(wGroupID+1), 0) || getServListGroupLevel((WORD)(wGroupID+1)) == 0)
+ { // is next id an sub-group, if yes, we cannot delete this group
+ ack->dwAction = SSA_GROUP_REMOVE;
+ ack->wContactId = 0;
+ ack->wGroupId = wGroupID;
+ ack->hContact = NULL;
+ ack->szGroup = null_strdup(szGroup); // we need that name
+ ack->szGroupName = szGroupName;
+ DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_REMOVEFROMLIST, 0, ack);
+
+ icq_sendServerGroup(dwCookie, ICQ_LISTS_REMOVEFROMLIST, ack->wGroupId, ack->szGroupName, NULL, 0, 0);
+ }
+ return CALLBACK_RESULT_POSTPONE;
+ }
+ // end operation
+ servlistPendingRemoveGroup(szGroup, groupID, PENDING_RESULT_SUCCESS);
+ // cleanup
+ SAFE_FREE((void**)&ack);
+ return CALLBACK_RESULT_CONTINUE;
+}
+
+
+void CIcqProto::servlistRemoveGroup(const char *szGroup, WORD wGroupId)
+{
+ if (!szGroup) return;
+
+ cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+
+ if (!ack)
+ { // cookie failed
+ NetLog_Server("Error: Failed to allocate cookie");
+ return;
+ }
+
+ // call thru pending operations - makes sure the group is ready for removal
+ servlistPendingAddGroup(szGroup, wGroupId, (LPARAM)ack, &CIcqProto::servlistRemoveGroup_Ready, TRUE);
+}
+
+
+/*void CIcqProto::servlistMoveGroup(const char *szGroup, WORD wNewGroupId)
+{
+// relocate the group
+}*/
+
+
+void CIcqProto::resetServContactAuthState(HANDLE hContact, DWORD dwUin)
+{
+ WORD wContactId = getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0);
+ WORD wGroupId = getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0);
+
+ if (wContactId && wGroupId)
+ {
+ cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+
+ if (ack)
+ { // we have cookie good, go on
+ ack->hContact = hContact;
+ ack->wContactId = wContactId;
+ ack->wGroupId = wGroupId;
+ ack->dwAction = SSA_CONTACT_FIX_AUTH;
+
+ DWORD dwCookie = AllocateCookie(CKT_SERVERLIST, 0, hContact, ack);
+
+ {
+ void *doubleObject = NULL;
+
+ icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_REMOVEFROMLIST, wGroupId, wContactId, SSO_CONTACT_FIXAUTH | SSOF_BEGIN_OPERATION | SSOF_END_OPERATION, 200, &doubleObject);
+ deleteSetting(hContact, DBSETTING_METAINFO_TOKEN);
+ deleteSetting(hContact, DBSETTING_METAINFO_TIME);
+ deleteSetting(hContact, DBSETTING_SERVLIST_DATA);
+ icq_sendServerContact(hContact, dwCookie, ICQ_LISTS_ADDTOLIST, wGroupId, wContactId, SSO_CONTACT_FIXAUTH | SSOF_BEGIN_OPERATION | SSOF_END_OPERATION, 200, &doubleObject);
+ }
+ }
+ else
+ NetLog_Server("Error: Failed to allocate cookie");
+ }
+}
+
+/*****************************************
+*
+* --- Miranda Contactlist Hooks ---
+*
+*/
+
+int CIcqProto::ServListDbSettingChanged(WPARAM wParam, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING* cws = (DBCONTACTWRITESETTING*)lParam;
+
+ // TODO: Queue changes that occur while offline
+ if (!icqOnline() || !m_bSsiEnabled || bIsSyncingCL)
+ return 0;
+
+#ifdef _DEBUG
+ if (cws->value.type == DBVT_DELETED)
+ NetLog_Server("DB-Events: Module \"%s\", setting \"%s\" deleted.", cws->szModule, cws->szSetting);
+ else
+ NetLog_Server("DB-Events: Module \"%s\", setting \"%s\" changed, data type %x.", cws->szModule, cws->szSetting, cws->value.type);
+#endif
+
+ if (!strcmpnull(cws->szModule, "CList"))
+ {
+ // Has contact been renamed?
+ if (!strcmpnull(cws->szSetting, "MyHandle") &&
+ getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE))
+ { // Update contact's details in server-list
+ servlistUpdateContact((HANDLE)wParam);
+ }
+
+ // Has contact been moved to another group?
+ if (!strcmpnull(cws->szSetting, "Group") &&
+ getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE))
+ { // Read group from DB
+ char* szNewGroup = getContactCListGroup((HANDLE)wParam);
+
+ SAFE_FREE(&szNewGroup);
+ }
+ }
+ else if (!strcmpnull(cws->szModule, "UserInfo"))
+ {
+ if (!strcmpnull(cws->szSetting, "MyNotes") &&
+ getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE))
+ { // Update contact's details in server-list
+ servlistUpdateContact((HANDLE)wParam);
+ }
+ }
+
+ return 0;
+}
+
+
+int CIcqProto::ServListDbContactDeleted(WPARAM wParam, LPARAM lParam)
+{
+#ifdef _DEBUG
+ NetLog_Server("DB-Events: Contact %x deleted.", wParam);
+#endif
+
+ DeleteFromContactsCache((HANDLE)wParam);
+
+ if ( !icqOnline() && m_bSsiEnabled)
+ { // contact was deleted only locally - retrieve full list on next connect
+ setSettingWord((HANDLE)wParam, "SrvRecordCount", 0);
+ }
+
+ if ( !icqOnline() || !m_bSsiEnabled)
+ return 0;
+
+ { // we need all server contacts on local buddy list
+ DWORD dwUIN;
+ uid_str szUID;
+
+ if (getContactUid((HANDLE)wParam, &dwUIN, &szUID))
+ return 0;
+
+ WORD wContactID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0);
+ WORD wGroupID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_GROUP, 0);
+ WORD wVisibleID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_PERMIT, 0);
+ WORD wInvisibleID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_DENY, 0);
+ WORD wIgnoreID = getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_IGNORE, 0);
+
+ // Remove from queue for user details request
+ icq_DequeueUser(dwUIN);
+
+ // Close all opened peer connections
+ CloseContactDirectConns((HANDLE)wParam);
+
+ if ((wGroupID && wContactID) || wVisibleID || wInvisibleID || wIgnoreID)
+ {
+ if (wContactID)
+ { // delete contact from server
+ servlistRemoveContact((HANDLE)wParam);
+ }
+
+ if (wVisibleID)
+ { // detete permit record
+ icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wVisibleID, SSI_ITEM_PERMIT);
+ }
+
+ if (wInvisibleID)
+ { // delete deny record
+ icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wInvisibleID, SSI_ITEM_DENY);
+ }
+
+ if (wIgnoreID)
+ { // delete ignore record
+ icq_removeServerPrivacyItem((HANDLE)wParam, dwUIN, szUID, wIgnoreID, SSI_ITEM_IGNORE);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+int CIcqProto::ServListCListGroupChange(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE)wParam;
+ CLISTGROUPCHANGE *grpchg = (CLISTGROUPCHANGE*)lParam;
+
+ if (!icqOnline() || !m_bSsiEnabled || bIsSyncingCL)
+ return 0;
+
+ // only change server-list if it is allowed
+ if (!getSettingByte(NULL, "StoreServerDetails", DEFAULT_SS_STORE))
+ return 0;
+
+
+ if (hContact == NULL)
+ { // change made to group
+ if (grpchg->pszNewName == NULL && grpchg->pszOldName != NULL)
+ { // group removed
+ char *szOldName = tchar_to_utf8(grpchg->pszOldName);
+ WORD wGroupId = getServListGroupLinkID(szOldName);
+
+#ifdef _DEBUG
+ NetLog_Server("CList-Events: Group %x:\"%s\" deleted.", wGroupId, szOldName);
+#endif
+ if (wGroupId)
+ { // the group is known, remove from server
+ servlistPostPacket(NULL, 0, SSO_BEGIN_OPERATION, 100); // start server modifications here
+ servlistRemoveGroup(szOldName, wGroupId);
+ }
+ SAFE_FREE(&szOldName);
+ }
+ else if (grpchg->pszNewName != NULL && grpchg->pszOldName != NULL)
+ { // group renamed
+ char *szNewName = tchar_to_utf8(grpchg->pszNewName);
+ char *szOldName = tchar_to_utf8(grpchg->pszOldName);
+ WORD wGroupId = getServListGroupLinkID(szOldName);
+
+#ifdef _DEBUG
+ NetLog_Server("CList-Events: Group %x:\"%s\" changed to \"%s\".", wGroupId, szOldName, szNewName);
+#endif
+ if (wGroupId)
+ { // group is known, rename on server
+ servlistRenameGroup(szOldName, wGroupId, szNewName);
+ }
+ SAFE_FREE(&szOldName);
+ SAFE_FREE(&szNewName);
+ }
+ }
+ else
+ { // change to contact
+ if (IsICQContact(hContact))
+ { // our contact, fine move on the server as well
+ char *szNewName = grpchg->pszNewName ? tchar_to_utf8(grpchg->pszNewName) : NULL;
+
+#ifdef _DEBUG
+ NetLog_Server("CList-Events: Contact %x moved to group \"%s\".", hContact, szNewName);
+#endif
+ servlistMoveContact(hContact, szNewName);
+ SAFE_FREE(&szNewName);
+ }
+ }
+ return 0;
+}
diff --git a/protocols/IcqOscarJ/src/icq_servlist.h b/protocols/IcqOscarJ/src/icq_servlist.h
new file mode 100644
index 0000000000..76118ce3c0
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_servlist.h
@@ -0,0 +1,172 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQ_SERVLIST_H
+#define __ICQ_SERVLIST_H
+
+// actions:
+#define SSA_CHECK_ROSTER 0 // request serv-list
+#define SSA_VISIBILITY 1 // update visibility
+#define SSA_CONTACT_UPDATE 2 // update contact's details
+#define SSA_GROUP_RENAME 5 // rename group
+#define SSA_PRIVACY_ADD 0xA // add privacy item
+#define SSA_PRIVACY_REMOVE 0xB // remove privacy item
+#define SSA_CONTACT_ADD 0x10 // add contact w/o auth
+#define SSA_CONTACT_SET_GROUP 0x12 // move to group
+#define SSA_CONTACT_REMOVE 0x13 // delete contact
+#define SSA_CONTACT_FIX_AUTH 0x40 // reuploading contact for auth re-request
+#define SSA_GROUP_ADD 0x15 // create group
+#define SSA_GROUP_REMOVE 0x16 // delete group
+#define SSA_GROUP_UPDATE 0x17 // update group
+#define SSA_SERVLIST_ACK 0x20 // send proto ack only (UploadUI)
+#define SSA_SETAVATAR 0x30
+#define SSA_REMOVEAVATAR 0x31
+#define SSA_IMPORT 7
+#define SSA_ACTION_GROUP 0x80 // grouped action
+
+struct CIcqProto;
+// callback prototypes for pending operation mechanism:
+typedef int (__cdecl CIcqProto::*PENDING_GROUP_CALLBACK)(const char* pszGroup, WORD wGroupId, LPARAM lParam, int nResult);
+typedef int (__cdecl CIcqProto::*PENDING_CONTACT_CALLBACK)(HANDLE hContact, WORD wContactId, WORD wGroupId, LPARAM lParam, int nResult);
+
+// cookie struct for SSI actions
+struct cookie_servlist_action
+{
+ HANDLE hContact;
+ char *szGroup;
+ WORD wContactId;
+ WORD wGroupId;
+ char *szGroupName;
+ WORD wNewContactId;
+ WORD wNewGroupId;
+ int dwAction;
+ LPARAM lParam;
+ int dwGroupCount;
+ cookie_servlist_action **pGroupItems;
+};
+
+// server id type groups
+#define SSIT_ITEM 0x00000000
+#define SSIT_GROUP 0x00010000
+
+// server id flags
+#define SSIF_UNHANDLED 0x01000000
+
+
+// pending operations
+#define PENDING_RESULT_SUCCESS 0x00
+#define PENDING_RESULT_INLINE 0x01
+#define PENDING_RESULT_FAILED 0x0F
+#define PENDING_RESULT_PURGE 0x10
+
+// serv-list update board
+#define SSOG_SINGLE 0x00010000
+#define SSOG_DOUBLE 0x00020000
+
+#define SSOF_CONTACT 0x00800000
+#define SSOF_BEGIN_OPERATION 0x00100000
+#define SSOF_END_OPERATION 0x00200000
+#define SSOF_IMPORT_OPERATION 0x00400000
+
+#define SSOP_ITEM_ACTION 0x01000000 | SSOG_SINGLE
+// SSA_PRIVACY_ADD
+// SSA_CONTACT_ADD
+// SSA_CONTACT_UPDATE
+// SSA_VISIBILITY
+// SSA_PRIVACY_REMOVE
+// SSA_CONTACT_REMOVE
+// SSA_SETAVATAR
+// SSA_REMOVEAVATAR
+#define SSOP_GROUP_ACTION 0x02000000 | SSOG_SINGLE
+// SSA_GROUP_ADD
+// SSA_GROUP_RENAME
+// SSA_GROUP_UPDATE
+// SSA_GROUP_REMOVE
+#define SSO_CONTACT_SETGROUP 0x04000000 | SSOG_DOUBLE
+// SSA_CONTACT_SET_GROUP
+#define SSO_CONTACT_FIXAUTH 0x06000000 | SSOG_DOUBLE
+// SSA_CONTACT_FIX_AUTH
+
+#define SSO_BEGIN_OPERATION 0x80000000
+#define SSO_END_OPERATION 0x40000000
+
+#define SSOF_SEND_DIRECTLY 0x10000000
+
+#define SSOF_ACTIONMASK 0x0000FFFF
+#define SSOF_GROUPINGMASK 0x0F0FFFFF
+
+
+#define MAX_SERVLIST_PACKET_ITEMS 200
+
+// server-list request handler item
+struct servlistgroupitem
+{ // generic parent
+ DWORD dwOperation;
+ cookie_servlist_action* cookie;
+ icq_packet packet;
+ // perhaps add some dummy bytes
+};
+
+struct servlistgroupitemdouble: public servlistgroupitem
+{
+ icq_packet packet2;
+ WORD wAction2;
+};
+
+struct ssiqueueditems
+{
+ time_t tAdded;
+ int dwTimeout;
+ int nItems;
+ servlistgroupitem* pItems[MAX_SERVLIST_PACKET_ITEMS];
+};
+
+
+// cookie structs for pending records
+struct servlistpendingoperation
+{
+ DWORD flags;
+ PENDING_GROUP_CALLBACK callback;
+ LPARAM param;
+};
+
+struct servlistpendingitem
+{
+ int nType;
+ HANDLE hContact;
+ char* szGroup;
+ WORD wContactID;
+ WORD wGroupID;
+
+ servlistpendingoperation* operations;
+ int operationsCount;
+};
+
+
+#endif /* __ICQ_SERVLIST_H */
diff --git a/protocols/IcqOscarJ/src/icq_uploadui.cpp b/protocols/IcqOscarJ/src/icq_uploadui.cpp
new file mode 100644
index 0000000000..f5e5c937b3
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_uploadui.cpp
@@ -0,0 +1,1019 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Implements Manage Server List dialog
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+static int bListInit = 0;
+static HANDLE hItemAll;
+static int dwUploadDelay = 1000; // initial setting, it is too low for icq server but good for short updates
+
+static HWND hwndUploadContacts=NULL;
+static const UINT settingsControls[]={IDOK};
+
+static WORD* pwGroupIds = NULL;
+static int cbGroupIds = 0;
+
+// Init default clist options
+static void ResetCListOptions(HWND hwndList)
+{
+ int i;
+
+ SendMessage(hwndList, CLM_SETBKBITMAP, 0, (LPARAM)(HBITMAP)NULL);
+ SendMessage(hwndList, CLM_SETBKCOLOR, GetSysColor(COLOR_WINDOW), 0);
+ SendMessage(hwndList, CLM_SETGREYOUTFLAGS, 0, 0);
+ SendMessage(hwndList, CLM_SETLEFTMARGIN, 2, 0);
+ SendMessage(hwndList, CLM_SETINDENT, 10, 0);
+ for(i=0; i<=FONTID_MAX; i++)
+ SendMessage(hwndList, CLM_SETTEXTCOLOR, i, GetSysColor(COLOR_WINDOWTEXT));
+ SetWindowLongPtr(hwndList, GWL_STYLE, GetWindowLongPtr(hwndList, GWL_STYLE)|CLS_SHOWHIDDEN);
+ if (CallService(MS_CLUI_GETCAPS, 0, 0) & CLUIF_HIDEEMPTYGROUPS) // hide empty groups
+ SendMessage(hwndList, CLM_SETHIDEEMPTYGROUPS, (WPARAM) TRUE, 0);
+}
+
+// Selects the "All contacts" checkbox if all other list entries
+// are selected, deselects it if not.
+static void UpdateAllContactsCheckmark(HWND hwndList, CIcqProto* ppro, HANDLE phItemAll)
+{
+ int check = 1;
+
+ HANDLE hContact = ppro->FindFirstContact();
+ while (hContact)
+ {
+ HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem)
+ {
+ if (!SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0))
+ { // if any of our contacts is unchecked, uncheck all contacts as well
+ check = 0;
+ break;
+ }
+ }
+ hContact = ppro->FindNextContact(hContact);
+ }
+
+ SendMessage(hwndList, CLM_SETCHECKMARK, (WPARAM)phItemAll, check);
+}
+
+// Loop over all contacts and update the checkmark
+// that indicates wether or not they are already uploaded
+static int UpdateCheckmarks(HWND hwndList, CIcqProto* ppro, HANDLE phItemAll)
+{
+ int bAll = 1;
+ bListInit = 1; // lock CLC events
+
+ HANDLE hContact = ppro->FindFirstContact();
+ while (hContact)
+ {
+ HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem)
+ {
+ if (ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0))
+ SendMessage(hwndList, CLM_SETCHECKMARK, (WPARAM)hItem, 1);
+ else
+ bAll = 0;
+ }
+ hContact = ppro->FindNextContact(hContact);
+ }
+
+ // Update the "All contacts" checkmark
+ if (phItemAll)
+ SendMessage(hwndList, CLM_SETCHECKMARK, (WPARAM)phItemAll, bAll);
+
+ bListInit = 0;
+
+ return bAll;
+}
+
+static void DeleteOtherContactsFromControl(HWND hCtrl, CIcqProto* ppro)
+{
+ HANDLE hContact;
+ HANDLE hItem;
+
+ hContact = db_find_first();
+ while (hContact)
+ {
+ hItem = (HANDLE)SendMessage(hCtrl, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem)
+ {
+ if (!ppro->IsICQContact(hContact))
+ SendMessage(hCtrl, CLM_DELETEITEM, (WPARAM)hItem, 0);
+ }
+ hContact = db_find_next(hContact);
+ }
+}
+
+static void AppendToUploadLog(HWND hwndDlg, const char *fmt, ...)
+{
+ va_list va;
+ char szText[1024];
+ int iItem;
+
+ va_start(va, fmt);
+ mir_vsnprintf(szText, sizeof(szText), fmt, va);
+ va_end(va);
+
+ iItem = ListBoxAddStringUtf(GetDlgItem(hwndDlg, IDC_LOG), szText);
+ SendDlgItemMessage(hwndDlg, IDC_LOG, LB_SETTOPINDEX, iItem, 0);
+}
+
+static void DeleteLastUploadLogLine(HWND hwndDlg)
+{
+ SendDlgItemMessage(hwndDlg, IDC_LOG, LB_DELETESTRING, SendDlgItemMessage(hwndDlg, IDC_LOG, LB_GETCOUNT, 0, 0)-1, 0);
+}
+
+static void GetLastUploadLogLine(HWND hwndDlg, char *szBuf, size_t cbBuf)
+{
+ WCHAR str[MAX_PATH];
+ SendDlgItemMessageW(hwndDlg, IDC_LOG, LB_GETTEXT, SendDlgItemMessage(hwndDlg, IDC_LOG, LB_GETCOUNT, 0, 0)-1, (LPARAM)str);
+ make_utf8_string_static(str, szBuf, cbBuf);
+}
+
+static int GroupEnumIdsEnumProc(const char *szSetting,LPARAM lParam)
+{
+ if (szSetting && strlennull(szSetting)<5)
+ { // it is probably server group
+ char val[MAX_PATH+2]; // dummy
+ DBVARIANT dbv;
+ DBCONTACTGETSETTING cgs;
+
+ dbv.type = DBVT_ASCIIZ;
+ dbv.pszVal = val;
+ dbv.cchVal = MAX_PATH;
+
+ cgs.szModule=(char*)lParam;
+ cgs.szSetting=szSetting;
+ cgs.pValue=&dbv;
+ if(CallService(MS_DB_CONTACT_GETSETTINGSTATIC,(WPARAM)NULL,(LPARAM)&cgs))
+ return 0; // this converts all string types to DBVT_ASCIIZ
+ if(dbv.type!=DBVT_ASCIIZ)
+ { // it is not a cached server-group name
+ return 0;
+ }
+ pwGroupIds = (WORD*)SAFE_REALLOC(pwGroupIds, (cbGroupIds+1)*sizeof(WORD));
+ pwGroupIds[cbGroupIds] = (WORD)strtoul(szSetting, NULL, 0x10);
+ cbGroupIds++;
+ }
+ return 0;
+}
+
+static void enumServerGroups(CIcqProto* ppro)
+{
+ DBCONTACTENUMSETTINGS dbces;
+
+ char szModule[MAX_PATH+9];
+
+ strcpy(szModule, ppro->m_szModuleName);
+ strcat(szModule, "SrvGroups");
+
+ dbces.pfnEnumProc = &GroupEnumIdsEnumProc;
+ dbces.szModule = szModule;
+ dbces.lParam = (LPARAM)szModule;
+
+ CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)NULL, (LPARAM)&dbces);
+}
+
+static DWORD sendUploadGroup(CIcqProto* ppro, WORD wAction, WORD wGroupId, char* szItemName)
+{
+ DWORD dwCookie;
+ cookie_servlist_action* ack;
+
+ if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)))
+ { // we have cookie good, go on
+ ack->wGroupId = wGroupId;
+ ack->dwAction = SSA_SERVLIST_ACK;
+ dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, wAction, 0, ack);
+ ack->lParam = dwCookie;
+
+ ppro->icq_sendServerGroup(dwCookie, wAction, ack->wGroupId, szItemName, NULL, 0, 0);
+ return dwCookie;
+ }
+ return 0;
+}
+
+static DWORD sendUploadBuddy(CIcqProto* ppro, HANDLE hContact, WORD wAction, DWORD dwUin, char *szUID, WORD wContactId, WORD wGroupId, WORD wItemType)
+{
+ DWORD dwCookie;
+ cookie_servlist_action* ack;
+
+ if (ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action)))
+ { // we have cookie good, go on
+ ack->hContact = hContact;
+ ack->wContactId = wContactId;
+ ack->wGroupId = wGroupId;
+ ack->dwAction = SSA_SERVLIST_ACK;
+ dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, wAction, hContact, ack);
+ ack->lParam = dwCookie;
+
+ if (wItemType == SSI_ITEM_BUDDY)
+ ppro->icq_sendServerContact(hContact, dwCookie, wAction, ack->wGroupId, ack->wContactId, SSOP_ITEM_ACTION | SSOF_CONTACT, 500, NULL);
+ else
+ ppro->icq_sendSimpleItem(dwCookie, wAction, dwUin, szUID, ack->wGroupId, ack->wContactId, wItemType, SSOP_ITEM_ACTION, 500);
+
+ return dwCookie;
+ }
+ return 0;
+}
+
+static char* getServerResultDesc(int wCode)
+{
+ switch (wCode)
+ {
+ case 0: return LPGEN("OK");
+ case 2: return LPGEN("NOT FOUND");
+ case 3: return LPGEN("ALREADY EXISTS");
+ case 0xA: return LPGEN("INVALID DATA");
+ case 0xC: return LPGEN("LIST FULL");
+ default: return LPGEN("FAILED");
+ }
+}
+
+#define ACTION_NONE 0
+#define ACTION_ADDBUDDY 1
+#define ACTION_ADDBUDDYAUTH 2
+#define ACTION_REMOVEBUDDY 3
+#define ACTION_ADDGROUP 4
+#define ACTION_REMOVEGROUP 5
+#define ACTION_UPDATESTATE 6
+#define ACTION_MOVECONTACT 7
+#define ACTION_ADDVISIBLE 8
+#define ACTION_REMOVEVISIBLE 9
+#define ACTION_ADDINVISIBLE 10
+#define ACTION_REMOVEINVISIBLE 11
+
+#define STATE_READY 1
+#define STATE_REGROUP 2
+#define STATE_ITEMS 3
+#define STATE_VISIBILITY 5
+#define STATE_CONSOLIDATE 4
+
+#define M_PROTOACK (WM_USER+100)
+#define M_UPLOADMORE (WM_USER+101)
+#define M_INITCLIST (WM_USER+102)
+
+static INT_PTR CALLBACK DlgProcUploadList(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam)
+{
+ CIcqProto* ppro = (CIcqProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ static int working;
+ static HANDLE hProtoAckHook;
+ static int currentSequence;
+ static int currentAction;
+ static int currentState;
+ static HANDLE hCurrentContact;
+ static int lastAckResult = 0;
+ static WORD wNewContactId;
+ static WORD wNewGroupId;
+ static char *szNewGroupName;
+ static WORD wNewVisibilityId;
+
+ switch(message) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ {
+ char str[MAX_PATH];
+
+ working = 0;
+ hProtoAckHook = NULL;
+ currentState = STATE_READY;
+
+ ResetCListOptions(GetDlgItem(hwndDlg, IDC_CLIST));
+
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Select contacts you want to store on server."), str, MAX_PATH));
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Ready..."), str, MAX_PATH));
+ }
+ return TRUE;
+
+ // The M_PROTOACK message is received when the
+ // server has responded to our last update packet
+ case M_PROTOACK:
+ {
+ int bMulti = 0;
+ ACKDATA *ack = (ACKDATA*)lParam;
+ char szLastLogLine[MAX_PATH];
+ char str[MAX_PATH];
+
+ // Is this an ack we are waiting for?
+ if (strcmpnull(ack->szModule, ppro->m_szModuleName))
+ break;
+
+ if (ack->type == ICQACKTYPE_RATEWARNING)
+ { // we are sending tooo fast, slow down the process
+ if (ack->hProcess != (HANDLE)1) break; // check class
+ if (ack->lParam == 2 || ack->lParam == 3) // check status
+ {
+ GetLastUploadLogLine(hwndDlg, szLastLogLine, MAX_PATH);
+ DeleteLastUploadLogLine(hwndDlg);
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Server rate warning -> slowing down the process."), str, MAX_PATH));
+ AppendToUploadLog(hwndDlg, szLastLogLine);
+
+ dwUploadDelay *= 2;
+
+ break;
+ }
+ if (ack->lParam == 4) dwUploadDelay /= 2; // the rate is ok, turn up
+ }
+
+ if (ack->type != ICQACKTYPE_SERVERCLIST)
+ break;
+
+ if ((int)ack->hProcess != currentSequence)
+ break;
+
+ lastAckResult = ack->result == ACKRESULT_SUCCESS ? 0 : 1;
+
+ switch (currentAction) {
+ case ACTION_ADDBUDDY:
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ ppro->setSettingByte(hCurrentContact, "Auth", 0);
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, wNewContactId);
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_GROUP, wNewGroupId);
+ break;
+ }
+ else
+ { // If the server refused to add the contact without authorization,
+ // we try again _with_ authorization TLV
+ DWORD dwUIN;
+ uid_str szUID;
+
+ ppro->setSettingByte(hCurrentContact, "Auth", 1);
+
+ if (!ppro->getContactUid(hCurrentContact, &dwUIN, &szUID))
+ {
+ currentAction = ACTION_ADDBUDDYAUTH;
+ currentSequence = sendUploadBuddy(ppro, hCurrentContact, ICQ_LISTS_ADDTOLIST, dwUIN, szUID, wNewContactId, wNewGroupId, SSI_ITEM_BUDDY);
+ }
+
+ return FALSE;
+ }
+
+ case ACTION_ADDBUDDYAUTH:
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, wNewContactId);
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_GROUP, wNewGroupId);
+ }
+ else
+ {
+ ppro->deleteSetting(hCurrentContact, "Auth");
+ ppro->FreeServerID(wNewContactId, SSIT_ITEM);
+ }
+
+ break;
+
+ case ACTION_REMOVEBUDDY:
+ if (ack->result == ACKRESULT_SUCCESS)
+ { // clear obsolete settings
+ ppro->FreeServerID(wNewContactId, SSIT_ITEM);
+ ppro->deleteSetting(hCurrentContact, DBSETTING_SERVLIST_ID);
+ ppro->deleteSetting(hCurrentContact, DBSETTING_SERVLIST_GROUP);
+ ppro->deleteSetting(hCurrentContact, "Auth");
+ }
+ break;
+
+ case ACTION_ADDGROUP:
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ void* groupData;
+ int groupSize;
+ cookie_servlist_action* ack;
+
+ ppro->setServListGroupName(wNewGroupId, szNewGroupName); // add group to list
+ ppro->setServListGroupLinkID(szNewGroupName, wNewGroupId); // grouppath is known
+
+ groupData = ppro->collectGroups(&groupSize);
+ groupData = SAFE_REALLOC(groupData, groupSize+2);
+ *(((WORD*)groupData)+(groupSize>>1)) = wNewGroupId; // add this new group id
+ groupSize += 2;
+
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (ack)
+ {
+ DWORD dwCookie; // we do not use this
+
+ ack->dwAction = SSA_SERVLIST_ACK;
+ dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack);
+
+ ppro->icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, 0);
+ }
+ SAFE_FREE((void**)&groupData);
+ }
+ else
+ ppro->FreeServerID(wNewGroupId, SSIT_GROUP);
+
+ SAFE_FREE((void**)&szNewGroupName);
+ break;
+
+ case ACTION_REMOVEGROUP:
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ void* groupData;
+ int groupSize;
+ cookie_servlist_action* ack;
+
+ ppro->FreeServerID(wNewGroupId, SSIT_GROUP);
+ ppro->setServListGroupName(wNewGroupId, NULL); // remove group from list
+ ppro->removeGroupPathLinks(wNewGroupId); // grouppath is known
+
+ groupData = ppro->collectGroups(&groupSize);
+
+ ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+ if (ack)
+ {
+ DWORD dwCookie; // we do not use this
+
+ ack->dwAction = SSA_SERVLIST_ACK;
+ dwCookie = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack);
+
+ ppro->icq_sendServerGroup(dwCookie, ICQ_LISTS_UPDATEGROUP, 0, ack->szGroupName, groupData, groupSize, 0);
+ }
+ SAFE_FREE((void**)&groupData);
+ }
+ break;
+
+ case ACTION_UPDATESTATE:
+ // do nothing
+ break;
+
+ case ACTION_MOVECONTACT:
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ ppro->FreeServerID(ppro->getSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, 0), SSIT_ITEM);
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_ID, wNewContactId);
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_GROUP, wNewGroupId);
+ dwUploadDelay *= 2; // we double the delay here (2 packets)
+ }
+ break;
+
+ case ACTION_ADDVISIBLE:
+ if (ack->result == ACKRESULT_SUCCESS)
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_PERMIT, wNewContactId);
+ else
+ ppro->FreeServerID(wNewContactId, SSIT_ITEM);
+ break;
+
+ case ACTION_ADDINVISIBLE:
+ if (ack->result == ACKRESULT_SUCCESS)
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_DENY, wNewContactId);
+ else
+ ppro->FreeServerID(wNewContactId, SSIT_ITEM);
+ break;
+
+ case ACTION_REMOVEVISIBLE:
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ ppro->FreeServerID(wNewContactId, SSIT_ITEM);
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_PERMIT, 0);
+ }
+ break;
+
+ case ACTION_REMOVEINVISIBLE:
+ if (ack->result == ACKRESULT_SUCCESS)
+ {
+ ppro->FreeServerID(wNewContactId, SSIT_ITEM);
+ ppro->setSettingWord(hCurrentContact, DBSETTING_SERVLIST_DENY, 0);
+ }
+ break;
+ }
+
+ // Update the log window
+ GetLastUploadLogLine(hwndDlg, szLastLogLine, MAX_PATH);
+ DeleteLastUploadLogLine(hwndDlg);
+ AppendToUploadLog(hwndDlg, "%s%s", szLastLogLine,
+ ICQTranslateUtfStatic(getServerResultDesc(ack->lParam), str, MAX_PATH));
+
+ if (!bMulti)
+ {
+ SetTimer(hwndDlg, M_UPLOADMORE, dwUploadDelay, 0); // delay
+ }
+ }
+ break;
+
+ case WM_TIMER:
+ {
+ switch (wParam)
+ {
+ case M_UPLOADMORE:
+ KillTimer(hwndDlg, M_UPLOADMORE);
+ if (currentAction == ACTION_MOVECONTACT)
+ dwUploadDelay /= 2; // turn it back
+
+ PostMessage(hwndDlg, M_UPLOADMORE, 0, 0);
+
+ return 0;
+ }
+ }
+
+ // The M_UPLOADMORE window message is received when the user presses 'Update'
+ // and every time an ack from the server has been taken care of.
+ case M_UPLOADMORE:
+ {
+ HANDLE hContact;
+ HANDLE hItem;
+ DWORD dwUin;
+ uid_str szUid;
+ char *pszNick;
+ char *pszGroup;
+ int isChecked;
+ int isOnServer;
+ BOOL bUidOk;
+ char str[MAX_PATH];
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_CLIST);
+
+ switch (currentState)
+ {
+ case STATE_REGROUP:
+
+ // TODO: iterate over all checked groups and create if needed
+ // if creation requires reallocation of groups do it here
+
+ currentState = STATE_ITEMS;
+ hCurrentContact = NULL;
+ PostMessage(hwndDlg, M_UPLOADMORE, 0, 0);
+ break;
+
+ case STATE_ITEMS:
+ // Iterate over all contacts until one is found that
+ // needs to be updated on the server
+ if (hCurrentContact == NULL)
+ hContact = ppro->FindFirstContact();
+ else // we do not want to go thru all contacts over and over again
+ {
+ hContact = hCurrentContact;
+ if (lastAckResult) // if the last operation on this contact fail, do not do it again, go to next
+ hContact = ppro->FindNextContact(hContact);
+ }
+
+ while (hContact)
+ {
+ hCurrentContact = hContact;
+
+ hItem = (HANDLE)SendMessage(hwndList, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem)
+ {
+ isChecked = SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0) != 0;
+ isOnServer = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) != 0;
+
+ bUidOk = !ppro->getContactUid(hContact, &dwUin, &szUid);
+
+ // Is this one out of sync?
+ if (bUidOk && (isChecked != isOnServer))
+ {
+ // Only upload custom nicks
+ pszNick = ppro->getSettingStringUtf(hContact, "CList", "MyHandle", NULL);
+
+ if (isChecked)
+ { // Queue for uploading
+ pszGroup = ppro->getContactCListGroup(hContact);
+ if (!strlennull(pszGroup))
+ pszGroup = null_strdup(DEFAULT_SS_GROUP);
+
+ // Get group ID from cache, if not ready use parent group, if still not ready create one
+ wNewGroupId = ppro->getServListGroupLinkID(pszGroup);
+ if (!wNewGroupId && strstrnull(pszGroup, "\\") != NULL)
+ { // if it is sub-group, take master parent
+ strstrnull(pszGroup, "\\")[0] = '\0';
+ wNewGroupId = ppro->getServListGroupLinkID(pszGroup);
+ }
+ if (!wNewGroupId && currentAction != ACTION_ADDGROUP)
+ { // if the group still does not exist and there was no try before, try to add group
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding group \"%s\"..."), str, MAX_PATH), pszGroup);
+
+ wNewGroupId = ppro->GenerateServerID(SSIT_GROUP, 0); // ???
+ szNewGroupName = pszGroup;
+ currentAction = ACTION_ADDGROUP;
+ currentSequence = sendUploadGroup(ppro, ICQ_LISTS_ADDTOLIST, wNewGroupId, pszGroup);
+ SAFE_FREE(&pszNick);
+
+ return FALSE;
+ }
+
+ SAFE_FREE(&pszGroup);
+
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Uploading %s..."), str, MAX_PATH), pszNick ? pszNick : strUID(dwUin, szUid));
+
+ currentAction = ACTION_ADDBUDDY;
+
+ if (wNewGroupId)
+ {
+ wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0);
+
+ currentSequence = sendUploadBuddy(ppro, hCurrentContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid,
+ wNewContactId, wNewGroupId, SSI_ITEM_BUDDY);
+ SAFE_FREE(&pszNick);
+
+ return FALSE;
+ }
+ else
+ {
+ char szLastLogLine[MAX_PATH];
+ // Update the log window with the failure and continue with next contact
+ GetLastUploadLogLine(hwndDlg, szLastLogLine, MAX_PATH);
+ DeleteLastUploadLogLine(hwndDlg);
+ AppendToUploadLog(hwndDlg, "%s%s", szLastLogLine, ICQTranslateUtfStatic(LPGEN("FAILED"), str, MAX_PATH));
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("No upload group available"), str, MAX_PATH));
+ ppro->NetLog_Server("Upload failed, no group");
+ currentState = STATE_READY;
+ }
+ }
+ else
+ { // Queue for deletion
+ if (pszNick)
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s..."), str, MAX_PATH), pszNick);
+ else
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s..."), str, MAX_PATH), strUID(dwUin, szUid));
+
+ wNewGroupId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0);
+ wNewContactId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0);
+ currentAction = ACTION_REMOVEBUDDY;
+ currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid,
+ wNewContactId, wNewGroupId, SSI_ITEM_BUDDY);
+ }
+ SAFE_FREE((void**)&pszNick);
+
+ break;
+ }
+ else if (bUidOk && isChecked)
+ { // the contact is and should be on server, check if it is in correct group, move otherwise
+ WORD wCurrentGroupId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0);
+
+ pszGroup = ppro->getContactCListGroup(hContact);
+ if (!strlennull(pszGroup))
+ pszGroup = null_strdup(DEFAULT_SS_GROUP);
+ wNewGroupId = ppro->getServListGroupLinkID(pszGroup);
+ if (!wNewGroupId && strstrnull(pszGroup, "\\") != NULL)
+ { // if it is sub-group, take master parent
+ strstrnull(pszGroup, "\\")[0] = '\0';
+ wNewGroupId = ppro->getServListGroupLinkID(pszGroup);
+ }
+ if (!wNewGroupId && currentAction != ACTION_ADDGROUP)
+ { // if the group still does not exist and there was no try before, try to add group
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding group \"%s\"..."), str, MAX_PATH), pszGroup);
+
+ wNewGroupId = ppro->GenerateServerID(SSIT_GROUP, 0);
+ szNewGroupName = pszGroup;
+ currentAction = ACTION_ADDGROUP;
+ currentSequence = sendUploadGroup(ppro, ICQ_LISTS_ADDTOLIST, wNewGroupId, pszGroup);
+
+ return FALSE;
+ }
+ if (wNewGroupId && (wNewGroupId != wCurrentGroupId))
+ { // we have a group the contact should be in, move it
+ WORD wCurrentContactId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0);
+ BYTE bAuth = ppro->getSettingByte(hContact, "Auth", 0);
+
+ pszNick = ppro->getSettingStringUtf(hContact, "CList", "MyHandle", NULL);
+
+ if (pszNick)
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Moving %s to group \"%s\"..."), str, MAX_PATH), pszNick, pszGroup);
+ else
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Moving %s to group \"%s\"..."), str, MAX_PATH), strUID(dwUin, szUid), pszGroup);
+
+ currentAction = ACTION_MOVECONTACT;
+ wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0);
+ sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid, wCurrentContactId, wCurrentGroupId, SSI_ITEM_BUDDY);
+ currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid, wNewContactId, wNewGroupId, SSI_ITEM_BUDDY);
+ SAFE_FREE((void**)&pszNick);
+ SAFE_FREE((void**)&pszGroup);
+
+ break;
+ }
+ SAFE_FREE((void**)&pszGroup);
+ }
+ }
+ hContact = db_find_next(hContact);
+ }
+ if (!hContact)
+ {
+ currentState = STATE_VISIBILITY;
+ hCurrentContact = NULL;
+ PostMessage(hwndDlg, M_UPLOADMORE, 0, 0);
+ }
+ break;
+
+ case STATE_VISIBILITY:
+ // Iterate over all contacts until one is found that
+ // needs to be updated on the server
+ if (hCurrentContact == NULL)
+ hContact = ppro->FindFirstContact();
+ else // we do not want to go thru all contacts over and over again
+ {
+ hContact = hCurrentContact;
+ if (lastAckResult) // if the last operation on this contact fail, do not do it again, go to next
+ hContact = ppro->FindNextContact(hContact);
+ }
+
+ while (hContact)
+ {
+ WORD wApparentMode = ppro->getSettingWord(hContact, "ApparentMode", 0);
+ WORD wDenyId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_DENY, 0);
+ WORD wPermitId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, 0);
+ WORD wIgnoreId = ppro->getSettingWord(hContact, DBSETTING_SERVLIST_IGNORE, 0);
+
+ hCurrentContact = hContact;
+ ppro->getContactUid(hContact, &dwUin, &szUid);
+
+ if (wApparentMode == ID_STATUS_ONLINE)
+ { // contact is on the visible list
+ if (wPermitId == 0)
+ {
+ currentAction = ACTION_ADDVISIBLE;
+ wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0);
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding %s to visible list..."), str, MAX_PATH), strUID(dwUin, szUid));
+ currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_PERMIT);
+ break;
+ }
+ }
+ if (wApparentMode == ID_STATUS_OFFLINE)
+ { // contact is on the invisible list
+ if (wDenyId == 0 && wIgnoreId == 0)
+ {
+ currentAction = ACTION_ADDINVISIBLE;
+ wNewContactId = ppro->GenerateServerID(SSIT_ITEM, 0);
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Adding %s to invisible list..."), str, MAX_PATH), strUID(dwUin, szUid));
+ currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_ADDTOLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_DENY);
+ break;
+ }
+ }
+ if (wApparentMode != ID_STATUS_ONLINE)
+ { // contact is not on visible list
+ if (wPermitId != 0)
+ {
+ currentAction = ACTION_REMOVEVISIBLE;
+ wNewContactId = wPermitId;
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s from visible list..."), str, MAX_PATH), strUID(dwUin, szUid));
+ currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_PERMIT);
+ break;
+ }
+ }
+ if (wApparentMode != ID_STATUS_OFFLINE)
+ { // contact is not on invisible list
+ if (wDenyId != 0)
+ {
+ currentAction = ACTION_REMOVEINVISIBLE;
+ wNewContactId = wDenyId;
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting %s from invisible list..."), str, MAX_PATH), strUID(dwUin, szUid));
+ currentSequence = sendUploadBuddy(ppro, hContact, ICQ_LISTS_REMOVEFROMLIST, dwUin, szUid, wNewContactId, 0, SSI_ITEM_DENY);
+ break;
+ }
+ }
+ hContact = db_find_next(hContact);
+ }
+ if (!hContact)
+ {
+ currentState = STATE_CONSOLIDATE;
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Cleaning groups"), str, MAX_PATH));
+ EnableDlgItem(hwndDlg, IDCANCEL, FALSE);
+ enumServerGroups(ppro);
+ PostMessage(hwndDlg, M_UPLOADMORE, 0, 0);
+ }
+ break;
+
+ case STATE_CONSOLIDATE: // updage group data, remove redundant groups
+ if (currentAction == ACTION_UPDATESTATE)
+ DeleteLastUploadLogLine(hwndDlg);
+
+ if (cbGroupIds) // some groups in the list
+ {
+ void* groupData;
+ int groupSize;
+
+ cbGroupIds--;
+ wNewGroupId = pwGroupIds[cbGroupIds];
+
+ if (groupData = ppro->collectBuddyGroup(wNewGroupId, &groupSize))
+ { // the group is still not empty, just update it
+ char* pszGroup = ppro->getServListGroupName(wNewGroupId);
+ cookie_servlist_action* ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+
+ ack->dwAction = SSA_SERVLIST_ACK;
+ ack->wGroupId = wNewGroupId;
+ currentSequence = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack);
+ ack->lParam = currentSequence;
+ currentAction = ACTION_UPDATESTATE;
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Updating group \"%s\"..."), str, MAX_PATH), pszGroup);
+
+ ppro->icq_sendServerGroup(currentSequence, ICQ_LISTS_UPDATEGROUP, wNewGroupId, pszGroup, groupData, groupSize, 0);
+
+ SAFE_FREE((void**)&pszGroup);
+ }
+ else // the group is empty, delete it if it does not have sub-groups
+ {
+ if (!ppro->CheckServerID((WORD)(wNewGroupId+1), 0) || ppro->getServListGroupLevel((WORD)(wNewGroupId+1)) == 0)
+ { // is next id an sub-group, if yes, we cannot delete this group
+ char *pszGroup = ppro->getServListGroupName(wNewGroupId);
+ currentAction = ACTION_REMOVEGROUP;
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Deleting group \"%s\"..."), str, MAX_PATH), pszGroup);
+ currentSequence = sendUploadGroup(ppro, ICQ_LISTS_REMOVEFROMLIST, wNewGroupId, pszGroup);
+ SAFE_FREE((void**)&pszGroup);
+ }
+ else // update empty group
+ {
+ char *pszGroup = ppro->getServListGroupName(wNewGroupId);
+ cookie_servlist_action *ack = (cookie_servlist_action*)SAFE_MALLOC(sizeof(cookie_servlist_action));
+
+ ack->dwAction = SSA_SERVLIST_ACK;
+ ack->wGroupId = wNewGroupId;
+ currentSequence = ppro->AllocateCookie(CKT_SERVERLIST, ICQ_LISTS_UPDATEGROUP, 0, ack);
+ ack->lParam = currentSequence;
+ currentAction = ACTION_UPDATESTATE;
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("Updating group \"%s\"..."), str, MAX_PATH), pszGroup);
+
+ ppro->icq_sendServerGroup(currentSequence, ICQ_LISTS_UPDATEGROUP, wNewGroupId, pszGroup, 0, 0, 0);
+
+ SAFE_FREE((void**)&pszGroup);
+ }
+ }
+ SAFE_FREE((void**)&groupData); // free the memory
+ }
+ else
+ { // all groups processed
+ SAFE_FREE((void**)&pwGroupIds);
+ currentState = STATE_READY;
+ }
+ break;
+ }
+
+ if (currentState == STATE_READY)
+ {
+ // All contacts are in sync
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("All operations complete"), str, MAX_PATH));
+ EnableDlgItem(hwndDlg, IDCANCEL, TRUE);
+ SetDlgItemTextUtf(hwndDlg, IDCANCEL, ICQTranslateUtfStatic(LPGEN("Close"), str, MAX_PATH));
+ // end server modifications here
+ ppro->servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100);
+ working = 0;
+ // SendMessage(hwndList, CLM_SETGREYOUTFLAGS,0,0);
+ UpdateCheckmarks(hwndList, ppro, hItemAll);
+ // EnableWindow(hwndList, FALSE);
+ if (hProtoAckHook)
+ UnhookEvent(hProtoAckHook);
+ }
+ break;
+ }
+
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDOK:
+ SendDlgItemMessage(hwndDlg, IDC_LOG, LB_RESETCONTENT, 0, 0);
+ if (!ppro->icqOnline())
+ {
+ char str[MAX_PATH];
+ AppendToUploadLog(hwndDlg, ICQTranslateUtfStatic(LPGEN("You have to be online to sychronize the server-list !"), str, MAX_PATH));
+ break;
+ }
+ working = 1;
+ hCurrentContact = NULL;
+ currentState = STATE_REGROUP;
+ currentAction = ACTION_NONE;
+ icq_ShowMultipleControls(hwndDlg, settingsControls, SIZEOF(settingsControls), SW_HIDE);
+ // SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_SETGREYOUTFLAGS, 0xFFFFFFFF, 0);
+ // InvalidateRect(GetDlgItem(hwndDlg, IDC_CLIST), NULL, FALSE);
+ EnableDlgItem(hwndDlg, IDC_CLIST, FALSE);
+ hProtoAckHook = HookEventMessage(ME_PROTO_ACK, hwndDlg, M_PROTOACK);
+ // start server modifications here
+ ppro->servlistPostPacket(NULL, 0, SSO_BEGIN_OPERATION | SSOF_IMPORT_OPERATION, 100);
+ PostMessage(hwndDlg, M_UPLOADMORE, 0, 0);
+ break;
+
+ case IDCANCEL: // TODO: this must be clean
+ DestroyWindow(hwndDlg);
+ break;
+ }
+ break;
+
+ case WM_NOTIFY:
+ switch(((NMHDR*)lParam)->idFrom) {
+ case IDC_CLIST:
+ {
+ HWND hClist = GetDlgItem(hwndDlg, IDC_CLIST);
+
+ switch(((NMHDR*)lParam)->code) {
+ case CLN_OPTIONSCHANGED:
+ ResetCListOptions(hClist);
+ break;
+
+ case CLN_NEWCONTACT:
+ case CLN_CONTACTMOVED:
+ // Delete non-icq contacts
+ DeleteOtherContactsFromControl(hClist, ppro);
+ if (hItemAll)
+ UpdateAllContactsCheckmark(hClist, ppro, hItemAll);
+ break;
+
+ case CLN_LISTREBUILT:
+ {
+ int bCheck = false;
+
+ // Delete non-icq contacts
+ if ( ppro ) {
+ DeleteOtherContactsFromControl(hClist, ppro);
+ if (!bListInit) // do not enter twice
+ bCheck = UpdateCheckmarks(hClist, ppro, NULL);
+ }
+
+ if (!hItemAll) // Add the "All contacts" item
+ {
+ CLCINFOITEM cii = {0};
+
+ cii.cbSize = sizeof(cii);
+ cii.flags = CLCIIF_GROUPFONT | CLCIIF_CHECKBOX;
+ cii.pszText = TranslateT(LPGEN("** All contacts **"));
+ hItemAll = (HANDLE)SendMessage(hClist, CLM_ADDINFOITEM, 0, (LPARAM)&cii);
+ }
+
+ SendMessage(hClist, CLM_SETCHECKMARK, (WPARAM)hItemAll, bCheck);
+ }
+ break;
+
+ case CLN_CHECKCHANGED:
+ {
+ NMCLISTCONTROL *nm = (NMCLISTCONTROL*)lParam;
+ HANDLE hContact;
+ HANDLE hItem;
+
+ if (bListInit) break;
+
+ if (nm->flags&CLNF_ISINFO)
+ {
+ int check;
+
+ check = SendMessage(hClist, CLM_GETCHECKMARK, (WPARAM)hItemAll, 0);
+
+ hContact = ppro->FindFirstContact();
+ while (hContact)
+ {
+ hItem = (HANDLE)SendMessage(hClist, CLM_FINDCONTACT, (WPARAM)hContact, 0);
+ if (hItem)
+ SendMessage(hClist, CLM_SETCHECKMARK, (WPARAM)hItem, check);
+ hContact = ppro->FindNextContact(hContact);
+ }
+ }
+ else
+ UpdateAllContactsCheckmark(hClist, ppro, hItemAll);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ break;
+
+ case WM_DESTROY:
+ if (hProtoAckHook)
+ UnhookEvent(hProtoAckHook);
+ if (working)
+ { // end server modifications here
+ ppro->servlistPostPacket(NULL, 0, SSO_END_OPERATION, 100);
+ }
+ hwndUploadContacts = NULL;
+ working = 0;
+ break;
+ }
+
+ return FALSE;
+}
+
+void CIcqProto::ShowUploadContactsDialog(void)
+{
+ if (hwndUploadContacts == NULL)
+ {
+ hItemAll = NULL;
+ hwndUploadContacts = CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_ICQUPLOADLIST), NULL, DlgProcUploadList, LPARAM(this));
+ }
+
+ SetForegroundWindow(hwndUploadContacts);
+}
diff --git a/protocols/IcqOscarJ/src/icq_xstatus.cpp b/protocols/IcqOscarJ/src/icq_xstatus.cpp
new file mode 100644
index 0000000000..b567105dbe
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_xstatus.cpp
@@ -0,0 +1,1381 @@
+// ---------------------------------------------------------------------------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 Angeli-Ka, Joe Kucera
+//
+// 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:
+//
+// Support for Custom Statuses
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+#include "m_extraicons.h"
+#include "..\icons_pack\src\resource.h"
+
+
+extern HANDLE hExtraXStatus;
+
+void CListShowMenuItem(HANDLE hMenuItem, BYTE bShow);
+
+BYTE CIcqProto::getContactXStatus(HANDLE hContact)
+{
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled)
+ return 0;
+
+ BYTE bXStatus = getSettingByte(hContact, DBSETTING_XSTATUS_ID, 0);
+
+ if (bXStatus < 1 || bXStatus > XSTATUS_COUNT) return 0;
+
+ return bXStatus;
+}
+
+
+DWORD CIcqProto::sendXStatusDetailsRequest(HANDLE hContact, int bForced)
+{
+ DWORD dwCookie = 0;
+
+ if (m_bXStatusEnabled && getContactXStatus(hContact) != 0)
+ { // only request custom status detail when the contact has one
+ int nNotifyLen = 94 + UINMAXLEN;
+ char *szNotify = (char*)_alloca(nNotifyLen);
+
+ null_snprintf(szNotify, nNotifyLen, "<srv><id>cAwaySrv</id><req><id>AwayStat</id><trans>1</trans><senderId>%d</senderId></req></srv>", m_dwLocalUIN);
+
+ dwCookie = SendXtrazNotifyRequest(hContact, "<Q><PluginID>srvMng</PluginID></Q>", szNotify, bForced);
+ }
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::requestXStatusDetails(HANDLE hContact, BOOL bAllowDelay)
+{
+ if (!validateStatusMessageRequest(hContact, MTYPE_SCRIPT_NOTIFY))
+ return 0; // apply privacy rules
+
+ if (!CheckContactCapabilities(hContact, CAPF_XSTATUS))
+ return 0; // contact does not have xstatus
+
+ // delay is disabled only if fired from dialog
+ if (!CheckContactCapabilities(hContact, CAPF_XTRAZ) && bAllowDelay)
+ return 0; // Contact does not support xtraz, do not request details
+
+ struct rates_xstatus_request: public rates_queue_item {
+ protected:
+ virtual rates_queue_item* copyItem(rates_queue_item *aDest = NULL) {
+ rates_xstatus_request *pDest = (rates_xstatus_request*)aDest;
+ if (!pDest)
+ pDest = new rates_xstatus_request(ppro, wGroup);
+
+ pDest->bForced = bForced;
+ return rates_queue_item::copyItem(pDest);
+ };
+ public:
+ rates_xstatus_request(CIcqProto *ppro, WORD wGroup): rates_queue_item(ppro, wGroup) { };
+ virtual ~rates_xstatus_request() { };
+
+ virtual void execute() {
+ dwCookie = ppro->sendXStatusDetailsRequest(hContact, bForced);
+ };
+
+ BOOL bForced;
+ DWORD dwCookie;
+ };
+
+ m_ratesMutex->Enter();
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND);
+ m_ratesMutex->Leave();
+
+ rates_xstatus_request rr(this, wGroup);
+ rr.bForced = !bAllowDelay;
+ rr.hContact = hContact;
+
+ // delay at least one sec if allowed
+ if (!handleRateItem(&rr, RQT_REQUEST, 1000, bAllowDelay))
+ return rr.dwCookie;
+
+ return -1; // delayed
+}
+
+
+static HANDLE LoadXStatusIconLibrary(TCHAR *path, const TCHAR *sub)
+{
+ TCHAR* p = _tcsrchr(path, '\\');
+ HANDLE hLib;
+
+ _tcscpy(p, sub);
+ _tcscat(p, _T("\\xstatus_ICQ.dll"));
+ if (hLib = LoadLibrary(path)) return hLib;
+ _tcscpy(p, sub);
+ _tcscat(p, _T("\\xstatus_icons.dll"));
+ if (hLib = LoadLibrary(path)) return hLib;
+ _tcscpy(p, _T("\\"));
+ return hLib;
+}
+
+static TCHAR *InitXStatusIconLibrary(TCHAR *buf, size_t buf_size)
+{
+ TCHAR path[2*MAX_PATH];
+ HMODULE hXStatusIconsDLL;
+
+ // get miranda's exe path
+ GetModuleFileName(NULL, path, MAX_PATH);
+
+ hXStatusIconsDLL = (HMODULE)LoadXStatusIconLibrary(path, _T("\\Icons"));
+ if (!hXStatusIconsDLL) // TODO: add "Custom Folders" support
+ hXStatusIconsDLL = (HMODULE)LoadXStatusIconLibrary(path, _T("\\Plugins"));
+
+ if (hXStatusIconsDLL)
+ {
+ null_strcpy(buf, path, buf_size - 1);
+
+ char ident[MAX_PATH];
+ if (LoadStringA(hXStatusIconsDLL, IDS_IDENTIFY, ident, sizeof(ident)) == 0 || strcmpnull(ident, "# Custom Status Icons #"))
+ { // library is invalid
+ *buf = 0;
+ }
+ FreeLibrary(hXStatusIconsDLL);
+ }
+ else
+ *buf = 0;
+
+ return buf;
+}
+
+
+HICON CIcqProto::getXStatusIcon(int bStatus, UINT flags)
+{
+ HICON icon = NULL;
+
+ if (bStatus > 0 && bStatus <= XSTATUS_COUNT)
+ icon = hXStatusIcons[bStatus - 1]->GetIcon((flags & LR_BIGICON) != 0);
+
+ if (flags & LR_SHARED || !icon)
+ return icon;
+ else
+ return CopyIcon(icon);
+}
+
+
+void CIcqProto::releaseXStatusIcon(int bStatus, UINT flags)
+{
+ if (bStatus > 0 && bStatus <= XSTATUS_COUNT) {
+ IcqIconHandle p = hXStatusIcons[bStatus - 1];
+ if (p)
+ p->ReleaseIcon((flags & LR_BIGICON) != 0);
+ }
+}
+
+
+void CIcqProto::setContactExtraIcon(HANDLE hContact, int xstatus)
+{
+ HANDLE hIcon;
+
+ if (hExtraXStatus == NULL)
+ {
+ if (xstatus > 0 && bXStatusExtraIconsReady < 2)
+ CListMW_ExtraIconsRebuild(0, 0);
+
+ hIcon = (xstatus <= 0 ? (HANDLE)-1 : hXStatusExtraIcons[xstatus-1]);
+
+ IconExtraColumn iec;
+
+ iec.cbSize = sizeof(iec);
+ iec.hImage = hIcon;
+ iec.ColumnType = EXTRA_ICON_ADV1;
+ CallService(MS_CLIST_EXTRA_SET_ICON, (WPARAM)hContact, (LPARAM)&iec);
+ }
+ else
+ {
+ hIcon = (HANDLE) -1;
+
+ if (xstatus <= 0)
+ {
+ ExtraIcon_SetIcon(hExtraXStatus, hContact, (char *) NULL);
+ }
+ else
+ {
+ char szTemp[MAX_PATH];
+ null_snprintf(szTemp, sizeof(szTemp), "%s_xstatus%d", m_szModuleName, xstatus-1);
+ ExtraIcon_SetIcon(hExtraXStatus, hContact, szTemp);
+ }
+ }
+
+ NotifyEventHooks(hxstatusiconchanged, (WPARAM)hContact, (LPARAM)hIcon);
+}
+
+
+int CIcqProto::CListMW_ExtraIconsRebuild(WPARAM wParam, LPARAM lParam)
+{
+ if ((m_bXStatusEnabled || m_bMoodsEnabled) && ServiceExists(MS_CLIST_EXTRA_ADD_ICON))
+ {
+ for (int i = 0; i < XSTATUS_COUNT; i++)
+ {
+ hXStatusExtraIcons[i] = (HANDLE)CallService(MS_CLIST_EXTRA_ADD_ICON, (WPARAM)getXStatusIcon(i + 1, LR_SHARED), 0);
+ releaseXStatusIcon(i + 1, 0);
+ }
+
+ if (!bXStatusExtraIconsReady)
+ { // try to hook the events again if they did not existed during init
+ HookProtoEvent(ME_CLIST_EXTRA_LIST_REBUILD, &CIcqProto::CListMW_ExtraIconsRebuild);
+ HookProtoEvent(ME_CLIST_EXTRA_IMAGE_APPLY, &CIcqProto::CListMW_ExtraIconsApply);
+ }
+
+ bXStatusExtraIconsReady = 2;
+ }
+ return 0;
+}
+
+
+int CIcqProto::CListMW_ExtraIconsApply(WPARAM wParam, LPARAM lParam)
+{
+ if ((m_bXStatusEnabled || m_bMoodsEnabled) && ServiceExists(MS_CLIST_EXTRA_SET_ICON))
+ {
+ if (IsICQContact((HANDLE)wParam))
+ {
+ // only apply icons to our contacts, do not mess others
+ DWORD bXStatus = getContactXStatus((HANDLE)wParam);
+
+ if ((m_bXStatusEnabled && CheckContactCapabilities((HANDLE)wParam, CAPF_XSTATUS)) ||
+ (m_bMoodsEnabled && CheckContactCapabilities((HANDLE)wParam, CAPF_STATUS_MOOD)))
+ setContactExtraIcon((HANDLE)wParam, bXStatus);
+ }
+ }
+ return 0;
+}
+
+#define NULLCAP {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+
+capstr capXStatus[XSTATUS_COUNT] = {
+ {0x01, 0xD8, 0xD7, 0xEE, 0xAC, 0x3B, 0x49, 0x2A, 0xA5, 0x8D, 0xD3, 0xD8, 0x77, 0xE6, 0x6B, 0x92},
+ {0x5A, 0x58, 0x1E, 0xA1, 0xE5, 0x80, 0x43, 0x0C, 0xA0, 0x6F, 0x61, 0x22, 0x98, 0xB7, 0xE4, 0xC7},
+ {0x83, 0xC9, 0xB7, 0x8E, 0x77, 0xE7, 0x43, 0x78, 0xB2, 0xC5, 0xFB, 0x6C, 0xFC, 0xC3, 0x5B, 0xEC},
+ {0xE6, 0x01, 0xE4, 0x1C, 0x33, 0x73, 0x4B, 0xD1, 0xBC, 0x06, 0x81, 0x1D, 0x6C, 0x32, 0x3D, 0x81},
+ {0x8C, 0x50, 0xDB, 0xAE, 0x81, 0xED, 0x47, 0x86, 0xAC, 0xCA, 0x16, 0xCC, 0x32, 0x13, 0xC7, 0xB7},
+ {0x3F, 0xB0, 0xBD, 0x36, 0xAF, 0x3B, 0x4A, 0x60, 0x9E, 0xEF, 0xCF, 0x19, 0x0F, 0x6A, 0x5A, 0x7F},
+ {0xF8, 0xE8, 0xD7, 0xB2, 0x82, 0xC4, 0x41, 0x42, 0x90, 0xF8, 0x10, 0xC6, 0xCE, 0x0A, 0x89, 0xA6},
+ {0x80, 0x53, 0x7D, 0xE2, 0xA4, 0x67, 0x4A, 0x76, 0xB3, 0x54, 0x6D, 0xFD, 0x07, 0x5F, 0x5E, 0xC6},
+ {0xF1, 0x8A, 0xB5, 0x2E, 0xDC, 0x57, 0x49, 0x1D, 0x99, 0xDC, 0x64, 0x44, 0x50, 0x24, 0x57, 0xAF},
+ {0x1B, 0x78, 0xAE, 0x31, 0xFA, 0x0B, 0x4D, 0x38, 0x93, 0xD1, 0x99, 0x7E, 0xEE, 0xAF, 0xB2, 0x18},
+ {0x61, 0xBE, 0xE0, 0xDD, 0x8B, 0xDD, 0x47, 0x5D, 0x8D, 0xEE, 0x5F, 0x4B, 0xAA, 0xCF, 0x19, 0xA7},
+ {0x48, 0x8E, 0x14, 0x89, 0x8A, 0xCA, 0x4A, 0x08, 0x82, 0xAA, 0x77, 0xCE, 0x7A, 0x16, 0x52, 0x08},
+ {0x10, 0x7A, 0x9A, 0x18, 0x12, 0x32, 0x4D, 0xA4, 0xB6, 0xCD, 0x08, 0x79, 0xDB, 0x78, 0x0F, 0x09},
+ {0x6F, 0x49, 0x30, 0x98, 0x4F, 0x7C, 0x4A, 0xFF, 0xA2, 0x76, 0x34, 0xA0, 0x3B, 0xCE, 0xAE, 0xA7},
+ {0x12, 0x92, 0xE5, 0x50, 0x1B, 0x64, 0x4F, 0x66, 0xB2, 0x06, 0xB2, 0x9A, 0xF3, 0x78, 0xE4, 0x8D},
+ {0xD4, 0xA6, 0x11, 0xD0, 0x8F, 0x01, 0x4E, 0xC0, 0x92, 0x23, 0xC5, 0xB6, 0xBE, 0xC6, 0xCC, 0xF0},
+ {0x60, 0x9D, 0x52, 0xF8, 0xA2, 0x9A, 0x49, 0xA6, 0xB2, 0xA0, 0x25, 0x24, 0xC5, 0xE9, 0xD2, 0x60},
+ {0x63, 0x62, 0x73, 0x37, 0xA0, 0x3F, 0x49, 0xFF, 0x80, 0xE5, 0xF7, 0x09, 0xCD, 0xE0, 0xA4, 0xEE},
+ {0x1F, 0x7A, 0x40, 0x71, 0xBF, 0x3B, 0x4E, 0x60, 0xBC, 0x32, 0x4C, 0x57, 0x87, 0xB0, 0x4C, 0xF1},
+ {0x78, 0x5E, 0x8C, 0x48, 0x40, 0xD3, 0x4C, 0x65, 0x88, 0x6F, 0x04, 0xCF, 0x3F, 0x3F, 0x43, 0xDF},
+ {0xA6, 0xED, 0x55, 0x7E, 0x6B, 0xF7, 0x44, 0xD4, 0xA5, 0xD4, 0xD2, 0xE7, 0xD9, 0x5C, 0xE8, 0x1F},
+ {0x12, 0xD0, 0x7E, 0x3E, 0xF8, 0x85, 0x48, 0x9E, 0x8E, 0x97, 0xA7, 0x2A, 0x65, 0x51, 0xE5, 0x8D},
+ {0xBA, 0x74, 0xDB, 0x3E, 0x9E, 0x24, 0x43, 0x4B, 0x87, 0xB6, 0x2F, 0x6B, 0x8D, 0xFE, 0xE5, 0x0F},
+ {0x63, 0x4F, 0x6B, 0xD8, 0xAD, 0xD2, 0x4A, 0xA1, 0xAA, 0xB9, 0x11, 0x5B, 0xC2, 0x6D, 0x05, 0xA1},
+ {0x2C, 0xE0, 0xE4, 0xE5, 0x7C, 0x64, 0x43, 0x70, 0x9C, 0x3A, 0x7A, 0x1C, 0xE8, 0x78, 0xA7, 0xDC},
+ {0x10, 0x11, 0x17, 0xC9, 0xA3, 0xB0, 0x40, 0xF9, 0x81, 0xAC, 0x49, 0xE1, 0x59, 0xFB, 0xD5, 0xD4},
+ {0x16, 0x0C, 0x60, 0xBB, 0xDD, 0x44, 0x43, 0xF3, 0x91, 0x40, 0x05, 0x0F, 0x00, 0xE6, 0xC0, 0x09},
+ {0x64, 0x43, 0xC6, 0xAF, 0x22, 0x60, 0x45, 0x17, 0xB5, 0x8C, 0xD7, 0xDF, 0x8E, 0x29, 0x03, 0x52},
+ {0x16, 0xF5, 0xB7, 0x6F, 0xA9, 0xD2, 0x40, 0x35, 0x8C, 0xC5, 0xC0, 0x84, 0x70, 0x3C, 0x98, 0xFA},
+ {0x63, 0x14, 0x36, 0xff, 0x3f, 0x8a, 0x40, 0xd0, 0xa5, 0xcb, 0x7b, 0x66, 0xe0, 0x51, 0xb3, 0x64},
+ {0xb7, 0x08, 0x67, 0xf5, 0x38, 0x25, 0x43, 0x27, 0xa1, 0xff, 0xcf, 0x4c, 0xc1, 0x93, 0x97, 0x97},
+ {0xdd, 0xcf, 0x0e, 0xa9, 0x71, 0x95, 0x40, 0x48, 0xa9, 0xc6, 0x41, 0x32, 0x06, 0xd6, 0xf2, 0x80},
+ NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
+ NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
+ NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
+ NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
+ NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP, NULLCAP,
+ NULLCAP, NULLCAP, NULLCAP, NULLCAP};
+
+const char *nameXStatus[XSTATUS_COUNT] = {
+ LPGEN("Angry"), // 23
+ LPGEN("Taking a bath"), // 1
+ LPGEN("Tired"), // 2
+ LPGEN("Birthday"), // 3
+ LPGEN("Drinking beer"), // 4
+ LPGEN("Thinking"), // 5
+ LPGEN("Eating"), // 80
+ LPGEN("Watching TV"), // 7
+ LPGEN("Meeting"), // 8
+ LPGEN("Coffee"), // 9
+ LPGEN("Listening to music"),// 10
+ LPGEN("Business"), // 11
+ LPGEN("Shooting"), // 12
+ LPGEN("Having fun"), // 13
+ LPGEN("On the phone"), // 14
+ LPGEN("Gaming"), // 15
+ LPGEN("Studying"), // 16
+ LPGEN("Shopping"), // 0
+ LPGEN("Feeling sick"), // 17
+ LPGEN("Sleeping"), // 18
+ LPGEN("Surfing"), // 19
+ LPGEN("Internet"), // 20
+ LPGEN("Working"), // 21
+ LPGEN("Typing"), // 22
+ LPGEN("Picnic"), // 66
+ LPGEN("Cooking"),
+ LPGEN("Smoking"),
+ LPGEN("I'm high"),
+ LPGEN("On WC"), // 68
+ LPGEN("To be or not to be"),// 77
+ LPGEN("Watching pro7 on TV"),
+ LPGEN("Love"), // 61
+ LPGEN("Hot Dog"), //6
+ LPGEN("Rough"), //24
+ LPGEN("Rock On"), //25
+ LPGEN("Baby"), //26
+ LPGEN("Soccer"), //27
+ LPGEN("Pirate"), //28
+ LPGEN("Cyclop"), //29
+ LPGEN("Monkey"), //30
+ LPGEN("Birdie"), //31
+ LPGEN("Cool"), //32
+ LPGEN("Evil"), //33
+ LPGEN("Alien"), //34
+ LPGEN("Scooter"), //35
+ LPGEN("Mask"), //36
+ LPGEN("Money"), //37
+ LPGEN("Pilot"), //38
+ LPGEN("Afro"), //39
+ LPGEN("St. Patrick"), //40
+ LPGEN("Headmaster"), //41
+ LPGEN("Lips"), //42
+ LPGEN("Ice-Cream"), //43
+ LPGEN("Pink Lady"), //44
+ LPGEN("Up yours"), //45
+ LPGEN("Laughing"), //46
+ LPGEN("Dog"), //47
+ LPGEN("Candy"), //48
+ LPGEN("Crazy Professor"),//50
+ LPGEN("Ninja"), //51
+ LPGEN("Cocktail"), //52
+ LPGEN("Punch"), //53
+ LPGEN("Donut"), //54
+ LPGEN("Feeling Good"), //55
+ LPGEN("Lollypop"), //56
+ LPGEN("Oink Oink"), //57
+ LPGEN("Kitty"), //58
+ LPGEN("Sumo"), //59
+ LPGEN("Broken hearted"),//60
+ LPGEN("Free for Chat"), //62
+ LPGEN("@home"), //63
+ LPGEN("@work"), //64
+ LPGEN("Strawberry"), //65
+ LPGEN("Angel"), //67
+ LPGEN("Pizza"), //69
+ LPGEN("Snoring"), //70
+ LPGEN("On my mobile"), //71
+ LPGEN("Depressed"), //72
+ LPGEN("Beetle"), //73
+ LPGEN("Double Rainbow"),//74
+ LPGEN("Basketball"), //75
+ LPGEN("Cupid shot me"), //76
+ LPGEN("Celebrating"), //78
+ LPGEN("Sushi"), //79
+ LPGEN("Playing"), //81
+ LPGEN("Writing") //84
+ };
+
+const int moodXStatus[XSTATUS_COUNT] = {
+ 23,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 80,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ 12,
+ 13,
+ 14,
+ 15,
+ 16,
+ 0,
+ 17,
+ 18,
+ 19,
+ 20,
+ 21,
+ 22,
+ 66,
+ -1,
+ -1,
+ -1,
+ 68,
+ 77,
+ -1,
+ 61,
+ 6,
+ 24,
+ 25,
+ 26,
+ 27,
+ 28,
+ 29,
+ 30,
+ 31,
+ 32,
+ 33,
+ 34,
+ 35,
+ 36,
+ 37,
+ 38,
+ 39,
+ 40,
+ 41,
+ 42,
+ 43,
+ 44,
+ 45,
+ 46,
+ 47,
+ 48,
+ 50,
+ 51,
+ 52,
+ 53,
+ 54,
+ 55,
+ 56,
+ 57,
+ 58,
+ 59,
+ 60,
+ 62,
+ 63,
+ 64,
+ 65,
+ 67,
+ 69,
+ 70,
+ 71,
+ 72,
+ 73,
+ 74,
+ 75,
+ 76,
+ 78,
+ 79,
+ 81,
+ 84};
+
+void CIcqProto::handleXStatusCaps(DWORD dwUIN, char *szUID, HANDLE hContact, BYTE *caps, int capsize, char *moods, int moodsize)
+{
+ int bChanged = FALSE;
+ int nCustomStatusID = 0, nMoodID = 0;
+
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled)
+ {
+ ClearContactCapabilities(hContact, CAPF_STATUS_MOOD | CAPF_XSTATUS);
+ return;
+ }
+ int nOldXStatusID = getContactXStatus(hContact);
+
+ if (m_bXStatusEnabled)
+ {
+ if (caps)
+ { // detect custom status capabilities
+ if (capsize > 0)
+ for (int i = 0; i < XSTATUS_COUNT; i++)
+ {
+ if (MatchCapability(caps, capsize, (const capstr*)capXStatus[i], BINARY_CAP_SIZE))
+ {
+ BYTE bXStatusId = (BYTE)(i+1);
+ char str[MAX_PATH];
+
+ SetContactCapabilities(hContact, CAPF_XSTATUS);
+
+ if (nOldXStatusID != bXStatusId)
+ { // only write default name when it is really needed, i.e. on Custom Status change
+ setSettingByte(hContact, DBSETTING_XSTATUS_ID, bXStatusId);
+ setSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
+ deleteSetting(hContact, DBSETTING_XSTATUS_MSG);
+
+ NetLog_Server("%s changed custom status to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
+ bChanged = TRUE;
+ }
+#ifdef _DEBUG
+ else
+ NetLog_Server("%s has custom status %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
+#endif
+
+ if (getSettingByte(NULL, "XStatusAuto", DEFAULT_XSTATUS_AUTO))
+ requestXStatusDetails(hContact, TRUE);
+
+ nCustomStatusID = bXStatusId;
+
+ break;
+ }
+ }
+
+ if (nCustomStatusID == 0)
+ {
+#ifdef _DEBUG
+ if (m_iStatus != ID_STATUS_OFFLINE && CheckContactCapabilities(hContact, CAPF_XSTATUS))
+ NetLog_Server("%s has removed custom status.", strUID(dwUIN, szUID));
+#endif
+ ClearContactCapabilities(hContact, CAPF_XSTATUS);
+ }
+ }
+#ifdef _DEBUG
+ else if (CheckContactCapabilities(hContact, CAPF_XSTATUS))
+ {
+ char str[MAX_PATH];
+ NetLog_Server("%s has custom status %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[nOldXStatusID-1], str, MAX_PATH));
+ }
+#endif
+ }
+ if (m_bMoodsEnabled)
+ {
+ if (moods && moodsize < 32)
+ { // process custom statuses (moods) from ICQ6
+ if (moodsize > 0)
+ for (int i = 0; i < XSTATUS_COUNT; i++)
+ {
+ char szMoodId[32], szMoodData[32];
+
+ null_strcpy(szMoodData, moods, moodsize);
+
+ if (moodXStatus[i] == -1) continue;
+ null_snprintf(szMoodId, SIZEOF(szMoodId), "0icqmood%d", moodXStatus[i]);
+ if (!strcmpnull(szMoodId, szMoodData))
+ {
+ BYTE bXStatusId = (BYTE)(i+1);
+ char str[MAX_PATH];
+
+ SetContactCapabilities(hContact, CAPF_STATUS_MOOD);
+
+ if (nCustomStatusID == 0 && nOldXStatusID != bXStatusId)
+ { // only write default name when it is really needed, i.e. on Custom Status change
+ setSettingByte(hContact, DBSETTING_XSTATUS_ID, bXStatusId);
+ setSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
+ deleteSetting(hContact, DBSETTING_XSTATUS_MSG);
+
+ NetLog_Server("%s changed mood to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
+ bChanged = TRUE;
+ }
+#ifdef _DEBUG
+ else if (nOldXStatusID != bXStatusId)
+ NetLog_Server("%s changed mood to %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
+ else
+ NetLog_Server("%s has mood %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[i], str, MAX_PATH));
+#endif
+ // cannot retrieve mood details here - need to be processed with new user details
+ nMoodID = bXStatusId;
+
+ break;
+ }
+ }
+
+ if (nMoodID == 0 && moods)
+ {
+#ifdef _DEBUG
+ if (m_iStatus != ID_STATUS_OFFLINE && CheckContactCapabilities(hContact, CAPF_STATUS_MOOD))
+ NetLog_Server("%s has removed mood.", strUID(dwUIN, szUID));
+#endif
+ ClearContactCapabilities(hContact, CAPF_STATUS_MOOD);
+ }
+ }
+#ifdef _DEBUG
+ else if (CheckContactCapabilities(hContact, CAPF_STATUS_MOOD))
+ { // Mood was not changed, but contact has one, add a small log notice
+ char str[MAX_PATH];
+ NetLog_Server("%s has mood %s.", strUID(dwUIN, szUID), ICQTranslateUtfStatic(nameXStatus[nOldXStatusID-1], str, MAX_PATH));
+ }
+#endif
+ }
+
+ if (nCustomStatusID != 0 && nMoodID != 0 && nCustomStatusID != nMoodID)
+ NetLog_Server("Warning: Diverse custom statuses detected, using custom status.");
+
+ if ((nCustomStatusID == 0 && (caps || !m_bXStatusEnabled)) && (nMoodID == 0 && (moods || !m_bMoodsEnabled)))
+ {
+ if (getSettingByte(hContact, DBSETTING_XSTATUS_ID, -1) != -1)
+ bChanged = TRUE;
+ deleteSetting(hContact, DBSETTING_XSTATUS_ID);
+ deleteSetting(hContact, DBSETTING_XSTATUS_NAME);
+ deleteSetting(hContact, DBSETTING_XSTATUS_MSG);
+ }
+
+ if (m_bXStatusEnabled != 10 && m_bMoodsEnabled != 10)
+ {
+ setContactExtraIcon(hContact, nCustomStatusID ? nCustomStatusID : (nMoodID ? nMoodID : (moods ? 0 : nOldXStatusID)));
+
+ if (bChanged)
+ NotifyEventHooks(hxstatuschanged, (WPARAM)hContact, 0);
+ }
+}
+
+
+void CIcqProto::updateServerCustomStatus(int fullUpdate)
+{
+ BYTE bXStatus = getContactXStatus(NULL);
+
+ if (fullUpdate)
+ { // update client capabilities
+ if (m_bXStatusEnabled)
+ setUserInfo();
+
+ char szMoodData[32];
+
+ // prepare mood id
+ if (m_bMoodsEnabled && bXStatus && moodXStatus[bXStatus-1] != -1)
+ null_snprintf(szMoodData, SIZEOF(szMoodData), "0icqmood%d", moodXStatus[bXStatus-1]);
+ else
+ szMoodData[0] = '\0';
+
+ SetStatusMood(szMoodData, 1500);
+ }
+
+ char *szStatusNote = NULL;
+
+ if (bXStatus && (m_bXStatusEnabled || m_bMoodsEnabled))
+ { // use custom status message as status note
+ szStatusNote = getSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, "");
+ }
+ else
+ { // retrieve standard status message (e.g. custom status set to none)
+ char **pszMsg = MirandaStatusToAwayMsg(m_iStatus);
+
+ m_modeMsgsMutex->Enter();
+ if (pszMsg)
+ szStatusNote = null_strdup(*pszMsg);
+ m_modeMsgsMutex->Leave();
+ // no default status message, set empty
+ if (!szStatusNote)
+ szStatusNote = null_strdup("");
+ }
+
+ if (szStatusNote)
+ SetStatusNote(szStatusNote, 1500, FALSE);
+
+ SAFE_FREE(&szStatusNote);
+}
+
+
+static WNDPROC OldMessageEditProc;
+
+static LRESULT CALLBACK MessageEditSubclassProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ switch(msg) {
+ case WM_CHAR:
+ if(wParam=='\n' && GetKeyState(VK_CONTROL)&0x8000)
+ {
+ PostMessage(GetParent(hwnd),WM_COMMAND,IDOK,0);
+ return 0;
+ }
+ if (wParam == 1 && GetKeyState(VK_CONTROL) & 0x8000)
+ { // ctrl-a
+ SendMessage(hwnd, EM_SETSEL, 0, -1);
+ return 0;
+ }
+ if (wParam == 23 && GetKeyState(VK_CONTROL) & 0x8000)
+ { // ctrl-w
+ SendMessage(GetParent(hwnd), WM_CLOSE, 0, 0);
+ return 0;
+ }
+ if (wParam == 127 && GetKeyState(VK_CONTROL) & 0x8000)
+ { // ctrl-backspace
+ DWORD start, end;
+ WCHAR *text;
+
+ SendMessage(hwnd, EM_GETSEL, (WPARAM) & end, (LPARAM) (PDWORD) NULL);
+ SendMessage(hwnd, WM_KEYDOWN, VK_LEFT, 0);
+ SendMessage(hwnd, EM_GETSEL, (WPARAM) & start, (LPARAM) (PDWORD) NULL);
+ text = GetWindowTextUcs(hwnd);
+ MoveMemory(text + start, text + end, sizeof(WCHAR) * (strlennull(text) + 1 - end));
+ SetWindowTextUcs(hwnd, text);
+ SAFE_FREE(&text);
+ SendMessage(hwnd, EM_SETSEL, start, start);
+ SendMessage(GetParent(hwnd), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(hwnd), EN_CHANGE), (LPARAM) hwnd);
+ return 0;
+ }
+ break;
+ }
+ return CallWindowProc(OldMessageEditProc,hwnd,msg,wParam,lParam);
+}
+
+struct SetXStatusData
+{
+ CIcqProto* ppro;
+ BYTE bAction;
+ BYTE bXStatus;
+ HANDLE hContact;
+ HANDLE hEvent;
+ DWORD iEvent;
+ int countdown;
+ char* okButtonFormat;
+};
+
+struct InitXStatusData
+{
+ CIcqProto* ppro;
+ BYTE bAction;
+ BYTE bXStatus;
+ char* szXStatusName;
+ char* szXStatusMsg;
+ HANDLE hContact;
+};
+
+#define HM_PROTOACK (WM_USER+10)
+static INT_PTR CALLBACK SetXStatusDlgProc(HWND hwndDlg,UINT message,WPARAM wParam,LPARAM lParam)
+{
+ SetXStatusData *dat = (SetXStatusData*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ char str[MAX_PATH];
+
+ switch(message) {
+ case HM_PROTOACK:
+ {
+ ACKDATA *ack = (ACKDATA*)lParam;
+ if (ack->type != ICQACKTYPE_XSTATUS_RESPONSE) break;
+ if (ack->hContact != dat->hContact) break;
+ if ((DWORD)ack->hProcess != dat->iEvent) break;
+
+ ShowDlgItem(hwndDlg, IDC_RETRXSTATUS, SW_HIDE);
+ ShowDlgItem(hwndDlg, IDC_XMSG, SW_SHOW);
+ ShowDlgItem(hwndDlg, IDC_XTITLE, SW_SHOW);
+ SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("Close"), str, MAX_PATH));
+ UnhookEvent(dat->hEvent); dat->hEvent = NULL;
+ char *szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_NAME, "");
+ SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, szText);
+ SAFE_FREE(&szText);
+ szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_MSG, "");
+ SetDlgItemTextUtf(hwndDlg, IDC_XMSG, szText);
+ SAFE_FREE(&szText);
+ }
+ break;
+
+ case WM_INITDIALOG:
+ {
+ InitXStatusData *init = (InitXStatusData*)lParam;
+
+ TranslateDialogDefault(hwndDlg);
+ dat = (SetXStatusData*)SAFE_MALLOC(sizeof(SetXStatusData));
+ dat->ppro = init->ppro;
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,(LONG_PTR)dat);
+ dat->bAction = init->bAction;
+
+ if (!init->bAction)
+ { // set our xStatus
+ dat->bXStatus = init->bXStatus;
+ SendDlgItemMessage(hwndDlg, IDC_XMSG, EM_LIMITTEXT, 1024, 0);
+ OldMessageEditProc = (WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XMSG),GWLP_WNDPROC,(LONG_PTR)MessageEditSubclassProc);
+ SetDlgItemTextUtf(hwndDlg, IDC_XMSG, init->szXStatusMsg);
+
+ if (dat->ppro->m_bXStatusEnabled)
+ { // custom status enabled, prepare title edit
+ SendDlgItemMessage(hwndDlg, IDC_XTITLE, EM_LIMITTEXT, 256, 0);
+ OldMessageEditProc = (WNDPROC)SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XTITLE),GWLP_WNDPROC,(LONG_PTR)MessageEditSubclassProc);
+ SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, init->szXStatusName);
+ }
+ else
+ { // only moods enabled, hide title, resize message edit control
+ ShowDlgItem(hwndDlg, IDC_XTITLE_STATIC, SW_HIDE);
+ ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE);
+ MoveDlgItem(hwndDlg, IDC_XMSG_STATIC, 5, 0, 179, 8);
+ MoveDlgItem(hwndDlg, IDC_XMSG, 5, 9, 179, 65);
+ }
+
+ dat->okButtonFormat = GetDlgItemTextUtf(hwndDlg,IDOK);
+ dat->countdown = 5;
+ SendMessage(hwndDlg, WM_TIMER, 0, 0);
+ SetTimer(hwndDlg,1,1000,0);
+ }
+ else
+ { // retrieve contact's xStatus
+ dat->hContact = init->hContact;
+ dat->bXStatus = dat->ppro->getContactXStatus(dat->hContact);
+ dat->okButtonFormat = NULL;
+ SendMessage(GetDlgItem(hwndDlg, IDC_XTITLE), EM_SETREADONLY, 1, 0);
+ SendMessage(GetDlgItem(hwndDlg, IDC_XMSG), EM_SETREADONLY, 1, 0);
+
+ if (dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_XSTATUS) && !dat->ppro->getSettingByte(NULL, "XStatusAuto", DEFAULT_XSTATUS_AUTO))
+ {
+ SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("Cancel"), str, MAX_PATH));
+ dat->hEvent = HookEventMessage(ME_PROTO_ACK, hwndDlg, HM_PROTOACK);
+ ShowDlgItem(hwndDlg, IDC_RETRXSTATUS, SW_SHOW);
+ ShowDlgItem(hwndDlg, IDC_XMSG, SW_HIDE);
+ ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE);
+ dat->iEvent = dat->ppro->requestXStatusDetails(dat->hContact, FALSE);
+ }
+ else
+ {
+ SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("Close"), str, MAX_PATH));
+ dat->hEvent = NULL;
+ char *szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_NAME, "");
+ SetDlgItemTextUtf(hwndDlg, IDC_XTITLE, szText);
+ SAFE_FREE(&szText);
+
+ if (dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_STATUS_MOOD) && !dat->ppro->CheckContactCapabilities(dat->hContact, CAPF_XSTATUS))
+ { // only for clients supporting just moods and not custom status
+ szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_STATUS_NOTE, "");
+ // hide title, resize message edit control
+ ShowDlgItem(hwndDlg, IDC_XTITLE_STATIC, SW_HIDE);
+ ShowDlgItem(hwndDlg, IDC_XTITLE, SW_HIDE);
+ MoveDlgItem(hwndDlg, IDC_XMSG_STATIC, 5, 0, 179, 8);
+ MoveDlgItem(hwndDlg, IDC_XMSG, 5, 9, 179, 65);
+ }
+ else
+ szText = dat->ppro->getSettingStringUtf(dat->hContact, DBSETTING_XSTATUS_MSG, "");
+
+ SetDlgItemTextUtf(hwndDlg, IDC_XMSG, szText);
+ SAFE_FREE(&szText);
+ }
+ }
+
+ if (dat->bXStatus)
+ {
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)dat->ppro->getXStatusIcon(dat->bXStatus, LR_SHARED | LR_BIGICON));
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)dat->ppro->getXStatusIcon(dat->bXStatus, LR_SHARED));
+ }
+
+ char buf[MAX_PATH];
+ char *format = GetWindowTextUtf(hwndDlg);
+
+ null_snprintf(str, sizeof(str), format, dat->bXStatus?ICQTranslateUtfStatic(nameXStatus[dat->bXStatus-1], buf, MAX_PATH):"");
+ SetWindowTextUtf(hwndDlg, str);
+ SAFE_FREE(&format);
+ return TRUE;
+ }
+ case WM_TIMER:
+ if(dat->countdown==-1)
+ {
+ DestroyWindow(hwndDlg);
+ break;
+ }
+ {
+ null_snprintf(str,sizeof(str),dat->okButtonFormat,dat->countdown);
+ SetDlgItemTextUtf(hwndDlg,IDOK,str);
+ }
+ dat->countdown--;
+ break;
+
+ case WM_COMMAND:
+ switch(LOWORD(wParam)) {
+ case IDOK:
+ DestroyWindow(hwndDlg);
+ break;
+ case IDC_XTITLE:
+ case IDC_XMSG:
+ if (!dat->bAction)
+ { // set our xStatus
+ KillTimer(hwndDlg,1);
+ SetDlgItemTextUtf(hwndDlg,IDOK,ICQTranslateUtfStatic(LPGEN("OK"), str, MAX_PATH));
+ }
+ break;
+ }
+ break;
+
+ case WM_DESTROY:
+ if (!dat->bAction)
+ { // set our xStatus
+ char szSetting[64];
+ char *szValue;
+
+ dat->ppro->setSettingByte(NULL, DBSETTING_XSTATUS_ID, dat->bXStatus);
+ szValue = GetDlgItemTextUtf(hwndDlg,IDC_XMSG);
+ null_snprintf(szSetting, 64, "XStatus%dMsg", dat->bXStatus);
+ dat->ppro->setSettingStringUtf(NULL, szSetting, szValue);
+ dat->ppro->setSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, szValue);
+ SAFE_FREE(&szValue);
+
+ if (dat->ppro->m_bXStatusEnabled)
+ {
+ szValue = GetDlgItemTextUtf(hwndDlg,IDC_XTITLE);
+ null_snprintf(szSetting, 64, "XStatus%dName", dat->bXStatus);
+ dat->ppro->setSettingStringUtf(NULL, szSetting, szValue);
+ dat->ppro->setSettingStringUtf(NULL, DBSETTING_XSTATUS_NAME, szValue);
+ SAFE_FREE(&szValue);
+
+ if (dat->bXStatus)
+ {
+ dat->ppro->releaseXStatusIcon(dat->bXStatus, LR_BIGICON);
+ dat->ppro->releaseXStatusIcon(dat->bXStatus, 0);
+ }
+ }
+ dat->ppro->updateServerCustomStatus(TRUE);
+
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XMSG),GWLP_WNDPROC,(LONG_PTR)OldMessageEditProc);
+ if (dat->ppro->m_bXStatusEnabled)
+ SetWindowLongPtr(GetDlgItem(hwndDlg,IDC_XTITLE),GWLP_WNDPROC,(LONG_PTR)OldMessageEditProc);
+ }
+ if (dat->hEvent) UnhookEvent(dat->hEvent);
+ SAFE_FREE(&dat->okButtonFormat);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, NULL);
+ SAFE_FREE((void**)&dat);
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ break;
+ }
+ return FALSE;
+}
+
+
+void CIcqProto::setXStatusEx(BYTE bXStatus, BYTE bQuiet)
+{
+ CLISTMENUITEM mi = {0};
+ BYTE bOldXStatus = getSettingByte(NULL, DBSETTING_XSTATUS_ID, 0);
+
+ mi.cbSize = sizeof(mi);
+
+ if (!m_bHideXStatusUI)
+ {
+ if (bOldXStatus <= XSTATUS_COUNT)
+ {
+ mi.flags = CMIM_FLAGS;
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hXStatusItems[bOldXStatus], (LPARAM)&mi);
+ }
+
+ mi.flags = CMIM_FLAGS | CMIF_CHECKED;
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hXStatusItems[bXStatus], (LPARAM)&mi);
+ }
+
+ if (bXStatus)
+ {
+ char szSetting[64];
+ char str[MAX_PATH];
+ char *szName = NULL, *szMsg = NULL;
+
+ if (m_bXStatusEnabled)
+ {
+ null_snprintf(szSetting, 64, "XStatus%dName", bXStatus);
+ szName = getSettingStringUtf(NULL, szSetting, ICQTranslateUtfStatic(nameXStatus[bXStatus-1], str, MAX_PATH));
+ }
+ null_snprintf(szSetting, 64, "XStatus%dMsg", bXStatus);
+ szMsg = getSettingStringUtf(NULL, szSetting, "");
+
+ null_snprintf(szSetting, 64, "XStatus%dStat", bXStatus);
+ if (!bQuiet && !getSettingByte(NULL, szSetting, 0))
+ {
+ InitXStatusData init;
+ init.ppro = this;
+ init.bAction = 0; // set
+ init.bXStatus = bXStatus;
+ init.szXStatusName = szName;
+ init.szXStatusMsg = szMsg;
+ CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SETXSTATUS), NULL, SetXStatusDlgProc, (LPARAM)&init);
+ }
+ else
+ {
+ setSettingByte(NULL, DBSETTING_XSTATUS_ID, bXStatus);
+ if (m_bXStatusEnabled)
+ setSettingStringUtf(NULL, DBSETTING_XSTATUS_NAME, szName);
+ setSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, szMsg);
+
+ updateServerCustomStatus(TRUE);
+ }
+ SAFE_FREE(&szName);
+ SAFE_FREE(&szMsg);
+ }
+ else
+ {
+ setSettingByte(NULL, DBSETTING_XSTATUS_ID, bXStatus);
+ deleteSetting(NULL, DBSETTING_XSTATUS_NAME);
+ deleteSetting(NULL, DBSETTING_XSTATUS_MSG);
+
+ updateServerCustomStatus(TRUE);
+ }
+}
+
+
+INT_PTR CIcqProto::menuXStatus(WPARAM wParam,LPARAM lParam,LPARAM fParam)
+{
+ setXStatusEx((BYTE)fParam, 0);
+ return 0;
+}
+
+
+void CIcqProto::InitXStatusItems(BOOL bAllowStatus)
+{
+ CLISTMENUITEM mi;
+ int i = 0, len = strlennull(m_szModuleName);
+ char srvFce[MAX_PATH + 64];
+ char szItem[MAX_PATH + 64];
+ int bXStatusMenuBuilt = 0;
+
+ BYTE bXStatus = getContactXStatus(NULL);
+
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled) return;
+
+ if (!bAllowStatus) return;
+
+ // Custom Status UI is disabled, no need to continue items' init
+ if (m_bHideXStatusUI || m_bHideXStatusMenu) return;
+
+ null_snprintf(szItem, sizeof(szItem), Translate("%s Custom Status"), m_szModuleName);
+ mi.cbSize = sizeof(mi);
+ mi.pszPopupName = szItem;
+ mi.popupPosition = 500084000;
+ mi.position = 2000040000;
+
+ for (i = 0; i <= XSTATUS_COUNT; i++)
+ {
+ null_snprintf(srvFce, sizeof(srvFce), "%s/menuXStatus%d", m_szModuleName, i);
+
+ mi.position++;
+
+ if (!i)
+ bXStatusMenuBuilt = ServiceExists(srvFce);
+
+ if (!bXStatusMenuBuilt)
+ CreateProtoServiceParam(srvFce+len, &CIcqProto::menuXStatus, i);
+
+ mi.flags = (i ? CMIF_ICONFROMICOLIB : 0) | (bXStatus == i?CMIF_CHECKED:0);
+ mi.icolibItem = i ? hXStatusIcons[i-1]->Handle() : NULL;
+ mi.pszName = i ? (char*)nameXStatus[i-1] : (char *)LPGEN("None");
+ mi.pszService = srvFce;
+ mi.pszContactOwner = m_szModuleName;
+
+ hXStatusItems[i] = Menu_AddStatusMenuItem(&mi);
+
+ // CMIF_HIDDEN does not work for adding services
+ CListShowMenuItem(hXStatusItems[i], !(m_bHideXStatusUI || m_bHideXStatusMenu));
+ }
+}
+
+
+void CIcqProto::InitXStatusIcons()
+{
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled)
+ return;
+
+ TCHAR lib[2*MAX_PATH] = {0};
+ TCHAR *icon_lib = InitXStatusIconLibrary(lib, SIZEOF(lib));
+
+ char szSection[MAX_PATH + 64], *szAccountName = tchar_to_utf8(m_tszUserName);
+ null_snprintf(szSection, sizeof(szSection), "Status Icons/%s/Custom Status", szAccountName);
+ SAFE_FREE(&szAccountName);
+
+ for (int i = 0; i < XSTATUS_COUNT; i++)
+ {
+ char szTemp[64];
+
+ null_snprintf(szTemp, sizeof(szTemp), "xstatus%d", i);
+ hXStatusIcons[i] = IconLibDefine(nameXStatus[i], szSection, m_szModuleName, szTemp, icon_lib, -(IDI_XSTATUS1+i));
+ }
+
+ // initialize arrays for CList custom status icons
+ memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid));
+ memset(hXStatusCListIcons, -1, sizeof(hXStatusCListIcons));
+}
+
+
+void CIcqProto::UninitXStatusIcons()
+{
+ for (int i = 0; i < XSTATUS_COUNT; i++)
+ IconLibRemove(&hXStatusIcons[i]);
+
+ // clear clist icon state indicators
+ memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid));
+}
+
+
+INT_PTR CIcqProto::ShowXStatusDetails(WPARAM wParam, LPARAM lParam)
+{
+ InitXStatusData init;
+ init.ppro = this;
+ init.bAction = 1; // retrieve
+ init.hContact = (HANDLE)wParam;
+ CreateDialogParam(hInst, MAKEINTRESOURCE(IDD_SETXSTATUS), NULL, SetXStatusDlgProc, (LPARAM)&init);
+
+ return 0;
+}
+
+INT_PTR CIcqProto::SetXStatus(WPARAM wParam, LPARAM lParam)
+{ // obsolete (TODO: remove in next version)
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 0;
+
+ if (wParam >= 0 && wParam <= XSTATUS_COUNT)
+ {
+ setXStatusEx((BYTE)wParam, 1);
+ return wParam;
+ }
+ return 0;
+}
+
+
+INT_PTR CIcqProto::GetXStatus(WPARAM wParam, LPARAM lParam)
+{ // obsolete (TODO: remove in next version)
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 0;
+
+ if (!icqOnline()) return 0;
+
+ BYTE status = getContactXStatus(NULL);
+
+ if (wParam) *((char**)wParam) = m_bXStatusEnabled ? DBSETTING_XSTATUS_NAME : NULL;
+ if (lParam) *((char**)lParam) = DBSETTING_XSTATUS_MSG;
+
+ return status;
+}
+
+
+INT_PTR CIcqProto::SetXStatusEx(WPARAM wParam, LPARAM lParam)
+{
+ ICQ_CUSTOM_STATUS *pData = (ICQ_CUSTOM_STATUS*)lParam;
+
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 1;
+
+ if (pData->cbSize < sizeof(ICQ_CUSTOM_STATUS)) return 1; // Failure
+
+ if (pData->flags & CSSF_MASK_STATUS)
+ { // set custom status
+ int status = *pData->status;
+
+ if (status >= 0 && status <= XSTATUS_COUNT)
+ setXStatusEx((BYTE)status, 1);
+ else
+ return 1; // Failure
+ }
+
+ if (pData->flags & (CSSF_MASK_NAME | CSSF_MASK_MESSAGE))
+ {
+ BYTE status = getContactXStatus(NULL);
+
+ if (!status) return 1; // Failure
+
+ if (m_bXStatusEnabled && (pData->flags & CSSF_MASK_NAME))
+ { // set custom status name
+ if (pData->flags & CSSF_UNICODE)
+ setSettingStringW(NULL, DBSETTING_XSTATUS_NAME, pData->pwszName);
+ else
+ setSettingString(NULL, DBSETTING_XSTATUS_NAME, pData->pszName);
+ }
+ if (pData->flags & CSSF_MASK_MESSAGE)
+ { // set custom status message
+ if (pData->flags & CSSF_UNICODE)
+ setSettingStringW(NULL, DBSETTING_XSTATUS_MSG, pData->pwszMessage);
+ else
+ setSettingString(NULL, DBSETTING_XSTATUS_MSG, pData->pszMessage);
+
+ // update status note if used for custom status message
+ updateServerCustomStatus(FALSE);
+ }
+ }
+
+ if (pData->flags & CSSF_DISABLE_UI)
+ { // hide menu items + contact menu item
+ m_bHideXStatusUI = (*pData->wParam) ? 0 : 1;
+ }
+
+ if (pData->flags & CSSF_DISABLE_MENU)
+ { // hide menu items only
+ m_bHideXStatusMenu = (*pData->wParam) ? 0 : 1;
+ }
+
+ return 0; // Success
+}
+
+
+INT_PTR CIcqProto::GetXStatusEx(WPARAM wParam, LPARAM lParam)
+{
+ ICQ_CUSTOM_STATUS *pData = (ICQ_CUSTOM_STATUS*)lParam;
+ HANDLE hContact = (HANDLE)wParam;
+
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 1;
+
+ if (pData->cbSize < sizeof(ICQ_CUSTOM_STATUS)) return 1; // Failure
+
+ if (pData->flags & CSSF_MASK_STATUS)
+ { // fill status member
+ *pData->status = getContactXStatus(hContact);
+ }
+
+ if (pData->flags & CSSF_MASK_NAME)
+ { // fill status name member
+ if (pData->flags & CSSF_DEFAULT_NAME)
+ {
+ int status = *pData->wParam;
+
+ if (status < 1 || status > XSTATUS_COUNT) return 1; // Failure
+
+ if (pData->flags & CSSF_UNICODE)
+ {
+ char *text = (char*)nameXStatus[status -1];
+
+ MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, text, -1, pData->pwszName, MAX_PATH);
+ }
+ else
+ strcpy(pData->pszName, (char*)nameXStatus[status - 1]);
+ }
+ else
+ { // moods does not support status title
+ if (!m_bXStatusEnabled) return 1;
+
+ if (pData->flags & CSSF_UNICODE)
+ {
+ char *str = getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, "");
+ WCHAR *wstr = make_unicode_string(str);
+
+ wcscpy(pData->pwszName, wstr);
+ SAFE_FREE(&str);
+ SAFE_FREE(&wstr);
+ }
+ else
+ {
+ DBVARIANT dbv = {0};
+
+ if (!getSettingString(hContact, DBSETTING_XSTATUS_NAME, &dbv) && dbv.pszVal)
+ strcpy(pData->pszName, dbv.pszVal);
+ else
+ strcpy(pData->pszName, "");
+
+ ICQFreeVariant(&dbv);
+ }
+ }
+ }
+
+ if (pData->flags & CSSF_MASK_MESSAGE)
+ { // fill status message member
+ if (pData->flags & CSSF_UNICODE)
+ {
+ char *str = getSettingStringUtf(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, "");
+ WCHAR *wstr = make_unicode_string(str);
+
+ wcscpy(pData->pwszMessage, wstr);
+ SAFE_FREE(&str);
+ SAFE_FREE(&wstr);
+ }
+ else
+ {
+ DBVARIANT dbv = {0};
+
+ if (!getSettingString(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, &dbv) && dbv.pszVal)
+ strcpy(pData->pszMessage, dbv.pszVal);
+ else
+ strcpy(pData->pszMessage, "");
+
+ ICQFreeVariant(&dbv);
+ }
+ }
+
+ if (pData->flags & CSSF_DISABLE_UI)
+ {
+ if (pData->wParam) *pData->wParam = !m_bHideXStatusUI;
+ }
+
+ if (pData->flags & CSSF_DISABLE_MENU)
+ {
+ if (pData->wParam) *pData->wParam = !m_bHideXStatusMenu;
+ }
+
+ if (pData->flags & CSSF_STATUSES_COUNT)
+ {
+ if (pData->wParam) *pData->wParam = XSTATUS_COUNT;
+ }
+
+ if (pData->flags & CSSF_STR_SIZES)
+ {
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (pData->wParam)
+ {
+ if (m_bXStatusEnabled && !getSettingString(hContact, DBSETTING_XSTATUS_NAME, &dbv))
+ *pData->wParam = strlennull(dbv.pszVal);
+ else
+ *pData->wParam = 0;
+ ICQFreeVariant(&dbv);
+ }
+ if (pData->lParam)
+ {
+ if (!getSettingString(hContact, CheckContactCapabilities(hContact, CAPF_STATUS_MOOD) ? DBSETTING_STATUS_NOTE : DBSETTING_XSTATUS_MSG, &dbv))
+ *pData->lParam = strlennull(dbv.pszVal);
+ else
+ *pData->lParam = 0;
+ ICQFreeVariant(&dbv);
+ }
+ }
+
+ return 0; // Success
+}
+
+
+INT_PTR CIcqProto::GetXStatusIcon(WPARAM wParam, LPARAM lParam)
+{
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled) return 0;
+
+ if (!wParam)
+ wParam = getContactXStatus(NULL);
+
+ if (wParam >= 1 && wParam <= XSTATUS_COUNT)
+ {
+ return (INT_PTR)getXStatusIcon((BYTE)wParam, lParam);
+ }
+ return 0;
+}
+
+
+INT_PTR CIcqProto::RequestXStatusDetails(WPARAM wParam, LPARAM lParam)
+{
+ HANDLE hContact = (HANDLE)wParam;
+
+ if (!m_bXStatusEnabled) return 0;
+
+ if (hContact && !getSettingByte(NULL, "XStatusAuto", DEFAULT_XSTATUS_AUTO) &&
+ getContactXStatus(hContact) && CheckContactCapabilities(hContact, CAPF_XSTATUS))
+ { // user has xstatus, no auto-retrieve details, valid contact, request details
+ return requestXStatusDetails(hContact, TRUE);
+ }
+ return 0;
+}
+
+
+INT_PTR CIcqProto::RequestAdvStatusIconIdx(WPARAM wParam, LPARAM lParam)
+{
+ if (!m_bXStatusEnabled && !m_bMoodsEnabled) return -1;
+
+ BYTE bXStatus = getContactXStatus((HANDLE)wParam);
+
+ if (bXStatus)
+ {
+ int idx=-1;
+
+ if (!bXStatusCListIconsValid[bXStatus-1])
+ { // adding icon
+ int idx = hXStatusCListIcons[bXStatus-1];
+ HIMAGELIST hCListImageList = (HIMAGELIST)CallService(MS_CLIST_GETICONSIMAGELIST,0,0);
+
+ if (hCListImageList)
+ {
+ HICON hXStatusIcon = getXStatusIcon(bXStatus, LR_SHARED);
+
+ if (idx > 0)
+ ImageList_ReplaceIcon(hCListImageList, idx, hXStatusIcon);
+ else
+ hXStatusCListIcons[bXStatus-1] = ImageList_AddIcon(hCListImageList, hXStatusIcon);
+ // mark icon index in the array as valid
+ bXStatusCListIconsValid[bXStatus-1] = TRUE;
+
+ releaseXStatusIcon(bXStatus, 0);
+ }
+ }
+ idx = bXStatusCListIconsValid[bXStatus-1] ? hXStatusCListIcons[bXStatus-1] : -1;
+
+ if (idx > 0)
+ return (idx & 0xFFFF) << 16;
+ }
+ return -1;
+}
diff --git a/protocols/IcqOscarJ/src/icq_xtraz.cpp b/protocols/IcqOscarJ/src/icq_xtraz.cpp
new file mode 100644
index 0000000000..2b73274365
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icq_xtraz.cpp
@@ -0,0 +1,466 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Internal Xtraz API
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+void CIcqProto::handleXtrazNotify(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC)
+{
+ char *szNotify = strstrnull(szMsg, "<NOTIFY>");
+ char *szQuery = strstrnull(szMsg, "<QUERY>");
+
+ HANDLE hContact = HContactFromUIN(dwUin, NULL);
+ if (hContact) // user sent us xtraz, he supports it
+ SetContactCapabilities(hContact, CAPF_XTRAZ);
+
+ if (szNotify && szQuery)
+ { // valid request
+ char *szWork, *szEnd;
+ int nNotifyLen, nQueryLen;
+
+ szNotify += 8;
+ szQuery += 7;
+ szEnd = strstrnull(szMsg, "</NOTIFY>");
+ if (!szEnd) szEnd = szMsg + nMsgLen;
+ nNotifyLen = (szEnd - szNotify);
+ szEnd = strstrnull(szMsg, "</QUERY>");
+ if (!szEnd) szEnd = szNotify;
+ szNotify = DemangleXml(szNotify, nNotifyLen);
+ nQueryLen = (szEnd - szQuery);
+ szQuery = DemangleXml(szQuery, nQueryLen);
+ szWork = strstrnull(szQuery, "<PluginID>");
+ szEnd = strstrnull(szQuery, "</PluginID>");
+#ifdef _DEBUG
+ NetLog_Server("Query: %s", szQuery);
+ NetLog_Server("Notify: %s", szNotify);
+#endif
+ if (szWork && szEnd)
+ { // this is our plugin
+ szWork += 10;
+ *szEnd = '\0';
+
+ if (!stricmpnull(szWork, "srvMng") && strstrnull(szNotify, "AwayStat"))
+ {
+ char *szSender = strstrnull(szNotify, "<senderId>");
+ char *szEndSend = strstrnull(szNotify, "</senderId>");
+
+ if (szSender && szEndSend)
+ {
+ szSender += 10;
+ *szEndSend = '\0';
+
+ if ((DWORD)atoi(szSender) == dwUin)
+ {
+ BYTE dwXId = m_bXStatusEnabled ? getContactXStatus(NULL) : 0;
+
+ if (dwXId && validateStatusMessageRequest(hContact, MTYPE_SCRIPT_NOTIFY))
+ { // apply privacy rules
+ NotifyEventHooks(m_modeMsgsEvent, (WPARAM)MTYPE_SCRIPT_NOTIFY, (LPARAM)dwUin);
+
+ char *tmp = getSettingStringUtf(NULL, DBSETTING_XSTATUS_NAME, "");
+ char *szXName = MangleXml(tmp, strlennull(tmp));
+ SAFE_FREE(&tmp);
+
+ tmp = getSettingStringUtf(NULL, DBSETTING_XSTATUS_MSG, "");
+ char *szXMsg = MangleXml(tmp, strlennull(tmp));
+ SAFE_FREE(&tmp);
+
+ int nResponseLen = 212 + strlennull(szXName) + strlennull(szXMsg) + UINMAXLEN + 2;
+ char *szResponse = (char*)_alloca(nResponseLen + 1);
+ // send response
+ null_snprintf(szResponse, nResponseLen,
+ "<ret event=\"OnRemoteNotification\">"
+ "<srv><id>cAwaySrv</id>"
+ "<val srv_id=\"cAwaySrv\"><Root>"
+ "<CASXtraSetAwayMessage></CASXtraSetAwayMessage>"
+ "<uin>%d</uin>"
+ "<index>%d</index>"
+ "<title>%s</title>"
+ "<desc>%s</desc></Root></val></srv></ret>",
+ m_dwLocalUIN, dwXId, szXName, szXMsg);
+
+ SAFE_FREE(&szXName);
+ SAFE_FREE(&szXMsg);
+
+ struct rates_xstatus_response: public rates_queue_item {
+ protected:
+ virtual rates_queue_item* copyItem(rates_queue_item *aDest = NULL) {
+ rates_xstatus_response *pDest = (rates_xstatus_response*)aDest;
+ if (!pDest)
+ pDest = new rates_xstatus_response(ppro, wGroup);
+
+ pDest->bThruDC = bThruDC;
+ pDest->dwMsgID1 = dwMsgID1;
+ pDest->dwMsgID2 = dwMsgID2;
+ pDest->wCookie = wCookie;
+ pDest->szResponse = null_strdup(szResponse);
+
+ return rates_queue_item::copyItem(pDest);
+ };
+ public:
+ rates_xstatus_response(CIcqProto *ppro, WORD wGroup): rates_queue_item(ppro, wGroup), szResponse(NULL) { };
+ virtual ~rates_xstatus_response() { if (bCreated) SAFE_FREE(&szResponse); };
+
+ virtual void execute() {
+ ppro->SendXtrazNotifyResponse(dwUin, dwMsgID1, dwMsgID2, wCookie, szResponse, strlennull(szResponse), bThruDC);
+ };
+
+ BOOL bThruDC;
+ DWORD dwMsgID1;
+ DWORD dwMsgID2;
+ WORD wCookie;
+ char *szResponse;
+ };
+
+ m_ratesMutex->Enter();
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE);
+ m_ratesMutex->Leave();
+
+ rates_xstatus_response rr(this, wGroup);
+ rr.hContact = hContact;
+ rr.dwUin = dwUin;
+ rr.bThruDC = bThruDC;
+ rr.dwMsgID1 = dwMID;
+ rr.dwMsgID2 = dwMID2;
+ rr.wCookie = wCookie;
+ rr.szResponse = szResponse;
+
+ handleRateItem(&rr, RQT_RESPONSE, 0, !bThruDC);
+ }
+ else if (dwXId)
+ NetLog_Server("Privacy: Ignoring XStatus request");
+ else
+ NetLog_Server("Error: We are not in XStatus, skipping");
+ }
+ else
+ NetLog_Server("Error: Invalid sender information");
+ }
+ else
+ NetLog_Server("Error: Missing sender information");
+ }
+ else
+ NetLog_Server("Error: Unknown plugin \"%s\" in Xtraz message", szWork);
+ }
+ else
+ NetLog_Server("Error: Missing PluginID in Xtraz message");
+
+ SAFE_FREE(&szNotify);
+ SAFE_FREE(&szQuery);
+ }
+ else
+ NetLog_Server("Error: Invalid Xtraz Notify message");
+}
+
+
+void CIcqProto::handleXtrazNotifyResponse(DWORD dwUin, HANDLE hContact, WORD wCookie, char* szMsg, int nMsgLen)
+{
+ char *szMem, *szRes, *szEnd;
+ int nResLen;
+
+#ifdef _DEBUG
+ NetLog_Server("Received Xtraz Notify Response");
+#endif
+
+ szRes = strstrnull(szMsg, "<RES>");
+ szEnd = strstrnull(szMsg, "</RES>");
+
+ if (szRes && szEnd)
+ { // valid response
+ char *szNode, *szWork;
+
+ szRes += 5;
+ nResLen = szEnd - szRes;
+
+ szMem = szRes = DemangleXml(szRes, nResLen);
+
+#ifdef _DEBUG
+ NetLog_Server("Response: %s", szRes);
+#endif
+
+ BroadcastAck(hContact, ICQACKTYPE_XTRAZNOTIFY_RESPONSE, ACKRESULT_SUCCESS, (HANDLE)wCookie, (LPARAM)szRes);
+
+NextVal:
+ szNode = strstrnull(szRes, "<val srv_id=");
+ if (szNode) szEnd = strstrnull(szNode, ">"); else szEnd = NULL;
+
+ if (szNode && szEnd)
+ {
+ *(szEnd-1) = '\0';
+ szNode += 13; //one more than the length of the string to skip ' or " too
+ szWork = szEnd + 1;
+
+ if (!stricmpnull(szNode, "cAwaySrv"))
+ {
+ int bChanged = FALSE;
+
+ *szEnd = ' ';
+ szNode = strstrnull(szWork, "<index>");
+ szEnd = strstrnull(szWork, "</index>");
+ if (szNode && szEnd)
+ {
+ szNode += 7;
+ *szEnd = '\0';
+ if (atoi(szNode) != getContactXStatus(hContact))
+ { // this is strange - but go on
+ NetLog_Server("Warning: XStatusIds do not match!");
+ }
+ *szEnd = ' ';
+ }
+ szNode = strstrnull(szWork, "<title>");
+ szEnd = strstrnull(szWork, "</title>");
+ if (szNode && szEnd)
+ { // we got XStatus title, save it
+ char *szXName, *szOldXName;
+ szNode += 7;
+ *szEnd = '\0';
+ szXName = DemangleXml(szNode, strlennull(szNode));
+ // check if the name changed
+ szOldXName = getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, NULL);
+ if (strcmpnull(szOldXName, szXName))
+ bChanged = TRUE;
+ SAFE_FREE(&szOldXName);
+ setSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, szXName);
+ SAFE_FREE(&szXName);
+ *szEnd = ' ';
+ }
+ szNode = strstrnull(szWork, "<desc>");
+ szEnd = strstrnull(szWork, "</desc>");
+ if (szNode && szEnd)
+ { // we got XStatus mode msg, save it
+ char *szXMsg, *szOldXMsg;
+ szNode += 6;
+ *szEnd = '\0';
+ szXMsg = DemangleXml(szNode, strlennull(szNode));
+ // check if the decription changed
+ szOldXMsg = getSettingStringUtf(hContact, DBSETTING_XSTATUS_NAME, NULL);
+ if (strcmpnull(szOldXMsg, szXMsg))
+ bChanged = TRUE;
+ SAFE_FREE(&szOldXMsg);
+ setSettingStringUtf(hContact, DBSETTING_XSTATUS_MSG, szXMsg);
+ SAFE_FREE(&szXMsg);
+ }
+ BroadcastAck(hContact, ICQACKTYPE_XSTATUS_RESPONSE, ACKRESULT_SUCCESS, (HANDLE)wCookie, 0);
+ if (bChanged)
+ NotifyEventHooks(hxstatuschanged, (WPARAM)hContact, 0);
+ }
+ else
+ {
+ char *szSrvEnd = strstrnull(szEnd, "</srv>");
+
+ if (szSrvEnd && strstrnull(szSrvEnd, "<val srv_id="))
+ { // check all values !
+ szRes = szSrvEnd + 6; // after first value
+ goto NextVal;
+ }
+ // no next val, we were unable to handle packet, write error
+ NetLog_Server("Error: Unknown serverId \"%s\" in Xtraz response", szNode);
+ }
+ }
+ else
+ NetLog_Server("Error: Missing serverId in Xtraz response");
+
+ SAFE_FREE(&szMem);
+ }
+ else
+ NetLog_Server("Error: Invalid Xtraz Notify response");
+}
+
+
+static char* getXmlPidItem(const char* szData, int nLen)
+{
+ const char *szPid, *szEnd;
+
+ szPid = strstrnull(szData, "<PID>");
+ szEnd = strstrnull(szData, "</PID>");
+
+ if (szPid && szEnd)
+ {
+ szPid += 5;
+
+ return DemangleXml(szPid, szEnd - szPid);
+ }
+ return NULL;
+}
+
+
+void CIcqProto::handleXtrazInvitation(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC)
+{
+ HANDLE hContact;
+ char* szPluginID;
+
+ hContact = HContactFromUIN(dwUin, NULL);
+ if (hContact) // user sent us xtraz, he supports it
+ SetContactCapabilities(hContact, CAPF_XTRAZ);
+
+ szPluginID = getXmlPidItem(szMsg, nMsgLen);
+ if (!strcmpnull(szPluginID, "ICQChatRecv"))
+ { // it is a invitation to multi-user chat
+ }
+ else
+ {
+ NetLog_Uni(bThruDC, "Error: Unknown plugin \"%s\" in Xtraz message", szPluginID);
+ }
+ SAFE_FREE(&szPluginID);
+}
+
+
+void CIcqProto::handleXtrazData(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szMsg, int nMsgLen, BOOL bThruDC)
+{
+ HANDLE hContact;
+ char* szPluginID;
+
+ hContact = HContactFromUIN(dwUin, NULL);
+ if (hContact) // user sent us xtraz, he supports it
+ SetContactCapabilities(hContact, CAPF_XTRAZ);
+
+ szPluginID = getXmlPidItem(szMsg, nMsgLen);
+ if (!strcmpnull(szPluginID, "viewCard"))
+ { // it is a greeting card
+ char *szWork, *szEnd, *szUrl, *szNum;
+
+ szWork = strstrnull(szMsg, "<InD>");
+ szEnd = strstrnull(szMsg, "</InD>");
+ if (szWork && szEnd)
+ {
+ int nDataLen = szEnd - szWork;
+
+ szUrl = (char*)_alloca(nDataLen);
+ memcpy(szUrl, szWork+5, nDataLen);
+ szUrl[nDataLen - 5] = '\0';
+
+ if (!_strnicmp(szUrl, "view_", 5))
+ {
+ szNum = szUrl + 5;
+ szWork = strstrnull(szUrl, ".html");
+ if (szWork)
+ {
+ strcpy(szWork, ".php");
+ strcat(szWork, szWork+5);
+ }
+ while (szWork = strstrnull(szUrl, "&amp;"))
+ { // unescape &amp; code
+ strcpy(szWork+1, szWork+5);
+ }
+ szWork = (char*)SAFE_MALLOC(nDataLen + MAX_PATH);
+ ICQTranslateUtfStatic(LPGEN("Greeting card:"), szWork, MAX_PATH);
+ strcat(szWork, "\r\nhttp://www.icq.com/friendship/pages/view_page_");
+ strcat(szWork, szNum);
+
+ // Create message to notify user
+ {
+ CCSDATA ccs;
+ PROTORECVEVENT pre = {0};
+ int bAdded;
+
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.hContact = HContactFromUIN(dwUin, &bAdded);
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ pre.timestamp = time(NULL);
+ pre.szMessage = szWork;
+ pre.flags = PREF_UTF;
+
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+ }
+ SAFE_FREE(&szWork);
+ }
+ else
+ NetLog_Uni(bThruDC, "Error: Non-standard greeting card message");
+ }
+ else
+ NetLog_Uni(bThruDC, "Error: Malformed greeting card message");
+ }
+ else
+ {
+ NetLog_Uni(bThruDC, "Error: Unknown plugin \"%s\" in Xtraz message", szPluginID);
+ }
+ SAFE_FREE(&szPluginID);
+}
+
+
+// Functions really sending Xtraz stuff
+DWORD CIcqProto::SendXtrazNotifyRequest(HANDLE hContact, char* szQuery, char* szNotify, int bForced)
+{
+ char *szQueryBody;
+ char *szNotifyBody;
+ DWORD dwUin;
+ int nBodyLen;
+ char *szBody;
+ DWORD dwCookie;
+
+ if (getContactUid(hContact, &dwUin, NULL))
+ return 0; // Invalid contact
+
+ if (!CheckContactCapabilities(hContact, CAPF_XTRAZ) && !bForced)
+ return 0; // Contact does not support xtraz, do not send anything
+
+ szQueryBody = MangleXml(szQuery, strlennull(szQuery));
+ szNotifyBody = MangleXml(szNotify, strlennull(szNotify));
+ nBodyLen = strlennull(szQueryBody) + strlennull(szNotifyBody) + 41;
+ szBody = (char*)_alloca(nBodyLen);
+ nBodyLen = null_snprintf(szBody, nBodyLen, "<N><QUERY>%s</QUERY><NOTIFY>%s</NOTIFY></N>", szQueryBody, szNotifyBody);
+ SAFE_FREE((void**)&szQueryBody);
+ SAFE_FREE((void**)&szNotifyBody);
+
+ // Set up the ack type
+ cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_SCRIPT_NOTIFY, ACKTYPE_CLIENT);
+ dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ // have we a open DC, send through that
+ if (m_bDCMsgEnabled && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ icq_sendXtrazRequestDirect(hContact, dwCookie, szBody, nBodyLen, MTYPE_SCRIPT_NOTIFY);
+ else
+ icq_sendXtrazRequestServ(dwUin, dwCookie, szBody, nBodyLen, pCookieData);
+
+ return dwCookie;
+}
+
+
+void CIcqProto::SendXtrazNotifyResponse(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szResponse, int nResponseLen, BOOL bThruDC)
+{
+ char *szResBody = MangleXml(szResponse, nResponseLen);
+ int nBodyLen = strlennull(szResBody) + 21;
+ char *szBody = (char*)_alloca(nBodyLen);
+ HANDLE hContact = HContactFromUIN(dwUin, NULL);
+
+ if (hContact != INVALID_HANDLE_VALUE && !CheckContactCapabilities(hContact, CAPF_XTRAZ))
+ {
+ SAFE_FREE(&szResBody);
+ return; // Contact does not support xtraz, do not send anything
+ }
+
+ nBodyLen = null_snprintf(szBody, nBodyLen, "<NR><RES>%s</RES></NR>", szResBody);
+ SAFE_FREE(&szResBody);
+
+ // Was request received thru DC and have we a open DC, send through that
+ if (bThruDC && IsDirectConnectionOpen(hContact, DIRECTCONN_STANDARD, 0))
+ icq_sendXtrazResponseDirect(hContact, wCookie, szBody, nBodyLen, MTYPE_SCRIPT_NOTIFY);
+ else
+ icq_sendXtrazResponseServ(dwUin, dwMID, dwMID2, wCookie, szBody, nBodyLen, MTYPE_SCRIPT_NOTIFY);
+}
diff --git a/protocols/IcqOscarJ/src/icqosc_svcs.cpp b/protocols/IcqOscarJ/src/icqosc_svcs.cpp
new file mode 100644
index 0000000000..ee6dd1731c
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icqosc_svcs.cpp
@@ -0,0 +1,816 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// High-level code for exported API services
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+INT_PTR CIcqProto::AddServerContact(WPARAM wParam, LPARAM lParam)
+{
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (!m_bSsiEnabled) return 0;
+
+ // Does this contact have a UID?
+ if (!getContactUid((HANDLE)wParam, &dwUin, &szUid) && !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_ID, 0) && !getSettingWord((HANDLE)wParam, DBSETTING_SERVLIST_IGNORE, 0))
+ { /// TODO: remove possible 0x6A TLV in contact server-list data!!!
+ // Read group from DB
+ char *pszGroup = getContactCListGroup((HANDLE)wParam);
+
+ servlistAddContact((HANDLE)wParam, pszGroup);
+ SAFE_FREE((void**)&pszGroup);
+ }
+ return 0;
+}
+
+
+static int LookupDatabaseSetting(const FieldNamesItem* table, int code, DBVARIANT *dbv, BYTE type)
+{
+ char *text = LookupFieldName(table, code);
+
+ if (!text)
+ {
+ dbv->type = DBVT_DELETED;
+ return 1;
+ }
+
+ if (type == DBVT_ASCIIZ)
+ {
+ dbv->pszVal = mir_strdup(Translate(text));
+ dbv->type = DBVT_ASCIIZ;
+ }
+ else if (type == DBVT_UTF8 || !type)
+ {
+ char tmp[MAX_PATH];
+
+ dbv->pszVal = mir_strdup(ICQTranslateUtfStatic(text, tmp, MAX_PATH));
+ dbv->type = DBVT_UTF8;
+ }
+ else if (type == DBVT_WCHAR)
+ {
+ WCHAR* wtext = make_unicode_string(text);
+
+ dbv->pwszVal = mir_wstrdup(TranslateW(wtext));
+ dbv->type = DBVT_WCHAR;
+
+ SAFE_FREE((void**)&wtext);
+ }
+ return 0; // Success
+}
+
+INT_PTR CIcqProto::GetInfoSetting(WPARAM wParam, LPARAM lParam)
+{
+ DBCONTACTGETSETTING *cgs = (DBCONTACTGETSETTING*)lParam;
+ BYTE type = cgs->pValue->type;
+
+ cgs->pValue->type = 0; // original type without conversion
+ INT_PTR rc = CallService(MS_DB_CONTACT_GETSETTING_STR, wParam, lParam);
+
+ if (!rc)
+ { // Success
+ DBVARIANT dbv;
+
+ memcpy(&dbv, cgs->pValue, sizeof(DBVARIANT));
+
+ if (dbv.type == DBVT_BLOB)
+ {
+ cgs->pValue->pbVal = (BYTE*)mir_alloc(dbv.cpbVal);
+
+ memcpy(cgs->pValue->pbVal, dbv.pbVal, dbv.cpbVal);
+ }
+ else if (dbv.type == DBVT_ASCIIZ || dbv.type == DBVT_UTF8)
+ { // convert to the desired type
+ if (!type)
+ type = dbv.type;
+
+ if (dbv.type == type)
+ { // type is correct, only move it to miranda's heap
+ cgs->pValue->pszVal = mir_strdup(dbv.pszVal);
+ }
+ else if (type == DBVT_WCHAR)
+ {
+ if (dbv.type != DBVT_UTF8)
+ {
+ int len = MultiByteToWideChar(CP_ACP, 0, dbv.pszVal, -1, NULL, 0);
+ cgs->pValue->pwszVal = (WCHAR*)mir_alloc((len + 1)*sizeof(WCHAR));
+ if (cgs->pValue->pwszVal == NULL)
+ rc = 1;
+ else
+ {
+ MultiByteToWideChar(CP_ACP, 0, dbv.pszVal, -1, cgs->pValue->pwszVal, len);
+ cgs->pValue->pwszVal[len] = '\0';
+ }
+ }
+ else
+ {
+ char *savePtr = dbv.pszVal ? strcpy((char*)_alloca(strlennull(dbv.pszVal) + 1), dbv.pszVal) : NULL;
+ if (!mir_utf8decode(savePtr, &cgs->pValue->pwszVal))
+ rc = 1;
+ }
+ }
+ else if (type == DBVT_UTF8)
+ {
+ cgs->pValue->pszVal = mir_utf8encode(dbv.pszVal);
+ if (cgs->pValue->pszVal == NULL)
+ rc = 1;
+ }
+ else if (type == DBVT_ASCIIZ)
+ {
+ cgs->pValue->pszVal = mir_strdup(dbv.pszVal);
+ mir_utf8decode(cgs->pValue->pszVal, NULL);
+ }
+
+ cgs->pValue->type = type;
+ }
+ else if (!strcmpnull(cgs->szModule, m_szModuleName) && (dbv.type == DBVT_BYTE || dbv.type == DBVT_WORD || dbv.type == DBVT_DWORD))
+ {
+ int code = (dbv.type == DBVT_BYTE) ? dbv.bVal : ((dbv.type == DBVT_WORD) ? dbv.wVal : dbv.dVal);
+
+ if (!strcmpnull(cgs->szSetting, "Language1") || !strcmpnull(cgs->szSetting, "Language2") || !strcmpnull(cgs->szSetting, "Language3"))
+ rc = LookupDatabaseSetting(languageField, code, cgs->pValue, type);
+ else if (!strcmpnull(cgs->szSetting, "Country") || !strcmpnull(cgs->szSetting, "OriginCountry") || !strcmpnull(cgs->szSetting, "CompanyCountry"))
+ {
+ if (code == 420) code = 42; // conversion of obsolete codes (OMG!)
+ else if (code == 421) code = 4201;
+ else if (code == 102) code = 1201;
+ rc = LookupDatabaseSetting(countryField, code, cgs->pValue, type);
+ }
+ else if (!strcmpnull(cgs->szSetting, "Gender"))
+ rc = LookupDatabaseSetting(genderField, code, cgs->pValue, type);
+ else if (!strcmpnull(cgs->szSetting, "MaritalStatus"))
+ rc = LookupDatabaseSetting(maritalField, code, cgs->pValue, type);
+ else if (!strcmpnull(cgs->szSetting, "StudyLevel"))
+ rc = LookupDatabaseSetting(studyLevelField, code, cgs->pValue, type);
+ else if (!strcmpnull(cgs->szSetting, "CompanyIndustry"))
+ rc = LookupDatabaseSetting(industryField, code, cgs->pValue, type);
+ else if (!strcmpnull(cgs->szSetting, "Interest0Cat") || !strcmpnull(cgs->szSetting, "Interest1Cat") || !strcmpnull(cgs->szSetting, "Interest2Cat") || !strcmpnull(cgs->szSetting, "Interest3Cat"))
+ rc = LookupDatabaseSetting(interestsField, code, cgs->pValue, type);
+ }
+ // Release database memory
+ ICQFreeVariant(&dbv);
+ }
+
+ return rc;
+}
+
+
+INT_PTR CIcqProto::ChangeInfoEx(WPARAM wParam, LPARAM lParam)
+{
+ if (icqOnline() && wParam)
+ {
+ PBYTE buf = NULL;
+ int buflen = 0;
+ BYTE b;
+
+ // userinfo
+ ppackTLVWord(&buf, &buflen, 0x1C2, (WORD)GetACP());
+
+ if (wParam & CIXT_CONTACT)
+ { // contact information
+ BYTE *pBlock = NULL;
+ int cbBlock = 0;
+ int nItems = 0;
+
+ // Emails
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "e-mail0", 0x78, 0x64, 0x00);
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "e-mail1", 0x78, 0x64, 0x00);
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "e-mail2", 0x78, 0x64, 0x00);
+ ppackTLVBlockItems(&buf, &buflen, 0x8C, &nItems, &pBlock, (WORD*)&cbBlock, FALSE);
+
+ // Phones
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "Phone", 0x6E, 0x64, 0x01);
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "CompanyPhone", 0x6E, 0x64, 0x02);
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "Cellular", 0x6E, 0x64, 0x03);
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "Fax", 0x6E, 0x64, 0x04);
+ nItems += ppackTLVWordStringItemFromDB(&pBlock, &cbBlock, "CompanyFax", 0x6E, 0x64, 0x05);
+ ppackTLVBlockItems(&buf, &buflen, 0xC8, &nItems, &pBlock, (WORD*)&cbBlock, FALSE);
+
+ ppackTLVByte(&buf, &buflen, 0x1EA, getSettingByte(NULL, "AllowSpam", 0));
+ }
+
+ if (wParam & CIXT_BASIC)
+ { // upload basic user info
+ ppackTLVStringUtfFromDB(&buf, &buflen, "Nick", 0x78);
+ ppackTLVStringUtfFromDB(&buf, &buflen, "FirstName", 0x64);
+ ppackTLVStringUtfFromDB(&buf, &buflen, "LastName", 0x6E);
+ ppackTLVStringUtfFromDB(&buf, &buflen, "About", 0x186);
+ }
+
+ if (wParam & CIXT_MORE)
+ {
+ b = getSettingByte(NULL, "Gender", 0);
+ ppackTLVByte(&buf, &buflen, 0x82, (BYTE)(b ? (b == 'M' ? 2 : 1) : 0));
+
+ ppackTLVDateFromDB(&buf, &buflen, "BirthYear", "BirthMonth", "BirthDay", 0x1A4);
+
+ ppackTLVWord(&buf, &buflen, 0xAA, getSettingByte(NULL, "Language1", 0));
+ ppackTLVWord(&buf, &buflen, 0xB4, getSettingByte(NULL, "Language2", 0));
+ ppackTLVWord(&buf, &buflen, 0xBE, getSettingByte(NULL, "Language3", 0));
+
+ ppackTLVWord(&buf, &buflen, 0x12C, getSettingByte(NULL, "MaritalStatus", 0));
+ }
+
+ if (wParam & CIXT_WORK)
+ {
+ BYTE *pBlock = NULL;
+ int cbBlock = 0;
+ int nItems = 1;
+
+ // Jobs
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyPosition", 0x64);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "Company", 0x6E);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyDepartment", 0x7D);
+ ppackTLVStringFromDB(&pBlock, &cbBlock, "CompanyHomepage", 0x78);
+ ppackTLVWord(&pBlock, &cbBlock, 0x82, getSettingWord(NULL, "CompanyIndustry", 0));
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyStreet", 0xAA);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyCity", 0xB4);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyState", 0xBE);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "CompanyZIP", 0xC8);
+ ppackTLVDWord(&pBlock, &cbBlock, 0xD2, getSettingWord(NULL, "CompanyCountry", 0));
+ /// TODO: pack unknown data (need to preserve them in Block Items)
+ ppackTLVBlockItems(&buf, &buflen, 0x118, &nItems, &pBlock, (WORD*)&cbBlock, TRUE);
+
+ // ppackTLVWord(&buf, &buflen, getSettingWord(NULL, "CompanyOccupation", 0), TLV_OCUPATION, 1); // Lost In Conversion
+ }
+
+ if (wParam & CIXT_EDUCATION)
+ {
+ BYTE *pBlock = NULL;
+ int cbBlock = 0;
+ int nItems = 1;
+
+ // Studies
+ ppackTLVWord(&pBlock, &cbBlock, 0x64, getSettingWord(NULL, "StudyLevel", 0));
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "StudyInstitute", 0x6E);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "StudyDegree", 0x78);
+ ppackTLVWord(&pBlock, &cbBlock, 0x8C, getSettingWord(NULL, "StudyYear", 0));
+ ppackTLVBlockItems(&buf, &buflen, 0x10E, &nItems, &pBlock, (WORD*)&cbBlock, TRUE);
+ }
+
+ if (wParam & CIXT_LOCATION)
+ {
+ BYTE *pBlock = NULL;
+ int cbBlock = 0;
+ int nItems = 1;
+
+ // Home Address
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "Street", 0x64);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "City", 0x6E);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "State", 0x78);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "ZIP", 0x82);
+ ppackTLVDWord(&pBlock, &cbBlock, 0x8C, getSettingWord(NULL, "Country", 0));
+ ppackTLVBlockItems(&buf, &buflen, 0x96, &nItems, &pBlock, (WORD*)&cbBlock, TRUE);
+
+ nItems = 1;
+ // Origin Address
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "OriginStreet", 0x64);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "OriginCity", 0x6E);
+ ppackTLVStringUtfFromDB(&pBlock, &cbBlock, "OriginState", 0x78);
+ ppackTLVDWord(&pBlock, &cbBlock, 0x8C, getSettingWord(NULL, "OriginCountry", 0));
+ ppackTLVBlockItems(&buf, &buflen, 0xA0, &nItems, &pBlock, (WORD*)&cbBlock, TRUE);
+
+ ppackTLVStringFromDB(&buf, &buflen, "Homepage", 0xFA);
+
+ // Timezone
+ WORD wTimezone = getSettingByte(NULL, "Timezone", 0);
+ if ((wTimezone & 0x0080) == 0x80) wTimezone |= 0xFF00; // extend signed number
+ ppackTLVWord(&buf, &buflen, 0x17C, wTimezone);
+ }
+
+ if (wParam & CIXT_BACKGROUND)
+ {
+ BYTE *pBlock = NULL;
+ int cbBlock = 0;
+ int nItems = 0;
+
+ // Interests
+ nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest0Text", 0x6E, 0x64, getSettingWord(NULL, "Interest0Cat", 0));
+ nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest1Text", 0x6E, 0x64, getSettingWord(NULL, "Interest1Cat", 0));
+ nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest2Text", 0x6E, 0x64, getSettingWord(NULL, "Interest2Cat", 0));
+ nItems += ppackTLVWordStringUtfItemFromDB(&pBlock, &cbBlock, "Interest3Text", 0x6E, 0x64, getSettingWord(NULL, "Interest3Cat", 0));
+ ppackTLVBlockItems(&buf, &buflen, 0x122, &nItems, &pBlock, (WORD*)&cbBlock, FALSE);
+
+
+ /* WORD w; /// not supported anymore
+
+ w = StringToListItemId("Past0", 0);
+ ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Past0Text", TLV_PASTINFO);
+ w = StringToListItemId("Past1", 0);
+ ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Past1Text", TLV_PASTINFO);
+ w = StringToListItemId("Past2", 0);
+ ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Past2Text", TLV_PASTINFO);
+
+ w = StringToListItemId("Affiliation0", 0);
+ ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Affiliation0Text", TLV_AFFILATIONS);
+ w = StringToListItemId("Affiliation1", 0);
+ ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Affiliation1Text", TLV_AFFILATIONS);
+ w = StringToListItemId("Affiliation2", 0);
+ ppackTLVWordLNTSfromDB(&buf, &buflen, w, "Affiliation2Text", TLV_AFFILATIONS);*/
+ }
+
+ DWORD dwCookie = icq_changeUserDirectoryInfoServ(buf, (WORD)buflen, DIRECTORYREQUEST_UPDATEOWNER);
+
+ SAFE_FREE((void**)&buf);
+
+ return dwCookie;
+ }
+
+ return 0; // Failure
+}
+
+
+INT_PTR CIcqProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam == AF_MAXSIZE)
+ {
+ POINT *size = (POINT*)lParam;
+
+ if (size)
+ {
+ size->x = 64;
+ size->y = 64;
+
+ return 0;
+ }
+ }
+ else if (wParam == AF_PROPORTION)
+ {
+ return PIP_NONE;
+ }
+ else if (wParam == AF_FORMATSUPPORTED)
+ {
+ if (lParam == PA_FORMAT_JPEG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_XML || lParam == PA_FORMAT_BMP)
+ return 1;
+ else
+ return 0;
+ }
+ else if (wParam == AF_ENABLED)
+ {
+ if (m_bSsiEnabled && m_bAvatarsEnabled)
+ return 1;
+ else
+ return 0;
+ }
+ else if (wParam == AF_DONTNEEDDELAYS)
+ {
+ return 0;
+ }
+ else if (wParam == AF_MAXFILESIZE)
+ { // server accepts images of 7168 bytees, not bigger
+ return 7168;
+ }
+ else if (wParam == AF_DELAYAFTERFAIL)
+ { // do not request avatar again if server gave an error
+ return 1 * 60 * 60 * 1000; // one hour
+ }
+ else if (wParam == AF_FETCHALWAYS)
+ { // avatars can be fetched all the time (server only operation)
+ return 1;
+ }
+ return 0;
+}
+
+
+INT_PTR CIcqProto::GetAvatarInfo(WPARAM wParam, LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATIONT *pai = (PROTO_AVATAR_INFORMATIONT*)lParam;
+ DWORD dwUIN;
+ uid_str szUID;
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (!m_bAvatarsEnabled) return GAIR_NOAVATAR;
+
+ if (getSetting(pai->hContact, "AvatarHash", &dbv) || dbv.type != DBVT_BLOB || (dbv.cpbVal != 0x14 && dbv.cpbVal != 0x09))
+ {
+ ICQFreeVariant(&dbv);
+ return GAIR_NOAVATAR; // we did not found avatar hash or hash invalid - no avatar available
+ }
+
+ if (getContactUid(pai->hContact, &dwUIN, &szUID))
+ {
+ ICQFreeVariant(&dbv);
+ return GAIR_NOAVATAR; // we do not support avatars for invalid contacts
+ }
+
+ int dwPaFormat = getSettingByte(pai->hContact, "AvatarType", PA_FORMAT_UNKNOWN);
+
+ if (dwPaFormat != PA_FORMAT_UNKNOWN)
+ { // we know the format, test file
+ TCHAR tszFile[MAX_PATH * 2 + 4];
+
+ GetFullAvatarFileName(dwUIN, szUID, dwPaFormat, tszFile, MAX_PATH * 2);
+
+ lstrcpyn(pai->filename, tszFile, SIZEOF(pai->filename)); // Avatar API does not support unicode :-(
+ pai->format = dwPaFormat;
+
+ if (!IsAvatarChanged(pai->hContact, dbv.pbVal, dbv.cpbVal))
+ { // hashes are the same
+ if (_taccess(tszFile, 0) == 0)
+ {
+ ICQFreeVariant(&dbv);
+
+ return GAIR_SUCCESS; // we have found the avatar file, whoala
+ }
+ }
+ }
+
+ if (IsAvatarChanged(pai->hContact, dbv.pbVal, dbv.cpbVal))
+ { // we didn't received the avatar before - this ensures we will not request avatar again and again
+ if ((wParam & GAIF_FORCE) != 0 && pai->hContact != 0)
+ { // request avatar data
+ TCHAR tszFile[MAX_PATH * 2 + 4];
+
+ GetAvatarFileName(dwUIN, szUID, tszFile, MAX_PATH * 2);
+ GetAvatarData(pai->hContact, dwUIN, szUID, dbv.pbVal, dbv.cpbVal, tszFile);
+ lstrcpyn(pai->filename, tszFile, SIZEOF(pai->filename)); // Avatar API does not support unicode :-(
+
+ ICQFreeVariant(&dbv);
+
+ return GAIR_WAITFOR;
+ }
+ }
+ ICQFreeVariant(&dbv);
+
+ return GAIR_NOAVATAR;
+}
+
+
+INT_PTR CIcqProto::GetMyAvatar(WPARAM wParam, LPARAM lParam)
+{
+ if (!m_bAvatarsEnabled) return -2;
+
+ if (!wParam) return -3;
+
+ TCHAR *tszFile = GetOwnAvatarFileName();
+ if (tszFile && !_taccess(tszFile, 0))
+ {
+ _tcsncpy((TCHAR*)wParam, tszFile, (int)lParam);
+ SAFE_FREE(&tszFile);
+ return 0;
+ }
+
+ SAFE_FREE(&tszFile);
+ return -1;
+}
+
+
+INT_PTR CIcqProto::GrantAuthorization(WPARAM wParam, LPARAM lParam)
+{
+ if (icqOnline() && wParam != 0)
+ {
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid((HANDLE)wParam, &dwUin, &szUid))
+ return 0; // Invalid contact
+
+ // send without reason, do we need any ?
+ icq_sendGrantAuthServ(dwUin, szUid, NULL);
+ // auth granted, remove contact menu item
+ deleteSetting((HANDLE)wParam, "Grant");
+ }
+
+ return 0;
+}
+
+int CIcqProto::OnIdleChanged(WPARAM wParam, LPARAM lParam)
+{
+ int bIdle = (lParam&IDF_ISIDLE);
+ int bPrivacy = (lParam&IDF_PRIVACY);
+
+ if (bPrivacy) return 0;
+
+ setSettingDword(NULL, "IdleTS", bIdle ? time(0) : 0);
+
+ if (m_bTempVisListEnabled) // remove temporary visible users
+ sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_REMOVETEMPVISIBLE, BUL_TEMPVISIBLE);
+
+ icq_setidle(bIdle ? 1 : 0);
+
+ return 0;
+}
+
+INT_PTR CIcqProto::RevokeAuthorization(WPARAM wParam, LPARAM lParam)
+{
+ if (icqOnline() && wParam != 0)
+ {
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid((HANDLE)wParam, &dwUin, &szUid))
+ return 0; // Invalid contact
+
+ if (MessageBoxUtf(NULL, LPGEN("Are you sure you want to revoke user's authorization (this will remove you from his/her list on some clients) ?"), LPGEN("Confirmation"), MB_ICONQUESTION | MB_YESNO) != IDYES)
+ return 0;
+
+ icq_sendRevokeAuthServ(dwUin, szUid);
+ }
+
+ return 0;
+}
+
+
+INT_PTR CIcqProto::SendSms(WPARAM wParam, LPARAM lParam)
+{
+ if (icqOnline() && wParam && lParam)
+ return icq_sendSMSServ((const char *)wParam, (const char *)lParam);
+
+ return 0; // Failure
+}
+
+INT_PTR CIcqProto::SendYouWereAdded(WPARAM wParam, LPARAM lParam)
+{
+ if (lParam && icqOnline())
+ {
+ CCSDATA* ccs = (CCSDATA*)lParam;
+ if (ccs->hContact)
+ {
+ DWORD dwUin, dwMyUin;
+
+ if (getContactUid(ccs->hContact, &dwUin, NULL))
+ return 1; // Invalid contact
+
+ dwMyUin = getContactUin(NULL);
+
+ if (dwUin)
+ {
+ icq_sendYouWereAddedServ(dwUin, dwMyUin);
+ return 0; // Success
+ }
+ }
+ }
+
+ return 1; // Failure
+}
+
+INT_PTR CIcqProto::SetMyAvatar(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR* tszFile = (TCHAR*)lParam;
+ int iRet = -1;
+
+ if (!m_bAvatarsEnabled || !m_bSsiEnabled) return -2;
+
+ if (tszFile)
+ { // set file for avatar
+ int dwPaFormat = DetectAvatarFormat(tszFile);
+ if (dwPaFormat != PA_FORMAT_XML)
+ {
+ // if it should be image, check if it is valid
+ HBITMAP avt = (HBITMAP)CallService(MS_UTILS_LOADBITMAPT, 0, (WPARAM)tszFile);
+ if (!avt) return iRet;
+ DeleteObject(avt);
+ }
+
+ TCHAR tszMyFile[MAX_PATH+1];
+ GetFullAvatarFileName(0, NULL, dwPaFormat, tszMyFile, MAX_PATH);
+ // if not in our storage, copy
+ if (lstrcmp(tszFile, tszMyFile) && !CopyFile(tszFile, tszMyFile, FALSE))
+ {
+ NetLog_Server("Failed to copy our avatar to local storage.");
+ return iRet;
+ }
+
+ BYTE *hash = calcMD5HashOfFile(tszMyFile);
+ if (hash)
+ {
+ BYTE* ihash = (BYTE*)_alloca(0x14);
+ // upload hash to server
+ ihash[0] = 0; //unknown
+ ihash[1] = dwPaFormat == PA_FORMAT_XML ? AVATAR_HASH_FLASH : AVATAR_HASH_STATIC; //hash type
+ ihash[2] = 1; //hash status
+ ihash[3] = 0x10; //hash len
+ memcpy(ihash+4, hash, 0x10);
+ updateServAvatarHash(ihash, 0x14);
+
+ if (setSettingBlob(NULL, "AvatarHash", ihash, 0x14))
+ {
+ NetLog_Server("Failed to save avatar hash.");
+ }
+
+ TCHAR tmp[MAX_PATH];
+ CallService(MS_UTILS_PATHTORELATIVET, (WPARAM)tszMyFile, (LPARAM)tmp);
+ setSettingStringT(NULL, "AvatarFile", tmp);
+
+ iRet = 0;
+
+ SAFE_FREE((void**)&hash);
+ }
+ }
+ else
+ { // delete user avatar
+ deleteSetting(NULL, "AvatarFile");
+ setSettingBlob(NULL, "AvatarHash", hashEmptyAvatar, 9);
+ updateServAvatarHash(hashEmptyAvatar, 9); // set blank avatar
+ iRet = 0;
+ }
+
+ return iRet;
+}
+
+INT_PTR CIcqProto::SetNickName(WPARAM wParam, LPARAM lParam)
+{
+ if (icqOnline())
+ {
+ setSettingString(NULL, "Nick", (char*)lParam);
+
+ return ChangeInfoEx(CIXT_BASIC, 0);
+ }
+
+ return 0; // Failure
+}
+
+INT_PTR CIcqProto::SetPassword(WPARAM wParam, LPARAM lParam)
+{
+ char *pwd = (char*)lParam;
+ int len = strlennull(pwd);
+
+ if (len && len < PASSWORDMAXLEN)
+ {
+ strcpy(m_szPassword, pwd);
+ m_bRememberPwd = TRUE;
+ }
+ return 0;
+}
+
+
+// TODO: Adding needs some more work in general
+
+HANDLE CIcqProto::AddToListByUIN(DWORD dwUin, DWORD dwFlags)
+{
+ int bAdded;
+ HANDLE hContact = HContactFromUIN(dwUin, &bAdded);
+ if (hContact)
+ {
+ if (!(dwFlags & PALF_TEMPORARY) && DBGetContactSettingByte(hContact, "CList", "NotOnList", 0))
+ {
+ setContactHidden(hContact, 0);
+ DBDeleteContactSetting(hContact, "CList", "NotOnList");
+ }
+
+ return hContact; // Success
+ }
+
+ return NULL; // Failure
+}
+
+
+HANDLE CIcqProto::AddToListByUID(const char *szUID, DWORD dwFlags)
+{
+ int bAdded;
+ HANDLE hContact = HContactFromUID(0, szUID, &bAdded);
+ if (hContact)
+ {
+ if (!(dwFlags & PALF_TEMPORARY) && DBGetContactSettingByte(hContact, "CList", "NotOnList", 0))
+ {
+ setContactHidden(hContact, 0);
+ DBDeleteContactSetting(hContact, "CList", "NotOnList");
+ }
+
+ return hContact; // Success
+ }
+
+ return NULL; // Failure
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::ICQAddRecvEvent(HANDLE hContact, WORD wType, PROTORECVEVENT* pre, DWORD cbBlob, PBYTE pBlob, DWORD flags)
+{
+ if (pre->flags & PREF_CREATEREAD)
+ flags |= DBEF_READ;
+
+ if (pre->flags & PREF_UTF)
+ flags |= DBEF_UTF;
+
+ if (hContact && DBGetContactSettingByte(hContact, "CList", "Hidden", 0))
+ {
+ DWORD dwUin;
+ uid_str szUid;
+
+ //setContactHidden(hContact, 0);
+
+ // if the contact was hidden, add to client-list if not in server-list authed
+ if (!getSettingWord(hContact, DBSETTING_SERVLIST_ID, 0) || getSettingByte(hContact, "Auth", 0))
+ {
+ getContactUid(hContact, &dwUin, &szUid);
+ icq_sendNewContact(dwUin, szUid); /// FIXME
+ }
+ }
+
+ AddEvent(hContact, wType, pre->timestamp, flags, cbBlob, pBlob);
+}
+
+INT_PTR __cdecl CIcqProto::IcqAddCapability(WPARAM wParam, LPARAM lParam)
+{
+ ICQ_CUSTOMCAP *icqCustomCapIn = (ICQ_CUSTOMCAP *)lParam;
+ ICQ_CUSTOMCAP *icqCustomCap = (ICQ_CUSTOMCAP *)malloc(sizeof(ICQ_CUSTOMCAP));
+ memcpy(icqCustomCap, icqCustomCapIn, sizeof(ICQ_CUSTOMCAP));
+ CustomCapList.push_back(icqCustomCap);
+// MessageBoxA(NULL, ((ICQ_CUSTOMCAP *)(lstCustomCaps->items[lstCustomCaps->realCount-1]))->name, "custom cap", MB_OK);
+ return 0;
+}
+
+
+INT_PTR __cdecl CIcqProto::IcqCheckCapability(WPARAM wParam, LPARAM lParam)
+{
+ int res = 0;
+ DBCONTACTGETSETTING dbcgs;
+ DBVARIANT dbvariant;
+ HANDLE hContact = (HANDLE)wParam;
+ ICQ_CUSTOMCAP *icqCustomCap = (ICQ_CUSTOMCAP *)lParam;
+ dbcgs.pValue = &dbvariant;
+ dbcgs.szModule = m_szModuleName;
+ dbcgs.szSetting = "CapBuf";
+
+ CallService(MS_DB_CONTACT_GETSETTING, (WPARAM)hContact, (LPARAM)&dbcgs);
+
+ if (dbvariant.type == DBVT_BLOB)
+ {
+ res = MatchCapability(dbvariant.pbVal, dbvariant.cpbVal, (const capstr*)&icqCustomCap->caps, 0x10)?1:0; // FIXME: Why icqCustomCap->caps is not capstr?
+ }
+
+ CallService(MS_DB_CONTACT_FREEVARIANT,0,(LPARAM)(DBVARIANT*)&dbvariant);
+
+ return res;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+INT_PTR icq_getEventTextMissedMessage(WPARAM wParam, LPARAM lParam)
+{
+ DBEVENTGETTEXT *pEvent = (DBEVENTGETTEXT *)lParam;
+
+ INT_PTR nRetVal = 0;
+ char *pszText = NULL;
+
+ if (pEvent->dbei->cbBlob > 1)
+ {
+ switch (((WORD*)pEvent->dbei->pBlob)[0])
+ {
+ case 0:
+ pszText = LPGEN("** This message was blocked by the ICQ server ** The message was invalid.");
+ break;
+
+ case 1:
+ pszText = LPGEN("** This message was blocked by the ICQ server ** The message was too long.");
+ break;
+
+ case 2:
+ pszText = LPGEN("** This message was blocked by the ICQ server ** The sender has flooded the server.");
+ break;
+
+ case 4:
+ pszText = LPGEN("** This message was blocked by the ICQ server ** You are too evil.");
+ break;
+
+ default:
+ pszText = LPGEN("** Unknown missed message event.");
+ break;
+ }
+ if (pEvent->datatype == DBVT_WCHAR)
+ {
+ WCHAR *pwszText;
+ int wchars = MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszText, strlennull(pszText), NULL, 0);
+
+ pwszText = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR));
+ pwszText[wchars] = 0;
+
+ MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, pszText, strlennull(pszText), pwszText, wchars);
+
+ nRetVal = (INT_PTR)mir_wstrdup(TranslateW(pwszText));
+ }
+ else if (pEvent->datatype == DBVT_ASCIIZ)
+ nRetVal = (INT_PTR)mir_strdup(Translate(pszText));
+ }
+
+ return nRetVal;
+}
diff --git a/protocols/IcqOscarJ/src/icqosc_svcs.h b/protocols/IcqOscarJ/src/icqosc_svcs.h
new file mode 100644
index 0000000000..844eecee51
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icqosc_svcs.h
@@ -0,0 +1,39 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __ICQOSC_SVCS_H
+#define __ICQOSC_SVCS_H
+
+
+#define ICQ_DB_GETEVENTTEXT_MISSEDMESSAGE "ICQ/GetEventTextMissedMessage"
+
+INT_PTR icq_getEventTextMissedMessage(WPARAM wParam, LPARAM lParam);
+
+
+#endif /* __ICQOSC_SVCS_H */
diff --git a/protocols/IcqOscarJ/src/icqoscar.cpp b/protocols/IcqOscarJ/src/icqoscar.cpp
new file mode 100644
index 0000000000..5ce67145cd
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icqoscar.cpp
@@ -0,0 +1,35 @@
+// ---------------------------------------------------------------------------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,2003,2004 Martin Öberg, Sam Kothari, Robert Rainwater
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// The only purpose of this file is to make sure that the precompiled headers
+// are included and compiled. The Visual Studio settings for this file must be
+// 'Create precompiled header file' and all the other .c files must be set to
+// 'User precompiled header file'. Remember to check this when adding new
+// files to the project...
+//
+// -----------------------------------------------------------------------------
+
+
+#include "icqoscar.h"
diff --git a/protocols/IcqOscarJ/src/icqoscar.h b/protocols/IcqOscarJ/src/icqoscar.h
new file mode 100644
index 0000000000..ef0e32a6fd
--- /dev/null
+++ b/protocols/IcqOscarJ/src/icqoscar.h
@@ -0,0 +1,134 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Includes all header files that should be precompiled to speed up compilation.
+//
+// -----------------------------------------------------------------------------
+#define MIRANDA_VER 0x0A00
+
+#define _WIN32_WINNT 0x0501
+#define _WIN32_IE 0x0501
+
+#include <m_stdhdr.h>
+
+// Windows includes
+#include <windows.h>
+#include <commctrl.h>
+#include <uxtheme.h>
+
+// Standard includes
+#include <stdio.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <io.h>
+#include <direct.h>
+#include <fcntl.h>
+#include <process.h>
+
+//C++
+#include <list>
+
+#ifndef _DEBUG
+#ifdef _MSC_VER
+ #include <crtdbg.h>
+#endif
+#endif
+
+#ifndef AW_VER_POSITIVE
+#define AW_VER_POSITIVE 0x00000004
+#endif
+
+#ifndef _ASSERTE
+#define _ASSERTE(x)
+#endif
+
+// Miranda IM SDK includes
+#include <newpluginapi.h> // This must be included first
+#include <m_clc.h>
+#include <m_clist.h>
+#include <m_clui.h>
+#include <m_database.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_netlib.h>
+#include <m_protocols.h>
+#include <m_protomod.h>
+#include <m_protosvc.h>
+#include <m_options.h>
+#include <m_system.h>
+#include <m_system_cpp.h>
+#include <m_userinfo.h>
+#include <m_utils.h>
+#include <m_idle.h>
+#include <m_popup.h>
+#include <m_clistint.h>
+#include <m_cluiframes.h>
+#include <m_ignore.h>
+#include <m_avatars.h>
+#include <win2k.h>
+
+// Project resources
+#include "resource.h"
+
+// ICQ plugin includes
+#include "version.h"
+#include "iconlib.h"
+#include "globals.h"
+#include "i18n.h"
+#include "icq_db.h"
+#include "cookies.h"
+#include "icq_packet.h"
+#include "utilities.h"
+#include "oscar_filetransfer.h"
+#include "icq_direct.h"
+#include "icq_server.h"
+#include "icqosc_svcs.h"
+#include "icq_servlist.h"
+#include "icq_http.h"
+#include "icq_fieldnames.h"
+#include "icq_constants.h"
+#include "capabilities.h"
+#include "guids.h"
+#include "init.h"
+#include "stdpackets.h"
+#include "tlv.h"
+#include "channels.h"
+#include "families.h"
+#include "m_icq.h"
+#include "m_icqplus.h"
+#include "icq_advsearch.h"
+#include "log.h"
+
+#include "icq_rates.h"
+
+#include "icq_avatar.h"
+
+#include "changeinfo/changeinfo.h"
+#include "icq_popups.h"
+#include "icq_proto.h"
+
+extern LIST<CIcqProto> g_Instances;
diff --git a/protocols/IcqOscarJ/src/init.cpp b/protocols/IcqOscarJ/src/init.cpp
new file mode 100644
index 0000000000..450ea73b16
--- /dev/null
+++ b/protocols/IcqOscarJ/src/init.cpp
@@ -0,0 +1,245 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+#include "m_extraicons.h"
+
+HINSTANCE hInst;
+int hLangpack;
+
+HANDLE hStaticServices[1];
+IcqIconHandle hStaticIcons[4];
+HANDLE hStaticHooks[1];;
+HANDLE hExtraXStatus = NULL;
+
+PLUGININFOEX pluginInfo = {
+ sizeof(PLUGININFOEX),
+ "IcqOscarJ Protocol",
+ __VERSION_DWORD,
+ "Support for ICQ network, enhanced.",
+ "Joe Kucera, Bio, Martin Öberg, Richard Hughes, Jon Keating, etc",
+ "jokusoftware@miranda-im.org",
+ "(C) 2000-2010 M.Öberg, R.Hughes, J.Keating, Bio, Angeli-Ka, G.Hazan, J.Kucera",
+ "http://miranda-ng.org/",
+ UNICODE_AWARE, //doesn't replace anything built-in
+ {0x73a9615c, 0x7d4e, 0x4555, {0xba, 0xdb, 0xee, 0x5, 0xdc, 0x92, 0x8e, 0xff}} // {73A9615C-7D4E-4555-BADB-EE05DC928EFF}
+};
+
+extern "C" PLUGININFOEX __declspec(dllexport) *MirandaPluginInfoEx(DWORD mirandaVersion)
+{
+ return &pluginInfo;
+}
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST};
+
+extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
+{
+ hInst = hinstDLL;
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static PROTO_INTERFACE* icqProtoInit( const char* pszProtoName, const TCHAR* tszUserName )
+{
+ CIcqProto *ppro = new CIcqProto(pszProtoName, tszUserName);
+ g_Instances.insert(ppro);
+ return ppro;
+}
+
+
+static int icqProtoUninit( PROTO_INTERFACE* ppro )
+{
+ g_Instances.remove(( CIcqProto* )ppro);
+ delete ( CIcqProto* )ppro;
+ return 0;
+}
+
+
+static int OnModulesLoaded( WPARAM, LPARAM )
+{
+ hExtraXStatus = ExtraIcon_Register("xstatus", "ICQ XStatus");
+ return 0;
+}
+
+
+extern "C" int __declspec(dllexport) Load(void)
+{
+ mir_getLP( &pluginInfo );
+
+ srand(time(NULL));
+ _tzset();
+
+ // Register the module
+ PROTOCOLDESCRIPTOR pd = {0};
+ pd.cbSize = sizeof(pd);
+ pd.szName = ICQ_PROTOCOL_NAME;
+ pd.type = PROTOTYPE_PROTOCOL;
+ pd.fnInit = icqProtoInit;
+ pd.fnUninit = icqProtoUninit;
+ CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&pd);
+
+ // Initialize charset conversion routines
+ InitI18N();
+
+ // Register static services
+ hStaticServices[0] = CreateServiceFunction(ICQ_DB_GETEVENTTEXT_MISSEDMESSAGE, icq_getEventTextMissedMessage);
+
+ {
+ // Define global icons
+ char szSectionName[MAX_PATH];
+ null_snprintf(szSectionName, sizeof(szSectionName), "Protocols/%s", ICQ_PROTOCOL_NAME);
+
+ TCHAR lib[MAX_PATH];
+ GetModuleFileName(hInst, lib, MAX_PATH);
+ hStaticIcons[ISI_AUTH_REQUEST] = IconLibDefine(LPGEN("Request authorization"), szSectionName, NULL, "req_auth", lib, -IDI_AUTH_ASK);
+ hStaticIcons[ISI_AUTH_GRANT] = IconLibDefine(LPGEN("Grant authorization"), szSectionName, NULL, "grant_auth", lib, -IDI_AUTH_GRANT);
+ hStaticIcons[ISI_AUTH_REVOKE] = IconLibDefine(LPGEN("Revoke authorization"), szSectionName, NULL, "revoke_auth", lib, -IDI_AUTH_REVOKE);
+ hStaticIcons[ISI_ADD_TO_SERVLIST] = IconLibDefine(LPGEN("Add to server list"), szSectionName, NULL, "add_to_server", lib, -IDI_SERVLIST_ADD);
+ }
+
+ hStaticHooks[0] = HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+
+ g_MenuInit();
+ return 0;
+}
+
+
+extern "C" int __declspec(dllexport) Unload(void)
+{
+ int i;
+
+ // Release static icon handles
+ for (i = 0; i < SIZEOF(hStaticIcons); i++)
+ IconLibRemove(&hStaticIcons[i]);
+
+ // Release static event hooks
+ for (i = 0; i < SIZEOF(hStaticHooks); i++)
+ if (hStaticHooks[i])
+ UnhookEvent(hStaticHooks[i]);
+
+ // destroying contact menu
+ g_MenuUninit();
+
+ // Destroy static service functions
+ for (i = 0; i < SIZEOF(hStaticServices); i++)
+ if (hStaticServices[i])
+ DestroyServiceFunction(hStaticServices[i]);
+
+ g_Instances.destroy();
+
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnPrebuildContactMenu event
+
+void CListShowMenuItem(HANDLE hMenuItem, BYTE bShow)
+{
+ CLISTMENUITEM mi = {0};
+
+ mi.cbSize = sizeof(mi);
+ if (bShow)
+ mi.flags = CMIM_FLAGS;
+ else
+ mi.flags = CMIM_FLAGS | CMIF_HIDDEN;
+
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuItem, (LPARAM)&mi);
+}
+
+static void CListSetMenuItemIcon(HANDLE hMenuItem, HICON hIcon)
+{
+ CLISTMENUITEM mi = {0};
+
+ mi.cbSize = sizeof(mi);
+ mi.flags = CMIM_FLAGS | CMIM_ICON;
+
+ mi.hIcon = hIcon;
+ CallService(MS_CLIST_MODIFYMENUITEM, (WPARAM)hMenuItem, (LPARAM)&mi);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnReloadIcons event
+
+int CIcqProto::OnReloadIcons(WPARAM wParam, LPARAM lParam)
+{
+ memset(bXStatusCListIconsValid, 0, sizeof(bXStatusCListIconsValid));
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// UpdateGlobalSettings event
+
+void CIcqProto::UpdateGlobalSettings()
+{
+ char szServer[MAX_PATH] = "";
+ getSettingStringStatic(NULL, "OscarServer", szServer, MAX_PATH);
+
+ m_bSecureConnection = getSettingByte(NULL, "SecureConnection", DEFAULT_SECURE_CONNECTION);
+ if (szServer[0])
+ {
+ if (strstr(szServer, "aol.com"))
+ setSettingString(NULL, "OscarServer", m_bSecureConnection ? DEFAULT_SERVER_HOST_SSL : DEFAULT_SERVER_HOST);
+
+ if (m_bSecureConnection && !_strnicmp(szServer, "login.", 6))
+ {
+ setSettingString(NULL, "OscarServer", DEFAULT_SERVER_HOST_SSL);
+ setSettingWord(NULL, "OscarPort", DEFAULT_SERVER_PORT_SSL);
+ }
+ }
+
+ if (m_hServerNetlibUser)
+ {
+ NETLIBUSERSETTINGS nlus = {0};
+
+ nlus.cbSize = sizeof(NETLIBUSERSETTINGS);
+ if (!m_bSecureConnection && CallService(MS_NETLIB_GETUSERSETTINGS, (WPARAM)m_hServerNetlibUser, (LPARAM)&nlus))
+ {
+ if (nlus.useProxy && nlus.proxyType == PROXYTYPE_HTTP)
+ m_bGatewayMode = 1;
+ else
+ m_bGatewayMode = 0;
+ }
+ else
+ m_bGatewayMode = 0;
+ }
+
+ m_bSecureLogin = getSettingByte(NULL, "SecureLogin", DEFAULT_SECURE_LOGIN);
+ m_bAimEnabled = getSettingByte(NULL, "AimEnabled", DEFAULT_AIM_ENABLED);
+ m_bUtfEnabled = getSettingByte(NULL, "UtfEnabled", DEFAULT_UTF_ENABLED);
+ m_wAnsiCodepage = getSettingWord(NULL, "AnsiCodePage", DEFAULT_ANSI_CODEPAGE);
+ m_bDCMsgEnabled = getSettingByte(NULL, "DirectMessaging", DEFAULT_DCMSG_ENABLED);
+ m_bTempVisListEnabled = getSettingByte(NULL, "TempVisListEnabled", DEFAULT_TEMPVIS_ENABLED);
+ m_bSsiEnabled = getSettingByte(NULL, "UseServerCList", DEFAULT_SS_ENABLED);
+ m_bSsiSimpleGroups = FALSE; /// TODO: enable, after server-list revolution is over
+ m_bAvatarsEnabled = getSettingByte(NULL, "AvatarsEnabled", DEFAULT_AVATARS_ENABLED);
+ m_bXStatusEnabled = getSettingByte(NULL, "XStatusEnabled", DEFAULT_XSTATUS_ENABLED);
+ m_bMoodsEnabled = getSettingByte(NULL, "MoodsEnabled", DEFAULT_MOODS_ENABLED);
+}
diff --git a/protocols/IcqOscarJ/src/init.h b/protocols/IcqOscarJ/src/init.h
new file mode 100644
index 0000000000..0e93a8a89f
--- /dev/null
+++ b/protocols/IcqOscarJ/src/init.h
@@ -0,0 +1,40 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+// Debug defines
+#define DBG_CAPHTML
+#define DBG_CAPMTN
+#define DBG_CAPXTRAZ
+#undef DBG_CAPXTRAZ_MUC
+#define DBG_NEWCAPS
+#define DBG_OSCARFT
+#define DBG_AIMCONTACTSEND
+
+void g_MenuInit();
+void g_MenuUninit();
diff --git a/protocols/IcqOscarJ/src/log.cpp b/protocols/IcqOscarJ/src/log.cpp
new file mode 100644
index 0000000000..402863a456
--- /dev/null
+++ b/protocols/IcqOscarJ/src/log.cpp
@@ -0,0 +1,161 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+extern BOOL bPopUpService;
+
+static const char *szLevelDescr[] = {LPGEN("ICQ Note"), LPGEN("ICQ Warning"), LPGEN("ICQ Error"), LPGEN("ICQ Fatal")};
+
+struct LogMessageInfo {
+ const char *szMsg;
+ const char *szTitle;
+ BYTE bLevel;
+};
+
+
+void __cdecl CIcqProto::icq_LogMessageThread(void* arg)
+{
+ LogMessageInfo *err = (LogMessageInfo*)arg;
+ if (!err)
+ return;
+
+ if (bPopUpService && getSettingByte(NULL, "PopupsLogEnabled", DEFAULT_LOG_POPUPS_ENABLED))
+ {
+ ShowPopUpMsg(NULL, err->szTitle, err->szMsg, err->bLevel);
+
+ SAFE_FREE((void**)&err->szMsg);
+ SAFE_FREE((void**)&err);
+
+ return;
+ }
+
+ bErrorBoxVisible = TRUE;
+ if (err->szMsg && err->szTitle)
+ MessageBoxUtf(NULL, err->szMsg, err->szTitle, MB_OK);
+ SAFE_FREE((void**)&err->szMsg);
+ SAFE_FREE((void**)&err);
+ bErrorBoxVisible = FALSE;
+}
+
+
+void CIcqProto::icq_LogMessage(int level, const char *szMsg)
+{
+ NetLog_Server("%s", szMsg);
+
+ int displayLevel = getSettingByte(NULL, "ShowLogLevel", LOG_WARNING);
+ if (level >= displayLevel)
+ {
+ if (!bErrorBoxVisible || !getSettingByte(NULL, "IgnoreMultiErrorBox", 0))
+ {
+ // error not shown or allowed multi - show messagebox
+ LogMessageInfo *lmi = (LogMessageInfo*)SAFE_MALLOC(sizeof(LogMessageInfo));
+ lmi->bLevel = (BYTE)level;
+ lmi->szMsg = null_strdup(szMsg);
+ lmi->szTitle = szLevelDescr[level];
+ ForkThread( &CIcqProto::icq_LogMessageThread, lmi);
+ }
+ }
+}
+
+void CIcqProto::icq_LogUsingErrorCode(int level, DWORD dwError, const char *szMsg)
+{
+ char szBuf[1024];
+ char str[1024];
+ char str2[64];
+ char szErrorMsg[512];
+ char *pszErrorMsg = NULL;
+ int bNeedFree = FALSE;
+
+ switch(dwError) {
+ case ERROR_TIMEOUT:
+ case WSAETIMEDOUT:
+ pszErrorMsg = LPGEN("The server did not respond to the connection attempt within a reasonable time, it may be temporarily down. Try again later.");
+ break;
+
+ case ERROR_GEN_FAILURE:
+ pszErrorMsg = LPGEN("The connection with the server was abortively closed during the connection attempt. You may have lost your local network connection.");
+ break;
+
+ case WSAEHOSTUNREACH:
+ case WSAENETUNREACH:
+ pszErrorMsg = LPGEN("Miranda was unable to resolve the name of a server to its numeric address. This is most likely caused by a catastrophic loss of your network connection (for example, your modem has disconnected), but if you are behind a proxy, you may need to use the 'Resolve hostnames through proxy' option in M->Options->Network.");
+ break;
+
+ case WSAEHOSTDOWN:
+ case WSAENETDOWN:
+ case WSAECONNREFUSED:
+ pszErrorMsg = LPGEN("Miranda was unable to make a connection with a server. It is likely that the server is down, in which case you should wait for a while and try again later.");
+ break;
+
+ case ERROR_ACCESS_DENIED:
+ pszErrorMsg = LPGEN("Your proxy rejected the user name and password that you provided. Please check them in M->Options->Network.");
+ break;
+
+ case WSAHOST_NOT_FOUND:
+ case WSANO_DATA:
+ pszErrorMsg = LPGEN("The server to which you are trying to connect does not exist. Check your spelling in M->Options->Network->ICQ.");
+ break;
+
+ default:
+ {
+ TCHAR err[512];
+
+ if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, 0, err, SIZEOF(err), NULL))
+ {
+
+ pszErrorMsg = make_utf8_string(err);
+
+ bNeedFree = TRUE;
+ }
+ break;
+ }
+ }
+
+ null_snprintf(szBuf, sizeof(szBuf), "%s%s%s (%s %d)",
+ szMsg ? ICQTranslateUtfStatic(szMsg, str, 1024) : "",
+ szMsg ? "\r\n\r\n" : "",
+ ICQTranslateUtfStatic(pszErrorMsg, szErrorMsg, 512),
+ ICQTranslateUtfStatic(LPGEN("error"), str2, 64),
+ dwError);
+
+ if (bNeedFree)
+ SAFE_FREE(&pszErrorMsg);
+
+ icq_LogMessage(level, szBuf);
+}
+
+void CIcqProto::icq_LogFatalParam(const char *szMsg, WORD wError)
+{
+ char str[MAX_PATH];
+ char buf[MAX_PATH];
+
+ null_snprintf(buf, MAX_PATH, ICQTranslateUtfStatic(szMsg, str, MAX_PATH), wError);
+ icq_LogMessage(LOG_FATAL, buf);
+}
diff --git a/protocols/IcqOscarJ/src/log.h b/protocols/IcqOscarJ/src/log.h
new file mode 100644
index 0000000000..6da25df0d7
--- /dev/null
+++ b/protocols/IcqOscarJ/src/log.h
@@ -0,0 +1,38 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __LOG_H
+#define __LOG_H
+
+#define LOG_NOTE 0 //trivial problems or problems that will already have been reported elsewhere
+#define LOG_WARNING 1 //problems that may have caused data loss
+#define LOG_ERROR 2 //problems that cause a disconnection from the network
+#define LOG_FATAL 3 //problems requiring user intervention: password wrong, rate exceeded, etc.
+
+#endif /* __LOG_H */
diff --git a/protocols/IcqOscarJ/src/oscar_filetransfer.cpp b/protocols/IcqOscarJ/src/oscar_filetransfer.cpp
new file mode 100644
index 0000000000..9630abc070
--- /dev/null
+++ b/protocols/IcqOscarJ/src/oscar_filetransfer.cpp
@@ -0,0 +1,2447 @@
+// ---------------------------------------------------------------------------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
+//
+// 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 "icqoscar.h"
+
+struct oscarthreadstartinfo
+{
+ int type;
+ int incoming;
+ HANDLE hContact;
+ HANDLE 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--)
+ {
+ int len = strlennull(files[i]);
+
+ if (!_strnicmp(files[i], szThisFile, len) && (szThisFile[len] == '\\' || szThisFile[len] == '/'))
+ {
+ const char *pszLastBackslash;
+
+ if (((pszLastBackslash = strrchr(files[i], '\\')) == NULL) &&
+ ((pszLastBackslash = strrchr(files[i], '/')) == NULL))
+ {
+ 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;
+
+ icq_lock l(oftMutex);
+
+ fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*(fileTransferCount + 1));
+ fileTransferList[fileTransferCount++] = ft;
+
+#ifdef _DEBUG
+ NetLog_Direct("OFT: FT struct 0x%x created", ft);
+#endif
+
+ return ft;
+}
+
+
+filetransfer *CIcqProto::CreateIcqFileTransfer()
+{
+ filetransfer *ft = (filetransfer*)SAFE_MALLOC(sizeof(filetransfer));
+
+ ft->ft_magic = FT_MAGIC_ICQ;
+
+ icq_lock l(oftMutex);
+
+ fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*(fileTransferCount + 1));
+ fileTransferList[fileTransferCount++] = (basic_filetransfer*)ft;
+
+#ifdef _DEBUG
+ NetLog_Direct("FT struct 0x%x created", ft);
+#endif
+
+ return ft;
+}
+
+
+int CIcqProto::getFileTransferIndex(void *ft)
+{
+ for (int i = 0; i < fileTransferCount; i++)
+ {
+ if (fileTransferList[i] == ft)
+ return i;
+ }
+ return -1;
+}
+
+
+void CIcqProto::ReleaseFileTransfer(void *ft)
+{
+ int i = getFileTransferIndex(ft);
+
+ if (i != -1)
+ {
+ fileTransferCount--;
+ fileTransferList[i] = fileTransferList[fileTransferCount];
+ fileTransferList = (basic_filetransfer**)SAFE_REALLOC(fileTransferList, sizeof(basic_filetransfer*)*fileTransferCount);
+ }
+}
+
+
+int CIcqProto::IsValidFileTransfer(void *ft)
+{
+ icq_lock l(oftMutex);
+
+ if (getFileTransferIndex(ft) != -1) return 1;
+
+ return 0;
+}
+
+
+int CIcqProto::IsValidOscarTransfer(void *ft)
+{
+ icq_lock l(oftMutex);
+
+ if (getFileTransferIndex(ft) != -1 && ((basic_filetransfer*)ft)->ft_magic == FT_MAGIC_OSCAR)
+ return 1;
+
+ return 0;
+}
+
+
+oscar_filetransfer* CIcqProto::FindOscarTransfer(HANDLE hContact, DWORD dwID1, DWORD dwID2)
+{
+ icq_lock l(oftMutex);
+
+ for (int i = 0; i < fileTransferCount; i++)
+ {
+ if (fileTransferList[i]->ft_magic == FT_MAGIC_OSCAR)
+ {
+ oscar_filetransfer *oft = (oscar_filetransfer*)fileTransferList[i];
+
+ if (oft->hContact == hContact && oft->pMessage.dwMsgID1 == dwID1 && oft->pMessage.dwMsgID2 == dwID2)
+ return oft;
+ }
+ }
+
+ return NULL;
+}
+
+
+// Release file transfer structure
+void CIcqProto::SafeReleaseFileTransfer(void **ft)
+{
+ basic_filetransfer **bft = (basic_filetransfer**)ft;
+
+ icq_lock l(oftMutex);
+
+ // Check for filetransfer validity
+ if (getFileTransferIndex(*ft) == -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->szThisFile);
+ SAFE_FREE(&ift->szThisSubdir);
+ if (ift->pszFiles)
+ {
+ for (int i = 0; i < (int)ift->dwFileCount; i++)
+ SAFE_FREE(&ift->pszFiles[i]);
+ SAFE_FREE((void**)&ift->pszFiles);
+ }
+ // Invalidate transfer
+ ReleaseFileTransfer(ift);
+#ifdef _DEBUG
+ NetLog_Direct("FT struct 0x%x released", ft);
+#endif
+ // Release memory
+ SAFE_FREE((void**)ft);
+ }
+ 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)
+ {
+/* for (int i = 0; i < oft->wFilesCount; i++)
+ SAFE_FREE(&oft->files_list[i]);*/
+ 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)
+ {
+#ifdef _DEBUG
+ NetLog_Direct("OFT: _close(%u)", oft->fileId);
+#endif
+ _close(oft->fileId);
+ }
+ // Invalidate transfer
+ ReleaseFileTransfer(oft);
+#ifdef _DEBUG
+ NetLog_Direct("OFT: FT struct 0x%x released", ft);
+#endif
+ // Release memory
+ SAFE_FREE((void**)ft);
+ }
+ }
+}
+
+
+// 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];
+ DWORD oldchecksum = checksum;
+
+ 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, NULL))
+ return listener; // Success
+
+ SAFE_FREE((void**)&listener);
+ }
+
+ return NULL; // Failure
+}
+
+
+void CIcqProto::ReleaseOscarListener(oscar_listener **pListener)
+{
+ oscar_listener *listener = *pListener;
+
+ if (listener)
+ { // Close listening port
+ if (listener->hBoundPort)
+ NetLib_SafeCloseHandle(&listener->hBoundPort);
+
+ NetLog_Direct("Oscar listener on port %d released.", listener->wPort);
+ }
+ SAFE_FREE((void**)pListener);
+}
+
+
+//
+// Miranda FT interface handlers & services
+/////////////////////////////
+
+void CIcqProto::handleRecvServMsgOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, DWORD dwID1, DWORD dwID2, WORD wCommand)
+{
+ HANDLE hContact = HContactFromUID(dwUin, szUID, NULL);
+
+ 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 = NULL;
+ char *pszDescription = NULL;
+ WORD wFilenameLength;
+
+ NetLog_Server("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, strlennull(str));
+
+ bTag = strstrnull(pszDescription, "<DESC>");
+ if (bTag)
+ { // take special Description - ICQJ's extension
+ eTag = strstrnull(bTag, "</DESC>");
+ if (eTag)
+ {
+ *eTag = '\0';
+ str = null_strdup(bTag + 6);
+ SAFE_FREE(&pszDescription);
+ pszDescription = str;
+ }
+ }
+ else
+ {
+ bTag = strstrnull(pszDescription, "<FS>");
+ if (bTag)
+ { // take only <FS> - Description tag if present
+ eTag = strstrnull(bTag, "</FS>");
+ if (eTag)
+ {
+ *eTag = '\0';
+ str = null_strdup(bTag + 4);
+ SAFE_FREE(&pszDescription);
+ pszDescription = str;
+ }
+ }
+ }
+ }
+ }
+ if (!strlennull(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)
+ {
+ NetLog_Server("Error: Malformed file request");
+ // release structures
+ SafeReleaseFileTransfer((void**)&ft);
+ SAFE_FREE(&pszDescription);
+ return;
+ }
+
+ BYTE* tBuf = tlv->pData;
+ WORD 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
+ 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(strlennull(pszFileName) + 1);
+
+ 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];
+ null_snprintf(pszFileName, 64, ICQTranslateUtfStatic(LPGEN("%d Files"), tmp, SIZEOF(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;
+ HANDLE hContact = HContactFromUID(dwUin, szUID, &bAdded);
+
+ ft->hContact = hContact;
+ ft->fileId = -1;
+
+ // Send chain event
+ char *szBlob = (char*)_alloca(sizeof(DWORD) + strlennull(pszFileName) + strlennull(pszDescription) + 2);
+ *(PDWORD)szBlob = 0;
+ strcpy(szBlob + sizeof(DWORD), pszFileName);
+ strcpy(szBlob + sizeof(DWORD) + strlennull(pszFileName) + 1, pszDescription);
+
+ TCHAR* ptszFileName = mir_utf8decodeT(pszFileName);
+
+ PROTORECVFILET pre = {0};
+ pre.flags = PREF_TCHAR;
+ pre.fileCount = 1;
+ pre.timestamp = time(NULL);
+ pre.tszDescription = mir_utf8decodeT(pszDescription);
+ pre.ptszFiles = &ptszFileName;
+ pre.lParam = (LPARAM)ft;
+
+ CCSDATA ccs;
+ ccs.szProtoService = PSR_FILE;
+ ccs.hContact = hContact;
+ ccs.wParam = 0;
+ ccs.lParam = (LPARAM)&pre;
+ CallService(MS_PROTO_CHAINRECV, 0, (LPARAM)&ccs);
+
+ mir_free(pre.tszDescription);
+ 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
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&ft);
+ }
+ }
+ else
+ NetLog_Server("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
+ NetLog_Server("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
+ NetLog_Server("Error: Invalid request, IP missing.");
+ }
+ else
+ NetLog_Server("Error: Invalid request, no such transfer");
+ }
+ else
+ NetLog_Server("Error: Uknown Stage %d request", wAckType);
+
+ disposeChain(&chain);
+ }
+ else
+ NetLog_Server("Error: Missing TLV chain in OFT request");
+ }
+ else if (wCommand == 1)
+ { // transfer cancelled/aborted
+ oscar_filetransfer *ft = FindOscarTransfer(hContact, dwID1, dwID2);
+
+ if (ft)
+ {
+ NetLog_Server("OFT: File transfer cancelled by %s", strUID(dwUin, szUID));
+
+ BroadcastAck(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((void**)&ft);
+ }
+ else
+ NetLog_Server("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
+ NetLog_Server("Warning: Received invalid rendezvous accept");
+ }
+ else
+ NetLog_Server("Error: Invalid request, no such transfer");
+ }
+ else
+ {
+ NetLog_Server("Error: Unknown wCommand=0x%x in OFT request", wCommand);
+ }
+}
+
+
+void CIcqProto::handleRecvServResponseOFT(BYTE *buf, WORD wLen, DWORD dwUin, char *szUID, void* ft)
+{
+ WORD wDataLen;
+
+ if (wLen < 2) return;
+
+ unpackWord(&buf, &wDataLen);
+
+ if (wDataLen == 2)
+ {
+ oscar_filetransfer *oft = (oscar_filetransfer*)ft;
+ WORD wStatus;
+
+ unpackWord(&buf, &wStatus);
+
+ switch (wStatus)
+ {
+ case 1:
+ { // FT denied (icq5)
+ NetLog_Server("OFT: File transfer denied by %s", strUID(dwUin, szUID));
+
+ BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_DENIED, (HANDLE)oft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oft);
+ }
+ break;
+
+ case 4: // Proxy error
+ {
+ icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Proxy error"));
+
+ BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oft);
+ }
+ break;
+
+ case 5: // Invalid request
+ {
+ icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Invalid request"));
+
+ BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oft);
+ }
+ break;
+
+ case 6: // Proxy Failed (IP = 0)
+ {
+ icq_LogMessage(LOG_ERROR, LPGEN("The file transfer failed: Proxy unavailable"));
+
+ BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oft);
+ }
+ break;
+
+ default:
+ {
+ NetLog_Server("OFT: Uknown request response code 0x%x", wStatus);
+
+ BroadcastAck(oft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)oft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oft);
+ }
+ }
+ }
+}
+
+
+// This function is called from the Netlib when someone is connecting to our oscar_listener
+static void oft_newConnectionReceived(HANDLE 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(( IcqThreadFunc )&CIcqProto::oft_connectionThread, otsi );
+}
+
+
+static char *oftGetFileContainer(oscar_filetransfer* oft, const char** files, int iFile)
+{
+ char szPath[MAX_PATH];
+ char* szFileName = 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 (!strcmpnull(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(HANDLE hContact, DWORD dwUin, char* szUid, const TCHAR** files, const TCHAR* pszDesc)
+{
+ oscar_filetransfer *ft;
+ int i, filesCount;
+ struct _stati64 statbuf;
+ char ** filesUtf;
+
+ // Initialize filetransfer struct
+ NetLog_Server("Init file send");
+
+ 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(TCHAR *) * filesCount);
+ ft->qwTotalSize = 0;
+
+ 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 (_tstati64(files[i], &statbuf))
+ NetLog_Server("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
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&ft);
+
+ return 0; // 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 4GB can be sent only separately."));
+ // Notify UI
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, (HANDLE)ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&ft);
+
+ return 0; // Failure
+ }
+
+ NetLog_Server("OFT: Found %d files.", ft->wFilesCount);
+
+ ft->szDescription = tchar_to_utf8(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;
+ 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];
+ int nFirstDirLen;
+
+ // default is no root dir
+ pszFiles = "";
+
+ if ((szFirstDiv = strstrnull(szFirstDir, "\\")) || (szFirstDiv = strstrnull(szFirstDir, "/")))
+ nFirstDirLen = szFirstDiv - szFirstDir;
+ else
+ nFirstDirLen = strlennull(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 = NULL;
+ 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, getSettingDword(NULL, "RealIP", 0));
+ }
+ else
+ { // try stage 1 proxy
+ ft->szThisFile = null_strdup(pszFiles);
+ OpenOscarConnection(hContact, ft, OCT_PROXY_INIT);
+ }
+ }
+
+ return ft; // Success
+}
+
+
+HANDLE CIcqProto::oftFileAllow(HANDLE hContact, HANDLE hTransfer, const TCHAR *szPath)
+{
+ oscar_filetransfer *ft = (oscar_filetransfer*)hTransfer;
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return 0; // Invalid contact
+
+ if (!IsValidOscarTransfer(ft))
+ return 0; // Invalid transfer
+
+ ft->szSavePath = tchar_to_utf8(szPath);
+
+ if (ft->szThisPath)
+ { // Append Directory name to the save path, when transfering a directory
+ ft->szSavePath = (char*)SAFE_REALLOC(ft->szSavePath, strlennull(ft->szSavePath) + strlennull(ft->szThisPath) + 4);
+ NormalizeBackslash(ft->szSavePath);
+ strcat(ft->szSavePath, ft->szThisPath);
+ NormalizeBackslash(ft->szSavePath);
+ }
+#ifdef _DEBUG
+ NetLog_Direct("OFT: Request accepted, saving to '%s'.", ft->szSavePath);
+#endif
+
+ // 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(HANDLE hContact, HANDLE hTransfer, const TCHAR *szReason)
+{
+ oscar_filetransfer *ft = (oscar_filetransfer*)hTransfer;
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (getContactUid(hContact, &dwUin, &szUid))
+ return 1; // Invalid contact
+
+ if (IsValidOscarTransfer(ft))
+ {
+ if (ft->hContact != hContact)
+ return 1; // Bad contact or hTransfer
+
+#ifdef _DEBUG
+ NetLog_Direct("OFT: Request denied.");
+#endif
+
+ oft_sendFileDeny(dwUin, szUid, ft);
+
+ // Release structure
+ SafeReleaseFileTransfer((void**)&ft);
+
+ return 0; // Success
+ }
+ return 1; // Invalid transfer
+}
+
+
+DWORD CIcqProto::oftFileCancel(HANDLE 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))
+ {
+ if (ft->hContact != hContact)
+ return 1; // Bad contact or hTransfer
+
+#ifdef _DEBUG
+ NetLog_Direct("OFT: Transfer cancelled.");
+#endif
+
+ oft_sendFileCancel(dwUin, szUid, ft);
+
+ BroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
+
+ // Release structure
+ SafeReleaseFileTransfer((void**)&ft);
+
+ return 0; // Success
+ }
+ return 1; // Invalid transfer
+}
+
+
+void CIcqProto::oftFileResume(oscar_filetransfer *ft, int action, const TCHAR *szFilename)
+{
+ int openFlags;
+
+ if (ft->connection == NULL)
+ return;
+
+ oscar_connection *oc = ft->connection;
+
+#ifdef _DEBUG
+ NetLog_Direct("OFT: Resume Transfer, Action: %d, FileName: '%s'", action, szFilename);
+#endif
+
+ 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 = tchar_to_utf8(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);
+#ifdef _DEBUG
+ NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, openFlags, 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."));
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&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);
+
+#ifdef _DEBUG
+ NetLog_Direct("OFT: Starting Smart-Resume");
+#endif
+
+ sendOFT2FramePacket(oc, OFT_TYPE_RESUMEREQUEST);
+
+ return;
+ }
+ else 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);
+ }
+ BroadcastAck(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)
+{
+ ZeroMemory(pfts, sizeof(PROTOFILETRANSFERSTATUS));
+ pfts->cbSize = 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 = ft->files_list;
+ else
+ pfts->pszFiles = NULL; /* FIXME */
+ pfts->totalFiles = ft->wFilesCount;
+ pfts->currentFileNumber = ft->iCurrentFile;
+ pfts->totalBytes = ft->qwTotalSize;
+ pfts->totalProgress = ft->qwBytesDone;
+ pfts->szWorkingDir = ft->szThisPath;
+ pfts->szCurrentFile = ft->szThisFile;
+ pfts->currentFileSize = ft->qwThisFileSize;
+ pfts->currentFileTime = ft->dwThisFileDate;
+ pfts->currentFileProgress = ft->qwFileBytesDone;
+}
+
+
+void CIcqProto::CloseOscarConnection(oscar_connection *oc)
+{
+ icq_lock 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(HANDLE hContact, oscar_filetransfer *ft, int type)
+{
+ oscarthreadstartinfo *otsi = (oscarthreadstartinfo*)SAFE_MALLOC(sizeof(oscarthreadstartinfo));
+
+ otsi->hContact = hContact;
+ otsi->type = type;
+ otsi->ft = ft;
+
+ ForkThread(( IcqThreadFunc )&CIcqProto::oft_connectionThread, otsi );
+}
+
+
+int CIcqProto::CreateOscarProxyConnection(oscar_connection *oc)
+{
+ NETLIBOPENCONNECTION nloc = {0};
+
+ // inform UI
+ BroadcastAck(oc->ft->hContact, ACKTYPE_FILE, ACKRESULT_CONNECTPROXY, oc->ft, 0);
+
+ nloc.szHost = OSCAR_PROXY_HOST;
+ nloc.wPort = getSettingWord(NULL, "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_hServerNetlibUser, "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;
+ NETLIBPACKETRECVER packetRecv={0};
+ HANDLE hPacketRecver;
+
+ 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 = getSettingDword(NULL, "IP", 0);
+ oc.dwLocalInternalIP = getSettingDword(NULL, "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
+ BroadcastAck(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
+ BroadcastAck(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 ":NULL, &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 ":NULL, &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
+ BroadcastAck(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
+ BroadcastAck(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
+ BroadcastAck(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 = getSettingWord(NULL, "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_hServerNetlibUser, "Proxy ", &nloc);
+ if (!oc.hConnection)
+ { // proxy connection failed, we are out of possibilities
+ BroadcastAck(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((void**)&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
+ BroadcastAck(oc.ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
+ // Release structure
+ SafeReleaseFileTransfer((void**)&oc.ft);
+ return;
+ }
+ }
+ }
+ else if (oc.type == OCT_PROXY)
+ { // stage 4
+ if (!CreateOscarProxyConnection(&oc))
+ { // proxy connection failed, we are out of possibilities
+ BroadcastAck(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((void**)&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((void**)&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
+ BroadcastAck(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);
+ }
+ }
+ hPacketRecver = (HANDLE)CallService(MS_NETLIB_CREATEPACKETRECVER, (WPARAM)oc.hConnection, 8192);
+ packetRecv.cbSize = sizeof(packetRecv);
+
+ // Packet receiving loop
+
+ while (oc.hConnection)
+ {
+ int recvResult;
+
+ packetRecv.dwTimeout = oc.wantIdleTime ? 0 : 120000;
+
+ recvResult = CallService(MS_NETLIB_GETMOREPACKETS, (WPARAM)hPacketRecver, (LPARAM)&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
+ icq_lock l(oftMutex);
+
+ if (getFileTransferIndex(oc.ft) != -1)
+ oc.ft->connection = NULL; // 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)
+ {
+ BroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
+
+ icq_LogMessage(LOG_ERROR, LPGEN("Connection lost during file transfer."));
+ // Release structure
+ SafeReleaseFileTransfer((void**)&oc.ft);
+ }
+ else if (oc.status == OCS_NEGOTIATION)
+ {
+ BroadcastAck(oc.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc.ft, 0);
+
+ icq_LogMessage(LOG_ERROR, LPGEN("File transfer negotiation failed for unknown reason."));
+ // Release structure
+ SafeReleaseFileTransfer((void**)&oc.ft);
+ }
+ }
+}
+
+
+void CIcqProto::sendOscarPacket(oscar_connection *oc, icq_packet *packet)
+{
+ if (oc->hConnection)
+ {
+ int nResult;
+
+ 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, int len)
+{
+ int bytesUsed = 0;
+
+ while (len > 0)
+ {
+ if (oc->status == OCS_DATA && (oc->ft->flags & OFTF_FILE_RECEIVING))
+ {
+ return oft_handleFileData(oc, buf, len);
+ }
+ else 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;
+ }
+
+ WORD 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, (WORD)(datalen - 8));
+
+ /* Increase pointers so we can check for more data */
+ buf += datalen;
+ len -= datalen;
+ bytesUsed += datalen;
+ }
+
+ return bytesUsed;
+}
+
+
+int CIcqProto::oft_handleProxyData(oscar_connection *oc, BYTE *buf, int len)
+{
+ oscar_filetransfer *ft = oc->ft;
+ BYTE *pBuf;
+ WORD datalen;
+ WORD wCommand;
+ int bytesUsed = 0;
+
+
+ while (len > 2)
+ {
+ 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
+ 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);
+
+ NetLog_Server("Proxy Error: %s (0x%x)", szError, wError);
+ // Notify UI
+ BroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, oc->ft, 0);
+ // Release structure
+ SafeReleaseFileTransfer((void**)&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
+ BroadcastAck(oc->hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, oc->ft, 0);
+ }
+ else
+ {
+ NetLog_Server("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
+ BroadcastAck(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;
+ }
+
+ NetLog_Server("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:
+ NetLog_Server("Unknown proxy command 0x%x", wCommand);
+ }
+
+ buf += datalen;
+ len -= datalen;
+ bytesUsed += datalen;
+ }
+
+ return bytesUsed;
+}
+
+
+int CIcqProto::oft_handleFileData(oscar_connection *oc, BYTE *buf, int len)
+{
+ oscar_filetransfer *ft = oc->ft;
+ DWORD dwLen = len;
+ int 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
+#ifdef _DEBUG
+ NetLog_Direct("Error: handleFileData(%u bytes) without fileId!", len);
+#endif
+ CloseOscarConnection(oc);
+ return 0;
+ }
+ _write(ft->fileId, buf, dwLen);
+ // update checksum
+ ft->dwRecvFileCheck = oft_calc_checksum((int)ft->qwFileBytesDone, buf, dwLen, ft->dwRecvFileCheck);
+ bytesUsed += dwLen;
+ ft->qwBytesDone += dwLen;
+ ft->qwFileBytesDone += dwLen;
+
+ if (GetTickCount() > ft->dwLastNotify + 700 || ft->qwFileBytesDone == ft->qwThisFileSize)
+ { // notify FT UI of our progress, at most every 700ms - do not be faster than Miranda
+ PROTOFILETRANSFERSTATUS pfts;
+
+ oft_buildProtoFileTransferStatus(ft, &pfts);
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts);
+ ft->dwLastNotify = GetTickCount();
+ }
+ if (ft->qwFileBytesDone == ft->qwThisFileSize)
+ {
+ /* EOF */
+ ft->flags &= ~OFTF_FILE_RECEIVING;
+
+#ifdef _DEBUG
+ NetLog_Direct("OFT: _close(%u)", ft->fileId);
+#endif
+ _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];
+
+ null_snprintf(szBuf, MAX_PATH, 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.");
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&ft);
+ }
+ else
+ { // ack received file
+ sendOFT2FramePacket(oc, OFT_TYPE_DONE);
+ oc->status = OCS_NEGOTIATION;
+ }
+
+ }
+ return bytesUsed;
+}
+
+
+void CIcqProto::handleOFT2FramePacket(oscar_connection *oc, WORD datatype, BYTE *pBuffer, WORD 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;
+
+ NetLog_Server("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
+ int i;
+
+ for (i = 0; i < strlennull(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(strlennull(ft->szSavePath)+strlennull(ft->szThisPath)+strlennull(ft->szThisFile)+3);
+ strcpy(szFullPath, ft->szSavePath);
+ NormalizeBackslash(szFullPath);
+ strcat(szFullPath, ft->szThisPath);
+ NormalizeBackslash(szFullPath);
+ // make sure the dest dir exists
+ if (MakeDirUtf(szFullPath))
+ NetLog_Direct("Failed to create destination directory!");
+
+ 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 (BroadcastAck(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."));
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oc->ft);
+ return;
+ }
+ }
+ // Send "we are ready"
+ oc->status = OCS_DATA;
+ ft->flags |= OFTF_FILE_RECEIVING;
+
+ sendOFT2FramePacket(oc, OFT_TYPE_READY);
+ BroadcastAck(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
+ DWORD dwResumeCheck, dwResumeOffset, dwFileCheck;
+
+ if (!(ft->flags & OFTF_SENDING))
+ { // just sanity check - this is only for sending client
+ NetLog_Direct("Error: Invalid Packet, closing.");
+ CloseOscarConnection(oc);
+
+ return;
+ }
+ // 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
+ DWORD dwResumeOffset, dwResumeCheck;
+
+ if (ft->flags & OFTF_SENDING)
+ { // just sanity check - this is only for receiving client
+ NetLog_Direct("Error: Invalid Packet, closing.");
+ CloseOscarConnection(oc);
+
+ return;
+ }
+ // 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;
+
+ BroadcastAck(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;
+ WORD wLen = 39 + getUINLen(m_dwLocalUIN);
+
+ packet.wLen = wLen;
+ init_generic_packet(&packet, 2);
+
+ packWord(&packet, 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;
+ WORD wLen = 41 + getUINLen(m_dwLocalUIN);
+
+ packet.wLen = wLen;
+ init_generic_packet(&packet, 2);
+
+ packWord(&packet, 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, (WORD)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);
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&pfts);
+ ft->dwLastNotify = GetTickCount();
+ }
+}
+
+void CIcqProto::oft_sendPeerInit(oscar_connection *oc)
+{
+ icq_lock l(oftMutex);
+
+ oscar_filetransfer *ft = oc->ft;
+ struct _stati64 statbuf;
+ char *pszThisFileName;
+
+ // prepare init frame
+ if (ft->iCurrentFile >= (int)ft->wFilesCount)
+ { // All files done, great!
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oc->ft);
+ return;
+ }
+
+ SAFE_FREE(&ft->szThisFile);
+ ft->szThisFile = null_strdup(ft->files[ft->iCurrentFile].szFile);
+ 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."));
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&oc->ft);
+ return;
+ }
+
+ { // create full relative filename
+ char* szThisContainer = ft->files[ft->iCurrentFile].szContainer;
+
+ pszThisFileName = (char*)SAFE_MALLOC(strlennull(ft->szThisFile) + strlennull(szThisContainer) + 4);
+ strcpy(pszThisFileName, szThisContainer);
+ NormalizeBackslash(pszThisFileName);
+ strcat(pszThisFileName, ExtractFileName(ft->szThisFile));
+ }
+ { // convert backslashes to dir markings
+ int i;
+ for (i = 0; i < strlennull(pszThisFileName); i++)
+ if (pszThisFileName[i] == '\\' || pszThisFileName[i] == '/')
+ pszThisFileName[i] = 0x01;
+ }
+
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0);
+
+ ft->fileId = OpenFileUtf(ft->szThisFile, _O_BINARY | _O_RDONLY, 0);
+#ifdef _DEBUG
+ NetLog_Direct("OFT: OpenFileUtf(%s, %u) returned %u", ft->szThisFile, _O_BINARY | _O_RDONLY, ft->fileId);
+#endif
+ if (ft->fileId == -1)
+ {
+#ifdef _DEBUG
+ NetLog_Direct("OFT: errno=%d", errno);
+#endif
+ SAFE_FREE((void**)&pszThisFileName);
+ 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."));
+ //
+ BroadcastAck(ft->hContact, ACKTYPE_FILE, ACKRESULT_FAILED, ft, 0);
+ // Release transfer
+ SafeReleaseFileTransfer((void**)&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, strlennull(pszThisFileName)))
+ {
+ ft->wEncoding = 0; // ascii
+ ft->cbRawFileName = strlennull(pszThisFileName) + 1;
+ if (ft->cbRawFileName < 64) ft->cbRawFileName = 64;
+ ft->rawFileName = (char*)SAFE_MALLOC(ft->cbRawFileName);
+ 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 = strlennull(pwsThisFile) * (int)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, (WORD)(strlennull(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 = 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);
+}
diff --git a/protocols/IcqOscarJ/src/oscar_filetransfer.h b/protocols/IcqOscarJ/src/oscar_filetransfer.h
new file mode 100644
index 0000000000..fa6ec9169e
--- /dev/null
+++ b/protocols/IcqOscarJ/src/oscar_filetransfer.h
@@ -0,0 +1,164 @@
+// ---------------------------------------------------------------------------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
+//
+// 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 headers
+//
+// -----------------------------------------------------------------------------
+#ifndef __OSCAR_FILETRANSFER_H
+#define __OSCAR_FILETRANSFER_H
+
+
+#define FT_MAGIC_ICQ 0x00
+#define FT_MAGIC_OSCAR 0x4F
+
+struct basic_filetransfer
+{
+ cookie_message_data pMessage;
+ BYTE ft_magic;
+};
+
+#define OFT_BUFFER_SIZE 8192
+
+struct oft_file_record
+{
+ char *szContainer;
+ char *szFile;
+};
+
+char *FindFilePathContainer(const char **files, int iFile, char *szContainer);
+
+
+// file-transfer status flags
+#define OFTF_INITIALIZED 0x0001 // connection established (ack received)
+#define OFTF_SENDING 0x0002 // sending files (receiving otherwise)
+#define OFTF_FILE_REQUEST_SENT 0x0004 // request sent (sending only)
+#define OFTF_FILE_REQUEST_RECEIVED 0x0008 // first request processed (receiving only)
+#define OFTF_FILE_SENDING 0x0010 // sending file contents
+#define OFTF_FILE_RECEIVING 0x0020 // receiving file contents
+#define OFTF_FILE_DONE 0x0040 // file finished
+
+struct oscar_filetransfer: public basic_filetransfer
+{
+ HANDLE hContact;
+ int flags; // combination of OFTF_*
+ int containerCount;
+ char **file_containers;
+ oft_file_record *files;
+ char **files_list; // sending only
+ int iCurrentFile;
+ int currentIsDir;
+ int bUseProxy;
+ DWORD dwProxyIP;
+ DWORD dwRemoteInternalIP;
+ DWORD dwRemoteExternalIP;
+ WORD wRemotePort;
+ char *szSavePath;
+ char *szDescription;
+ char *szThisFile;
+ char *szThisPath;
+ // Request sequence
+ DWORD dwCookie;
+ WORD wReqNum;
+ // OFT2 header data
+ WORD wEncrypt, wCompress;
+ WORD wFilesCount,wFilesLeft;
+ WORD wPartsCount, wPartsLeft;
+ DWORD64 qwTotalSize;
+ DWORD64 qwThisFileSize;
+ DWORD dwThisFileDate; // modification date
+ DWORD dwThisFileCheck;
+ DWORD dwRecvForkCheck, dwThisForkSize;
+ DWORD dwThisFileCreation; // creation date (not used)
+ DWORD dwThisForkCheck;
+ DWORD64 qwBytesDone;
+ DWORD dwRecvFileCheck;
+ char rawIDString[32];
+ BYTE bHeaderFlags;
+ BYTE bNameOff, bSizeOff;
+ BYTE rawDummy[69];
+ BYTE rawMacInfo[16];
+ WORD wEncoding, wSubEncoding;
+ WORD cbRawFileName;
+ char *rawFileName;
+ // helper data
+ DWORD64 qwFileBytesDone;
+ int fileId;
+ struct oscar_connection *connection;
+ struct oscar_listener *listener;
+ DWORD dwLastNotify;
+ int resumeAction;
+};
+
+#define OFT_TYPE_REQUEST 0x0101 // I am going to send you this file, is that ok?
+#define OFT_TYPE_READY 0x0202 // Yes, it is ok for you to send me that file
+#define OFT_TYPE_DONE 0x0204 // I received that file with no problems
+#define OFT_TYPE_RESUMEREQUEST 0x0205 // Resume transferring from position
+#define OFT_TYPE_RESUMEREADY 0x0106 // Ok, I am ready to send it
+#define OFT_TYPE_RESUMEACK 0x0207 // Fine, ready to receive
+
+void SafeReleaseFileTransfer(void **ft);
+
+struct oscar_connection
+{
+ HANDLE hContact;
+ HANDLE hConnection;
+ int status;
+ DWORD dwUin;
+ uid_str szUid;
+ DWORD dwLocalInternalIP;
+ DWORD dwLocalExternalIP;
+ int type;
+ int incoming;
+ oscar_filetransfer *ft;
+ int wantIdleTime;
+};
+
+#define OCT_NORMAL 0
+#define OCT_REVERSE 1
+#define OCT_PROXY 2
+#define OCT_PROXY_INIT 3
+#define OCT_PROXY_RECV 4
+#define OCT_CLOSING 10
+
+#define OCS_READY 0
+#define OCS_CONNECTED 1
+#define OCS_NEGOTIATION 2
+#define OCS_RESUME 3
+#define OCS_DATA 4
+#define OCS_PROXY 8
+#define OCS_WAITING 10
+
+struct oscar_listener
+{
+ CIcqProto *ppro;
+ WORD wPort;
+ HANDLE hBoundPort;
+ oscar_filetransfer *ft;
+};
+
+
+#endif /* __OSCAR_FILETRANSFER_H */
+
diff --git a/protocols/IcqOscarJ/src/resource.h b/protocols/IcqOscarJ/src/resource.h
new file mode 100644
index 0000000000..1dbd905dde
--- /dev/null
+++ b/protocols/IcqOscarJ/src/resource.h
@@ -0,0 +1,176 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Developer Studio generated include file.
+// Used by resources.rc
+//
+#define IDI_ICQ 101
+#define IDS_IDENTIFY 102
+#define IDD_ICQACCOUNT 103
+#define IDD_ASKAUTH 104
+#define IDD_LOGINPW 105
+#define IDD_OPT_ICQ 108
+#define IDD_OPT_ICQCONTACTS 109
+#define IDD_OPT_ICQFEATURES 110
+#define IDD_OPT_ICQPRIVACY 111
+#define IDI_AUTH_ASK 150
+#define IDI_AUTH_GRANT 151
+#define IDI_AUTH_REVOKE 152
+#define IDI_SERVLIST_ADD 160
+#define IDD_INFO_ICQ 240
+#define IDD_ICQADVANCEDSEARCH 242
+#define IDD_ICQUPLOADLIST 253
+#define IDD_SETXSTATUS 256
+#define IDD_PWCONFIRM 300
+#define IDD_INFO_CHANGEINFO 301
+#define IDD_OPT_POPUPS 400
+#define IDC_POPUPS_ENABLED 410
+#define IDC_POPUPS_LOG_ENABLED 411
+#define IDC_POPUPS_SPAM_ENABLED 412
+#define IDC_POPUP_LOG0_TEXTCOLOR 420
+#define IDC_POPUP_LOG1_TEXTCOLOR 421
+#define IDC_POPUP_LOG2_TEXTCOLOR 422
+#define IDC_POPUP_LOG3_TEXTCOLOR 423
+#define IDC_POPUP_SPAM_TEXTCOLOR 425
+#define IDC_POPUP_LOG0_BACKCOLOR 430
+#define IDC_POPUP_LOG1_BACKCOLOR 431
+#define IDC_POPUP_LOG2_BACKCOLOR 432
+#define IDC_POPUP_LOG3_BACKCOLOR 433
+#define IDC_POPUP_SPAM_BACKCOLOR 435
+#define IDC_POPUP_LOG0_TIMEOUT 440
+#define IDC_POPUP_LOG1_TIMEOUT 441
+#define IDC_POPUP_LOG2_TIMEOUT 442
+#define IDC_POPUP_LOG3_TIMEOUT 443
+#define IDC_POPUP_SPAM_TIMEOUT 444
+#define IDC_USEWINCOLORS 450
+#define IDC_USESYSICONS 451
+#define IDC_PREVIEW 455
+#define IDC_LOG 1001
+#define IDI_EXPANDSTRINGEDIT 1001
+#define IDC_SAVEPASS 1004
+#define IDC_RETRXSTATUS 1005
+#define IDC_XTITLE_STATIC 1006
+#define IDC_XMSG_STATIC 1007
+#define IDC_SSL 1008
+#define IDC_MD5LOGIN 1009
+#define IDC_UTFENABLE 1010
+#define IDC_XTITLE 1010
+#define IDC_KEEPALIVE 1011
+#define IDC_XMSG 1011
+#define IDC_UTFALL 1012
+#define IDC_UTFSTATIC 1013
+#define IDC_UTFCODEPAGE 1014
+#define IDC_PW 1015
+#define IDC_TEMPVISIBLE 1015
+#define IDC_REGISTER 1016
+#define IDC_EDITAUTH 1017
+#define IDC_LOGINPW 1018
+#define IDC_INSTRUCTION 1019
+#define IDC_PASSWORD 1020
+#define IDC_SUPTIME 1020
+#define IDC_DCENABLE 1020
+#define IDC_DCPASSIVE 1021
+#define IDC_OLDPASS 1021
+#define IDC_ICQNUM 1022
+#define IDC_USEPOPUPCOLORS 1023
+#define IDC_USEDEFCOLORS 1024
+#define IDC_AIMENABLE 1030
+#define IDC_CLIST 1035
+#define IDC_XSTATUSENABLE 1040
+#define IDC_XSTATUSAUTO 1041
+#define IDC_XSTATUSRESET 1042
+#define IDC_MOODSENABLE 1043
+#define IDC_KILLSPAMBOTS 1045
+#define IDC_EMAIL 1048
+#define IDC_NICK 1053
+#define IDC_GENDER 1060
+#define IDC_CITY 1061
+#define IDC_STATE 1062
+#define IDC_COUNTRY 1063
+#define IDC_COMPANY 1066
+#define IDC_DEPARTMENT 1067
+#define IDC_POSITION 1069
+#define IDC_IP 1094
+#define IDC_UINSTATIC 1122
+#define IDC_UIN 1123
+#define IDC_STATIC11 1154
+#define IDC_STATIC12 1155
+#define IDC_ICQSERVER 1171
+#define IDC_ICQPORT 1172
+#define IDC_VERSION 1179
+#define IDC_FIRSTNAME 1224
+#define IDC_LASTNAME 1225
+#define IDC_REALIP 1230
+#define IDC_RECONNECTREQD 1239
+#define IDC_OFFLINETOENABLE 1240
+#define IDC_PORT 1249
+#define IDC_MIRVER 1251
+#define IDC_ONLINESINCE 1252
+#define IDC_SYSTEMUPTIME 1253
+#define IDC_IDLETIME 1254
+#define IDC_STATUS 1255
+#define IDC_SLOWSEND 1301
+#define IDC_ONLYSERVERACKS 1302
+#define IDC_LOGLEVEL 1331
+#define IDC_LEVELDESCR 1332
+#define IDC_NOERRMULTI 1333
+#define IDC_STICQGROUP 1374
+#define IDC_AGERANGE 1410
+#define IDC_MARITALSTATUS 1411
+#define IDC_KEYWORDS 1412
+#define IDC_LANGUAGE 1414
+#define IDC_WORKFIELD 1421
+#define IDC_PASTCAT 1422
+#define IDC_PASTKEY 1423
+#define IDC_INTERESTSCAT 1424
+#define IDC_INTERESTSKEY 1425
+#define IDC_ORGANISATION 1426
+#define IDC_ORGKEYWORDS 1427
+#define IDC_OTHERGROUP 1429
+#define IDC_ONLINEONLY 1430
+#define IDC_HOMEPAGECAT 1431
+#define IDC_HOMEPAGEKEY 1432
+#define IDC_SUMMARYGROUP 1434
+#define IDC_WORKGROUP 1435
+#define IDC_LOCATIONGROUP 1436
+#define IDC_BACKGROUNDGROUP 1437
+#define IDC_NEWUINLINK 1438
+#define IDC_LOOKUPLINK 1439
+#define IDC_RESETSERVER 1472
+#define IDC_UPLOADNOW 1521
+#define IDC_GROUPS 1522
+#define IDC_ALLGROUPS 1526
+#define IDC_VISIBILITY 1527
+#define IDC_IGNORE 1528
+#define IDC_ENABLE 1529
+#define IDC_LOADFROMSERVER 1530
+#define IDC_ADDSERVER 1532
+#define IDC_SAVETOSERVER 1533
+#define IDC_ENABLEAVATARS 1536
+#define IDC_AUTOLOADAVATARS 1537
+#define IDC_STRICTAVATARCHECK 1539
+#define IDC_WEBAWARE 1546
+#define IDC_DCALLOW_ANY 1547
+#define IDC_DCALLOW_CLIST 1548
+#define IDC_DCALLOW_AUTH 1549
+#define IDC_PUBLISHPRIMARY 1550
+#define IDC_ADD_ANY 1551
+#define IDC_ADD_AUTH 1552
+#define IDC_STATUSMSG_CLIST 1553
+#define IDC_STATUSMSG_VISIBLE 1554
+#define IDC_STATIC_NOTONLINE 1555
+#define IDC_STATIC_DC2 1556
+#define IDC_STATIC_DC1 1557
+#define IDC_STATIC_CLIST 1558
+#define IDC_SAVE 1600
+#define IDC_LIST 1601
+#define IDC_UPLOADING 1602
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 113
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1026
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/protocols/IcqOscarJ/src/stdpackets.cpp b/protocols/IcqOscarJ/src/stdpackets.cpp
new file mode 100644
index 0000000000..3bc9502560
--- /dev/null
+++ b/protocols/IcqOscarJ/src/stdpackets.cpp
@@ -0,0 +1,1895 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+extern const int moodXStatus[];
+
+/*****************************************************************************
+*
+* Some handy extra pack functions for basic message type headers
+*
+*/
+
+// This is the part of the message header that is common for all message channels
+static void packServMsgSendHeader(icq_packet *p, DWORD dwSequence, DWORD dwID1, DWORD dwID2, DWORD dwUin, const char *szUID, WORD wFmt, WORD wLen)
+{
+ serverPacketInit(p, (WORD)(21 + getUIDLen(dwUin, szUID) + wLen));
+ packFNACHeader(p, ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, 0, dwSequence | ICQ_MSG_SRV_SEND<<0x10);
+ packLEDWord(p, dwID1); // Msg ID part 1
+ packLEDWord(p, dwID2); // Msg ID part 2
+ packWord(p, wFmt); // Message channel
+ packUID(p, dwUin, szUID); // User ID
+}
+
+
+static void packServIcqExtensionHeader(icq_packet *p, CIcqProto *ppro, WORD wLen, WORD wType, WORD wSeq, WORD wCmd = ICQ_META_CLI_REQUEST)
+{
+ serverPacketInit(p, (WORD)(24 + wLen));
+ packFNACHeader(p, ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST, 0, wSeq | (wCmd<<0x10));
+ packWord(p, 0x01); // TLV type 1
+ packWord(p, (WORD)(10 + wLen)); // TLV len
+ packLEWord(p, (WORD)(8 + wLen)); // Data chunk size (TLV.Length-2)
+ packLEDWord(p, ppro->m_dwLocalUIN); // My UIN
+ packLEWord(p, wType); // Request type
+ packWord(p, wSeq);
+}
+
+
+static void packServIcqDirectoryHeader(icq_packet *p, CIcqProto *ppro, WORD wLen, WORD wType, WORD wCommand, WORD wSeq, WORD wSubCommand = ICQ_META_CLI_REQUEST)
+{
+ packServIcqExtensionHeader(p, ppro, wLen + 0x1E, CLI_META_INFO_REQ, wSeq, wSubCommand);
+ packLEWord(p, wType);
+ packLEWord(p, wLen + 0x1A);
+ packFNACHeader(p, 0x5b9, wCommand, 0, 0, 2);
+ packWord(p, 0);
+ packWord(p, (WORD)GetACP());
+ packDWord(p, 2);
+}
+
+
+static void packServTLV5HeaderBasic(icq_packet *p, WORD wLen, DWORD ID1, DWORD ID2, WORD wCommand, const plugin_guid pGuid)
+{
+ // TLV(5) header
+ packWord(p, 0x05); // Type
+ packWord(p, (WORD)(26 + wLen)); // Len
+ // TLV(5) data
+ packWord(p, wCommand); // Command
+ packLEDWord(p, ID1); // msgid1
+ packLEDWord(p, ID2); // msgid2
+ packGUID(p, pGuid); // capabilities (4 dwords)
+}
+
+
+static void packServTLV5HeaderMsg(icq_packet *p, WORD wLen, DWORD ID1, DWORD ID2, WORD wAckType)
+{
+ packServTLV5HeaderBasic(p, (WORD)(wLen + 10), ID1, ID2, 0, MCAP_SRV_RELAY_FMT);
+
+ packTLVWord(p, 0x0A, wAckType); // TLV: 0x0A Acktype: 1 for normal, 2 for ack
+ packDWord(p, 0x000F0000); // TLV: 0x0F empty
+}
+
+
+static void packServTLV2711Header(icq_packet *packet, WORD wCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD X1, WORD X2, int nLen)
+{
+ packWord(packet, 0x2711); // Type
+ packWord(packet, (WORD)(51 + nLen)); // Len
+ // TLV(0x2711) data
+ packLEWord(packet, 0x1B); // Unknown
+ packByte(packet, (BYTE)wVersion); // Client (message) version
+ packGUID(packet, PSIG_MESSAGE);
+ packDWord(packet, CLIENTFEATURES);
+ packDWord(packet, DC_TYPE);
+ packLEWord(packet, wCookie); // Reference cookie
+ packLEWord(packet, 0x0E); // Unknown
+ packLEWord(packet, wCookie); // Reference cookie again
+ packDWord(packet, 0); // Unknown (12 bytes)
+ packDWord(packet, 0); // -
+ packDWord(packet, 0); // -
+ packByte(packet, bMsgType); // Message type
+ packByte(packet, bMsgFlags); // Flags
+ packLEWord(packet, X1); // Accepted
+ packWord(packet, X2); // Unknown, priority?
+}
+
+
+static void packServDCInfo(icq_packet *p, CIcqProto* ppro, BOOL bEmpty)
+{
+ packTLVDWord(p, 0x03, bEmpty ? 0 : ppro->getSettingDword(NULL, "RealIP", 0)); // TLV: 0x03 DWORD IP
+ packTLVWord(p, 0x05, (WORD)(bEmpty ? 0 : ppro->wListenPort)); // TLV: 0x05 Listen port
+}
+
+
+static void packServChannel2Header(icq_packet *p, CIcqProto* ppro, DWORD dwUin, WORD wLen, DWORD dwID1, DWORD dwID2, DWORD dwCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD wPriority, int isAck, int includeDcInfo, BYTE bRequestServerAck)
+{
+ packServMsgSendHeader(p, dwCookie, dwID1, dwID2, dwUin, NULL, 0x0002, (WORD)(wLen + 95 + (bRequestServerAck?4:0) + (includeDcInfo?14:0)));
+
+ packWord(p, 0x05); // TLV type
+ packWord(p, (WORD)(wLen + 91 + (includeDcInfo?14:0))); /* TLV len */
+ packWord(p, (WORD)(isAck ? 2: 0)); /* not aborting anything */
+ packLEDWord(p, dwID1); // Msg ID part 1
+ packLEDWord(p, dwID2); // Msg ID part 2
+ packGUID(p, MCAP_SRV_RELAY_FMT); /* capability (4 dwords) */
+ packDWord(p, 0x000A0002); // TLV: 0x0A WORD: 1 for normal, 2 for ack
+ packWord(p, (WORD)(isAck ? 2 : 1));
+
+ if (includeDcInfo)
+ packServDCInfo(p, ppro, FALSE);
+
+ packDWord(p, 0x000F0000); // TLV: 0x0F empty
+
+ packServTLV2711Header(p, (WORD)dwCookie, wVersion, bMsgType, bMsgFlags, (WORD)MirandaStatusToIcq(ppro->m_iStatus), wPriority, wLen);
+}
+
+
+static void packServAdvancedReply(icq_packet *p, DWORD dwUin, const char *szUid, DWORD dwID1, DWORD dwID2, WORD wCookie, WORD wLen)
+{
+ serverPacketInit(p, (WORD)(getUIDLen(dwUin, szUid) + 23 + wLen));
+ packFNACHeader(p, ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE, 0, ICQ_MSG_RESPONSE<<0x10 | (wCookie & 0x7FFF));
+ packLEDWord(p, dwID1); // Msg ID part 1
+ packLEDWord(p, dwID2); // Msg ID part 2
+ packWord(p, 0x02); // Channel
+ packUID(p, dwUin, szUid); // Contact UID
+ packWord(p, 0x03); // Msg specific formating
+}
+
+
+static void packServAdvancedMsgReply(icq_packet *p, DWORD dwUin, const char *szUid, DWORD dwID1, DWORD dwID2, WORD wCookie, WORD wVersion, BYTE bMsgType, BYTE bMsgFlags, WORD wLen)
+{
+ packServAdvancedReply(p, dwUin, szUid, dwID1, dwID2, wCookie, (WORD)(wLen + 51));
+
+ packLEWord(p, 0x1B); // Unknown
+ packByte(p, (BYTE)wVersion); // Protocol version
+ packGUID(p, PSIG_MESSAGE);
+ packDWord(p, CLIENTFEATURES);
+ packDWord(p, DC_TYPE);
+ packLEWord(p, wCookie); // Reference
+ packLEWord(p, 0x0E); // Unknown
+ packLEWord(p, wCookie); // Reference
+ packDWord(p, 0); // Unknown
+ packDWord(p, 0); // Unknown
+ packDWord(p, 0); // Unknown
+ packByte(p, bMsgType); // Message type
+ packByte(p, bMsgFlags); // Message flags
+ packLEWord(p, 0); // Ack status code ( 0 = accepted, this is hardcoded because
+ // it is only used this way yet)
+ packLEWord(p, 0); // Unused priority field
+}
+
+
+void packMsgColorInfo(icq_packet *packet)
+{ // TODO: make configurable
+ packLEDWord(packet, 0x00000000); // Foreground colour
+ packLEDWord(packet, 0x00FFFFFF); // Background colour
+}
+
+
+void packEmptyMsg(icq_packet *packet)
+{
+ packLEWord(packet, 1);
+ packByte(packet, 0);
+}
+
+/*****************************************************************************
+*
+* Functions to actually send the stuff
+*
+*/
+
+void CIcqProto::icq_sendCloseConnection()
+{
+ icq_packet packet;
+
+ packet.wLen = 0;
+ write_flap(&packet, ICQ_CLOSE_CHAN);
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::icq_requestnewfamily(WORD wFamily, void (CIcqProto::*familyhandler)(HANDLE hConn, char* cookie, WORD cookieLen))
+{
+ icq_packet packet;
+ cookie_family_request *request;
+ int bRequestSSL = m_bSecureConnection && (wFamily != ICQ_AVATAR_FAMILY); // Avatar servers does not support SSL
+
+ request = (cookie_family_request*)SAFE_MALLOC(sizeof(cookie_family_request));
+ request->wFamily = wFamily;
+ request->familyHandler = familyhandler;
+
+ DWORD dwCookie = AllocateCookie(CKT_SERVICEREQUEST, ICQ_CLIENT_NEW_SERVICE, 0, request); // generate and alloc cookie
+
+ serverPacketInit(&packet, 12 + (bRequestSSL ? 4 : 0));
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_NEW_SERVICE, 0, dwCookie);
+ packWord(&packet, wFamily);
+ if (bRequestSSL)
+ packDWord(&packet, 0x008C0000); // use SSL
+
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::icq_setidle(int bAllow)
+{
+ icq_packet packet;
+
+ if (bAllow != m_bIdleAllow)
+ {
+ /* SNAC 1,11 */
+ serverPacketInit(&packet, 14);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_IDLE);
+ if (bAllow==1)
+ packDWord(&packet, 0x0000003C);
+ else
+ packDWord(&packet, 0x00000000);
+
+ m_bIdleAllow = bAllow;
+ sendServPacket(&packet);
+ }
+}
+
+
+void CIcqProto::icq_setstatus(WORD wStatus, const char *szStatusNote)
+{
+ icq_packet packet;
+ char *szCurrentStatusNote = szStatusNote ? getSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, NULL) : NULL;
+ WORD wStatusMoodLen = 0, wStatusNoteLen = 0, wSessionDataLen = 0;
+ char *szMoodData = NULL;
+
+ if (szStatusNote && strcmpnull(szCurrentStatusNote, szStatusNote))
+ { // status note was changed, update now
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (m_bMoodsEnabled && !getSettingString(NULL, DBSETTING_STATUS_MOOD, &dbv))
+ szMoodData = null_strdup(dbv.pszVal);
+
+ ICQFreeVariant(&dbv);
+
+ wStatusNoteLen = strlennull(szStatusNote);
+ wStatusMoodLen = strlennull(szMoodData);
+
+ wSessionDataLen = (wStatusNoteLen ? wStatusNoteLen + 4 : 0) + 4 + wStatusMoodLen + 4;
+ }
+ SAFE_FREE(&szCurrentStatusNote);
+
+ // Pack data in packet
+ serverPacketInit(&packet, (WORD)(18 + (wSessionDataLen ? wSessionDataLen + 4 : 0)));
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS);
+ packWord(&packet, 0x06); // TLV 6
+ packWord(&packet, 0x04); // TLV length
+ packWord(&packet, GetMyStatusFlags()); // Status flags
+ packWord(&packet, wStatus); // Status
+ if (wSessionDataLen)
+ { // Pack session data
+ packWord(&packet, 0x1D); // TLV 1D
+ packWord(&packet, wSessionDataLen); // TLV length
+ packWord(&packet, 0x02); // Item Type
+ if (wStatusNoteLen)
+ {
+ packWord(&packet, 0x400 | (WORD)(wStatusNoteLen + 4)); // Flags + Item Length
+ packWord(&packet, wStatusNoteLen); // Text Length
+ packBuffer(&packet, (LPBYTE)szStatusNote, wStatusNoteLen);
+ packWord(&packet, 0); // Encoding not specified (utf-8 is default)
+ }
+ else
+ packWord(&packet, 0); // Flags + Item Length
+ packWord(&packet, 0x0E); // Item Type
+ packWord(&packet, wStatusMoodLen); // Flags + Item Length
+ if (wStatusMoodLen)
+ packBuffer(&packet, (LPBYTE)szMoodData, wStatusMoodLen); // Mood
+
+ // Save current status note
+ setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, szStatusNote);
+ }
+ // Release memory
+ SAFE_FREE(&szMoodData);
+
+ // Send packet
+ sendServPacket(&packet);
+}
+
+
+DWORD CIcqProto::icq_SendChannel1Message(DWORD dwUin, char *szUID, HANDLE hContact, char *pszText, cookie_message_data *pCookieData)
+{
+ icq_packet packet;
+ WORD wPacketLength;
+
+ WORD wMessageLen = strlennull(pszText);
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ wPacketLength = 25;
+ else
+ wPacketLength = 21;
+
+ // Pack the standard header
+ packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 1, (WORD)(wPacketLength + wMessageLen));
+
+ // Pack first TLV
+ packWord(&packet, 0x0002); // TLV(2)
+ packWord(&packet, (WORD)(wMessageLen + 13)); // TLV len
+
+ // Pack client features
+ packWord(&packet, 0x0501); // TLV(501)
+ packWord(&packet, 0x0001); // TLV len
+ packByte(&packet, 0x1); // Features, meaning unknown, duplicated from ICQ Lite
+
+ // Pack text TLV
+ packWord(&packet, 0x0101); // TLV(2)
+ packWord(&packet, (WORD)(wMessageLen + 4)); // TLV len
+ packWord(&packet, 0x0003); // Message charset number, again copied from ICQ Lite
+ packWord(&packet, 0x0000); // Message charset subset
+ packBuffer(&packet, (LPBYTE)pszText, (WORD)(wMessageLen)); // Message text
+
+ // Pack request server ack TLV
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ packDWord(&packet, 0x00030000); // TLV(3)
+
+ // Pack store on server TLV
+ packDWord(&packet, 0x00060000); // TLV(6)
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_SendChannel1MessageW(DWORD dwUin, char *szUID, HANDLE hContact, WCHAR *pszText, cookie_message_data *pCookieData)
+{
+ icq_packet packet;
+ WORD wMessageLen;
+ DWORD dwCookie;
+ WORD wPacketLength;
+ WCHAR *ppText;
+ int i;
+
+ wMessageLen = strlennull(pszText) * (int)sizeof(WCHAR);
+ dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ wPacketLength = 26;
+ else
+ wPacketLength = 22;
+
+ // Pack the standard header
+ packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 1, (WORD)(wPacketLength + wMessageLen));
+
+ // Pack first TLV
+ packWord(&packet, 0x0002); // TLV(2)
+ packWord(&packet, (WORD)(wMessageLen + 14)); // TLV len
+
+ // Pack client features
+ packWord(&packet, 0x0501); // TLV(501)
+ packWord(&packet, 0x0002); // TLV len
+ packWord(&packet, 0x0106); // Features, meaning unknown, duplicated from ICQ 2003b
+
+ // Pack text TLV
+ packWord(&packet, 0x0101); // TLV(2)
+ packWord(&packet, (WORD)(wMessageLen + 4)); // TLV len
+ packWord(&packet, 0x0002); // Message charset number, again copied from ICQ 2003b
+ packWord(&packet, 0x0000); // Message charset subset
+ ppText = pszText; // we must convert the widestring
+ for (i = 0; i<wMessageLen; i+=2, ppText++)
+ packWord(&packet, *ppText);
+
+ // Pack request server ack TLV
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ packDWord(&packet, 0x00030000); // TLV(3)
+
+ // Pack store on server TLV
+ packDWord(&packet, 0x00060000); // TLV(6)
+
+ sendServPacket(&packet);
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_SendChannel2Message(DWORD dwUin, HANDLE hContact, const char *szMessage, int nBodyLen, WORD wPriority, cookie_message_data *pCookieData, char *szCap)
+{
+ icq_packet packet;
+
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ // Pack the standard header
+ packServChannel2Header(&packet, this, dwUin, (WORD)(nBodyLen + (szCap ? 53:11)), pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwCookie, ICQ_VERSION, (BYTE)pCookieData->bMessageType, 0,
+ wPriority, 0, 0, (BYTE)((pCookieData->nAckType == ACKTYPE_SERVER)?1:0));
+
+ packLEWord(&packet, (WORD)(nBodyLen+1)); // Length of message
+ packBuffer(&packet, (LPBYTE)szMessage, (WORD)(nBodyLen+1)); // Message
+ packMsgColorInfo(&packet);
+
+ if (szCap)
+ {
+ packLEDWord(&packet, 0x00000026); // length of GUID
+ packBuffer(&packet, (LPBYTE)szCap, 0x26); // UTF-8 GUID
+ }
+
+ // Pack request server ack TLV
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ packDWord(&packet, 0x00030000); // TLV(3)
+
+ sendServPacket(&packet);
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_SendChannel2Contacts(DWORD dwUin, char *szUid, HANDLE hContact, const char *pData, WORD wDataLen, const char *pNames, WORD wNamesLen, cookie_message_data *pCookieData)
+{
+ icq_packet packet;
+
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, pCookieData);
+
+ WORD wPacketLength = wDataLen + wNamesLen + 0x12;
+
+ // Pack the standard header
+ packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUid, 2, (WORD)(wPacketLength + ((pCookieData->nAckType == ACKTYPE_SERVER)?0x22:0x1E)));
+
+ packServTLV5HeaderBasic(&packet, wPacketLength, pCookieData->dwMsgID1, pCookieData->dwMsgID2, 0, MCAP_CONTACTS);
+
+ packTLVWord(&packet, 0x0A, 1); // TLV: 0x0A Acktype: 1 for normal, 2 for ack
+ packDWord(&packet, 0x000F0000); // TLV: 0x0F empty
+ packTLV(&packet, 0x2711, wDataLen, (LPBYTE)pData); // TLV: 0x2711 Content (Contact UIDs)
+ packTLV(&packet, 0x2712, wNamesLen, (LPBYTE)pNames);// TLV: 0x2712 Extended Content (Contact NickNames)
+
+ // Pack request ack TLV
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ {
+ packDWord(&packet, 0x00030000); // TLV(3)
+ }
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_SendChannel4Message(DWORD dwUin, HANDLE hContact, BYTE bMsgType, WORD wMsgLen, const char *szMsg, cookie_message_data *pCookieData)
+{
+ icq_packet packet;
+ WORD wPacketLength;
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ wPacketLength = 28;
+ else
+ wPacketLength = 24;
+
+ // Pack the standard header
+ packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, NULL, 4, (WORD)(wPacketLength + wMsgLen));
+
+ // Pack first TLV
+ packWord(&packet, 0x05); // TLV(5)
+ packWord(&packet, (WORD)(wMsgLen + 16)); // TLV len
+ packLEDWord(&packet, m_dwLocalUIN); // My UIN
+ packByte(&packet, bMsgType); // Message type
+ packByte(&packet, 0); // Message flags
+ packLEWord(&packet, wMsgLen); // Message length
+ packBuffer(&packet, (LPBYTE)szMsg, wMsgLen); // Message text
+ packMsgColorInfo(&packet);
+
+ // Pack request ack TLV
+ if (pCookieData->nAckType == ACKTYPE_SERVER)
+ {
+ packDWord(&packet, 0x00030000); // TLV(3)
+ }
+
+ // Pack store on server TLV
+ packDWord(&packet, 0x00060000); // TLV(6)
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+void CIcqProto::sendOwnerInfoRequest(void)
+{
+ icq_packet packet;
+
+ cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
+ pCookieData->bRequestType = DIRECTORYREQUEST_INFOOWNER;
+
+ DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData);
+ WORD wDataLen = getUINLen(m_dwLocalUIN) + 4;
+
+ packServIcqDirectoryHeader(&packet, this, wDataLen + 8, META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie);
+ packWord(&packet, 0x03); // with interests (ICQ6 uses 2 at login)
+ packDWord(&packet, 0x01);
+ packWord(&packet, wDataLen);
+
+ packTLVUID(&packet, 0x32, m_dwLocalUIN, NULL);
+
+ sendServPacket(&packet);
+}
+
+
+DWORD CIcqProto::sendUserInfoMultiRequest(BYTE *pRequestData, WORD wDataLen, int nItems)
+{
+ icq_packet packet;
+
+ cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
+ if (!pCookieData) return 0; // Failure
+ pCookieData->bRequestType = DIRECTORYREQUEST_INFOMULTI;
+
+ DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData);
+
+ packServIcqDirectoryHeader(&packet, this, wDataLen + 2, META_DIRECTORY_QUERY, DIRECTORY_QUERY_MULTI_INFO, (WORD)dwCookie);
+ packWord(&packet, nItems);
+ packBuffer(&packet, pRequestData, wDataLen);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendGetInfoServ(HANDLE hContact, DWORD dwUin, int bManual)
+{
+ icq_packet packet;
+ DWORD dwCookie = 0;
+
+ if (IsServerOverRate(ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST, bManual ? RML_IDLE_10 : RML_IDLE_50))
+ return dwCookie;
+
+ DBVARIANT infoToken = {DBVT_DELETED};
+ BYTE *pToken = NULL;
+ WORD cbToken = 0;
+
+ if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &infoToken))
+ { // retrieve user details using privacy token
+ cbToken = infoToken.cpbVal;
+ pToken = (BYTE*)_alloca(cbToken);
+ memcpy(pToken, infoToken.pbVal, cbToken);
+
+ ICQFreeVariant(&infoToken);
+ }
+
+ cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
+ pCookieData->bRequestType = DIRECTORYREQUEST_INFOUSER;
+
+ dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, hContact, (void*)pCookieData);
+ WORD wDataLen = cbToken + getUINLen(dwUin) + (cbToken ? 8 : 4);
+
+ packServIcqDirectoryHeader(&packet, this, wDataLen + 8, META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie);
+ packWord(&packet, 0x03);
+ packDWord(&packet, 1);
+ packWord(&packet, wDataLen);
+ if (pToken)
+ packTLV(&packet, 0x3C, cbToken, pToken);
+ packTLVUID(&packet, 0x32, dwUin, NULL);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendGetAimProfileServ(HANDLE hContact, char* szUid)
+{
+ icq_packet packet;
+ BYTE bUIDlen = strlennull(szUid);
+
+ if (IsServerOverRate(ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, RML_IDLE_10))
+ return 0;
+
+ cookie_fam15_data *pCookieData = (cookie_fam15_data*)SAFE_MALLOC(sizeof(cookie_fam15_data));
+ pCookieData->bRequestType = REQUESTTYPE_PROFILE;
+
+ DWORD dwCookie = AllocateCookie(CKT_FAMILYSPECIAL, ICQ_LOCATION_REQ_USER_INFO, hContact, (void*)pCookieData);
+
+ serverPacketInit(&packet, (WORD)(13 + bUIDlen));
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, dwCookie);
+ packWord(&packet, 0x01); // request profile info
+ packByte(&packet, bUIDlen);
+ packBuffer(&packet, (LPBYTE)szUid, bUIDlen);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendGetAwayMsgServ(HANDLE hContact, DWORD dwUin, int type, WORD wVersion)
+{
+ icq_packet packet;
+
+ if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_IDLE_30))
+ return 0;
+
+ cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type);
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ packServChannel2Header(&packet, this, dwUin, 3, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwCookie, wVersion, (BYTE)type, 3, 1, 0, 0, 0);
+ packEmptyMsg(&packet); // Message
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendGetAwayMsgServExt(HANDLE hContact, DWORD dwUin, char *szUID, int type, WORD wVersion)
+{
+ icq_packet packet;
+
+ if (IsServerOverRate(ICQ_MSG_FAMILY, ICQ_MSG_SRV_SEND, RML_IDLE_30))
+ return 0;
+
+ cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (BYTE)type);
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, szUID, 2, 122 + getPluginTypeIdLen(type));
+
+ // TLV(5) header
+ packServTLV5HeaderMsg(&packet, 82 + getPluginTypeIdLen(type), pCookieData->dwMsgID1, pCookieData->dwMsgID2, 1);
+
+ // TLV(0x2711) header
+ packServTLV2711Header(&packet, (WORD)dwCookie, wVersion, MTYPE_PLUGIN, 0, 0, 0x100, 27 + getPluginTypeIdLen(type));
+ //
+ packLEWord(&packet, 0); // Empty msg
+
+ packPluginTypeId(&packet, type);
+
+ packLEDWord(&packet, 0x15);
+ packLEDWord(&packet, 0);
+ packLEDWord(&packet, 0x0D);
+ packBuffer(&packet, (LPBYTE)"text/x-aolrtf", 0x0D);
+
+ // Send the monster
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendGetAimAwayMsgServ(HANDLE hContact, char *szUID, int type)
+{
+ icq_packet packet;
+ BYTE bUIDlen = strlennull(szUID);
+
+ cookie_message_data *pCookieData = CreateMessageCookie(MTYPE_AUTOAWAY, (byte)type);
+ DWORD dwCookie = AllocateCookie(CKT_MESSAGE, 0, hContact, (void*)pCookieData);
+
+ serverPacketInit(&packet, (WORD)(13 + bUIDlen));
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_REQ_USER_INFO, 0, dwCookie);
+ packWord(&packet, 0x03);
+ packUID(&packet, 0, szUID);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+void CIcqProto::icq_sendSetAimAwayMsgServ(const char *szMsg)
+{
+ icq_packet packet;
+ WORD wMsgLen = strlennull(szMsg);
+
+ DWORD dwCookie = GenerateCookie(ICQ_LOCATION_SET_USER_INFO);
+
+ if (wMsgLen)
+ {
+ if (wMsgLen > 0x1000) wMsgLen = 0x1000; // limit length
+
+ if (IsUSASCII(szMsg, wMsgLen))
+ {
+ const char* fmt = "text/x-aolrtf; charset=\"us-ascii\"";
+ const WORD fmtlen = (WORD)strlen(fmt);
+
+ serverPacketInit(&packet, 23 + wMsgLen + fmtlen);
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie);
+ packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02");
+ packTLV(&packet, 0x03, fmtlen, (LPBYTE)fmt);
+ packTLV(&packet, 0x04, wMsgLen, (LPBYTE)szMsg);
+ }
+ else
+ {
+ const char* fmt = "text/x-aolrtf; charset=\"unicode-2-0\"";
+ const WORD fmtlen = (WORD)strlen(fmt);
+
+ WCHAR *szMsgW = make_unicode_string(szMsg);
+ wMsgLen = (WORD)strlennull(szMsgW) * sizeof(WCHAR);
+
+ WCHAR *szMsgW2 = (WCHAR*)alloca(wMsgLen), *szMsgW3 = szMsgW;
+ unpackWideString((BYTE**)&szMsgW3, szMsgW2, wMsgLen);
+ SAFE_FREE(&szMsgW);
+
+ serverPacketInit(&packet, 23 + wMsgLen + fmtlen);
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie);
+ packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02");
+ packTLV(&packet, 0x03, fmtlen, (LPBYTE)fmt);
+ packTLV(&packet, 0x04, wMsgLen, (LPBYTE)szMsgW2);
+ }
+ }
+ else
+ {
+ serverPacketInit(&packet, 19);
+ packFNACHeader(&packet, ICQ_LOCATION_FAMILY, ICQ_LOCATION_SET_USER_INFO, 0, dwCookie);
+ packTLV(&packet, 0x0f, 1, (LPBYTE)"\x02");
+ packTLV(&packet, 0x04, 0, NULL);
+ }
+
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::icq_sendFileSendServv7(filetransfer* ft, const char *szFiles)
+{
+ icq_packet packet;
+ WORD wDescrLen = 0, wFilesLen = 0;
+ char *szFilesAnsi = NULL, *szDescrAnsi = NULL;
+
+ if (!utf8_decode(szFiles, &szFilesAnsi))
+ szFilesAnsi = NULL;
+ else
+ wFilesLen = strlennull(szFilesAnsi);
+
+ if (!utf8_decode(ft->szDescription, &szDescrAnsi))
+ szDescrAnsi = NULL;
+ else
+ wDescrLen = strlennull(szDescrAnsi);
+
+ packServChannel2Header(&packet, this, ft->dwUin, (WORD)(18 + wDescrLen + wFilesLen), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ICQ_VERSION, MTYPE_FILEREQ, 0, 1, 0, 1, 1);
+
+ packLEWord(&packet, (WORD)(wDescrLen + 1));
+ packBuffer(&packet, (LPBYTE)szDescrAnsi, (WORD)(wDescrLen + 1));
+ packLEDWord(&packet, 0); // unknown
+ packLEWord(&packet, (WORD)(wFilesLen + 1));
+ packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
+ packLEDWord(&packet, ft->dwTotalSize);
+ packLEDWord(&packet, 0); // unknown
+
+ SAFE_FREE(&szFilesAnsi);
+ SAFE_FREE(&szDescrAnsi);
+
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::icq_sendFileSendServv8(filetransfer* ft, const char *szFiles, int nAckType)
+{
+ icq_packet packet;
+ WORD wDescrLen = 0, wFilesLen = 0;
+ char *szFilesAnsi = NULL, *szDescrAnsi = NULL;
+
+ if (!utf8_decode(szFiles, &szFilesAnsi))
+ szFilesAnsi = NULL;
+ else
+ wFilesLen = strlennull(szFilesAnsi);
+
+ if (!utf8_decode(ft->szDescription, &szDescrAnsi))
+ szDescrAnsi = NULL;
+ else
+ wDescrLen = strlennull(szDescrAnsi);
+
+ // 202 + UIN len + file description (no null) + file name (null included)
+ // Packet size = Flap length + 4
+ WORD wFlapLen = 178 + wDescrLen + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0);
+ packServMsgSendHeader(&packet, ft->dwCookie, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwUin, NULL, 2, wFlapLen);
+
+ // TLV(5) header
+ packServTLV5HeaderMsg(&packet, (WORD)(138 + wDescrLen + wFilesLen), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 1);
+
+ // Port & IP information
+ packServDCInfo(&packet, this, FALSE);
+
+ // TLV(0x2711) header
+ packServTLV2711Header(&packet, (WORD)ft->dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)MirandaStatusToIcq(m_iStatus), 0x100, 69 + wDescrLen + wFilesLen);
+
+ packEmptyMsg(&packet); // Message (unused)
+
+ packPluginTypeId(&packet, MTYPE_FILEREQ);
+
+ packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length
+ packLEDWord(&packet, wDescrLen); // Description
+ packBuffer(&packet, (LPBYTE)szDescrAnsi, wDescrLen);
+ packWord(&packet, 0x8c82); // Unknown (port?), seen 0x80F6
+ packWord(&packet, 0x0222); // Unknown, seen 0x2e01
+ packLEWord(&packet, (WORD)(wFilesLen + 1));
+ packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
+ packLEDWord(&packet, ft->dwTotalSize);
+ packLEDWord(&packet, 0x0008c82); // Unknown, (seen 0xf680 ~33000)
+
+ SAFE_FREE(&szFilesAnsi);
+ SAFE_FREE(&szDescrAnsi);
+
+ // Pack request server ack TLV
+ if (nAckType == ACKTYPE_SERVER)
+ packDWord(&packet, 0x00030000); // TLV(3)
+
+ // Send the monster
+ sendServPacket(&packet);
+}
+
+
+/* also sends rejections */
+void CIcqProto::icq_sendFileAcceptServv8(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char *szFiles, const char *szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType)
+{
+ icq_packet packet;
+ WORD wDescrLen, wFilesLen;
+ char *szFilesAnsi = NULL, *szDescrAnsi = NULL;
+
+ /* if !accepted, szDescr == szReason, szFiles = "" */
+
+ if (!accepted) szFiles = "";
+
+ if (!utf8_decode(szFiles, &szFilesAnsi))
+ szFilesAnsi = NULL;
+
+ if (!utf8_decode(szDescr, &szDescrAnsi))
+ szDescrAnsi = NULL;
+
+ wDescrLen = strlennull(szDescrAnsi);
+ wFilesLen = strlennull(szFilesAnsi);
+
+ // 202 + UIN len + file description (no null) + file name (null included)
+ // Packet size = Flap length + 4
+ WORD wFlapLen = 178 + wDescrLen + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0);
+ packServMsgSendHeader(&packet, dwCookie, TS1, TS2, dwUin, NULL, 2, wFlapLen);
+
+ // TLV(5) header
+ packServTLV5HeaderMsg(&packet, (WORD)(138 + wDescrLen + wFilesLen), TS1, TS2, 2);
+
+ // Port & IP information
+ packServDCInfo(&packet, this, !accepted);
+
+ // TLV(0x2711) header
+ packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)(accepted ? 0:1), 0, 69 + wDescrLen + wFilesLen);
+ //
+ packEmptyMsg(&packet); // Message (unused)
+
+ packPluginTypeId(&packet, MTYPE_FILEREQ);
+
+ packLEDWord(&packet, (WORD)(18 + wDescrLen + wFilesLen + 1)); // Remaining length
+ packLEDWord(&packet, wDescrLen); // Description
+ packBuffer(&packet, (LPBYTE)szDescrAnsi, wDescrLen);
+ packWord(&packet, wPort); // Port
+ packWord(&packet, 0x00); // Unknown
+ packLEWord(&packet, (WORD)(wFilesLen + 1));
+ packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
+ packLEDWord(&packet, dwTotalSize);
+ packLEDWord(&packet, (DWORD)wPort); // Unknown
+
+ SAFE_FREE(&szFilesAnsi);
+ SAFE_FREE(&szDescrAnsi);
+
+ // Pack request server ack TLV
+ if (nAckType == ACKTYPE_SERVER)
+ {
+ packDWord(&packet, 0x00030000); // TLV(3)
+ }
+
+ // Send the monster
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::icq_sendFileAcceptServv7(DWORD dwUin, DWORD TS1, DWORD TS2, DWORD dwCookie, const char* szFiles, const char* szDescr, DWORD dwTotalSize, WORD wPort, BOOL accepted, int nAckType)
+{
+ icq_packet packet;
+ WORD wDescrLen, wFilesLen;
+ char *szFilesAnsi = NULL, *szDescrAnsi = NULL;
+
+ /* if !accepted, szDescr == szReason, szFiles = "" */
+
+ if (!accepted) szFiles = "";
+
+ if (!utf8_decode(szFiles, &szFilesAnsi))
+ szFilesAnsi = NULL;
+
+ if (!utf8_decode(szDescr, &szDescrAnsi))
+ szDescrAnsi = NULL;
+
+ wDescrLen = strlennull(szDescrAnsi);
+ wFilesLen = strlennull(szFilesAnsi);
+
+ // 150 + UIN len + file description (with null) + file name (2 nulls)
+ // Packet size = Flap length + 4
+ WORD wFlapLen = 127 + wDescrLen + 1 + wFilesLen + (nAckType == ACKTYPE_SERVER?4:0);
+ packServMsgSendHeader(&packet, dwCookie, TS1, TS2, dwUin, NULL, 2, wFlapLen);
+
+ // TLV(5) header
+ packServTLV5HeaderMsg(&packet, (WORD)(88 + wDescrLen + wFilesLen), TS1, TS2, 2);
+
+ // Port & IP information
+ packServDCInfo(&packet, this, !accepted);
+
+ // TLV(0x2711) header
+ packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_FILEREQ, 0, (WORD)(accepted ? 0:1), 0, 19 + wDescrLen + wFilesLen);
+ //
+ packLEWord(&packet, (WORD)(wDescrLen + 1)); // Description
+ packBuffer(&packet, (LPBYTE)szDescrAnsi, (WORD)(wDescrLen + 1));
+ packWord(&packet, wPort); // Port
+ packWord(&packet, 0x00); // Unknown
+ packLEWord(&packet, (WORD)(wFilesLen + 2));
+ packBuffer(&packet, (LPBYTE)szFilesAnsi, (WORD)(wFilesLen + 1));
+ packByte(&packet, 0);
+ packLEDWord(&packet, dwTotalSize);
+ packLEDWord(&packet, (DWORD)wPort); // Unknown
+
+ SAFE_FREE(&szFilesAnsi);
+ SAFE_FREE(&szDescrAnsi);
+
+ // Pack request server ack TLV
+ if (nAckType == ACKTYPE_SERVER)
+ {
+ packDWord(&packet, 0x00030000); // TLV(3)
+ }
+
+ // Send the monster
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::icq_sendFileAcceptServ(DWORD dwUin, filetransfer *ft, int nAckType)
+{
+ char *szDesc = ft->szDescription;
+
+ if (ft->bEmptyDesc) szDesc = ""; // keep empty if it originally was (Trillian workaround)
+
+ if (ft->nVersion >= 8)
+ {
+ icq_sendFileAcceptServv8(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szDesc, ft->dwTotalSize, wListenPort, TRUE, nAckType);
+ NetLog_Server("Sent file accept v%u through server, port %u", 8, wListenPort);
+ }
+ else
+ {
+ icq_sendFileAcceptServv7(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szDesc, ft->dwTotalSize, wListenPort, TRUE, nAckType);
+ NetLog_Server("Sent file accept v%u through server, port %u", 7, wListenPort);
+ }
+}
+
+
+void CIcqProto::icq_sendFileDenyServ(DWORD dwUin, filetransfer *ft, const char *szReason, int nAckType)
+{
+ if (ft->nVersion >= 8)
+ {
+ icq_sendFileAcceptServv8(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szReason, ft->dwTotalSize, wListenPort, FALSE, nAckType);
+ NetLog_Server("Sent file deny v%u through server", 8);
+ }
+ else
+ {
+ icq_sendFileAcceptServv7(dwUin, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, ft->dwCookie, ft->szFilename, szReason, ft->dwTotalSize, wListenPort, FALSE, nAckType);
+ NetLog_Server("Sent file deny v%u through server", 7);
+ }
+}
+
+
+void CIcqProto::icq_sendAwayMsgReplyServ(DWORD dwUin, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char** szMsg)
+{
+ HANDLE hContact = HContactFromUIN(dwUin, NULL);
+
+ if (validateStatusMessageRequest(hContact, msgType))
+ {
+ NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dwUin);
+
+ icq_lock l(m_modeMsgsMutex);
+
+ if (szMsg && *szMsg)
+ {
+ char *pszMsg = NULL;
+ WORD wReplyVersion = ICQ_VERSION;
+
+ if (wVersion == 9)
+ {
+ pszMsg = *szMsg;
+ wReplyVersion = 9;
+ }
+ else
+ { // only v9 protocol supports UTF-8 mode messagees
+ WORD wMsgLen = strlennull(*szMsg) + 1;
+ char *szAnsiMsg = (char*)_alloca(wMsgLen);
+
+ utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen);
+ pszMsg = szAnsiMsg;
+ }
+
+ WORD wMsgLen = strlennull(pszMsg);
+
+ // limit msg len to max snac size - we get disconnected if exceeded
+ if (wMsgLen > MAX_MESSAGESNACSIZE)
+ wMsgLen = MAX_MESSAGESNACSIZE;
+
+ icq_packet packet;
+
+ packServAdvancedMsgReply(&packet, dwUin, NULL, dwMsgID1, dwMsgID2, wCookie, wReplyVersion, msgType, 3, (WORD)(wMsgLen + 3));
+ packLEWord(&packet, (WORD)(wMsgLen + 1));
+ packBuffer(&packet, (LPBYTE)pszMsg, wMsgLen);
+ packByte(&packet, 0);
+
+ sendServPacket(&packet);
+ }
+ }
+}
+
+
+void CIcqProto::icq_sendAwayMsgReplyServExt(DWORD dwUin, char *szUID, DWORD dwMsgID1, DWORD dwMsgID2, WORD wCookie, WORD wVersion, BYTE msgType, char **szMsg)
+{
+ HANDLE hContact = HContactFromUID(dwUin, szUID, NULL);
+
+ if (validateStatusMessageRequest(hContact, msgType))
+ {
+ NotifyEventHooks(m_modeMsgsEvent, (WPARAM)msgType, (LPARAM)dwUin);
+
+ icq_lock l(m_modeMsgsMutex);
+
+ if (szMsg && *szMsg)
+ {
+ char *pszMsg = NULL;
+ WORD wReplyVersion = ICQ_VERSION;
+
+ if (wVersion == 9)
+ {
+ pszMsg = *szMsg;
+ wReplyVersion = 9;
+ }
+ else
+ { // only v9 protocol supports UTF-8 mode messagees
+ WORD wMsgLen = strlennull(*szMsg) + 1;
+ char *szAnsiMsg = (char*)_alloca(wMsgLen);
+
+ utf8_decode_static(*szMsg, szAnsiMsg, wMsgLen);
+ pszMsg = szAnsiMsg;
+ }
+ // convert to HTML
+ char *mng = MangleXml(pszMsg, strlennull(pszMsg));
+ pszMsg = (char*)SAFE_MALLOC(strlennull(mng) + 28);
+ strcpy(pszMsg, "<HTML><BODY>"); /// TODO: add support for RTL & user customizable font
+ strcat(pszMsg, mng);
+ SAFE_FREE(&mng);
+ strcat(pszMsg, "</BODY></HTML>");
+
+ WORD wMsgLen = strlennull(pszMsg);
+
+ // limit msg len to max snac size - we get disconnected if exceeded /// FIXME: correct HTML cutting
+ if (wMsgLen > MAX_MESSAGESNACSIZE)
+ wMsgLen = MAX_MESSAGESNACSIZE;
+
+ icq_packet packet;
+
+ packServAdvancedMsgReply(&packet, dwUin, szUID, dwMsgID1, dwMsgID2, wCookie, wReplyVersion, MTYPE_PLUGIN, 0, wMsgLen + 27 + getPluginTypeIdLen(msgType));
+ packLEWord(&packet, 0); // Message size
+ packPluginTypeId(&packet, msgType);
+
+ packLEDWord(&packet, wMsgLen + 21);
+ packLEDWord(&packet, wMsgLen);
+ packBuffer(&packet, (LPBYTE)pszMsg, wMsgLen);
+
+ packLEDWord(&packet, 0x0D);
+ packBuffer(&packet, (LPBYTE)"text/x-aolrtf", 0x0D);
+
+ sendServPacket(&packet);
+ SAFE_FREE(&pszMsg);
+ }
+ }
+}
+
+
+void CIcqProto::icq_sendAdvancedMsgAck(DWORD dwUin, DWORD dwTimestamp, DWORD dwTimestamp2, WORD wCookie, BYTE bMsgType, BYTE bMsgFlags)
+{
+ icq_packet packet;
+
+ packServAdvancedMsgReply(&packet, dwUin, NULL, dwTimestamp, dwTimestamp2, wCookie, ICQ_VERSION, bMsgType, bMsgFlags, 11);
+ packEmptyMsg(&packet); // Status message
+ packMsgColorInfo(&packet);
+
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::icq_sendContactsAck(DWORD dwUin, char *szUid, DWORD dwMsgID1, DWORD dwMsgID2)
+{
+ icq_packet packet;
+
+ packServMsgSendHeader(&packet, 0, dwMsgID1, dwMsgID2, dwUin, szUid, 2, 0x1E);
+ packServTLV5HeaderBasic(&packet, 0, dwMsgID1, dwMsgID2, 2, MCAP_CONTACTS);
+
+ sendServPacket(&packet);
+}
+
+
+// Searches
+
+DWORD CIcqProto::SearchByUin(DWORD dwUin)
+{
+ WORD wInfoLen;
+ icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer
+ // I should be ashamed! ;)
+
+ // Calculate data size
+ wInfoLen = 4 + getUINLen(dwUin);
+
+ // Initialize our handy data buffer
+ pBuffer.wPlace = 0;
+ pBuffer.pData = (BYTE *)_alloca(wInfoLen);
+ pBuffer.wLen = wInfoLen;
+
+ // Initialize our handy data buffer
+ packTLVUID(&pBuffer, 0x32, dwUin, NULL);
+
+ // Send it off for further packing
+ return sendDirectorySearchPacket(pBuffer.pData, wInfoLen, 0, FALSE);
+}
+
+
+DWORD CIcqProto::SearchByNames(const char *pszNick, const char *pszFirstName, const char *pszLastName, WORD wPage)
+{ // use directory search like ICQ6 does
+ WORD wInfoLen = 0;
+ WORD wNickLen,wFirstLen,wLastLen;
+ icq_packet pBuffer; // I reuse the ICQ packet type as a generic buffer
+ // I should be ashamed! ;)
+
+ wNickLen = strlennull(pszNick);
+ wFirstLen = strlennull(pszFirstName);
+ wLastLen = strlennull(pszLastName);
+
+ _ASSERTE(wFirstLen || wLastLen || wNickLen);
+
+
+ // Calculate data size
+ if (wFirstLen)
+ wInfoLen = wFirstLen + 4;
+ if (wLastLen)
+ wInfoLen += wLastLen + 4;
+ if (wNickLen)
+ wInfoLen += wNickLen + 4;
+
+ // Initialize our handy data buffer
+ pBuffer.wPlace = 0;
+ pBuffer.pData = (BYTE *)_alloca(wInfoLen);
+ pBuffer.wLen = wInfoLen;
+
+ // Pack the search details
+ if (wNickLen)
+ packTLV(&pBuffer, 0x78, wNickLen, (PBYTE)pszNick);
+
+ if (wLastLen)
+ packTLV(&pBuffer, 0x6E, wLastLen, (PBYTE)pszLastName);
+
+ if (wFirstLen)
+ packTLV(&pBuffer, 0x64, wFirstLen, (PBYTE)pszFirstName);
+
+ // Send it off for further packing
+ if (wInfoLen)
+ return sendDirectorySearchPacket(pBuffer.pData, wInfoLen, wPage, FALSE);
+ else
+ return 0; // Failure
+}
+
+
+DWORD CIcqProto::SearchByMail(const char* pszEmail)
+{
+ DWORD dwCookie = 0;
+ WORD wInfoLen = 0;
+ WORD wEmailLen;
+ BYTE *pBuffer;
+ int pBufferPos;
+
+ wEmailLen = strlennull(pszEmail);
+
+ _ASSERTE(wEmailLen);
+
+ if (wEmailLen > 0)
+ {
+ // Calculate data size
+ wInfoLen = wEmailLen + 7;
+
+ // Initialize our handy data buffer
+ pBuffer = (BYTE *)_alloca(wInfoLen);
+ pBufferPos = 0;
+
+ // Pack the search details
+ packLETLVLNTS(&pBuffer, &pBufferPos, pszEmail, TLV_EMAIL);
+
+ // Send it off for further packing
+ dwCookie = sendTLVSearchPacket(SEARCHTYPE_EMAIL, (char*)pBuffer, META_SEARCH_EMAIL, wInfoLen, FALSE);
+ }
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::sendDirectorySearchPacket(const BYTE *pSearchData, WORD wDataLen, WORD wPage, BOOL bOnlineUsersOnly)
+{
+ icq_packet packet;
+ DWORD dwCookie;
+
+ _ASSERTE(pSearchData);
+ _ASSERTE(wDataLen >= 4);
+
+ cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
+ if (pCookieData)
+ {
+ pCookieData->bRequestType = DIRECTORYREQUEST_SEARCH;
+ dwCookie = AllocateCookie(CKT_DIRECTORY_QUERY, 0, NULL, (void*)pCookieData);
+ }
+ else
+ return 0;
+
+ // Pack headers
+ packServIcqDirectoryHeader(&packet, this, wDataLen + (bOnlineUsersOnly ? 14 : 8), META_DIRECTORY_QUERY, DIRECTORY_QUERY_INFO, (WORD)dwCookie);
+ packWord(&packet, 0x02);
+
+ // Pack requested page number
+ packWord(&packet, wPage);
+
+ // Pack search data
+ packWord(&packet, 0x0001);
+ packWord(&packet, wDataLen + (bOnlineUsersOnly ? 6 : 0));
+ packBuffer(&packet, pSearchData, wDataLen);
+
+ if (bOnlineUsersOnly)
+ { // Pack "Online users only" flag
+ packTLVWord(&packet, 0x136, 1);
+ }
+
+ // Go!
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::sendTLVSearchPacket(BYTE bType, char* pSearchDataBuf, WORD wSearchType, WORD wInfoLen, BOOL bOnlineUsersOnly)
+{
+ icq_packet packet;
+ cookie_search* pCookie;
+
+ _ASSERTE(pSearchDataBuf);
+ _ASSERTE(wInfoLen >= 4);
+
+ pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search));
+ if (!pCookie)
+ return 0;
+
+ pCookie->bSearchType = bType;
+ DWORD dwCookie = AllocateCookie(CKT_SEARCH, 0, 0, pCookie);
+
+ // Pack headers
+ packServIcqExtensionHeader(&packet, this, (WORD)(wInfoLen + (wSearchType==META_SEARCH_GENERIC?7:2)), CLI_META_INFO_REQ, (WORD)dwCookie);
+
+ // Pack search type
+ packLEWord(&packet, wSearchType);
+
+ // Pack search data
+ packBuffer(&packet, (LPBYTE)pSearchDataBuf, wInfoLen);
+
+ if (wSearchType == META_SEARCH_GENERIC && bOnlineUsersOnly)
+ { // Pack "Online users only" flag - only for generic search
+ BYTE bData = 1;
+ packTLV(&packet, TLV_ONLINEONLY, 1, &bData);
+ }
+
+ // Go!
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendAdvancedSearchServ(BYTE* fieldsBuffer,int bufferLen)
+{
+ icq_packet packet;
+ DWORD dwCookie;
+
+ cookie_search *pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search));
+ if (pCookie)
+ {
+ pCookie->bSearchType = SEARCHTYPE_DETAILS;
+ dwCookie = AllocateCookie(CKT_SEARCH, 0, 0, pCookie);
+ }
+ else
+ return 0;
+
+ packServIcqExtensionHeader(&packet, this, (WORD)bufferLen, CLI_META_INFO_REQ, (WORD)dwCookie);
+ packBuffer(&packet, (LPBYTE)fieldsBuffer, (WORD)bufferLen);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_searchAimByEmail(const char* pszEmail, DWORD dwSearchId)
+{
+ icq_packet packet;
+ DWORD dwCookie;
+ cookie_search* pCookie;
+ WORD wEmailLen;
+
+ if (!FindCookie(dwSearchId, NULL, (void**)&pCookie))
+ {
+ dwSearchId = 0;
+ pCookie = (cookie_search*)SAFE_MALLOC(sizeof(cookie_search));
+ pCookie->bSearchType = SEARCHTYPE_EMAIL;
+ }
+
+ if (pCookie)
+ {
+ pCookie->dwMainId = dwSearchId;
+ pCookie->szObject = null_strdup(pszEmail);
+ dwCookie = AllocateCookie(CKT_SEARCH, ICQ_LOOKUP_REQUEST, 0, pCookie);
+ }
+ else
+ return 0;
+
+ wEmailLen = strlennull(pszEmail);
+ serverPacketInit(&packet, (WORD)(10 + wEmailLen));
+ packFNACHeader(&packet, ICQ_LOOKUP_FAMILY, ICQ_LOOKUP_REQUEST, 0, dwCookie);
+ packBuffer(&packet, (LPBYTE)pszEmail, wEmailLen);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_changeUserPasswordServ(const char *szPassword)
+{
+ icq_packet packet;
+ WORD wPasswordLen = strlennull(szPassword);
+ DWORD dwCookie = GenerateCookie(0);
+
+ packServIcqExtensionHeader(&packet, this, (WORD)(wPasswordLen + 4), CLI_META_INFO_REQ, (WORD)dwCookie, ICQ_META_SRV_UPDATE);
+ packLEWord(&packet, META_SET_PASSWORD_REQ);
+ packLEWord(&packet, wPasswordLen);
+ packBuffer(&packet, (BYTE*)szPassword, wPasswordLen);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_changeUserDirectoryInfoServ(const BYTE *pData, WORD wDataLen, BYTE bRequestType)
+{
+ icq_packet packet;
+ cookie_directory_data *pCookieData = (cookie_directory_data*)SAFE_MALLOC(sizeof(cookie_directory_data));
+ pCookieData->bRequestType = bRequestType;
+ DWORD dwCookie = AllocateCookie(CKT_DIRECTORY_UPDATE, 0, NULL, pCookieData);
+
+ packServIcqDirectoryHeader(&packet, this, wDataLen + 4, META_DIRECTORY_UPDATE, DIRECTORY_SET_INFO, (WORD)dwCookie, ICQ_META_SRV_UPDATE);
+ packWord(&packet, 0x0003);
+ packWord(&packet, wDataLen);
+ packBuffer(&packet, pData, wDataLen);
+
+ sendServPacket(&packet);
+
+ return dwCookie;
+}
+
+
+DWORD CIcqProto::icq_sendSMSServ(const char *szPhoneNumber, const char *szMsg)
+{
+ icq_packet packet;
+ DWORD dwCookie;
+ WORD wBufferLen;
+ char* szBuffer = NULL;
+ char* szMyNick = NULL;
+ char szTime[30];
+ time_t now;
+ int nBufferSize;
+
+ now = time(NULL);
+ strftime(szTime, sizeof(szTime), "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now));
+ /* Sun, 00 Jan 0000 00:00:00 GMT */
+
+ szMyNick = null_strdup((char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)(HANDLE)NULL, 0));
+ nBufferSize = 1 + strlennull(szMyNick) + strlennull(szPhoneNumber) + strlennull(szMsg) + sizeof("<icq_sms_message><destination></destination><text></text><codepage>1252</codepage><encoding>utf8</encoding><senders_UIN>0000000000</senders_UIN><senders_name></senders_name><delivery_receipt>Yes</delivery_receipt><time>Sun, 00 Jan 0000 00:00:00 GMT</time></icq_sms_message>");
+
+ if (szBuffer = (char *)_alloca(nBufferSize))
+ {
+
+ wBufferLen = null_snprintf(szBuffer, nBufferSize,
+ "<icq_sms_message>"
+ "<destination>"
+ "%s" /* phone number */
+ "</destination>"
+ "<text>"
+ "%s" /* body */
+ "</text>"
+ "<codepage>"
+ "1252"
+ "</codepage>"
+ "<encoding>"
+ "utf8"
+ "</encoding>"
+ "<senders_UIN>"
+ "%u" /* my UIN */
+ "</senders_UIN>"
+ "<senders_name>"
+ "%s" /* my nick */
+ "</senders_name>"
+ "<delivery_receipt>"
+ "Yes"
+ "</delivery_receipt>"
+ "<time>"
+ "%s" /* time */
+ "</time>"
+ "</icq_sms_message>",
+ szPhoneNumber, szMsg, m_dwLocalUIN, szMyNick, szTime);
+
+ dwCookie = GenerateCookie(0);
+
+ packServIcqExtensionHeader(&packet, this, (WORD)(wBufferLen + 27), CLI_META_INFO_REQ, (WORD)dwCookie);
+ packWord(&packet, 0x8214); /* send sms */
+ packWord(&packet, 1);
+ packWord(&packet, 0x16);
+ packDWord(&packet, 0);
+ packDWord(&packet, 0);
+ packDWord(&packet, 0);
+ packDWord(&packet, 0);
+ packWord(&packet, 0);
+ packWord(&packet, (WORD)(wBufferLen + 1));
+ packBuffer(&packet, (LPBYTE)szBuffer, (WORD)(1 + wBufferLen));
+
+ sendServPacket(&packet);
+ }
+ else
+ {
+ dwCookie = 0;
+ }
+
+ SAFE_FREE((void**)&szMyNick);
+ return dwCookie;
+}
+
+void CIcqProto::icq_sendGenericContact(DWORD dwUin, const char *szUid, WORD wFamily, WORD wSubType)
+{
+ icq_packet packet;
+ int nUinLen;
+
+ nUinLen = getUIDLen(dwUin, szUid);
+
+ serverPacketInit(&packet, (WORD)(nUinLen + 11));
+ packFNACHeader(&packet, wFamily, wSubType);
+ packUID(&packet, dwUin, szUid);
+
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendNewContact(DWORD dwUin, const char *szUid)
+{
+ /* Try to add to temporary buddy list */
+ icq_sendGenericContact(dwUin, szUid, ICQ_BUDDY_FAMILY, ICQ_USER_ADDTOTEMPLIST);
+}
+
+
+void CIcqProto::icq_sendRemoveContact(DWORD dwUin, const char *szUid)
+{
+ /* Remove from temporary buddy list */
+ icq_sendGenericContact(dwUin, szUid, ICQ_BUDDY_FAMILY, ICQ_USER_REMOVEFROMTEMPLIST);
+}
+
+
+// list==0: visible list
+// list==1: invisible list
+void CIcqProto::icq_sendChangeVisInvis(HANDLE hContact, DWORD dwUin, char* szUID, int list, int add)
+{ // TODO: This needs grouping & rate management
+ // Tell server to change our server-side contact visbility list
+ if (m_bSsiEnabled)
+ {
+ WORD wContactId;
+ char* szSetting;
+ WORD wType;
+
+ if (list == 0)
+ {
+ wType = SSI_ITEM_PERMIT;
+ szSetting = DBSETTING_SERVLIST_PERMIT;
+ }
+ else
+ {
+ wType = SSI_ITEM_DENY;
+ szSetting = DBSETTING_SERVLIST_DENY;
+ }
+
+ if (add)
+ {
+ // check if we should make the changes, this is 2nd level check
+ if (getSettingWord(hContact, szSetting, 0) != 0)
+ return;
+
+ // Add
+ wContactId = GenerateServerID(SSIT_ITEM, 0);
+
+ icq_addServerPrivacyItem(hContact, dwUin, szUID, wContactId, wType);
+
+ setSettingWord(hContact, szSetting, wContactId);
+ }
+ else
+ {
+ // Remove
+ wContactId = getSettingWord(hContact, szSetting, 0);
+
+ if (wContactId)
+ {
+ icq_removeServerPrivacyItem(hContact, dwUin, szUID, wContactId, wType);
+
+ deleteSetting(hContact, szSetting);
+ }
+ }
+ }
+
+ // Notify server that we have changed
+ // our client side visibility list
+ {
+ int nUinLen;
+ icq_packet packet;
+ WORD wSnac = 0;
+
+ if (list && m_iStatus == ID_STATUS_INVISIBLE)
+ return;
+
+ if (!list && m_iStatus != ID_STATUS_INVISIBLE)
+ return;
+
+
+ if (list && add)
+ wSnac = ICQ_CLI_ADDINVISIBLE;
+ else if (list && !add)
+ wSnac = ICQ_CLI_REMOVEINVISIBLE;
+ else if (!list && add)
+ wSnac = ICQ_CLI_ADDVISIBLE;
+ else if (!list && !add)
+ wSnac = ICQ_CLI_REMOVEVISIBLE;
+
+ nUinLen = getUIDLen(dwUin, szUID);
+
+ serverPacketInit(&packet, (WORD)(nUinLen + 11));
+ packFNACHeader(&packet, ICQ_BOS_FAMILY, wSnac);
+ packUID(&packet, dwUin, szUID);
+
+ sendServPacket(&packet);
+ }
+}
+
+void CIcqProto::icq_sendEntireVisInvisList(int list)
+{
+ if (list)
+ sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDINVISIBLE, BUL_INVISIBLE);
+ else
+ sendEntireListServ(ICQ_BOS_FAMILY, ICQ_CLI_ADDVISIBLE, BUL_VISIBLE);
+}
+
+void CIcqProto::icq_sendRevokeAuthServ(DWORD dwUin, char *szUid)
+{
+ icq_sendGenericContact(dwUin, szUid, ICQ_LISTS_FAMILY, ICQ_LISTS_REVOKEAUTH);
+}
+
+void CIcqProto::icq_sendGrantAuthServ(DWORD dwUin, const char *szUid, const char *szMsg)
+{
+ icq_packet packet;
+ BYTE nUinlen;
+ char *szUtfMsg = NULL;
+ WORD nMsglen;
+
+ nUinlen = getUIDLen(dwUin, szUid);
+
+ // Prepare custom utf-8 message
+ szUtfMsg = ansi_to_utf8(szMsg);
+ nMsglen = strlennull(szUtfMsg);
+
+ serverPacketInit(&packet, (WORD)(15 + nUinlen + nMsglen));
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_GRANTAUTH);
+ packUID(&packet, dwUin, szUid);
+ packWord(&packet, nMsglen);
+ packBuffer(&packet, (LPBYTE)szUtfMsg, nMsglen);
+ packWord(&packet, 0);
+
+ SAFE_FREE((void**)&szUtfMsg);
+
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendAuthReqServ(DWORD dwUin, char *szUid, const char *szMsg)
+{
+ icq_packet packet;
+ BYTE nUinlen;
+ WORD nMsglen;
+
+ nUinlen = getUIDLen(dwUin, szUid);
+ nMsglen = strlennull(szMsg);
+
+ serverPacketInit(&packet, (WORD)(15 + nUinlen + nMsglen));
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_REQUESTAUTH);
+ packUID(&packet, dwUin, szUid);
+ packWord(&packet, nMsglen);
+ packBuffer(&packet, (LPBYTE)szMsg, nMsglen);
+ packWord(&packet, 0);
+
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendAuthResponseServ(DWORD dwUin, char* szUid, int auth, const TCHAR *szReason)
+{
+ icq_packet packet;
+ BYTE nUinLen = getUIDLen(dwUin, szUid);
+
+ // Prepare custom utf-8 reason
+ char *szUtfReason = tchar_to_utf8(szReason);
+ WORD nReasonLen = strlennull(szUtfReason);
+
+ serverPacketInit(&packet, (WORD)(16 + nUinLen + nReasonLen));
+ packFNACHeader(&packet, ICQ_LISTS_FAMILY, ICQ_LISTS_CLI_AUTHRESPONSE);
+ packUID(&packet, dwUin, szUid);
+ packByte(&packet, (BYTE)auth);
+ packWord(&packet, nReasonLen);
+ packBuffer(&packet, (LPBYTE)szUtfReason, nReasonLen);
+ packWord(&packet, 0);
+
+ SAFE_FREE(&szUtfReason);
+
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendYouWereAddedServ(DWORD dwUin, DWORD dwMyUin)
+{
+ icq_packet packet;
+ DWORD dwID1;
+ DWORD dwID2;
+
+ dwID1 = time(NULL);
+ dwID2 = RandRange(0, 0x00FF);
+
+ packServMsgSendHeader(&packet, 0, dwID1, dwID2, dwUin, NULL, 0x0004, 17);
+ packWord(&packet, 0x0005); // TLV(5)
+ packWord(&packet, 0x0009);
+ packLEDWord(&packet, dwMyUin);
+ packByte(&packet, MTYPE_ADDED);
+ packByte(&packet, 0); // msg-flags
+ packEmptyMsg(&packet); // NTS
+ packDWord(&packet, 0x00060000); // TLV(6)
+
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendXtrazRequestServ(DWORD dwUin, DWORD dwCookie, char* szBody, int nBodyLen, cookie_message_data *pCookieData)
+{
+ icq_packet packet;
+ WORD wCoreLen;
+
+ wCoreLen = 11 + getPluginTypeIdLen(pCookieData->bMessageType) + nBodyLen;
+ packServMsgSendHeader(&packet, dwCookie, pCookieData->dwMsgID1, pCookieData->dwMsgID2, dwUin, NULL, 2, (WORD)(99 + wCoreLen));
+
+ // TLV(5) header
+ packServTLV5HeaderMsg(&packet, (WORD)(55 + wCoreLen), pCookieData->dwMsgID1, pCookieData->dwMsgID2, 1);
+
+ // TLV(0x2711) header
+ packServTLV2711Header(&packet, (WORD)dwCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, 0, 0x100, wCoreLen);
+ //
+ packEmptyMsg(&packet);
+
+ packPluginTypeId(&packet, pCookieData->bMessageType);
+
+ packLEDWord(&packet, nBodyLen + 4);
+ packLEDWord(&packet, nBodyLen);
+ packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen);
+
+ // Pack request server ack TLV
+ packDWord(&packet, 0x00030000); // TLV(3)
+
+ // Send the monster
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendXtrazResponseServ(DWORD dwUin, DWORD dwMID, DWORD dwMID2, WORD wCookie, char* szBody, int nBodyLen, int nType)
+{
+ icq_packet packet;
+
+ packServAdvancedMsgReply(&packet, dwUin, NULL, dwMID, dwMID2, wCookie, ICQ_VERSION, MTYPE_PLUGIN, 0, (WORD)(getPluginTypeIdLen(nType) + 11 + nBodyLen));
+ //
+ packEmptyMsg(&packet);
+
+ packPluginTypeId(&packet, nType);
+
+ packLEDWord(&packet, nBodyLen + 4);
+ packLEDWord(&packet, nBodyLen);
+ packBuffer(&packet, (LPBYTE)szBody, (WORD)nBodyLen);
+
+ // Send the monster
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendReverseReq(directconnect *dc, DWORD dwCookie, cookie_message_data *pCookie)
+{
+ icq_packet packet;
+
+ packServMsgSendHeader(&packet, dwCookie, pCookie->dwMsgID1, pCookie->dwMsgID2, dc->dwRemoteUin, NULL, 2, 0x47);
+
+ packServTLV5HeaderBasic(&packet, 0x29, pCookie->dwMsgID1, pCookie->dwMsgID2, 0, MCAP_REVERSE_DC_REQ);
+
+ packTLVWord(&packet, 0x0A, 1); // TLV: 0x0A Acktype: 1 for normal, 2 for ack
+ packDWord(&packet, 0x000F0000); // TLV: 0x0F empty
+ packDWord(&packet, 0x2711001B); // TLV: 0x2711 Content
+ // TLV(0x2711) data
+ packLEDWord(&packet, m_dwLocalUIN); // Our UIN
+ packDWord(&packet, dc->dwLocalExternalIP);// IP to connect to
+ packLEDWord(&packet, wListenPort); // Port to connect to
+ packByte(&packet, DC_NORMAL); // generic DC type
+ packDWord(&packet, dc->dwRemotePort); // unknown
+ packDWord(&packet, wListenPort); // port again ?
+ packLEWord(&packet, ICQ_VERSION); // DC Version
+ packLEDWord(&packet, dwCookie); // Req Cookie
+
+ // Send the monster
+ sendServPacket(&packet);
+}
+
+void CIcqProto::icq_sendReverseFailed(directconnect* dc, DWORD dwMsgID1, DWORD dwMsgID2, DWORD dwCookie)
+{
+ icq_packet packet;
+ int nUinLen = getUINLen(dc->dwRemoteUin);
+
+ serverPacketInit(&packet, (WORD)(nUinLen + 74));
+ packFNACHeader(&packet, ICQ_MSG_FAMILY, ICQ_MSG_RESPONSE, 0, ICQ_MSG_RESPONSE<<0x10 | (dwCookie & 0x7FFF));
+ packLEDWord(&packet, dwMsgID1); // Msg ID part 1
+ packLEDWord(&packet, dwMsgID2); // Msg ID part 2
+ packWord(&packet, 0x02);
+ packUIN(&packet, dc->dwRemoteUin);
+ packWord(&packet, 0x03);
+ packLEDWord(&packet, dc->dwRemoteUin);
+ packLEDWord(&packet, dc->dwRemotePort);
+ packLEDWord(&packet, wListenPort);
+ packLEWord(&packet, ICQ_VERSION);
+ packLEDWord(&packet, dwCookie);
+
+ sendServPacket(&packet);
+}
+
+
+// OSCAR file-transfer packets starts here
+//
+void CIcqProto::oft_sendFileRequest(DWORD dwUin, char *szUid, oscar_filetransfer *ft, const char *pszFiles, DWORD dwLocalInternalIP)
+{
+ icq_packet packet;
+
+ char *szCoolStr = (char*)_alloca(strlennull(ft->szDescription)+strlennull(pszFiles) + 160);
+ sprintf(szCoolStr, "<ICQ_COOL_FT><FS>%s</FS><S>%I64u</S><SID>1</SID><DESC>%s</DESC></ICQ_COOL_FT>", pszFiles, ft->qwTotalSize, ft->szDescription);
+ szCoolStr = MangleXml(szCoolStr, strlennull(szCoolStr));
+
+ WORD wDataLen = 93 + strlennull(szCoolStr) + strlennull(pszFiles);
+ if (ft->bUseProxy) wDataLen += 4;
+
+ packServMsgSendHeader(&packet, ft->dwCookie, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, (WORD)(wDataLen + 0x1E));
+ packServTLV5HeaderBasic(&packet, wDataLen, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, MCAP_FILE_TRANSFER);
+
+ packTLVWord(&packet, 0x0A, ++ft->wReqNum); // Request sequence
+ packDWord(&packet, 0x000F0000); // Unknown
+ packTLV(&packet, 0x0D, 5, (LPBYTE)"utf-8"); // Charset
+ packTLV(&packet, 0x0C, (WORD)strlennull(szCoolStr), (LPBYTE)szCoolStr); // User message (CoolData XML)
+ SAFE_FREE(&szCoolStr);
+ if (ft->bUseProxy)
+ {
+ packTLVDWord(&packet, 0x02, ft->dwProxyIP); // Proxy IP
+ packTLVDWord(&packet, 0x16, ft->dwProxyIP ^ 0x0FFFFFFFF); // Proxy IP check
+ }
+ else
+ {
+ packTLVDWord(&packet, 0x02, dwLocalInternalIP);
+ packTLVDWord(&packet, 0x16, dwLocalInternalIP ^ 0x0FFFFFFFF);
+ }
+ packTLVDWord(&packet, 0x03, dwLocalInternalIP); // Client IP
+ if (ft->bUseProxy)
+ {
+ packTLVWord(&packet, 0x05, ft->wRemotePort);
+ packTLVWord(&packet, 0x17, (WORD)(ft->wRemotePort ^ 0x0FFFF));
+ packDWord(&packet, 0x00100000); // Proxy flag
+ }
+ else
+ {
+ oscar_listener *pListener = (oscar_listener*)ft->listener;
+
+ packTLVWord(&packet, 0x05, pListener->wPort);
+ packTLVWord(&packet, 0x15, (WORD)((pListener->wPort) ^ 0x0FFFF));
+ }
+ { // TLV(0x2711)
+ packWord(&packet, 0x2711);
+ packWord(&packet, (WORD)(9 + strlennull(pszFiles)));
+ packWord(&packet, (WORD)(ft->wFilesCount == 1 ? 1 : 2));
+ packWord(&packet, ft->wFilesCount);
+ packDWord(&packet, (DWORD)ft->qwTotalSize);
+ packBuffer(&packet, (LPBYTE)pszFiles, (WORD)(strlennull(pszFiles) + 1));
+ }
+ packTLV(&packet, 0x2712, 5, (LPBYTE)"utf-8");
+ { // TLV(0x2713)
+ packWord(&packet, 0x2713);
+ packWord(&packet, 8);
+ packQWord(&packet, ft->qwTotalSize);
+ }
+
+ sendServPacket(&packet); // Send the monster
+}
+
+
+void CIcqProto::oft_sendFileReply(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResult)
+{
+ icq_packet packet;
+
+ packServMsgSendHeader(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, 0x1E);
+ packServTLV5HeaderBasic(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, wResult, MCAP_FILE_TRANSFER);
+
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::oft_sendFileAccept(DWORD dwUin, char *szUid, oscar_filetransfer *ft)
+{
+ oft_sendFileReply(dwUin, szUid, ft, 0x02);
+}
+
+
+void CIcqProto::oft_sendFileResponse(DWORD dwUin, char *szUid, oscar_filetransfer *ft, WORD wResponse)
+{
+ icq_packet packet;
+
+ packServAdvancedReply(&packet, dwUin, szUid, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, 4);
+ packWord(&packet, 0x02); // Length of following data
+ packWord(&packet, wResponse); // Response code
+
+ sendServPacket(&packet);
+}
+
+
+void CIcqProto::oft_sendFileDeny(DWORD dwUin, char *szUid, oscar_filetransfer *ft)
+{
+ if (dwUin)
+ { // ICQ clients uses special deny file transfer
+ oft_sendFileResponse(dwUin, szUid, ft, 0x01);
+ }
+ else
+ oft_sendFileReply(dwUin, szUid, ft, 0x01);
+}
+
+
+void CIcqProto::oft_sendFileCancel(DWORD dwUin, char *szUid, oscar_filetransfer *ft)
+{
+ oft_sendFileReply(dwUin, szUid, ft, 0x01);
+}
+
+
+void CIcqProto::oft_sendFileRedirect(DWORD dwUin, char *szUid, oscar_filetransfer *ft, DWORD dwIP, WORD wPort, int bProxy)
+{
+ icq_packet packet;
+
+ packServMsgSendHeader(&packet, 0, ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, dwUin, szUid, 2, (WORD)(bProxy ? 0x4a : 0x4e));
+ packServTLV5HeaderBasic(&packet, (WORD)(bProxy ? 0x2C : 0x30), ft->pMessage.dwMsgID1, ft->pMessage.dwMsgID2, 0, MCAP_FILE_TRANSFER);
+ // Connection point data
+ packTLVWord(&packet, 0x0A, ++ft->wReqNum); // Ack Type
+ packTLVWord(&packet, 0x14, 0x0A); // Unknown ?
+ packTLVDWord(&packet, 0x02, dwIP); // Internal IP / Proxy IP
+ packTLVDWord(&packet, 0x16, dwIP ^ 0x0FFFFFFFF); // IP Check ?
+ if (!bProxy)
+ packTLVDWord(&packet, 0x03, dwIP);
+ packTLVWord(&packet, 0x05, wPort); // Listening Port
+ packTLVWord(&packet, 0x17, (WORD)(wPort ^ 0x0FFFF)); // Port Check ?
+ if (bProxy)
+ packDWord(&packet, 0x00100000); // Proxy Flag
+
+ sendServPacket(&packet);
+}
diff --git a/protocols/IcqOscarJ/src/stdpackets.h b/protocols/IcqOscarJ/src/stdpackets.h
new file mode 100644
index 0000000000..b656d713c6
--- /dev/null
+++ b/protocols/IcqOscarJ/src/stdpackets.h
@@ -0,0 +1,43 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Low-level functions that really sends data to server
+//
+// -----------------------------------------------------------------------------
+#ifndef __STDPACKETS_H
+#define __STDPACKETS_H
+
+struct icq_contactsend_s
+{
+ DWORD uin;
+ char *uid;
+ char *szNick;
+};
+
+
+void packMsgColorInfo(icq_packet *packet);
+
+#endif /* __STDPACKETS_H */
diff --git a/protocols/IcqOscarJ/src/tlv.cpp b/protocols/IcqOscarJ/src/tlv.cpp
new file mode 100644
index 0000000000..7c4eafca3a
--- /dev/null
+++ b/protocols/IcqOscarJ/src/tlv.cpp
@@ -0,0 +1,391 @@
+// ---------------------------------------------------------------------------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-2009 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Helper functions for Oscar TLV chains
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+
+/* set maxTlvs<=0 to get all TLVs in length, or a positive integer to get at most the first n */
+oscar_tlv_chain* readIntoTLVChain(BYTE **buf, WORD wLen, int maxTlvs)
+{
+ oscar_tlv_chain *now, *last, *chain = NULL;
+ WORD now_tlv_len;
+ int len = wLen;
+
+ if (!buf || !wLen) return NULL;
+
+ while (len > 0) /* don't use unsigned variable for this check */
+ {
+ now = (oscar_tlv_chain *)SAFE_MALLOC(sizeof(oscar_tlv_chain));
+
+ if (!now)
+ {
+ disposeChain(&chain);
+ return NULL;
+ }
+
+ unpackWord(buf, &(now->tlv.wType));
+ unpackWord(buf, &now_tlv_len);
+ now->tlv.wLen = now_tlv_len;
+ len -= 4;
+
+ if (now_tlv_len < 1)
+ {
+ now->tlv.pData = NULL;
+ }
+ else if (now_tlv_len <= len)
+ {
+ now->tlv.pData = (BYTE *)SAFE_MALLOC(now_tlv_len);
+ if (now->tlv.pData)
+ memcpy(now->tlv.pData, *buf, now_tlv_len);
+ }
+ else
+ { // the packet is shorter than it should be
+ SAFE_FREE((void**)&now);
+ return chain; // give at least the rest of chain
+ }
+
+ if (chain) // keep the original order
+ last->next = now;
+ else
+ chain = now;
+
+ last = now;
+
+ len -= now_tlv_len;
+ *buf += now_tlv_len;
+
+ if (--maxTlvs == 0)
+ break;
+ }
+
+ return chain;
+}
+
+// Returns a pointer to the TLV with type wType and number wIndex in the chain
+// If wIndex = 1, the first matching TLV will be returned, if wIndex = 2,
+// the second matching one will be returned.
+// wIndex must be > 0
+oscar_tlv* oscar_tlv_chain::getTLV(WORD wType, WORD wIndex)
+{
+ int i = 0;
+ oscar_tlv_chain *list = this;
+
+ while (list)
+ {
+ if (list->tlv.wType == wType)
+ i++;
+ if (i >= wIndex)
+ return &list->tlv;
+ list = list->next;
+ }
+
+ return NULL;
+}
+
+
+WORD oscar_tlv_chain::getChainLength()
+{
+ int len = 0;
+ oscar_tlv_chain *list = this;
+
+ while (list)
+ {
+ len += list->tlv.wLen + 4;
+ list = list->next;
+ }
+ return len;
+}
+
+
+oscar_tlv* oscar_tlv_chain::putTLV(WORD wType, WORD wLen, BYTE *pData, BOOL bReplace)
+{
+ oscar_tlv *tlv = getTLV(wType, 1);
+
+ if (tlv && bReplace)
+ {
+ SAFE_FREE((void**)&tlv->pData);
+ }
+ else
+ {
+ oscar_tlv_chain *last = this;
+
+ while (last && last->next)
+ last = last->next;
+
+ if (last)
+ {
+ last->next = (oscar_tlv_chain*)SAFE_MALLOC(sizeof(oscar_tlv_chain));
+ tlv = &last->next->tlv;
+ tlv->wType = wType;
+ }
+ }
+ if (tlv)
+ {
+ tlv->wLen = wLen;
+ tlv->pData = (PBYTE)SAFE_MALLOC(wLen);
+ memcpy(tlv->pData, pData, wLen);
+ }
+ return tlv;
+}
+
+
+oscar_tlv_chain* oscar_tlv_chain::removeTLV(oscar_tlv *tlv)
+{
+ oscar_tlv_chain *list = this, *prev = NULL, *chain = this;
+
+ while (list)
+ {
+ if (&list->tlv == tlv)
+ {
+ if (prev) // relink
+ prev->next = list->next;
+ else if (list->next)
+ { // move second item's tlv to the first item
+ list->tlv = list->next->tlv;
+ list = list->next;
+ }
+ else // result is an empty chain (NULL)
+ chain = NULL;
+ // release chain item memory
+ SAFE_FREE((void**)&list->tlv.pData);
+ SAFE_FREE((void**)&list);
+ }
+ prev = list;
+ list = list->next;
+ }
+
+ return chain;
+}
+
+
+WORD oscar_tlv_chain::getLength(WORD wType, WORD wIndex)
+{
+ oscar_tlv *tlv = getTLV(wType, wIndex);
+ if (tlv)
+ return tlv->wLen;
+
+ return 0;
+}
+
+
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+/* Values are returned in MSB format */
+/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */
+
+DWORD oscar_tlv_chain::getDWord(WORD wType, WORD wIndex)
+{
+ DWORD dw = 0;
+
+ oscar_tlv *tlv = getTLV(wType, wIndex);
+ if (tlv && tlv->wLen >= 4)
+ {
+ dw |= (*((tlv->pData)+0) << 24);
+ dw |= (*((tlv->pData)+1) << 16);
+ dw |= (*((tlv->pData)+2) << 8);
+ dw |= (*((tlv->pData)+3));
+ }
+
+ return dw;
+}
+
+
+WORD oscar_tlv_chain::getWord(WORD wType, WORD wIndex)
+{
+ WORD w = 0;
+
+ oscar_tlv *tlv = getTLV(wType, wIndex);
+ if (tlv && tlv->wLen >= 2)
+ {
+ w |= (*((tlv->pData)+0) << 8);
+ w |= (*((tlv->pData)+1));
+ }
+
+ return w;
+}
+
+
+BYTE oscar_tlv_chain::getByte(WORD wType, WORD wIndex)
+{
+ BYTE b = 0;
+
+ oscar_tlv *tlv = getTLV(wType, wIndex);
+ if (tlv && tlv->wLen)
+ {
+ b = *(tlv->pData);
+ }
+
+ return b;
+}
+
+
+int oscar_tlv_chain::getNumber(WORD wType, WORD wIndex)
+{
+ oscar_tlv *tlv = getTLV(wType, wIndex);
+
+ if (tlv)
+ {
+ if (tlv->wLen == 1)
+ return getByte(wType, wIndex);
+ else if (tlv->wLen == 2)
+ return getWord(wType, wIndex);
+ else if (tlv->wLen == 4)
+ return getDWord(wType, wIndex);
+ }
+ return 0;
+}
+
+
+double oscar_tlv_chain::getDouble(WORD wType, WORD wIndex)
+{
+ oscar_tlv *tlv = getTLV(wType, wIndex);
+
+ if (tlv && tlv->wLen == 8)
+ {
+ BYTE *buf = tlv->pData;
+ double d = 0;
+
+ unpackQWord(&buf, (DWORD64*)&d);
+
+ return d;
+ }
+ return 0;
+}
+
+
+char* oscar_tlv_chain::getString(WORD wType, WORD wIndex)
+{
+ char *str = NULL;
+
+ oscar_tlv *tlv = getTLV(wType, wIndex);
+ if (tlv)
+ {
+ str = (char*)SAFE_MALLOC(tlv->wLen + 1); /* For \0 */
+
+ if (!str) return NULL;
+
+ memcpy(str, tlv->pData, tlv->wLen);
+ str[tlv->wLen] = '\0';
+ }
+
+ return str;
+}
+
+
+void disposeChain(oscar_tlv_chain **chain)
+{
+ if (!chain || !*chain)
+ return;
+
+ oscar_tlv_chain *now = *chain;
+
+ while (now)
+ {
+ oscar_tlv_chain *next = now->next;
+
+ SAFE_FREE((void**)&now->tlv.pData);
+ SAFE_FREE((void**)&now);
+ now = next;
+ }
+
+ *chain = NULL;
+}
+
+
+oscar_tlv_record_list* readIntoTLVRecordList(BYTE **buf, WORD wLen, int nCount)
+{
+ oscar_tlv_record_list *list = NULL, *last;
+
+ while (wLen >= 2)
+ {
+ WORD wRecordSize;
+
+ unpackWord(buf, &wRecordSize);
+ wLen -= 2;
+ if (wRecordSize && wRecordSize <= wLen)
+ {
+ oscar_tlv_record_list *pRecord = (oscar_tlv_record_list*)SAFE_MALLOC(sizeof(oscar_tlv_record_list));
+ BYTE *pData = *buf;
+
+ *buf += wRecordSize;
+ wLen -= wRecordSize;
+
+ pRecord->item = readIntoTLVChain(&pData, wRecordSize, 0);
+ if (pRecord->item)
+ { // keep the order
+ if (list)
+ last->next = pRecord;
+ else
+ list = pRecord;
+
+ last = pRecord;
+ }
+ else
+ SAFE_FREE((void**)&pRecord);
+ }
+
+ if (--nCount == 0) break;
+ }
+ return list;
+}
+
+
+void disposeRecordList(oscar_tlv_record_list** list)
+{
+ if (!list || !*list)
+ return;
+
+ oscar_tlv_record_list *now = *list;
+
+ while (now)
+ {
+ oscar_tlv_record_list *next = now->next;
+
+ disposeChain(&now->item);
+ SAFE_FREE((void**)&now);
+ now = next;
+ }
+
+ *list = NULL;
+}
+
+
+oscar_tlv_chain* oscar_tlv_record_list::getRecordByTLV(WORD wType, int nValue)
+{
+ oscar_tlv_record_list *list = this;
+
+ while (list)
+ {
+ if (list->item && list->item->getTLV(wType, 1) && list->item->getNumber(wType, 1) == nValue)
+ return list->item;
+ list = list->next;
+ }
+
+ return NULL;
+}
diff --git a/protocols/IcqOscarJ/src/tlv.h b/protocols/IcqOscarJ/src/tlv.h
new file mode 100644
index 0000000000..5cee00c7c4
--- /dev/null
+++ b/protocols/IcqOscarJ/src/tlv.h
@@ -0,0 +1,81 @@
+// ---------------------------------------------------------------------------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-2008 Joe Kucera
+//
+// 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.
+//
+// -----------------------------------------------------------------------------
+// DESCRIPTION:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __TLV_H
+#define __TLV_H
+
+/*---------* Structures *--------------*/
+
+struct oscar_tlv
+{
+ WORD wType;
+ WORD wLen;
+ BYTE *pData;
+};
+
+
+struct oscar_tlv_chain
+{
+ oscar_tlv tlv;
+ oscar_tlv_chain *next;
+
+ WORD getChainLength();
+
+ oscar_tlv* getTLV(WORD wType, WORD wIndex);
+ oscar_tlv* putTLV(WORD wType, WORD wLen, BYTE *pData, BOOL bReplace);
+ oscar_tlv_chain* removeTLV(oscar_tlv *tlv);
+ WORD getLength(WORD wType, WORD wIndex);
+
+ DWORD getDWord(WORD wType, WORD wIndex);
+ WORD getWord(WORD wType, WORD wIndex);
+ BYTE getByte(WORD wType, WORD wIndex);
+ int getNumber(WORD wType, WORD wIndex);
+ double getDouble(WORD wType, WORD wIndex);
+ char* getString(WORD wType, WORD wIndex);
+};
+
+
+struct oscar_tlv_record_list
+{
+ oscar_tlv_chain *item;
+ oscar_tlv_record_list *next;
+
+ oscar_tlv_chain* getRecordByTLV(WORD wType, int nValue);
+};
+
+/*---------* Functions *---------------*/
+
+oscar_tlv_chain* readIntoTLVChain(BYTE **buf, WORD wLen, int maxTlvs);
+void disposeChain(oscar_tlv_chain** chain);
+
+oscar_tlv_record_list* readIntoTLVRecordList(BYTE **buf, WORD wLen, int nCount);
+void disposeRecordList(oscar_tlv_record_list** list);
+
+
+#endif /* __TLV_H */
diff --git a/protocols/IcqOscarJ/src/utilities.cpp b/protocols/IcqOscarJ/src/utilities.cpp
new file mode 100644
index 0000000000..70f7547108
--- /dev/null
+++ b/protocols/IcqOscarJ/src/utilities.cpp
@@ -0,0 +1,2183 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#include "icqoscar.h"
+
+
+struct gateway_index
+{
+ HANDLE hConn;
+ DWORD dwIndex;
+};
+
+static icq_critical_section *gatewayMutex = NULL;
+
+static gateway_index *gateways = NULL;
+static int gatewayCount = 0;
+
+static DWORD *spammerList = NULL;
+static int spammerListCount = 0;
+
+
+void MoveDlgItem(HWND hwndDlg, int iItem, int left, int top, int width, int height)
+{
+ RECT rc;
+
+ rc.left = left;
+ rc.top = top;
+ rc.right = left + width;
+ rc.bottom = top + height;
+ MapDialogRect(hwndDlg, &rc);
+ MoveWindow(GetDlgItem(hwndDlg, iItem), rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, FALSE);
+}
+
+
+void EnableDlgItem(HWND hwndDlg, UINT control, int state)
+{
+ EnableWindow(GetDlgItem(hwndDlg, control), state);
+}
+
+
+void ShowDlgItem(HWND hwndDlg, UINT control, int state)
+{
+ ShowWindow(GetDlgItem(hwndDlg, control), state);
+}
+
+
+void icq_EnableMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state)
+{
+ for (int i = 0; i < cControls; i++)
+ EnableDlgItem(hwndDlg, controls[i], state);
+}
+
+
+void icq_ShowMultipleControls(HWND hwndDlg, const UINT *controls, int cControls, int state)
+{
+ for (int i = 0; i < cControls; i++)
+ ShowDlgItem(hwndDlg, controls[i], state);
+}
+
+
+// Maps the ICQ status flag (as seen in the status change SNACS) and returns
+// a Miranda style status.
+int IcqStatusToMiranda(WORD nIcqStatus)
+{
+ int nMirandaStatus;
+
+ // :NOTE: The order in which the flags are compared are important!
+ // I dont like this method but it works.
+
+ if (nIcqStatus & ICQ_STATUSF_INVISIBLE)
+ nMirandaStatus = ID_STATUS_INVISIBLE;
+ else
+ if (nIcqStatus & ICQ_STATUSF_DND)
+ nMirandaStatus = ID_STATUS_DND;
+ else
+ if (nIcqStatus & ICQ_STATUSF_OCCUPIED)
+ nMirandaStatus = ID_STATUS_OCCUPIED;
+ else
+ if (nIcqStatus & ICQ_STATUSF_NA)
+ nMirandaStatus = ID_STATUS_NA;
+ else
+ if (nIcqStatus & ICQ_STATUSF_AWAY)
+ nMirandaStatus = ID_STATUS_AWAY;
+ else
+ if (nIcqStatus & ICQ_STATUSF_FFC)
+ nMirandaStatus = ID_STATUS_FREECHAT;
+ else
+ // Can be discussed, but I think 'online' is the most generic ICQ status
+ nMirandaStatus = ID_STATUS_ONLINE;
+
+ return nMirandaStatus;
+}
+
+WORD MirandaStatusToIcq(int nMirandaStatus)
+{
+ WORD nIcqStatus;
+
+ switch (nMirandaStatus) {
+case ID_STATUS_ONLINE:
+ nIcqStatus = ICQ_STATUS_ONLINE;
+ break;
+
+case ID_STATUS_AWAY:
+ nIcqStatus = ICQ_STATUS_AWAY;
+ break;
+
+case ID_STATUS_OUTTOLUNCH:
+case ID_STATUS_NA:
+ nIcqStatus = ICQ_STATUS_NA;
+ break;
+
+case ID_STATUS_ONTHEPHONE:
+case ID_STATUS_OCCUPIED:
+ nIcqStatus = ICQ_STATUS_OCCUPIED;
+ break;
+
+case ID_STATUS_DND:
+ nIcqStatus = ICQ_STATUS_DND;
+ break;
+
+case ID_STATUS_INVISIBLE:
+ nIcqStatus = ICQ_STATUS_INVISIBLE;
+ break;
+
+case ID_STATUS_FREECHAT:
+ nIcqStatus = ICQ_STATUS_FFC;
+ break;
+
+case ID_STATUS_OFFLINE:
+ // Oscar doesnt have anything that maps to this status. This should never happen.
+ _ASSERTE(nMirandaStatus != ID_STATUS_OFFLINE);
+ nIcqStatus = 0;
+ break;
+
+default:
+ // Online seems to be a good default.
+ // Since it cant be offline, it must be a new type of online status.
+ nIcqStatus = ICQ_STATUS_ONLINE;
+ break;
+ }
+
+ return nIcqStatus;
+}
+
+int MirandaStatusToSupported(int nMirandaStatus)
+{
+ int nSupportedStatus;
+
+ switch (nMirandaStatus) {
+
+ // These status mode does not need any mapping
+case ID_STATUS_ONLINE:
+case ID_STATUS_AWAY:
+case ID_STATUS_NA:
+case ID_STATUS_OCCUPIED:
+case ID_STATUS_DND:
+case ID_STATUS_INVISIBLE:
+case ID_STATUS_OFFLINE:
+ nSupportedStatus = nMirandaStatus;
+ break;
+
+case ID_STATUS_FREECHAT:
+ nSupportedStatus = ID_STATUS_ONLINE;
+ break;
+
+ // This mode is not support and must be mapped to something else
+case ID_STATUS_OUTTOLUNCH:
+ nSupportedStatus = ID_STATUS_NA;
+ break;
+
+ // This mode is not support and must be mapped to something else
+case ID_STATUS_ONTHEPHONE:
+ nSupportedStatus = ID_STATUS_OCCUPIED;
+ break;
+
+ // This is not supposed to happen.
+default:
+ _ASSERTE(0);
+ // Online seems to be a good default.
+ nSupportedStatus = ID_STATUS_ONLINE;
+ break;
+ }
+
+ return nSupportedStatus;
+}
+
+char *MirandaStatusToString(int mirandaStatus)
+{
+ return (char*)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, mirandaStatus, 0);
+}
+
+char *MirandaStatusToStringUtf(int mirandaStatus)
+{ // return miranda status description in utf-8, use unicode service is possible
+ return tchar_to_utf8((TCHAR*)CallService(MS_CLIST_GETSTATUSMODEDESCRIPTION, mirandaStatus, GSMDF_TCHAR));
+}
+
+char** CIcqProto::MirandaStatusToAwayMsg(int nStatus)
+{
+ switch (nStatus) {
+
+case ID_STATUS_ONLINE:
+ return &m_modeMsgs.szOnline;
+
+case ID_STATUS_AWAY:
+ return &m_modeMsgs.szAway;
+
+case ID_STATUS_NA:
+ return &m_modeMsgs.szNa;
+
+case ID_STATUS_OCCUPIED:
+ return &m_modeMsgs.szOccupied;
+
+case ID_STATUS_DND:
+ return &m_modeMsgs.szDnd;
+
+case ID_STATUS_FREECHAT:
+ return &m_modeMsgs.szFfc;
+
+default:
+ return NULL;
+ }
+}
+
+int AwayMsgTypeToStatus(int nMsgType)
+{
+ switch (nMsgType) {
+case MTYPE_AUTOONLINE:
+ return ID_STATUS_ONLINE;
+
+case MTYPE_AUTOAWAY:
+ return ID_STATUS_AWAY;
+
+case MTYPE_AUTOBUSY:
+ return ID_STATUS_OCCUPIED;
+
+case MTYPE_AUTONA:
+ return ID_STATUS_NA;
+
+case MTYPE_AUTODND:
+ return ID_STATUS_DND;
+
+case MTYPE_AUTOFFC:
+ return ID_STATUS_FREECHAT;
+
+default:
+ return ID_STATUS_OFFLINE;
+ }
+}
+
+
+void SetGatewayIndex(HANDLE hConn, DWORD dwIndex)
+{
+ icq_lock l(gatewayMutex);
+
+ for (int i = 0; i < gatewayCount; i++)
+ {
+ if (hConn == gateways[i].hConn)
+ {
+ gateways[i].dwIndex = dwIndex;
+ return;
+ }
+ }
+
+ gateways = (gateway_index *)SAFE_REALLOC(gateways, sizeof(gateway_index) * (gatewayCount + 1));
+ gateways[gatewayCount].hConn = hConn;
+ gateways[gatewayCount].dwIndex = dwIndex;
+ gatewayCount++;
+}
+
+
+DWORD GetGatewayIndex(HANDLE hConn)
+{
+ icq_lock l(gatewayMutex);
+
+ for (int i = 0; i < gatewayCount; i++)
+ {
+ if (hConn == gateways[i].hConn)
+ return gateways[i].dwIndex;
+ }
+
+ return 1; // this is default
+}
+
+
+void FreeGatewayIndex(HANDLE hConn)
+{
+ icq_lock l(gatewayMutex);
+
+ for (int i = 0; i < gatewayCount; i++)
+ {
+ if (hConn == gateways[i].hConn)
+ {
+ gatewayCount--;
+ memmove(&gateways[i], &gateways[i+1], sizeof(gateway_index) * (gatewayCount - i));
+ gateways = (gateway_index*)SAFE_REALLOC(gateways, sizeof(gateway_index) * gatewayCount);
+
+ // Gateway found, exit loop
+ break;
+ }
+ }
+}
+
+
+void CIcqProto::AddToSpammerList(DWORD dwUIN)
+{
+ icq_lock l(gatewayMutex);
+
+ spammerList = (DWORD *)SAFE_REALLOC(spammerList, sizeof(DWORD) * (spammerListCount + 1));
+ spammerList[spammerListCount] = dwUIN;
+ spammerListCount++;
+}
+
+
+BOOL CIcqProto::IsOnSpammerList(DWORD dwUIN)
+{
+ icq_lock l(gatewayMutex);
+
+ for (int i = 0; i < spammerListCount; i++)
+ {
+ if (dwUIN == spammerList[i])
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+// ICQ contacts cache
+
+void CIcqProto::AddToContactsCache(HANDLE hContact, DWORD dwUin, const char *szUid)
+{
+ if (!hContact || (!dwUin && !szUid))
+ return;
+
+#ifdef _DEBUG
+ NetLog_Server("Adding contact to cache: %u%s%s", dwUin, dwUin ? "" : " - ", dwUin ? "" : szUid);
+#endif
+
+ icq_contacts_cache *cache_item = (icq_contacts_cache*)SAFE_MALLOC(sizeof(icq_contacts_cache));
+ cache_item->hContact = hContact;
+ cache_item->dwUin = dwUin;
+ if (!dwUin)
+ cache_item->szUid = null_strdup(szUid);
+
+ icq_lock l(contactsCacheMutex);
+ contactsCache.insert(cache_item);
+}
+
+
+void CIcqProto::InitContactsCache()
+{
+ if (!gatewayMutex)
+ gatewayMutex = new icq_critical_section();
+ else
+ gatewayMutex->_Lock();
+
+ contactsCacheMutex = new icq_critical_section();
+
+ // build cache
+ icq_lock l(contactsCacheMutex);
+
+ HANDLE hContact = FindFirstContact();
+
+ while (hContact)
+ {
+ DWORD dwUin;
+ uid_str szUid;
+
+ if (!getContactUid(hContact, &dwUin, &szUid))
+ AddToContactsCache(hContact, dwUin, szUid);
+
+ hContact = FindNextContact(hContact);
+ }
+}
+
+
+void CIcqProto::UninitContactsCache(void)
+{
+ contactsCacheMutex->Enter();
+
+ // cleanup the cache
+ for (int i = 0; i < contactsCache.getCount(); i++)
+ {
+ icq_contacts_cache *cache_item = contactsCache[i];
+
+ SAFE_FREE((void**)&cache_item->szUid);
+ SAFE_FREE((void**)&cache_item);
+ }
+
+ contactsCache.destroy();
+
+ contactsCacheMutex->Leave();
+
+ SAFE_DELETE(&contactsCacheMutex);
+
+ if (gatewayMutex && gatewayMutex->getLockCount() > 1)
+ gatewayMutex->_Release();
+ else
+ SAFE_DELETE(&gatewayMutex);
+}
+
+
+void CIcqProto::DeleteFromContactsCache(HANDLE hContact)
+{
+ icq_lock l(contactsCacheMutex);
+
+ for (int i = 0; i < contactsCache.getCount(); i++)
+ {
+ icq_contacts_cache *cache_item = contactsCache[i];
+
+ if (cache_item->hContact == hContact)
+ {
+#ifdef _DEBUG
+ NetLog_Server("Removing contact from cache: %u%s%s, position: %u", cache_item->dwUin, cache_item->dwUin ? "" : " - ", cache_item->dwUin ? "" : cache_item->szUid, i);
+#endif
+ contactsCache.remove(i);
+ // Release memory
+ SAFE_FREE((void**)&cache_item->szUid);
+ SAFE_FREE((void**)&cache_item);
+ break;
+ }
+ }
+}
+
+
+HANDLE CIcqProto::HandleFromCacheByUid(DWORD dwUin, const char *szUid)
+{
+ icq_contacts_cache cache_item = {NULL, dwUin, szUid};
+
+ icq_lock l(contactsCacheMutex);
+ // find in list
+ int i = contactsCache.getIndex(&cache_item);
+ if (i != -1)
+ return contactsCache[i]->hContact;
+
+ return NULL;
+}
+
+
+HANDLE CIcqProto::HContactFromUIN(DWORD dwUin, int *Added)
+{
+ if (Added) *Added = 0;
+
+ HANDLE hContact = HandleFromCacheByUid(dwUin, NULL);
+ if (hContact) return hContact;
+
+ hContact = FindFirstContact();
+ while (hContact)
+ {
+ DWORD dwContactUin;
+
+ dwContactUin = getContactUin(hContact);
+ if (dwContactUin == dwUin)
+ {
+ AddToContactsCache(hContact, dwUin, NULL);
+ return hContact;
+ }
+
+ hContact = FindNextContact(hContact);
+ }
+
+ //not present: add
+ if (Added)
+ {
+ hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0);
+ if (!hContact)
+ {
+ NetLog_Server("Failed to create ICQ contact %u", dwUin);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)m_szModuleName) != 0)
+ {
+ // For some reason we failed to register the protocol to this contact
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)hContact, 0);
+ NetLog_Server("Failed to register ICQ contact %u", dwUin);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ setSettingDword(hContact, UNIQUEIDSETTING, dwUin);
+
+ if (!bIsSyncingCL)
+ {
+ DBWriteContactSettingByte(hContact, "CList", "NotOnList", 1);
+ setContactHidden(hContact, 1);
+
+ setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+
+ icq_QueueUser(hContact);
+
+ if (icqOnline())
+ icq_sendNewContact(dwUin, NULL);
+ }
+ AddToContactsCache(hContact, dwUin, NULL);
+ *Added = 1;
+
+ return hContact;
+ }
+
+ // not in list, check that uin do not belong to us
+ if (getContactUin(NULL) == dwUin)
+ return NULL;
+
+ return INVALID_HANDLE_VALUE;
+}
+
+
+HANDLE CIcqProto::HContactFromUID(DWORD dwUin, const char *szUid, int *Added)
+{
+ if (dwUin)
+ return HContactFromUIN(dwUin, Added);
+
+ if (Added) *Added = 0;
+
+ if (!m_bAimEnabled) return INVALID_HANDLE_VALUE;
+
+ HANDLE hContact = HandleFromCacheByUid(dwUin, szUid);
+ if (hContact) return hContact;
+
+ hContact = FindFirstContact();
+ while (hContact)
+ {
+ DWORD dwContactUin;
+ uid_str szContactUid;
+
+ if (!getContactUid(hContact, &dwContactUin, &szContactUid))
+ {
+ if (!dwContactUin && !stricmpnull(szContactUid, szUid))
+ {
+ if (strcmpnull(szContactUid, szUid))
+ { // fix case in SN
+ setSettingString(hContact, UNIQUEIDSETTING, szUid);
+ }
+ return hContact;
+ }
+ }
+ hContact = FindNextContact(hContact);
+ }
+
+ //not present: add
+ if (Added)
+ {
+ hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0);
+ CallService(MS_PROTO_ADDTOCONTACT, (WPARAM)hContact, (LPARAM)m_szModuleName);
+
+ setSettingString(hContact, UNIQUEIDSETTING, szUid);
+
+ if (!bIsSyncingCL)
+ {
+ DBWriteContactSettingByte(hContact, "CList", "NotOnList", 1);
+ setContactHidden(hContact, 1);
+
+ setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+
+ if (icqOnline())
+ icq_sendNewContact(0, szUid);
+ }
+ AddToContactsCache(hContact, 0, szUid);
+ *Added = 1;
+
+ return hContact;
+ }
+
+ return INVALID_HANDLE_VALUE;
+}
+
+
+HANDLE CIcqProto::HContactFromAuthEvent(HANDLE hEvent)
+{
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ DWORD body[3];
+
+ dbei.cbBlob = sizeof(DWORD)*2;
+ dbei.pBlob = (PBYTE)&body;
+
+ if (CallService(MS_DB_EVENT_GET, (WPARAM)hEvent, (LPARAM)&dbei))
+ return INVALID_HANDLE_VALUE;
+
+ if (dbei.eventType != EVENTTYPE_AUTHREQUEST)
+ return INVALID_HANDLE_VALUE;
+
+ if (strcmpnull(dbei.szModule, m_szModuleName))
+ return INVALID_HANDLE_VALUE;
+
+ return DbGetAuthEventContact(&dbei);
+}
+
+char *NickFromHandle(HANDLE hContact)
+{
+ if (hContact == INVALID_HANDLE_VALUE)
+ return null_strdup(Translate("<invalid>"));
+
+ return null_strdup((char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, 0));
+}
+
+char *NickFromHandleUtf(HANDLE hContact)
+{
+ if (hContact == INVALID_HANDLE_VALUE)
+ return ICQTranslateUtf(LPGEN("<invalid>"));
+
+ return tchar_to_utf8((TCHAR*)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR));
+}
+
+char *strUID(DWORD dwUIN, char *pszUID)
+{
+ if (dwUIN && pszUID)
+ _ltoa(dwUIN, pszUID, 10);
+
+ return pszUID;
+}
+
+
+/* a strlen() that likes NULL */
+int __fastcall strlennull(const char *string)
+{
+ if (string)
+ return (int)strlen(string);
+
+ return 0;
+}
+
+
+/* a wcslen() that likes NULL */
+int __fastcall strlennull(const WCHAR *string)
+{
+ if (string)
+ return (int)wcslen(string);
+
+ return 0;
+}
+
+
+/* a strcmp() that likes NULL */
+int __fastcall strcmpnull(const char *str1, const char *str2)
+{
+ if (str1 && str2)
+ return strcmp(str1, str2);
+
+ if (!str1 && !str2)
+ return 0;
+
+ return 1;
+}
+
+/* a stricmp() that likes NULL */
+int __fastcall stricmpnull(const char *str1, const char *str2)
+{
+ if (str1 && str2)
+ return _stricmp(str1, str2);
+
+ if (!str1 && !str2)
+ return 0;
+
+ return 1;
+}
+
+char* __fastcall strstrnull(const char *str, const char *substr)
+{
+ if (str)
+ return (char*)strstr(str, substr);
+
+ return NULL;
+}
+
+int null_snprintf(char *buffer, size_t count, const char *fmt, ...)
+{
+ va_list va;
+ int len;
+
+ ZeroMemory(buffer, count);
+ va_start(va, fmt);
+ len = _vsnprintf(buffer, count-1, fmt, va);
+ va_end(va);
+ return len;
+}
+
+int null_snprintf(WCHAR *buffer, size_t count, const WCHAR *fmt, ...)
+{
+ va_list va;
+ int len;
+
+ ZeroMemory(buffer, count * sizeof(WCHAR));
+ va_start(va, fmt);
+ len = _vsnwprintf(buffer, count, fmt, va);
+ va_end(va);
+ return len;
+}
+
+
+char* __fastcall null_strdup(const char *string)
+{
+ if (string)
+ return _strdup(string);
+
+ return NULL;
+}
+
+
+WCHAR* __fastcall null_strdup(const WCHAR *string)
+{
+ if (string)
+ return wcsdup(string);
+
+ return NULL;
+}
+
+
+char* __fastcall null_strcpy(char *dest, const char *src, size_t maxlen)
+{
+ if (!dest)
+ return NULL;
+
+ if (src && src[0])
+ {
+ strncpy(dest, src, maxlen);
+ dest[maxlen] = '\0';
+ }
+ else
+ dest[0] = '\0';
+
+ return dest;
+}
+
+
+WCHAR* __fastcall null_strcpy(WCHAR *dest, const WCHAR *src, size_t maxlen)
+{
+ if (!dest)
+ return NULL;
+
+ if (src && src[0])
+ {
+ wcsncpy(dest, src, maxlen);
+ dest[maxlen] = '\0';
+ }
+ else
+ dest[0] = '\0';
+
+ return dest;
+}
+
+
+int __fastcall null_strcut(char *string, int maxlen)
+{ // limit the string to max length (null & utf-8 strings ready)
+ int len = (int)strlennull(string);
+
+ if (len < maxlen)
+ return len;
+
+ len = maxlen;
+
+ if (UTF8_IsValid(string)) // handle utf-8 string
+ { // find the first byte of possible multi-byte character
+ while ((string[len] & 0xc0) == 0x80) len--;
+ }
+ // simply cut the string
+ string[len] = '\0';
+
+ return len;
+}
+
+
+void parseServerAddress(char* szServer, WORD* wPort)
+{
+ int i = 0;
+
+ while (szServer[i] && szServer[i] != ':') i++;
+ if (szServer[i] == ':')
+ { // port included
+ *wPort = atoi(&szServer[i + 1]);
+ } // otherwise do not change port
+
+ szServer[i] = '\0';
+}
+
+char *DemangleXml(const char *string, int len)
+{
+ char *szWork = (char*)SAFE_MALLOC(len+1), *szChar = szWork;
+ int i;
+
+ for (i=0; i<len; i++)
+ {
+ if (!_strnicmp(string+i, "&gt;", 4))
+ {
+ *szChar = '>';
+ szChar++;
+ i += 3;
+ }
+ else if (!_strnicmp(string+i, "&lt;", 4))
+ {
+ *szChar = '<';
+ szChar++;
+ i += 3;
+ }
+ else if (!_strnicmp(string+i, "&amp;", 5))
+ {
+ *szChar = '&';
+ szChar++;
+ i += 4;
+ }
+ else if (!_strnicmp(string+i, "&quot;", 6))
+ {
+ *szChar = '"';
+ szChar++;
+ i += 5;
+ }
+ else
+ {
+ *szChar = string[i];
+ szChar++;
+ }
+ }
+ *szChar = '\0';
+
+ return szWork;
+}
+
+char *MangleXml(const char *string, int len)
+{
+ int i, l = 1;
+ char *szWork, *szChar;
+
+ for (i = 0; i<len; i++)
+ {
+ if (string[i]=='<' || string[i]=='>') l += 4; else if (string[i]=='&') l += 5; else if (string[i]=='"') l += 6; else l++;
+ }
+ szChar = szWork = (char*)SAFE_MALLOC(l + 1);
+ for (i = 0; i<len; i++)
+ {
+ if (string[i]=='<')
+ {
+ *(DWORD*)szChar = ';tl&';
+ szChar += 4;
+ }
+ else if (string[i]=='>')
+ {
+ *(DWORD*)szChar = ';tg&';
+ szChar += 4;
+ }
+ else if (string[i]=='&')
+ {
+ *(DWORD*)szChar = 'pma&';
+ szChar += 4;
+ *szChar = ';';
+ szChar++;
+ }
+ else if (string[i]=='"')
+ {
+ *(DWORD*)szChar = 'ouq&';
+ szChar += 4;
+ *(WORD*)szChar = ';t';
+ szChar += 2;
+ }
+ else
+ {
+ *szChar = string[i];
+ szChar++;
+ }
+ }
+ *szChar = '\0';
+
+ return szWork;
+}
+
+char *EliminateHtml(const char *string, int len)
+{
+ char *tmp = (char*)SAFE_MALLOC(len + 1);
+ int i,j;
+ BOOL tag = FALSE;
+ char *res;
+
+ for (i=0,j=0;i<len;i++)
+ {
+ if (!tag && string[i] == '<')
+ {
+ if ((i + 4 <= len) && (!_strnicmp(string + i, "<br>", 4) || !_strnicmp(string + i, "<br/>", 5)))
+ { // insert newline
+ tmp[j] = '\r';
+ j++;
+ tmp[j] = '\n';
+ j++;
+ }
+ tag = TRUE;
+ }
+ else if (tag && string[i] == '>')
+ {
+ tag = FALSE;
+ }
+ else if (!tag)
+ {
+ tmp[j] = string[i];
+ j++;
+ }
+ tmp[j] = '\0';
+ }
+ SAFE_FREE((void**)&string);
+ res = DemangleXml(tmp, strlennull(tmp));
+ SAFE_FREE((void**)&tmp);
+
+ return res;
+}
+
+char *ApplyEncoding(const char *string, const char *pszEncoding)
+{ // decode encoding to Utf-8
+ if (string && pszEncoding)
+ { // we do only encodings known to icq5.1 // TODO: check if this is enough
+ if (!_strnicmp(pszEncoding, "utf-8", 5))
+ { // it is utf-8 encoded
+ return null_strdup(string);
+ }
+ if (!_strnicmp(pszEncoding, "unicode-2-0", 11))
+ { // it is UCS-2 encoded
+ int wLen = strlennull((WCHAR*)string) + 1;
+ WCHAR *szStr = (WCHAR*)_alloca(wLen*2);
+ BYTE *tmp = (BYTE*)string;
+
+ unpackWideString(&tmp, szStr, (WORD)(wLen*2));
+
+ return make_utf8_string(szStr);
+ }
+ if (!_strnicmp(pszEncoding, "iso-8859-1", 10))
+ { // we use "Latin I" instead - it does the job
+ return ansi_to_utf8_codepage(string, 1252);
+ }
+ }
+ if (string)
+ { // consider it CP_ACP
+ return ansi_to_utf8(string);
+ }
+
+ return NULL;
+}
+
+void CIcqProto::ResetSettingsOnListReload()
+{
+ // Reset a bunch of session specific settings
+ setSettingWord(NULL, DBSETTING_SERVLIST_PRIVACY, 0);
+ setSettingWord(NULL, DBSETTING_SERVLIST_METAINFO, 0);
+ setSettingWord(NULL, DBSETTING_SERVLIST_AVATAR, 0);
+ setSettingWord(NULL, DBSETTING_SERVLIST_PHOTO, 0);
+ setSettingWord(NULL, "SrvRecordCount", 0);
+ deleteSetting(NULL, DBSETTING_SERVLIST_UNHANDLED);
+
+ HANDLE hContact = FindFirstContact();
+
+ while (hContact)
+ {
+ // All these values will be restored during the serv-list receive
+ setSettingWord(hContact, DBSETTING_SERVLIST_ID, 0);
+ setSettingWord(hContact, DBSETTING_SERVLIST_GROUP, 0);
+ setSettingWord(hContact, DBSETTING_SERVLIST_PERMIT, 0);
+ setSettingWord(hContact, DBSETTING_SERVLIST_DENY, 0);
+ deleteSetting(hContact, DBSETTING_SERVLIST_IGNORE);
+ setSettingByte(hContact, "Auth", 0);
+ deleteSetting(hContact, DBSETTING_SERVLIST_DATA);
+
+ hContact = FindNextContact(hContact);
+ }
+
+ FlushSrvGroupsCache();
+}
+
+void CIcqProto::ResetSettingsOnConnect()
+{
+ // Reset a bunch of session specific settings
+ setSettingByte(NULL, "SrvVisibility", 0);
+ setSettingDword(NULL, "IdleTS", 0);
+
+ HANDLE hContact = FindFirstContact();
+
+ while (hContact)
+ {
+ setSettingDword(hContact, "LogonTS", 0);
+ setSettingDword(hContact, "IdleTS", 0);
+ setSettingDword(hContact, "TickTS", 0);
+ setSettingByte(hContact, "TemporaryVisible", 0);
+
+ // All these values will be restored during the login
+ if (getContactStatus(hContact) != ID_STATUS_OFFLINE)
+ setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+
+ hContact = FindNextContact(hContact);
+ }
+}
+
+void CIcqProto::ResetSettingsOnLoad()
+{
+ setSettingDword(NULL, "IdleTS", 0);
+ setSettingDword(NULL, "LogonTS", 0);
+
+ HANDLE hContact = FindFirstContact();
+
+ while (hContact)
+ {
+ setSettingDword(hContact, "LogonTS", 0);
+ setSettingDword(hContact, "IdleTS", 0);
+ setSettingDword(hContact, "TickTS", 0);
+ if (getContactStatus(hContact) != ID_STATUS_OFFLINE)
+ {
+ setSettingWord(hContact, "Status", ID_STATUS_OFFLINE);
+
+ deleteSetting(hContact, DBSETTING_XSTATUS_ID);
+ deleteSetting(hContact, DBSETTING_XSTATUS_NAME);
+ deleteSetting(hContact, DBSETTING_XSTATUS_MSG);
+ }
+ setSettingByte(hContact, "DCStatus", 0);
+
+ hContact = FindNextContact(hContact);
+ }
+}
+
+int RandRange(int nLow, int nHigh)
+{
+ return nLow + (int)((nHigh-nLow+1)*rand()/(RAND_MAX+1.0));
+}
+
+
+BOOL IsStringUIN(const char *pszString)
+{
+ int i;
+ int nLen = strlennull(pszString);
+
+ if (nLen > 0 && pszString[0] != '0')
+ {
+ for (i=0; i<nLen; i++)
+ if ((pszString[i] < '0') || (pszString[i] > '9'))
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+void __cdecl CIcqProto::ProtocolAckThread(icq_ack_args* pArguments)
+{
+ Sleep(150);
+
+ if (pArguments->nAckResult == ACKRESULT_SUCCESS)
+ NetLog_Server("Sent fake message ack");
+ else if (pArguments->nAckResult == ACKRESULT_FAILED)
+ NetLog_Server("Message delivery failed");
+
+ BroadcastAck(pArguments->hContact, pArguments->nAckType, pArguments->nAckResult, pArguments->hSequence, pArguments->pszMessage);
+
+ SAFE_FREE((void**)(char **)&pArguments->pszMessage);
+ SAFE_FREE((void**)&pArguments);
+}
+
+void CIcqProto::SendProtoAck(HANDLE hContact, DWORD dwCookie, int nAckResult, int nAckType, char* pszMessage)
+{
+ icq_ack_args* pArgs = (icq_ack_args*)SAFE_MALLOC(sizeof(icq_ack_args)); // This will be freed in the new thread
+ pArgs->hContact = hContact;
+ pArgs->hSequence = (HANDLE)dwCookie;
+ pArgs->nAckResult = nAckResult;
+ pArgs->nAckType = nAckType;
+ pArgs->pszMessage = (LPARAM)null_strdup(pszMessage);
+
+ ForkThread(( IcqThreadFunc )&CIcqProto::ProtocolAckThread, pArgs );
+}
+
+void CIcqProto::SetCurrentStatus(int nStatus)
+{
+ int nOldStatus = m_iStatus;
+
+ m_iStatus = nStatus;
+ BroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)nOldStatus, nStatus);
+}
+
+
+int CIcqProto::IsMetaInfoChanged(HANDLE hContact)
+{
+ DBVARIANT infoToken = {DBVT_DELETED};
+ int res = 0;
+
+ if (!getSetting(hContact, DBSETTING_METAINFO_TOKEN, &infoToken))
+ { // contact does have info from directory, check if it is not outdated
+ double dInfoTime = 0;
+ double dInfoSaved = 0;
+
+ if ((dInfoTime = getSettingDouble(hContact, DBSETTING_METAINFO_TIME, 0)) > 0)
+ {
+ if ((dInfoSaved = getSettingDouble(hContact, DBSETTING_METAINFO_SAVED, 0)) > 0)
+ {
+ if (dInfoSaved < dInfoTime)
+ res = 2; // directory info outdated
+ }
+ else
+ res = 1; // directory info not saved at all
+ }
+
+ ICQFreeVariant(&infoToken);
+ }
+ else
+ { // it cannot be detected if user info was not changed, so use a generic threshold
+ DBVARIANT infoSaved = {DBVT_DELETED};
+ DWORD dwInfoTime = 0;
+
+ if (!getSetting(hContact, DBSETTING_METAINFO_SAVED, &infoSaved))
+ {
+ if (infoSaved.type == DBVT_BLOB && infoSaved.cpbVal == 8)
+ {
+ double dwTime = *(double*)infoSaved.pbVal;
+
+ dwInfoTime = (dwTime - 25567) * 86400;
+ }
+ else if (infoSaved.type == DBVT_DWORD)
+ dwInfoTime = infoSaved.dVal;
+
+ ICQFreeVariant(&infoSaved);
+
+ if ((time(NULL) - dwInfoTime) > 14*3600*24)
+ {
+ res = 3; // threshold exceeded
+ }
+ }
+ else
+ res = 4; // no timestamp found
+ }
+
+ return res;
+}
+
+
+void __cdecl CIcqProto::SetStatusNoteThread(void *pDelay)
+{
+ if (pDelay)
+ SleepEx((DWORD)pDelay, TRUE);
+
+ cookieMutex->Enter();
+
+ if (icqOnline() && (setStatusNoteText || setStatusMoodData))
+ { // send status note change packets, write status note to database
+ if (setStatusNoteText)
+ { // change status note in directory
+ m_ratesMutex->Enter();
+ if (m_rates)
+ { // rate management
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_EXTENSIONS_FAMILY, ICQ_META_CLI_REQUEST);
+
+ while (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_LIMIT))
+ { // we are over rate, need to wait before sending
+ int nDelay = m_rates->getDelayToLimitLevel(wGroup, RML_IDLE_10);
+
+ m_ratesMutex->Leave();
+ cookieMutex->Leave();
+#ifdef _DEBUG
+ NetLog_Server("Rates: SetStatusNote delayed %dms", nDelay);
+#endif
+ SleepEx(nDelay, TRUE); // do not keep things locked during sleep
+ cookieMutex->Enter();
+ m_ratesMutex->Enter();
+ if (!m_rates) // we lost connection when we slept, go away
+ break;
+ }
+ }
+ m_ratesMutex->Leave();
+
+ BYTE *pBuffer = NULL;
+ int cbBuffer = 0;
+
+ ppackTLV(&pBuffer, &cbBuffer, 0x226, strlennull(setStatusNoteText), (BYTE*)setStatusNoteText);
+ icq_changeUserDirectoryInfoServ(pBuffer, cbBuffer, DIRECTORYREQUEST_UPDATENOTE);
+
+ SAFE_FREE((void**)&pBuffer);
+ }
+
+ if (setStatusNoteText || setStatusMoodData)
+ { // change status note and mood in session data
+ m_ratesMutex->Enter();
+ if (m_rates)
+ { // rate management
+ WORD wGroup = m_rates->getGroupFromSNAC(ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS);
+
+ while (m_rates->getNextRateLevel(wGroup) < m_rates->getLimitLevel(wGroup, RML_LIMIT))
+ { // we are over rate, need to wait before sending
+ int nDelay = m_rates->getDelayToLimitLevel(wGroup, RML_IDLE_10);
+
+ m_ratesMutex->Leave();
+ cookieMutex->Leave();
+#ifdef _DEBUG
+ NetLog_Server("Rates: SetStatusNote delayed %dms", nDelay);
+#endif
+ SleepEx(nDelay, TRUE); // do not keep things locked during sleep
+ cookieMutex->Enter();
+ m_ratesMutex->Enter();
+ if (!m_rates) // we lost connection when we slept, go away
+ break;
+ }
+ }
+ m_ratesMutex->Leave();
+
+ // check if the session data were not updated already
+ char *szCurrentStatusNote = getSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, NULL);
+ char *szCurrentStatusMood = NULL;
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (m_bMoodsEnabled && !getSettingString(NULL, DBSETTING_STATUS_MOOD, &dbv))
+ szCurrentStatusMood = dbv.pszVal;
+
+ if (!setStatusNoteText && szCurrentStatusNote)
+ setStatusNoteText = null_strdup(szCurrentStatusNote);
+ if (m_bMoodsEnabled && !setStatusMoodData && szCurrentStatusMood)
+ setStatusMoodData = null_strdup(szCurrentStatusMood);
+
+ if (strcmpnull(szCurrentStatusNote, setStatusNoteText) || (m_bMoodsEnabled && strcmpnull(szCurrentStatusMood, setStatusMoodData)))
+ {
+ setSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, setStatusNoteText);
+ if (m_bMoodsEnabled)
+ setSettingString(NULL, DBSETTING_STATUS_MOOD, setStatusMoodData);
+
+ WORD wStatusNoteLen = strlennull(setStatusNoteText);
+ WORD wStatusMoodLen = m_bMoodsEnabled ? strlennull(setStatusMoodData) : 0;
+ icq_packet packet;
+ WORD wDataLen = (wStatusNoteLen ? wStatusNoteLen + 4 : 0) + 4 + wStatusMoodLen + 4;
+
+ serverPacketInit(&packet, wDataLen + 14);
+ packFNACHeader(&packet, ICQ_SERVICE_FAMILY, ICQ_CLIENT_SET_STATUS);
+ // Change only session data
+ packWord(&packet, 0x1D); // TLV 1D
+ packWord(&packet, wDataLen); // TLV length
+ packWord(&packet, 0x02); // Item Type
+ if (wStatusNoteLen)
+ {
+ packWord(&packet, 0x400 | (WORD)(wStatusNoteLen + 4)); // Flags + Item Length
+ packWord(&packet, wStatusNoteLen); // Text Length
+ packBuffer(&packet, (LPBYTE)setStatusNoteText, wStatusNoteLen);
+ packWord(&packet, 0); // Encoding not specified (utf-8 is default)
+ }
+ else
+ packWord(&packet, 0); // Flags + Item Length
+ packWord(&packet, 0x0E); // Item Type
+ packWord(&packet, wStatusMoodLen); // Flags + Item Length
+ if (wStatusMoodLen)
+ packBuffer(&packet, (LPBYTE)setStatusMoodData, wStatusMoodLen); // Mood
+
+ sendServPacket(&packet);
+ }
+ SAFE_FREE(&szCurrentStatusNote);
+ ICQFreeVariant(&dbv);
+ }
+ }
+ SAFE_FREE(&setStatusNoteText);
+ SAFE_FREE(&setStatusMoodData);
+
+ cookieMutex->Leave();
+}
+
+
+int CIcqProto::SetStatusNote(const char *szStatusNote, DWORD dwDelay, int bForce)
+{
+ int bChanged = FALSE;
+
+ // bForce is intended for login sequence - need to call this earlier than icqOnline()
+ // the process is delayed and icqOnline() is ready when needed inside SetStatusNoteThread()
+ if (!bForce && !icqOnline()) return bChanged;
+
+ // reuse generic critical section (used for cookies list and object variables locks)
+ icq_lock l(cookieMutex);
+
+ if (!setStatusNoteText && (!m_bMoodsEnabled || !setStatusMoodData))
+ { // check if the status note was changed and if yes, create thread to change it
+ char *szCurrentStatusNote = getSettingStringUtf(NULL, DBSETTING_STATUS_NOTE, NULL);
+
+ if (strcmpnull(szCurrentStatusNote, szStatusNote))
+ { // status note was changed
+ // create thread to change status note on existing server connection
+ setStatusNoteText = null_strdup(szStatusNote);
+
+ if (dwDelay)
+ ForkThread(&CIcqProto::SetStatusNoteThread, (void*)dwDelay);
+ else // we cannot afford any delay, so do not run in separate thread
+ SetStatusNoteThread(NULL);
+
+ bChanged = TRUE;
+ }
+ SAFE_FREE(&szCurrentStatusNote);
+ }
+ else
+ { // only alter status note object with new status note, keep the thread waiting for execution
+ SAFE_FREE(&setStatusNoteText);
+ setStatusNoteText = null_strdup(szStatusNote);
+
+ bChanged = TRUE;
+ }
+
+ return bChanged;
+}
+
+
+int CIcqProto::SetStatusMood(const char *szMoodData, DWORD dwDelay)
+{
+ int bChanged = FALSE;
+
+ if (!icqOnline()) return bChanged;
+
+ // reuse generic critical section (used for cookies list and object variables locks)
+ icq_lock l(cookieMutex);
+
+ if (!setStatusNoteText && !setStatusMoodData)
+ { // check if the status mood was changed and if yes, create thread to change it
+ char *szCurrentStatusMood = NULL;
+ DBVARIANT dbv = {DBVT_DELETED};
+
+ if (!getSettingString(NULL, DBSETTING_STATUS_MOOD, &dbv))
+ szCurrentStatusMood = dbv.pszVal;
+
+ if (strcmpnull(szCurrentStatusMood, szMoodData))
+ { // status mood was changed
+ // create thread to change status mood on existing server connection
+ setStatusMoodData = null_strdup(szMoodData);
+ if (dwDelay)
+ ForkThread(&CIcqProto::SetStatusNoteThread, (void*)dwDelay);
+ else // we cannot afford any delay, so do not run in separate thread
+ SetStatusNoteThread(NULL);
+
+ bChanged = TRUE;
+ }
+ ICQFreeVariant(&dbv);
+ }
+ else
+ { // only alter status mood object with new status mood, keep the thread waiting for execution
+ SAFE_FREE(&setStatusMoodData);
+ setStatusMoodData = null_strdup(szMoodData);
+
+ bChanged = TRUE;
+ }
+
+ return bChanged;
+}
+
+
+void CIcqProto::writeDbInfoSettingTLVStringUtf(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv)
+{
+ oscar_tlv *pTLV = chain->getTLV(wTlv, 1);
+
+ if (pTLV && pTLV->wLen > 0)
+ {
+ char *str = (char*)_alloca(pTLV->wLen + 1);
+
+ memcpy(str, pTLV->pData, pTLV->wLen);
+ str[pTLV->wLen] = '\0';
+ setSettingStringUtf(hContact, szSetting, str);
+ }
+ else
+ deleteSetting(hContact, szSetting);
+}
+
+
+void CIcqProto::writeDbInfoSettingTLVString(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv)
+{
+ oscar_tlv *pTLV = chain->getTLV(wTlv, 1);
+
+ if (pTLV && pTLV->wLen > 0)
+ {
+ char *str = (char*)_alloca(pTLV->wLen + 1);
+
+ memcpy(str, pTLV->pData, pTLV->wLen);
+ str[pTLV->wLen] = '\0';
+ setSettingString(hContact, szSetting, str);
+ }
+ else
+ deleteSetting(hContact, szSetting);
+}
+
+
+void CIcqProto::writeDbInfoSettingTLVWord(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv)
+{
+ int num = chain->getNumber(wTlv, 1);
+
+ if (num > 0)
+ setSettingWord(hContact, szSetting, num);
+ else
+ deleteSetting(hContact, szSetting);
+}
+
+
+void CIcqProto::writeDbInfoSettingTLVByte(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv)
+{
+ int num = chain->getNumber(wTlv, 1);
+
+ if (num > 0)
+ setSettingByte(hContact, szSetting, num);
+ else
+ deleteSetting(hContact, szSetting);
+}
+
+
+void CIcqProto::writeDbInfoSettingTLVDouble(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv)
+{
+ double num = chain->getDouble(wTlv, 1);
+
+ if (num > 0)
+ setSettingDouble(hContact, szSetting, num);
+ else
+ deleteSetting(hContact, szSetting);
+}
+
+void CIcqProto::writeDbInfoSettingTLVDate(HANDLE hContact, const char* szSettingYear, const char* szSettingMonth, const char* szSettingDay, oscar_tlv_chain* chain, WORD wTlv)
+{
+ double time = chain->getDouble(wTlv, 1);
+
+ if (time > 0)
+ { // date is stored as double with unit equal to a day, incrementing since 1/1/1900 0:00 GMT
+ SYSTEMTIME sTime = {0};
+ if (VariantTimeToSystemTime(time + 2, &sTime))
+ {
+ setSettingWord(hContact, szSettingYear, sTime.wYear);
+ setSettingByte(hContact, szSettingMonth, (BYTE)sTime.wMonth);
+ setSettingByte(hContact, szSettingDay, (BYTE)sTime.wDay);
+ }
+ else
+ {
+ deleteSetting(hContact, szSettingYear);
+ deleteSetting(hContact, szSettingMonth);
+ deleteSetting(hContact, szSettingDay);
+ }
+ }
+ else
+ {
+ deleteSetting(hContact, szSettingYear);
+ deleteSetting(hContact, szSettingMonth);
+ deleteSetting(hContact, szSettingDay);
+ }
+}
+
+
+void CIcqProto::writeDbInfoSettingTLVBlob(HANDLE hContact, const char *szSetting, oscar_tlv_chain *chain, WORD wTlv)
+{
+ oscar_tlv *pTLV = chain->getTLV(wTlv, 1);
+
+ if (pTLV && pTLV->wLen > 0)
+ setSettingBlob(hContact, szSetting, pTLV->pData, pTLV->wLen);
+ else
+ deleteSetting(hContact, szSetting);
+}
+
+
+BOOL CIcqProto::writeDbInfoSettingString(HANDLE hContact, const char* szSetting, char** buf, WORD* pwLength)
+{
+ WORD wLen;
+
+ if (*pwLength < 2)
+ return FALSE;
+
+ unpackLEWord((LPBYTE*)buf, &wLen);
+ *pwLength -= 2;
+
+ if (*pwLength < wLen)
+ return FALSE;
+
+ if ((wLen > 0) && (**buf) && ((*buf)[wLen-1]==0)) // Make sure we have a proper string
+ {
+ WORD wCp = getSettingWord(hContact, "InfoCodePage", getSettingWord(hContact, "InfoCP", CP_ACP));
+
+ if (wCp != CP_ACP)
+ {
+ char *szUtf = ansi_to_utf8_codepage(*buf, wCp);
+
+ if (szUtf)
+ {
+ setSettingStringUtf(hContact, szSetting, szUtf);
+ SAFE_FREE((void**)&szUtf);
+ }
+ else
+ setSettingString(hContact, szSetting, *buf);
+ }
+ else
+ setSettingString(hContact, szSetting, *buf);
+ }
+ else
+ deleteSetting(hContact, szSetting);
+
+ *buf += wLen;
+ *pwLength -= wLen;
+
+ return TRUE;
+}
+
+BOOL CIcqProto::writeDbInfoSettingWord(HANDLE hContact, const char *szSetting, char **buf, WORD* pwLength)
+{
+ WORD wVal;
+
+
+ if (*pwLength < 2)
+ return FALSE;
+
+ unpackLEWord((LPBYTE*)buf, &wVal);
+ *pwLength -= 2;
+
+ if (wVal != 0)
+ setSettingWord(hContact, szSetting, wVal);
+ else
+ deleteSetting(hContact, szSetting);
+
+ return TRUE;
+}
+
+BOOL CIcqProto::writeDbInfoSettingWordWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength)
+{
+ WORD wVal;
+ char sbuf[MAX_PATH];
+ char *text;
+
+ if (*pwLength < 2)
+ return FALSE;
+
+ unpackLEWord((LPBYTE*)buf, &wVal);
+ *pwLength -= 2;
+
+ text = LookupFieldNameUtf(table, wVal, sbuf, MAX_PATH);
+ if (text)
+ setSettingStringUtf(hContact, szSetting, text);
+ else
+ deleteSetting(hContact, szSetting);
+
+ return TRUE;
+}
+
+BOOL CIcqProto::writeDbInfoSettingByte(HANDLE hContact, const char *pszSetting, char **buf, WORD* pwLength)
+{
+ BYTE byVal;
+
+ if (*pwLength < 1)
+ return FALSE;
+
+ unpackByte((LPBYTE*)buf, &byVal);
+ *pwLength -= 1;
+
+ if (byVal != 0)
+ setSettingByte(hContact, pszSetting, byVal);
+ else
+ deleteSetting(hContact, pszSetting);
+
+ return TRUE;
+}
+
+BOOL CIcqProto::writeDbInfoSettingByteWithTable(HANDLE hContact, const char *szSetting, const FieldNamesItem *table, char **buf, WORD* pwLength)
+{
+ BYTE byVal;
+ char sbuf[MAX_PATH];
+ char *text;
+
+ if (*pwLength < 1)
+ return FALSE;
+
+ unpackByte((LPBYTE*)buf, &byVal);
+ *pwLength -= 1;
+
+ text = LookupFieldNameUtf(table, byVal, sbuf, MAX_PATH);
+ if (text)
+ setSettingStringUtf(hContact, szSetting, text);
+ else
+ deleteSetting(hContact, szSetting);
+
+ return TRUE;
+}
+
+char* time2text(time_t time)
+{
+ tm *local = localtime(&time);
+
+ if (local)
+ {
+ char *str = asctime(local);
+ str[24] = '\0'; // remove new line
+ return str;
+ }
+ else
+ return "<invalid>";
+}
+
+
+BOOL CIcqProto::validateStatusMessageRequest(HANDLE hContact, WORD byMessageType)
+{
+ // Privacy control
+ if (getSettingByte(NULL, "StatusMsgReplyCList", 0))
+ {
+ // Don't send statusmessage to unknown contacts
+ if (hContact == INVALID_HANDLE_VALUE)
+ return FALSE;
+
+ // Don't send statusmessage to temporary contacts or hidden contacts
+ if (DBGetContactSettingByte(hContact, "CList", "NotOnList", 0) ||
+ DBGetContactSettingByte(hContact, "CList", "Hidden", 0))
+ return FALSE;
+
+ // Don't send statusmessage to invisible contacts
+ if (getSettingByte(NULL, "StatusMsgReplyVisible", 0))
+ {
+ WORD wStatus = getContactStatus(hContact);
+ if (wStatus == ID_STATUS_OFFLINE)
+ return FALSE;
+ }
+ }
+
+ // Dont send messages to people you are hiding from
+ if (hContact != INVALID_HANDLE_VALUE &&
+ getSettingWord(hContact, "ApparentMode", 0) == ID_STATUS_OFFLINE)
+ {
+ return FALSE;
+ }
+
+ // Dont respond to request for other statuses than your current one
+ if ((byMessageType == MTYPE_AUTOONLINE && m_iStatus != ID_STATUS_ONLINE) ||
+ (byMessageType == MTYPE_AUTOAWAY && m_iStatus != ID_STATUS_AWAY) ||
+ (byMessageType == MTYPE_AUTOBUSY && m_iStatus != ID_STATUS_OCCUPIED) ||
+ (byMessageType == MTYPE_AUTONA && m_iStatus != ID_STATUS_NA) ||
+ (byMessageType == MTYPE_AUTODND && m_iStatus != ID_STATUS_DND) ||
+ (byMessageType == MTYPE_AUTOFFC && m_iStatus != ID_STATUS_FREECHAT))
+ {
+ return FALSE;
+ }
+
+ if (hContact != INVALID_HANDLE_VALUE && m_iStatus==ID_STATUS_INVISIBLE &&
+ getSettingWord(hContact, "ApparentMode", 0) != ID_STATUS_ONLINE)
+ {
+ if (!getSettingByte(hContact, "TemporaryVisible", 0))
+ { // Allow request to temporary visible contacts
+ return FALSE;
+ }
+ }
+
+ // All OK!
+ return TRUE;
+}
+
+
+void __fastcall SAFE_DELETE(MZeroedObject **p)
+{
+ if (*p)
+ {
+ delete *p;
+ *p = NULL;
+ }
+}
+
+
+void __fastcall SAFE_DELETE(lockable_struct **p)
+{
+ if (*p)
+ {
+ (*p)->_Release();
+ *p = NULL;
+ }
+}
+
+
+void __fastcall SAFE_FREE(void** p)
+{
+ if (*p)
+ {
+ free(*p);
+ *p = NULL;
+ }
+}
+
+
+void* __fastcall SAFE_MALLOC(size_t size)
+{
+ void* p = NULL;
+
+ if (size)
+ {
+ p = malloc(size);
+
+ if (p)
+ ZeroMemory(p, size);
+ }
+ return p;
+}
+
+
+void* __fastcall SAFE_REALLOC(void* p, size_t size)
+{
+ if (p)
+ {
+ return realloc(p, size);
+ }
+ else
+ return SAFE_MALLOC(size);
+}
+
+
+DWORD ICQWaitForSingleObject(HANDLE hObject, DWORD dwMilliseconds, int bWaitAlways)
+{
+ DWORD dwResult;
+
+ do { // will get WAIT_IO_COMPLETION for QueueUserAPC(), ignore it unless terminating
+ dwResult = WaitForSingleObjectEx(hObject, dwMilliseconds, TRUE);
+ } while (dwResult == WAIT_IO_COMPLETION && (bWaitAlways || !Miranda_Terminated()));
+
+ return dwResult;
+}
+
+
+HANDLE NetLib_OpenConnection(HANDLE hUser, const char* szIdent, NETLIBOPENCONNECTION* nloc)
+{
+ Netlib_Logf(hUser, "%sConnecting to %s:%u", szIdent?szIdent:"", nloc->szHost, nloc->wPort);
+
+ nloc->cbSize = sizeof(NETLIBOPENCONNECTION);
+ nloc->flags |= NLOCF_V2;
+
+ return (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)hUser, (LPARAM)nloc);
+}
+
+
+HANDLE CIcqProto::NetLib_BindPort(NETLIBNEWCONNECTIONPROC_V2 pFunc, void* lParam, WORD* pwPort, DWORD* pdwIntIP)
+{
+ NETLIBBIND nlb = {0};
+
+ nlb.cbSize = sizeof(NETLIBBIND);
+ nlb.pfnNewConnectionV2 = pFunc;
+ nlb.pExtra = lParam;
+ SetLastError(ERROR_INVALID_PARAMETER); // this must be here - NetLib does not set any error :((
+
+ HANDLE hBoundPort = (HANDLE)CallService(MS_NETLIB_BINDPORT, (WPARAM)m_hDirectNetlibUser, (LPARAM)&nlb);
+
+ if (pwPort) *pwPort = nlb.wPort;
+ if (pdwIntIP) *pdwIntIP = nlb.dwInternalIP;
+
+ return hBoundPort;
+}
+
+
+void NetLib_CloseConnection(HANDLE *hConnection, int bServerConn)
+{
+ if (*hConnection)
+ {
+ NetLib_SafeCloseHandle(hConnection);
+
+ if (bServerConn)
+ FreeGatewayIndex(*hConnection);
+ }
+}
+
+
+void NetLib_SafeCloseHandle(HANDLE *hConnection)
+{
+ if (*hConnection)
+ {
+ Netlib_CloseHandle(*hConnection);
+ *hConnection = NULL;
+ }
+}
+
+
+int CIcqProto::NetLog_Server(const char *fmt,...)
+{
+ va_list va;
+ char szText[1024];
+
+ va_start(va,fmt);
+ mir_vsnprintf(szText,sizeof(szText),fmt,va);
+ va_end(va);
+ return CallService(MS_NETLIB_LOG,(WPARAM)m_hServerNetlibUser,(LPARAM)szText);
+}
+
+int CIcqProto::NetLog_Direct(const char *fmt,...)
+{
+ va_list va;
+ char szText[1024];
+
+ va_start(va,fmt);
+ mir_vsnprintf(szText,sizeof(szText),fmt,va);
+ va_end(va);
+ return CallService(MS_NETLIB_LOG,(WPARAM)m_hDirectNetlibUser,(LPARAM)szText);
+}
+
+int CIcqProto::NetLog_Uni(BOOL bDC, const char *fmt,...)
+{
+ va_list va;
+ char szText[1024];
+ HANDLE hNetlib;
+
+ va_start(va,fmt);
+ mir_vsnprintf(szText,sizeof(szText),fmt,va);
+ va_end(va);
+
+ if (bDC)
+ hNetlib = m_hDirectNetlibUser;
+ else
+ hNetlib = m_hServerNetlibUser;
+
+ return CallService(MS_NETLIB_LOG,(WPARAM)hNetlib,(LPARAM)szText);
+}
+
+int CIcqProto::BroadcastAck(HANDLE hContact,int type,int result,HANDLE hProcess,LPARAM lParam)
+{
+ ACKDATA ack={0};
+
+ ack.cbSize = sizeof(ACKDATA);
+ ack.szModule = m_szModuleName;
+ ack.hContact = hContact;
+ ack.type = type;
+ ack.result = result;
+ ack.hProcess = hProcess;
+ ack.lParam = lParam;
+ return CallService(MS_PROTO_BROADCASTACK,0,(LPARAM)&ack);
+}
+
+char* __fastcall ICQTranslateUtf(const char *src)
+{ // this takes UTF-8 strings only!!!
+ char *szRes = NULL;
+
+ if (!strlennull(src))
+ { // for the case of empty strings
+ return null_strdup(src);
+ }
+
+ { // we can use unicode translate (0.5+)
+ WCHAR* usrc = make_unicode_string(src);
+
+ szRes = make_utf8_string(TranslateW(usrc));
+
+ SAFE_FREE((void**)&usrc);
+ }
+ return szRes;
+}
+
+char* __fastcall ICQTranslateUtfStatic(const char *src, char *buf, size_t bufsize)
+{ // this takes UTF-8 strings only!!!
+ if (strlennull(src))
+ { // we can use unicode translate (0.5+)
+ WCHAR *usrc = make_unicode_string(src);
+
+ make_utf8_string_static(TranslateW(usrc), buf, bufsize);
+
+ SAFE_FREE((void**)&usrc);
+ }
+ else
+ buf[0] = '\0';
+
+ return buf;
+}
+
+void CIcqProto::ForkThread( IcqThreadFunc pFunc, void* arg )
+{
+ CloseHandle(( HANDLE )mir_forkthreadowner(( pThreadFuncOwner )*( void** )&pFunc, this, arg, NULL ));
+}
+
+HANDLE CIcqProto::ForkThreadEx( IcqThreadFunc pFunc, void* arg, UINT* threadID )
+{
+ return ( HANDLE )mir_forkthreadowner(( pThreadFuncOwner )*( void** )&pFunc, this, arg, threadID );
+}
+
+
+char* CIcqProto::GetUserStoredPassword(char *szBuffer, int cbSize)
+{
+ if (!getSettingStringStatic(NULL, "Password", szBuffer, cbSize))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING, strlennull(szBuffer) + 1, (LPARAM)szBuffer);
+
+ if (strlennull(szBuffer))
+ return szBuffer;
+ }
+ return NULL;
+}
+
+
+char* CIcqProto::GetUserPassword(BOOL bAlways)
+{
+ if (m_szPassword[0] != '\0' && (m_bRememberPwd || bAlways))
+ return m_szPassword;
+
+ if (GetUserStoredPassword(m_szPassword, sizeof(m_szPassword)))
+ {
+ m_bRememberPwd = TRUE;
+
+ return m_szPassword;
+ }
+
+ return NULL;
+}
+
+
+WORD CIcqProto::GetMyStatusFlags()
+{
+ WORD wFlags = 0;
+
+ // Webaware setting bit flag
+ if (getSettingByte(NULL, "WebAware", 0))
+ wFlags |= STATUS_WEBAWARE;
+
+ // DC setting bit flag
+ switch (getSettingByte(NULL, "DCType", 0))
+ {
+ case 0:
+ break;
+
+ case 1:
+ wFlags |= STATUS_DCCONT;
+ break;
+
+ case 2:
+ wFlags |= STATUS_DCAUTH;
+ break;
+
+ default:
+ wFlags |= STATUS_DCDISABLED;
+ break;
+ }
+ return wFlags;
+}
+
+
+int IsValidRelativePath(const char *filename)
+{
+ if (strstrnull(filename, "..\\") || strstrnull(filename, "../") ||
+ strstrnull(filename, ":\\") || strstrnull(filename, ":/") ||
+ filename[0] == '\\' || filename[0] == '/')
+ return 0; // Contains malicious chars, Failure
+
+ return 1; // Success
+}
+
+
+const char* ExtractFileName(const char *fullname)
+{
+ const char *szFileName;
+
+ // already is only filename
+ if (((szFileName = strrchr(fullname, '\\')) == NULL) && ((szFileName = strrchr(fullname, '/')) == NULL))
+ return fullname;
+
+ return szFileName + 1; // skip backslash
+}
+
+
+char* FileNameToUtf(const TCHAR *filename)
+{
+ // reasonable only on NT systems
+ HINSTANCE hKernel = GetModuleHandle(_T("KERNEL32"));
+ DWORD (CALLBACK *RealGetLongPathName)(LPCWSTR, LPWSTR, DWORD);
+
+ *(FARPROC *)&RealGetLongPathName = GetProcAddress(hKernel, "GetLongPathNameW");
+
+ if (RealGetLongPathName)
+ { // the function is available (it is not on old NT systems)
+ WCHAR *usFileName = NULL;
+ int wchars = RealGetLongPathName(filename, usFileName, 0);
+ usFileName = (WCHAR*)_alloca((wchars + 1) * sizeof(WCHAR));
+ RealGetLongPathName(filename, usFileName, wchars);
+
+ return make_utf8_string(usFileName);
+ }
+ return make_utf8_string(filename);
+}
+
+
+int FileAccessUtf(const char *path, int mode)
+{
+ int size = strlennull(path) + 2;
+ TCHAR *szPath = (TCHAR*)_alloca(size * sizeof(TCHAR));
+
+ if (utf8_to_tchar_static(path, szPath, size))
+ return _taccess(szPath, mode);
+
+ return -1;
+}
+
+
+int FileStatUtf(const char *path, struct _stati64 *buffer)
+{
+ int size = strlennull(path) + 2;
+ TCHAR *szPath = (TCHAR*)_alloca(size * sizeof(TCHAR));
+
+ if (utf8_to_tchar_static(path, szPath, size))
+ return _tstati64(szPath, buffer);
+
+ return -1;
+}
+
+
+int MakeDirUtf(const char *dir)
+{
+ int wRes = -1;
+ int size = strlennull(dir) + 2;
+ TCHAR *szDir = (TCHAR*)_alloca(size * sizeof(TCHAR));
+
+ if (utf8_to_tchar_static(dir, szDir, size))
+ { // _tmkdir can created only one dir at once
+ wRes = _tmkdir(szDir);
+ // check if dir not already existed - return success if yes
+ if (wRes == -1 && errno == 17 /* EEXIST */)
+ wRes = 0;
+ else if (wRes && errno == 2 /* ENOENT */)
+ { // failed, try one directory less first
+ char *szLast = (char*)strrchr(dir, '\\');
+ if (!szLast) szLast = (char*)strrchr(dir, '/');
+ if (szLast)
+ {
+ char cOld = *szLast;
+
+ *szLast = '\0';
+ if (!MakeDirUtf(dir))
+ wRes = _tmkdir(szDir);
+
+ *szLast = cOld;
+ }
+ }
+ }
+
+ return wRes;
+}
+
+
+int OpenFileUtf(const char *filename, int oflag, int pmode)
+{
+ int size = strlennull(filename) + 2;
+ TCHAR *szFile = (TCHAR*)_alloca(size * sizeof(TCHAR));
+
+ if (utf8_to_tchar_static(filename, szFile, size))
+ return _topen(szFile, oflag, pmode);
+
+ return -1;
+}
+
+
+WCHAR *GetWindowTextUcs(HWND hWnd)
+{
+ WCHAR *utext;
+ int nLen = GetWindowTextLengthW(hWnd);
+ utext = (WCHAR*)SAFE_MALLOC((nLen+2)*sizeof(WCHAR));
+ GetWindowTextW(hWnd, utext, nLen + 1);
+ return utext;
+}
+
+
+void SetWindowTextUcs(HWND hWnd, WCHAR *text)
+{
+ SetWindowTextW(hWnd, text);
+}
+
+
+char* GetWindowTextUtf(HWND hWnd)
+{
+ int nLen = GetWindowTextLength(hWnd);
+ TCHAR *szText = (TCHAR*)_alloca((nLen + 2) * sizeof(TCHAR));
+
+ GetWindowText(hWnd, szText, nLen + 1);
+
+ return tchar_to_utf8(szText);
+}
+
+
+char* GetDlgItemTextUtf(HWND hwndDlg, int iItem)
+{
+ return GetWindowTextUtf(GetDlgItem(hwndDlg, iItem));
+}
+
+
+void SetWindowTextUtf(HWND hWnd, const char *szText)
+{
+ int size = strlennull(szText) + 2;
+ TCHAR *tszText = (TCHAR*)_alloca(size * sizeof(TCHAR));
+
+ if (utf8_to_tchar_static(szText, tszText, size))
+ SetWindowText(hWnd, tszText);
+}
+
+
+void SetDlgItemTextUtf(HWND hwndDlg, int iItem, const char *szText)
+{
+ SetWindowTextUtf(GetDlgItem(hwndDlg, iItem), szText);
+}
+
+
+static int ControlAddStringUtf(HWND ctrl, DWORD msg, const char *szString)
+{
+ char str[MAX_PATH];
+ char *szItem = ICQTranslateUtfStatic(szString, str, MAX_PATH);
+ int item = -1;
+ WCHAR *wItem = make_unicode_string(szItem);
+ item = SendMessage(ctrl, msg, 0, (LPARAM)wItem);
+ SAFE_FREE((void**)&wItem);
+ return item;
+}
+
+int ComboBoxAddStringUtf(HWND hCombo, const char *szString, DWORD data)
+{
+ int item = ControlAddStringUtf(hCombo, CB_ADDSTRING, szString);
+ SendMessage(hCombo, CB_SETITEMDATA, item, data);
+
+ return item;
+}
+
+int ListBoxAddStringUtf(HWND hList, const char *szString)
+{
+ return ControlAddStringUtf(hList, LB_ADDSTRING, szString);
+}
+
+int MessageBoxUtf(HWND hWnd, const char *szText, const char *szCaption, UINT uType)
+{
+ int res;
+ char str[1024];
+ char cap[MAX_PATH];
+ WCHAR *text = make_unicode_string(ICQTranslateUtfStatic(szText, str, 1024));
+ WCHAR *caption = make_unicode_string(ICQTranslateUtfStatic(szCaption, cap, MAX_PATH));
+ res = MessageBoxW(hWnd, text, caption, uType);
+ SAFE_FREE((void**)&caption);
+ SAFE_FREE((void**)&text);
+ return res;
+}
+
+char* CIcqProto::ConvertMsgToUserSpecificAnsi(HANDLE hContact, const char* szMsg)
+{ // this takes utf-8 encoded message
+ WORD wCP = getSettingWord(hContact, "CodePage", m_wAnsiCodepage);
+ char* szAnsi = NULL;
+
+ if (wCP != CP_ACP) // convert to proper codepage
+ if (!utf8_decode_codepage(szMsg, &szAnsi, wCP))
+ return NULL;
+
+ return szAnsi;
+}
+
+// just broadcast generic send error with dummy cookie and return that cookie
+DWORD CIcqProto::ReportGenericSendError(HANDLE hContact, int nType, const char* szErrorMsg)
+{
+ DWORD dwCookie = GenerateCookie(0);
+ SendProtoAck(hContact, dwCookie, ACKRESULT_FAILED, nType, Translate(szErrorMsg));
+ return dwCookie;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::CreateProtoService(const char* szService, IcqServiceFunc serviceProc)
+{
+ char temp[MAX_PATH*2];
+
+ null_snprintf(temp, sizeof(temp), "%s%s", m_szModuleName, szService);
+ CreateServiceFunctionObj( temp, ( MIRANDASERVICEOBJ )*( void** )&serviceProc, this );
+}
+
+void CIcqProto::CreateProtoServiceParam(const char* szService, IcqServiceFuncParam serviceProc, LPARAM lParam)
+{
+ char temp[MAX_PATH*2];
+
+ null_snprintf(temp, sizeof(temp), "%s%s", m_szModuleName, szService);
+ CreateServiceFunctionObjParam( temp, ( MIRANDASERVICEOBJPARAM )*( void** )&serviceProc, this, lParam );
+}
+
+
+HANDLE CIcqProto::HookProtoEvent(const char* szEvent, IcqEventFunc pFunc)
+{
+ return ::HookEventObj(szEvent, (MIRANDAHOOKOBJ)*(void**)&pFunc, this);
+}
+
+
+HANDLE CIcqProto::CreateProtoEvent(const char* szEvent)
+{
+ char str[MAX_PATH + 32];
+ strcpy(str, m_szModuleName);
+ strcat(str, szEvent);
+ return CreateHookableEvent(str);
+}
diff --git a/protocols/IcqOscarJ/src/utilities.h b/protocols/IcqOscarJ/src/utilities.h
new file mode 100644
index 0000000000..962ecce15f
--- /dev/null
+++ b/protocols/IcqOscarJ/src/utilities.h
@@ -0,0 +1,188 @@
+// ---------------------------------------------------------------------------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
+//
+// 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:
+//
+// Describe me here please...
+//
+// -----------------------------------------------------------------------------
+#ifndef __UTILITIES_H
+#define __UTILITIES_H
+
+
+struct icq_ack_args
+{
+ HANDLE hContact;
+ int nAckType;
+ int nAckResult;
+ HANDLE hSequence;
+ LPARAM pszMessage;
+};
+
+struct icq_contacts_cache
+{
+ HANDLE hContact;
+ DWORD dwUin;
+ const char *szUid;
+};
+
+
+/*---------* Functions *---------------*/
+
+void MoveDlgItem(HWND hwndDlg, int iItem, int left, int top, int width, int height);
+void EnableDlgItem(HWND hwndDlg, UINT control, int state);
+void ShowDlgItem(HWND hwndDlg, UINT control, int state);
+void icq_EnableMultipleControls(HWND hwndDlg, const UINT* controls, int cControls, int state);
+void icq_ShowMultipleControls(HWND hwndDlg, const UINT* controls, int cControls, int state);
+int IcqStatusToMiranda(WORD wStatus);
+WORD MirandaStatusToIcq(int nStatus);
+int MirandaStatusToSupported(int nMirandaStatus);
+char *MirandaStatusToString(int);
+char *MirandaStatusToStringUtf(int);
+
+int AwayMsgTypeToStatus(int nMsgType);
+
+void SetGatewayIndex(HANDLE hConn, DWORD dwIndex);
+DWORD GetGatewayIndex(HANDLE hConn);
+void FreeGatewayIndex(HANDLE hConn);
+
+char *NickFromHandle(HANDLE hContact);
+char *NickFromHandleUtf(HANDLE hContact);
+char *strUID(DWORD dwUIN, char *pszUID);
+
+int __fastcall strlennull(const char *string);
+int __fastcall strcmpnull(const char *str1, const char *str2);
+int __fastcall stricmpnull(const char *str1, const char *str2);
+char* __fastcall strstrnull(const char *str, const char *substr);
+int null_snprintf(char *buffer, size_t count, const char *fmt, ...);
+char* __fastcall null_strdup(const char *string);
+char* __fastcall null_strcpy(char *dest, const char *src, size_t maxlen);
+int __fastcall null_strcut(char *string, int maxlen);
+
+int __fastcall strlennull(const WCHAR *string);
+int null_snprintf(WCHAR *buffer, size_t count, const WCHAR *fmt, ...);
+WCHAR* __fastcall null_strdup(const WCHAR *string);
+WCHAR* __fastcall null_strcpy(WCHAR *dest, const WCHAR *src, size_t maxlen);
+
+void parseServerAddress(char *szServer, WORD* wPort);
+
+char *DemangleXml(const char *string, int len);
+char *MangleXml(const char *string, int len);
+char *EliminateHtml(const char *string, int len);
+char *ApplyEncoding(const char *string, const char *pszEncoding);
+
+int RandRange(int nLow, int nHigh);
+
+BOOL IsStringUIN(const char *pszString);
+
+char* time2text(time_t time);
+
+BOOL validateStatusMessageRequest(HANDLE hContact, WORD byMessageType);
+
+void __fastcall SAFE_FREE(void** p);
+void* __fastcall SAFE_MALLOC(size_t size);
+void* __fastcall SAFE_REALLOC(void* p, size_t size);
+
+__inline static void SAFE_FREE(char** str) { SAFE_FREE((void**)str); }
+__inline static void SAFE_FREE(WCHAR** str) { SAFE_FREE((void**)str); }
+
+struct lockable_struct: public MZeroedObject
+{
+private:
+ int nLockCount;
+public:
+ lockable_struct() { _Lock(); };
+ virtual ~lockable_struct() {};
+
+ void _Lock() { nLockCount++; };
+ void _Release() { nLockCount--; if (!nLockCount) delete this; };
+
+ int getLockCount() { return nLockCount; };
+};
+
+void __fastcall SAFE_DELETE(MZeroedObject **p);
+void __fastcall SAFE_DELETE(lockable_struct **p);
+
+DWORD ICQWaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds, int bWaitAlways = FALSE);
+
+
+struct icq_critical_section: public lockable_struct
+{
+private:
+ HANDLE hMutex;
+
+public:
+ icq_critical_section() { hMutex = CreateMutex(NULL, FALSE, NULL); }
+ ~icq_critical_section() { CloseHandle(hMutex); }
+
+ void Enter(void) { ICQWaitForSingleObject(hMutex, INFINITE, TRUE); }
+ void Leave(void) { ReleaseMutex(hMutex); }
+};
+
+__inline static void SAFE_DELETE(icq_critical_section **p) { SAFE_DELETE((lockable_struct**)p); }
+
+struct icq_lock
+{
+private:
+ icq_critical_section *pMutex;
+public:
+ icq_lock(icq_critical_section *mutex) { pMutex = mutex; pMutex->Enter(); };
+ ~icq_lock() { pMutex->Leave(); pMutex = NULL; };
+};
+
+
+HANDLE NetLib_OpenConnection(HANDLE hUser, const char* szIdent, NETLIBOPENCONNECTION* nloc);
+void NetLib_CloseConnection(HANDLE *hConnection, int bServerConn);
+void NetLib_SafeCloseHandle(HANDLE *hConnection);
+
+char* __fastcall ICQTranslateUtf(const char *src);
+char* __fastcall ICQTranslateUtfStatic(const char *src, char *buf, size_t bufsize);
+
+WORD GetMyStatusFlags();
+
+/* Unicode FS utility functions */
+
+int IsValidRelativePath(const char *filename);
+const char* ExtractFileName(const char *fullname);
+char* FileNameToUtf(const TCHAR *filename);
+
+int FileAccessUtf(const char *path, int mode);
+int FileStatUtf(const char *path, struct _stati64 *buffer);
+int MakeDirUtf(const char *dir);
+int OpenFileUtf(const char *filename, int oflag, int pmode);
+
+/* Unicode UI utility functions */
+WCHAR* GetWindowTextUcs(HWND hWnd);
+void SetWindowTextUcs(HWND hWnd, WCHAR *text);
+char *GetWindowTextUtf(HWND hWnd);
+char *GetDlgItemTextUtf(HWND hwndDlg, int iItem);
+void SetWindowTextUtf(HWND hWnd, const char *szText);
+void SetDlgItemTextUtf(HWND hwndDlg, int iItem, const char *szText);
+
+int ComboBoxAddStringUtf(HWND hCombo, const char *szString, DWORD data);
+int ListBoxAddStringUtf(HWND hList, const char *szString);
+
+int MessageBoxUtf(HWND hWnd, const char *szText, const char *szCaption, UINT uType);
+
+#endif /* __UTILITIES_H */
diff --git a/protocols/IcqOscarJ/src/version.h b/protocols/IcqOscarJ/src/version.h
new file mode 100644
index 0000000000..0231d7a7ea
--- /dev/null
+++ b/protocols/IcqOscarJ/src/version.h
@@ -0,0 +1,3 @@
+#define __FILEVERSION_STRING 0,11,0,1
+#define __VERSION_STRING "0.11.0.1"
+#define __VERSION_DWORD PLUGIN_MAKE_VERSION(0, 11, 0, 1)