summaryrefslogtreecommitdiff
path: root/protocols/MSN/src
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/MSN/src')
-rw-r--r--protocols/MSN/src/des.c639
-rw-r--r--protocols/MSN/src/des.h170
-rw-r--r--protocols/MSN/src/ezxml.c967
-rw-r--r--protocols/MSN/src/ezxml.h165
-rw-r--r--protocols/MSN/src/msn.cpp151
-rw-r--r--protocols/MSN/src/msn_auth.cpp485
-rw-r--r--protocols/MSN/src/msn_avatar.cpp122
-rw-r--r--protocols/MSN/src/msn_chat.cpp469
-rw-r--r--protocols/MSN/src/msn_commands.cpp1753
-rw-r--r--protocols/MSN/src/msn_contact.cpp272
-rw-r--r--protocols/MSN/src/msn_errors.cpp93
-rw-r--r--protocols/MSN/src/msn_ftold.cpp395
-rw-r--r--protocols/MSN/src/msn_global.h884
-rw-r--r--protocols/MSN/src/msn_http.cpp130
-rw-r--r--protocols/MSN/src/msn_libstr.cpp319
-rw-r--r--protocols/MSN/src/msn_links.cpp171
-rw-r--r--protocols/MSN/src/msn_lists.cpp623
-rw-r--r--protocols/MSN/src/msn_mail.cpp424
-rw-r--r--protocols/MSN/src/msn_menu.cpp460
-rw-r--r--protocols/MSN/src/msn_mime.cpp536
-rw-r--r--protocols/MSN/src/msn_misc.cpp1293
-rw-r--r--protocols/MSN/src/msn_msgqueue.cpp190
-rw-r--r--protocols/MSN/src/msn_msgsplit.cpp124
-rw-r--r--protocols/MSN/src/msn_natdetect.cpp496
-rw-r--r--protocols/MSN/src/msn_opts.cpp684
-rw-r--r--protocols/MSN/src/msn_p2p.cpp2525
-rw-r--r--protocols/MSN/src/msn_p2ps.cpp292
-rw-r--r--protocols/MSN/src/msn_proto.cpp1107
-rw-r--r--protocols/MSN/src/msn_proto.h567
-rw-r--r--protocols/MSN/src/msn_soapab.cpp1713
-rw-r--r--protocols/MSN/src/msn_soapstore.cpp781
-rw-r--r--protocols/MSN/src/msn_srv.cpp378
-rw-r--r--protocols/MSN/src/msn_ssl.cpp140
-rw-r--r--protocols/MSN/src/msn_std.cpp65
-rw-r--r--protocols/MSN/src/msn_svcs.cpp624
-rw-r--r--protocols/MSN/src/msn_switchboard.cpp53
-rw-r--r--protocols/MSN/src/msn_threads.cpp769
-rw-r--r--protocols/MSN/src/msn_ws.cpp180
-rw-r--r--protocols/MSN/src/resource.h100
-rw-r--r--protocols/MSN/src/stdafx.cpp18
-rw-r--r--protocols/MSN/src/version.h33
41 files changed, 21360 insertions, 0 deletions
diff --git a/protocols/MSN/src/des.c b/protocols/MSN/src/des.c
new file mode 100644
index 0000000000..030fb983cd
--- /dev/null
+++ b/protocols/MSN/src/des.c
@@ -0,0 +1,639 @@
+/*
+ * FIPS-46-3 compliant Triple-DES implementation
+ *
+ * Copyright (C) 2006-2007 Christophe Devine
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License, version 2.1 as published by the Free Software Foundation.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+/*
+ * DES, on which TDES is based, was originally designed by IBM in
+ * 1974 and adopted as a standard by NIST (formerly NBS).
+ *
+ * http://csrc.nist.gov/publications/fips/fips46-3/fips46-3.pdf
+ */
+
+
+#ifndef __GNUC__
+#pragma hdrstop
+#endif
+
+#ifndef _CRT_SECURE_NO_DEPRECATE
+#define _CRT_SECURE_NO_DEPRECATE 1
+#endif
+
+#include <string.h>
+
+#include "des.h"
+
+/*
+ * 32-bit integer manipulation macros (big endian)
+ */
+#ifndef GET_UINT32_BE
+#define GET_UINT32_BE(n,b,i) \
+{ \
+ (n) = ( (unsigned long) (b)[(i) ] << 24 ) \
+ | ( (unsigned long) (b)[(i) + 1] << 16 ) \
+ | ( (unsigned long) (b)[(i) + 2] << 8 ) \
+ | ( (unsigned long) (b)[(i) + 3] ); \
+}
+#endif
+#ifndef PUT_UINT32_BE
+#define PUT_UINT32_BE(n,b,i) \
+{ \
+ (b)[(i) ] = (unsigned char) ( (n) >> 24 ); \
+ (b)[(i) + 1] = (unsigned char) ( (n) >> 16 ); \
+ (b)[(i) + 2] = (unsigned char) ( (n) >> 8 ); \
+ (b)[(i) + 3] = (unsigned char) ( (n) ); \
+}
+#endif
+
+/*
+ * Expanded DES S-boxes
+ */
+static const unsigned long SB1[64] =
+{
+ 0x01010400, 0x00000000, 0x00010000, 0x01010404,
+ 0x01010004, 0x00010404, 0x00000004, 0x00010000,
+ 0x00000400, 0x01010400, 0x01010404, 0x00000400,
+ 0x01000404, 0x01010004, 0x01000000, 0x00000004,
+ 0x00000404, 0x01000400, 0x01000400, 0x00010400,
+ 0x00010400, 0x01010000, 0x01010000, 0x01000404,
+ 0x00010004, 0x01000004, 0x01000004, 0x00010004,
+ 0x00000000, 0x00000404, 0x00010404, 0x01000000,
+ 0x00010000, 0x01010404, 0x00000004, 0x01010000,
+ 0x01010400, 0x01000000, 0x01000000, 0x00000400,
+ 0x01010004, 0x00010000, 0x00010400, 0x01000004,
+ 0x00000400, 0x00000004, 0x01000404, 0x00010404,
+ 0x01010404, 0x00010004, 0x01010000, 0x01000404,
+ 0x01000004, 0x00000404, 0x00010404, 0x01010400,
+ 0x00000404, 0x01000400, 0x01000400, 0x00000000,
+ 0x00010004, 0x00010400, 0x00000000, 0x01010004
+};
+
+static const unsigned long SB2[64] =
+{
+ 0x80108020, 0x80008000, 0x00008000, 0x00108020,
+ 0x00100000, 0x00000020, 0x80100020, 0x80008020,
+ 0x80000020, 0x80108020, 0x80108000, 0x80000000,
+ 0x80008000, 0x00100000, 0x00000020, 0x80100020,
+ 0x00108000, 0x00100020, 0x80008020, 0x00000000,
+ 0x80000000, 0x00008000, 0x00108020, 0x80100000,
+ 0x00100020, 0x80000020, 0x00000000, 0x00108000,
+ 0x00008020, 0x80108000, 0x80100000, 0x00008020,
+ 0x00000000, 0x00108020, 0x80100020, 0x00100000,
+ 0x80008020, 0x80100000, 0x80108000, 0x00008000,
+ 0x80100000, 0x80008000, 0x00000020, 0x80108020,
+ 0x00108020, 0x00000020, 0x00008000, 0x80000000,
+ 0x00008020, 0x80108000, 0x00100000, 0x80000020,
+ 0x00100020, 0x80008020, 0x80000020, 0x00100020,
+ 0x00108000, 0x00000000, 0x80008000, 0x00008020,
+ 0x80000000, 0x80100020, 0x80108020, 0x00108000
+};
+
+static const unsigned long SB3[64] =
+{
+ 0x00000208, 0x08020200, 0x00000000, 0x08020008,
+ 0x08000200, 0x00000000, 0x00020208, 0x08000200,
+ 0x00020008, 0x08000008, 0x08000008, 0x00020000,
+ 0x08020208, 0x00020008, 0x08020000, 0x00000208,
+ 0x08000000, 0x00000008, 0x08020200, 0x00000200,
+ 0x00020200, 0x08020000, 0x08020008, 0x00020208,
+ 0x08000208, 0x00020200, 0x00020000, 0x08000208,
+ 0x00000008, 0x08020208, 0x00000200, 0x08000000,
+ 0x08020200, 0x08000000, 0x00020008, 0x00000208,
+ 0x00020000, 0x08020200, 0x08000200, 0x00000000,
+ 0x00000200, 0x00020008, 0x08020208, 0x08000200,
+ 0x08000008, 0x00000200, 0x00000000, 0x08020008,
+ 0x08000208, 0x00020000, 0x08000000, 0x08020208,
+ 0x00000008, 0x00020208, 0x00020200, 0x08000008,
+ 0x08020000, 0x08000208, 0x00000208, 0x08020000,
+ 0x00020208, 0x00000008, 0x08020008, 0x00020200
+};
+
+static const unsigned long SB4[64] =
+{
+ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802080, 0x00800081, 0x00800001, 0x00002001,
+ 0x00000000, 0x00802000, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00800080, 0x00800001,
+ 0x00000001, 0x00002000, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002001, 0x00002080,
+ 0x00800081, 0x00000001, 0x00002080, 0x00800080,
+ 0x00002000, 0x00802080, 0x00802081, 0x00000081,
+ 0x00800080, 0x00800001, 0x00802000, 0x00802081,
+ 0x00000081, 0x00000000, 0x00000000, 0x00802000,
+ 0x00002080, 0x00800080, 0x00800081, 0x00000001,
+ 0x00802001, 0x00002081, 0x00002081, 0x00000080,
+ 0x00802081, 0x00000081, 0x00000001, 0x00002000,
+ 0x00800001, 0x00002001, 0x00802080, 0x00800081,
+ 0x00002001, 0x00002080, 0x00800000, 0x00802001,
+ 0x00000080, 0x00800000, 0x00002000, 0x00802080
+};
+
+static const unsigned long SB5[64] =
+{
+ 0x00000100, 0x02080100, 0x02080000, 0x42000100,
+ 0x00080000, 0x00000100, 0x40000000, 0x02080000,
+ 0x40080100, 0x00080000, 0x02000100, 0x40080100,
+ 0x42000100, 0x42080000, 0x00080100, 0x40000000,
+ 0x02000000, 0x40080000, 0x40080000, 0x00000000,
+ 0x40000100, 0x42080100, 0x42080100, 0x02000100,
+ 0x42080000, 0x40000100, 0x00000000, 0x42000000,
+ 0x02080100, 0x02000000, 0x42000000, 0x00080100,
+ 0x00080000, 0x42000100, 0x00000100, 0x02000000,
+ 0x40000000, 0x02080000, 0x42000100, 0x40080100,
+ 0x02000100, 0x40000000, 0x42080000, 0x02080100,
+ 0x40080100, 0x00000100, 0x02000000, 0x42080000,
+ 0x42080100, 0x00080100, 0x42000000, 0x42080100,
+ 0x02080000, 0x00000000, 0x40080000, 0x42000000,
+ 0x00080100, 0x02000100, 0x40000100, 0x00080000,
+ 0x00000000, 0x40080000, 0x02080100, 0x40000100
+};
+
+static const unsigned long SB6[64] =
+{
+ 0x20000010, 0x20400000, 0x00004000, 0x20404010,
+ 0x20400000, 0x00000010, 0x20404010, 0x00400000,
+ 0x20004000, 0x00404010, 0x00400000, 0x20000010,
+ 0x00400010, 0x20004000, 0x20000000, 0x00004010,
+ 0x00000000, 0x00400010, 0x20004010, 0x00004000,
+ 0x00404000, 0x20004010, 0x00000010, 0x20400010,
+ 0x20400010, 0x00000000, 0x00404010, 0x20404000,
+ 0x00004010, 0x00404000, 0x20404000, 0x20000000,
+ 0x20004000, 0x00000010, 0x20400010, 0x00404000,
+ 0x20404010, 0x00400000, 0x00004010, 0x20000010,
+ 0x00400000, 0x20004000, 0x20000000, 0x00004010,
+ 0x20000010, 0x20404010, 0x00404000, 0x20400000,
+ 0x00404010, 0x20404000, 0x00000000, 0x20400010,
+ 0x00000010, 0x00004000, 0x20400000, 0x00404010,
+ 0x00004000, 0x00400010, 0x20004010, 0x00000000,
+ 0x20404000, 0x20000000, 0x00400010, 0x20004010
+};
+
+static const unsigned long SB7[64] =
+{
+ 0x00200000, 0x04200002, 0x04000802, 0x00000000,
+ 0x00000800, 0x04000802, 0x00200802, 0x04200800,
+ 0x04200802, 0x00200000, 0x00000000, 0x04000002,
+ 0x00000002, 0x04000000, 0x04200002, 0x00000802,
+ 0x04000800, 0x00200802, 0x00200002, 0x04000800,
+ 0x04000002, 0x04200000, 0x04200800, 0x00200002,
+ 0x04200000, 0x00000800, 0x00000802, 0x04200802,
+ 0x00200800, 0x00000002, 0x04000000, 0x00200800,
+ 0x04000000, 0x00200800, 0x00200000, 0x04000802,
+ 0x04000802, 0x04200002, 0x04200002, 0x00000002,
+ 0x00200002, 0x04000000, 0x04000800, 0x00200000,
+ 0x04200800, 0x00000802, 0x00200802, 0x04200800,
+ 0x00000802, 0x04000002, 0x04200802, 0x04200000,
+ 0x00200800, 0x00000000, 0x00000002, 0x04200802,
+ 0x00000000, 0x00200802, 0x04200000, 0x00000800,
+ 0x04000002, 0x04000800, 0x00000800, 0x00200002
+};
+
+static const unsigned long SB8[64] =
+{
+ 0x10001040, 0x00001000, 0x00040000, 0x10041040,
+ 0x10000000, 0x10001040, 0x00000040, 0x10000000,
+ 0x00040040, 0x10040000, 0x10041040, 0x00041000,
+ 0x10041000, 0x00041040, 0x00001000, 0x00000040,
+ 0x10040000, 0x10000040, 0x10001000, 0x00001040,
+ 0x00041000, 0x00040040, 0x10040040, 0x10041000,
+ 0x00001040, 0x00000000, 0x00000000, 0x10040040,
+ 0x10000040, 0x10001000, 0x00041040, 0x00040000,
+ 0x00041040, 0x00040000, 0x10041000, 0x00001000,
+ 0x00000040, 0x10040040, 0x00001000, 0x00041040,
+ 0x10001000, 0x00000040, 0x10000040, 0x10040000,
+ 0x10040040, 0x10000000, 0x00040000, 0x10001040,
+ 0x00000000, 0x10041040, 0x00040040, 0x10000040,
+ 0x10040000, 0x10001000, 0x10001040, 0x00000000,
+ 0x10041040, 0x00041000, 0x00041000, 0x00001040,
+ 0x00001040, 0x00040040, 0x10000000, 0x10041000
+};
+
+/*
+ * PC1: left and right halves bit-swap
+ */
+static const unsigned long LHs[16] =
+{
+ 0x00000000, 0x00000001, 0x00000100, 0x00000101,
+ 0x00010000, 0x00010001, 0x00010100, 0x00010101,
+ 0x01000000, 0x01000001, 0x01000100, 0x01000101,
+ 0x01010000, 0x01010001, 0x01010100, 0x01010101
+};
+
+static const unsigned long RHs[16] =
+{
+ 0x00000000, 0x01000000, 0x00010000, 0x01010000,
+ 0x00000100, 0x01000100, 0x00010100, 0x01010100,
+ 0x00000001, 0x01000001, 0x00010001, 0x01010001,
+ 0x00000101, 0x01000101, 0x00010101, 0x01010101,
+};
+
+/*
+ * Initial Permutation macro
+ */
+#define DES_IP(X,Y) \
+{ \
+ T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \
+ T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \
+ T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \
+ T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \
+ Y = ((Y << 1) | (Y >> 31)) & 0xFFFFFFFF; \
+ T = (X ^ Y) & 0xAAAAAAAA; Y ^= T; X ^= T; \
+ X = ((X << 1) | (X >> 31)) & 0xFFFFFFFF; \
+}
+
+/*
+ * Final Permutation macro
+ */
+#define DES_FP(X,Y) \
+{ \
+ X = ((X << 31) | (X >> 1)) & 0xFFFFFFFF; \
+ T = (X ^ Y) & 0xAAAAAAAA; X ^= T; Y ^= T; \
+ Y = ((Y << 31) | (Y >> 1)) & 0xFFFFFFFF; \
+ T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \
+ T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \
+ T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \
+ T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \
+}
+
+/*
+ * DES round macro
+ */
+#define DES_ROUND(X,Y) \
+{ \
+ T = *SK++ ^ X; \
+ Y ^= SB8[ (T ) & 0x3F ] ^ \
+ SB6[ (T >> 8) & 0x3F ] ^ \
+ SB4[ (T >> 16) & 0x3F ] ^ \
+ SB2[ (T >> 24) & 0x3F ]; \
+ \
+ T = *SK++ ^ ((X << 28) | (X >> 4)); \
+ Y ^= SB7[ (T ) & 0x3F ] ^ \
+ SB5[ (T >> 8) & 0x3F ] ^ \
+ SB3[ (T >> 16) & 0x3F ] ^ \
+ SB1[ (T >> 24) & 0x3F ]; \
+}
+
+static void des_main_ks( unsigned long SK[32], unsigned char key[8] )
+{
+ int i;
+ unsigned long X, Y, T;
+
+ GET_UINT32_BE( X, key, 0 );
+ GET_UINT32_BE( Y, key, 4 );
+
+ /*
+ * Permuted Choice 1
+ */
+ T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4);
+ T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T );
+
+ X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2)
+ | (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] )
+ | (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6)
+ | (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4);
+
+ Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2)
+ | (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] )
+ | (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6)
+ | (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4);
+
+ X &= 0x0FFFFFFF;
+ Y &= 0x0FFFFFFF;
+
+ /*
+ * calculate subkeys
+ */
+ for ( i = 0; i < 16; i++ )
+ {
+ if ( i < 2 || i == 8 || i == 15 )
+ {
+ X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF;
+ Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF;
+ }
+ else
+ {
+ X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF;
+ Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF;
+ }
+
+ *SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000)
+ | ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000)
+ | ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000)
+ | ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000)
+ | ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000)
+ | ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000)
+ | ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400)
+ | ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100)
+ | ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010)
+ | ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004)
+ | ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001);
+
+ *SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000)
+ | ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000)
+ | ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000)
+ | ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000)
+ | ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000)
+ | ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000)
+ | ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000)
+ | ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400)
+ | ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100)
+ | ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011)
+ | ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002);
+ }
+}
+
+/*
+ * DES key schedule (56-bit)
+ */
+void des_set_key( des_context *ctx, unsigned char key[8] )
+{
+ int i;
+
+ des_main_ks( ctx->esk, key );
+
+ for ( i = 0; i < 32; i += 2 )
+ {
+ ctx->dsk[i ] = ctx->esk[30 - i];
+ ctx->dsk[i + 1] = ctx->esk[31 - i];
+ }
+}
+
+static void des_crypt( unsigned long SK[32],
+ unsigned char input[8],
+ unsigned char output[8] )
+{
+ unsigned long X, Y, T;
+
+ GET_UINT32_BE( X, input, 0 );
+ GET_UINT32_BE( Y, input, 4 );
+
+ DES_IP( X, Y );
+
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+
+ DES_FP( Y, X );
+
+ PUT_UINT32_BE( Y, output, 0 );
+ PUT_UINT32_BE( X, output, 4 );
+}
+
+/*
+ * DES block encryption (ECB mode)
+ */
+void des_encrypt( des_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] )
+{
+ des_crypt( ctx->esk, input, output );
+}
+
+/*
+ * DES block decryption (ECB mode)
+ */
+void des_decrypt( des_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] )
+{
+ des_crypt( ctx->dsk, input, output );
+}
+
+/*
+ * DES-CBC buffer encryption
+ */
+void des_cbc_encrypt( des_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len )
+{
+ int i;
+
+ while( len > 0 )
+ {
+ for ( i = 0; i < 8; i++ )
+ output[i] = input[i] ^ iv[i];
+
+ des_crypt( ctx->esk, output, output );
+ memcpy( iv, output, 8 );
+
+ input += 8;
+ output += 8;
+ len -= 8;
+ }
+}
+
+/*
+ * DES-CBC buffer decryption
+ */
+void des_cbc_decrypt( des_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len )
+{
+ int i;
+ unsigned char temp[8];
+
+ while( len > 0 )
+ {
+ memcpy( temp, input, 8 );
+ des_crypt( ctx->dsk, input, output );
+
+ for ( i = 0; i < 8; i++ )
+ output[i] = output[i] ^ iv[i];
+
+ memcpy( iv, temp, 8 );
+
+ input += 8;
+ output += 8;
+ len -= 8;
+ }
+}
+
+/*
+ * Triple-DES key schedule (112-bit)
+ */
+void des3_set_2keys( des3_context *ctx, unsigned char key[16] )
+{
+ int i;
+
+ des_main_ks( ctx->esk , key );
+ des_main_ks( ctx->dsk + 32, key + 8 );
+
+ for ( i = 0; i < 32; i += 2 )
+ {
+ ctx->dsk[i ] = ctx->esk[30 - i];
+ ctx->dsk[i + 1] = ctx->esk[31 - i];
+
+ ctx->esk[i + 32] = ctx->dsk[62 - i];
+ ctx->esk[i + 33] = ctx->dsk[63 - i];
+
+ ctx->esk[i + 64] = ctx->esk[ i];
+ ctx->esk[i + 65] = ctx->esk[ 1 + i];
+
+ ctx->dsk[i + 64] = ctx->dsk[ i];
+ ctx->dsk[i + 65] = ctx->dsk[ 1 + i];
+ }
+}
+
+/*
+ * Triple-DES key schedule (168-bit)
+ */
+void des3_set_3keys( des3_context *ctx, unsigned char key[24] )
+{
+ int i;
+
+ des_main_ks( ctx->esk , key );
+ des_main_ks( ctx->dsk + 32, key + 8 );
+ des_main_ks( ctx->esk + 64, key + 16 );
+
+ for ( i = 0; i < 32; i += 2 )
+ {
+ ctx->dsk[i ] = ctx->esk[94 - i];
+ ctx->dsk[i + 1] = ctx->esk[95 - i];
+
+ ctx->esk[i + 32] = ctx->dsk[62 - i];
+ ctx->esk[i + 33] = ctx->dsk[63 - i];
+
+ ctx->dsk[i + 64] = ctx->esk[30 - i];
+ ctx->dsk[i + 65] = ctx->esk[31 - i];
+ }
+}
+
+static void des3_crypt( unsigned long SK[96],
+ unsigned char input[8],
+ unsigned char output[8] )
+{
+ unsigned long X, Y, T;
+
+ GET_UINT32_BE( X, input, 0 );
+ GET_UINT32_BE( Y, input, 4 );
+
+ DES_IP( X, Y );
+
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+ DES_ROUND( X, Y ); DES_ROUND( Y, X );
+
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+ DES_ROUND( Y, X ); DES_ROUND( X, Y );
+
+ DES_FP( Y, X );
+
+ PUT_UINT32_BE( Y, output, 0 );
+ PUT_UINT32_BE( X, output, 4 );
+}
+
+/*
+ * Triple-DES block encryption (ECB mode)
+ */
+void des3_encrypt( des3_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] )
+{
+ des3_crypt( ctx->esk, input, output );
+}
+
+/*
+ * Triple-DES block decryption (ECB mode)
+ */
+void des3_decrypt( des3_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] )
+{
+ des3_crypt( ctx->dsk, input, output );
+}
+
+/*
+ * 3DES-CBC buffer encryption
+ */
+void des3_cbc_encrypt( des3_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len )
+{
+ int i;
+
+ while( len > 0 )
+ {
+ for ( i = 0; i < 8; i++ )
+ output[i] = input[i] ^ iv[i];
+
+ des3_crypt( ctx->esk, output, output );
+ memcpy( iv, output, 8 );
+
+ input += 8;
+ output += 8;
+ len -= 8;
+ }
+}
+
+/*
+ * 3DES-CBC buffer decryption
+ */
+void des3_cbc_decrypt( des3_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len )
+{
+ int i;
+ unsigned char temp[8];
+
+ while( len > 0 )
+ {
+ memcpy( temp, input, 8 );
+ des3_crypt( ctx->dsk, input, output );
+
+ for ( i = 0; i < 8; i++ )
+ output[i] = output[i] ^ iv[i];
+
+ memcpy( iv, temp, 8 );
+
+ input += 8;
+ output += 8;
+ len -= 8;
+ }
+}
diff --git a/protocols/MSN/src/des.h b/protocols/MSN/src/des.h
new file mode 100644
index 0000000000..e1fc923fad
--- /dev/null
+++ b/protocols/MSN/src/des.h
@@ -0,0 +1,170 @@
+/**
+ * \file des.h
+ */
+#ifndef _DES_H
+#define _DES_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief DES context structure
+ */
+typedef struct
+{
+ unsigned long esk[32]; /*!< DES encryption subkeys */
+ unsigned long dsk[32]; /*!< DES decryption subkeys */
+}
+des_context;
+
+/**
+ * \brief Triple-DES context structure
+ */
+typedef struct
+{
+ unsigned long esk[96]; /*!< Triple-DES encryption subkeys */
+ unsigned long dsk[96]; /*!< Triple-DES decryption subkeys */
+}
+des3_context;
+
+/**
+ * \brief DES key schedule (56-bit)
+ *
+ * \param ctx DES context to be initialized
+ * \param key 8-byte secret key
+ */
+void des_set_key( des_context *ctx, unsigned char key[8] );
+
+/**
+ * \brief DES block encryption (ECB mode)
+ *
+ * \param ctx DES context
+ * \param input plaintext block
+ * \param output ciphertext block
+ */
+void des_encrypt( des_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] );
+
+/**
+ * \brief DES block decryption (ECB mode)
+ *
+ * \param ctx DES context
+ * \param input ciphertext block
+ * \param output plaintext block
+ */
+void des_decrypt( des_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] );
+
+/**
+ * \brief DES-CBC buffer encryption
+ *
+ * \param ctx DES context
+ * \param iv initialization vector (modified after use)
+ * \param input buffer holding the plaintext
+ * \param output buffer holding the ciphertext
+ * \param len length of the data to be encrypted
+ */
+void des_cbc_encrypt( des_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len );
+
+/**
+ * \brief DES-CBC buffer decryption
+ *
+ * \param ctx DES context
+ * \param iv initialization vector (modified after use)
+ * \param input buffer holding the ciphertext
+ * \param output buffer holding the plaintext
+ * \param len length of the data to be decrypted
+ */
+void des_cbc_decrypt( des_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len );
+
+/**
+ * \brief Triple-DES key schedule (112-bit)
+ *
+ * \param ctx 3DES context to be initialized
+ * \param key 16-byte secret key
+ */
+void des3_set_2keys( des3_context *ctx, unsigned char key[16] );
+
+/**
+ * \brief Triple-DES key schedule (168-bit)
+ *
+ * \param ctx 3DES context to be initialized
+ * \param key 24-byte secret key
+ */
+void des3_set_3keys( des3_context *ctx, unsigned char key[24] );
+
+/**
+ * \brief Triple-DES block encryption (ECB mode)
+ *
+ * \param ctx 3DES context
+ * \param input plaintext block
+ * \param output ciphertext block
+ */
+void des3_encrypt( des3_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] );
+
+/**
+ * \brief Triple-DES block decryption (ECB mode)
+ *
+ * \param ctx 3DES context
+ * \param input ciphertext block
+ * \param output plaintext block
+ */
+void des3_decrypt( des3_context *ctx,
+ unsigned char input[8],
+ unsigned char output[8] );
+
+/**
+ * \brief 3DES-CBC buffer encryption
+ *
+ * \param ctx 3DES context
+ * \param iv initialization vector (modified after use)
+ * \param input buffer holding the plaintext
+ * \param output buffer holding the ciphertext
+ * \param len length of the data to be encrypted
+ */
+void des3_cbc_encrypt( des3_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len );
+
+/**
+ * \brief 3DES-CBC buffer decryption
+ *
+ * \param ctx 3DES context
+ * \param iv initialization vector (modified after use)
+ * \param input buffer holding the ciphertext
+ * \param output buffer holding the plaintext
+ * \param len length of the data to be decrypted
+ */
+void des3_cbc_decrypt( des3_context *ctx,
+ unsigned char iv[8],
+ unsigned char *input,
+ unsigned char *output,
+ int len );
+
+/*
+ * \brief Checkup routine
+ *
+ * \return 0 if successful, or 1 if the test failed
+ */
+int des_self_test( int verbose );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* des.h */
diff --git a/protocols/MSN/src/ezxml.c b/protocols/MSN/src/ezxml.c
new file mode 100644
index 0000000000..41ef70598e
--- /dev/null
+++ b/protocols/MSN/src/ezxml.c
@@ -0,0 +1,967 @@
+/* ezxml.c
+ *
+ * Copyright 2004-2006 Aaron Voisine <aaron@voisine.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#if defined(_DEBUG) && !defined(__GNUC__)
+ #define _CRTDBG_MAP_ALLOC
+ #include <stdlib.h>
+ #include <crtdbg.h>
+#else
+ #include <stdlib.h>
+#endif
+
+#include <limits.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "ezxml.h"
+
+#ifndef SIZE_MAX
+#define SIZE_MAX UINT_MAX
+#endif
+
+#define EZXML_WS "\t\r\n " // whitespace
+#define EZXML_ERRL 128 // maximum error string length
+
+typedef struct ezxml_root *ezxml_root_t;
+struct ezxml_root { // additional data for the root tag
+ struct ezxml xml; // is a super-struct built on top of ezxml struct
+ ezxml_t cur; // current xml tree insertion point
+ char *m; // original xml string
+ size_t len; // length of allocated memory for mmap, -1 for malloc
+ char *u; // UTF-8 conversion of string if original was UTF-16
+ char *s; // start of work area
+ char *e; // end of work area
+ char **ent; // general entities (ampersand sequences)
+ char ***attr; // default attributes
+ char ***pi; // processing instructions
+ short standalone; // non-zero if <?xml standalone="yes"?>
+ char err[EZXML_ERRL]; // error string
+};
+
+char *EZXML_NIL[] = { NULL }; // empty, null terminated array of strings
+
+// returns the first child tag with the given name or NULL if not found
+ezxml_t ezxml_child(ezxml_t xml, const char *name)
+{
+ xml = (xml) ? xml->child : NULL;
+ while (xml && strcmp(name, xml->name)) xml = xml->sibling;
+ return xml;
+}
+
+// returns the Nth tag with the same name in the same subsection or NULL if not
+// found
+ezxml_t ezxml_idx(ezxml_t xml, int idx)
+{
+ for (; xml && idx; idx--) xml = xml->next;
+ return xml;
+}
+
+// returns the value of the requested tag attribute or NULL if not found
+const char *ezxml_attr(ezxml_t xml, const char *attr)
+{
+ int i = 0, j = 1;
+ ezxml_root_t root = (ezxml_root_t)xml;
+
+ if (! xml || ! xml->attr) return NULL;
+ while (xml->attr[i] && strcmp(attr, xml->attr[i])) i += 2;
+ if (xml->attr[i]) return xml->attr[i + 1]; // found attribute
+
+ while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag
+ for (i = 0; root->attr[i] && strcmp(xml->name, root->attr[i][0]); i++);
+ if (! root->attr[i]) return NULL; // no matching default attributes
+ while (root->attr[i][j] && strcmp(attr, root->attr[i][j])) j += 3;
+ return (root->attr[i][j]) ? root->attr[i][j + 1] : NULL; // found default
+}
+
+// same as ezxml_get but takes an already initialized va_list
+ezxml_t ezxml_vget(ezxml_t xml, va_list ap)
+{
+ char *name = va_arg(ap, char *);
+ int idx = -1;
+
+ if (name && *name) {
+ idx = va_arg(ap, int);
+ xml = ezxml_child(xml, name);
+ }
+ return (idx < 0) ? xml : ezxml_vget(ezxml_idx(xml, idx), ap);
+}
+
+// Traverses the xml tree to retrieve a specific subtag. Takes a variable
+// length list of tag names and indexes. The argument list must be terminated
+// by either an index of -1 or an empty string tag name. Example:
+// title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1);
+// This retrieves the title of the 3rd book on the 1st shelf of library.
+// Returns NULL if not found.
+ezxml_t ezxml_get(ezxml_t xml, ...)
+{
+ va_list ap;
+ ezxml_t r;
+
+ va_start(ap, xml);
+ r = ezxml_vget(xml, ap);
+ va_end(ap);
+ return r;
+}
+
+// returns a null terminated array of processing instructions for the given
+// target
+const char **ezxml_pi(ezxml_t xml, const char *target)
+{
+ ezxml_root_t root = (ezxml_root_t)xml;
+ int i = 0;
+
+ if (! root) return (const char **)EZXML_NIL;
+ while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag
+ while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target
+ return (const char **)((root->pi[i]) ? root->pi[i] + 1 : EZXML_NIL);
+}
+
+// set an error string and return root
+ezxml_t ezxml_err(ezxml_root_t root, char *s, const char *err, ...)
+{
+ va_list ap;
+ int line = 1;
+ char *t, fmt[EZXML_ERRL];
+
+ for (t = root->s; t < s; t++) if (*t == '\n') line++;
+ _snprintf(fmt, EZXML_ERRL, "[error near line %d]: %s", line, err);
+
+ va_start(ap, err);
+ _vsnprintf(root->err, EZXML_ERRL, fmt, ap);
+ va_end(ap);
+
+ return &root->xml;
+}
+
+// Recursively decodes entity and character references and normalizes new lines
+// ent is a null terminated array of alternating entity names and values. set t
+// to '&' for general entity decoding, '%' for parameter entity decoding, 'c'
+// for cdata sections, ' ' for attribute normalization, or '*' for non-cdata
+// attribute normalization. Returns s, or if the decoded string is longer than
+// s, returns a malloced string that must be freed.
+char *ezxml_decode(char *s, char **ent, char t)
+{
+ char *e, *r = s, *m = s;
+ long b, c, d, l;
+/*
+ for (; *s; s++) { // normalize line endings
+ while (*s == '\r') {
+ *(s++) = '\n';
+ if (*s == '\n') memmove(s, (s + 1), strlen(s));
+ }
+ }
+*/
+ for (s = r; ; ) {
+ while (*s && *s != '&' && (*s != '%' || t != '%') && (*s & 0x80 || !isspace(*s))) s++;
+
+ if (! *s) break;
+ else if (t != 'c' && ! strncmp(s, "&#", 2)) { // character reference
+ if (s[2] == 'x') c = strtol(s + 3, &e, 16); // base 16
+ else c = strtol(s + 2, &e, 10); // base 10
+ if (! c || *e != ';') { s++; continue; } // not a character ref
+
+ if (c < 0x80) *(s++) = (char)c; // US-ASCII subset
+ else { // multi-byte UTF-8 sequence
+ for (b = 0, d = c; d; d /= 2) b++; // number of bits in c
+ b = (b - 2) / 5; // number of bytes in payload
+ *(s++) = (char)((0xFF << (7 - b)) | (c >> (6 * b))); // head
+ while (b) *(s++) = (char)(0x80 | ((c >> (6 * --b)) & 0x3F)); // payload
+ }
+
+ memmove(s, strchr(s, ';') + 1, strlen(strchr(s, ';')));
+ }
+ else if ((*s == '&' && (t == '&' || t == ' ' || t == '*')) ||
+ (*s == '%' && t == '%')) { // entity reference
+ for (b = 0; ent[b] && strncmp(s + 1, ent[b], strlen(ent[b]));
+ b += 2); // find entity in entity list
+
+ if (ent[b++]) { // found a match
+ if ((c = (long)strlen(ent[b])) - 1 > (e = strchr(s, ';')) - s) {
+ l = (d = (long)(s - r)) + c + (long)strlen(e); // new length
+ r = (r == m) ? strcpy(malloc(l), r) : realloc(r, l);
+ e = strchr((s = r + d), ';'); // fix up pointers
+ }
+
+ memmove(s + c, e + 1, strlen(e)); // shift rest of string
+ strncpy(s, ent[b], c); // copy in replacement text
+ }
+ else s++; // not a known entity
+ }
+ else if ((t == ' ' || t == '*') && isspace(*s)) *(s++) = ' ';
+ else s++; // no decoding needed
+ }
+
+ if (t == '*') { // normalize spaces for non-cdata attributes
+ for (s = r; *s; s++) {
+ if ((l = (long)strspn(s, " "))) memmove(s, s + l, strlen(s + l) + 1);
+ while (*s && *s != ' ') s++;
+ }
+ if (--s >= r && *s == ' ') *s = '\0'; // trim any trailing space
+ }
+ return r;
+}
+
+// called when parser finds start of new tag
+void ezxml_open_tag(ezxml_root_t root, char *name, char **attr)
+{
+ ezxml_t xml = root->cur;
+
+ if (xml->name) xml = ezxml_add_child(xml, name, strlen(xml->txt));
+ else xml->name = name; // first open tag
+
+ xml->attr = attr;
+ root->cur = xml; // update tag insertion point
+}
+
+// called when parser finds character content between open and closing tag
+void ezxml_char_content(ezxml_root_t root, char *s, size_t len, char t)
+{
+ ezxml_t xml = root->cur;
+ char *m = s;
+ size_t l;
+
+ if (! xml || ! xml->name || ! len) return; // sanity check
+
+ s[len] = '\0'; // null terminate text (calling functions anticipate this)
+ len = strlen(s = ezxml_decode(s, root->ent, t)) + 1;
+
+ if (! *(xml->txt)) xml->txt = s; // initial character content
+ else { // allocate our own memory and make a copy
+ xml->txt = (xml->flags & EZXML_TXTM) // allocate some space
+ ? realloc(xml->txt, (l = strlen(xml->txt)) + len)
+ : strcpy(malloc((l = strlen(xml->txt)) + len), xml->txt);
+ strcpy(xml->txt + l, s); // add new char content
+ if (s != m) free(s); // free s if it was malloced by ezxml_decode()
+ }
+
+ if (xml->txt != m) ezxml_set_flag(xml, EZXML_TXTM);
+}
+
+// called when parser finds closing tag
+ezxml_t ezxml_close_tag(ezxml_root_t root, char *name, char *s)
+{
+ if (! root->cur || ! root->cur->name || strcmp(name, root->cur->name))
+ return ezxml_err(root, s, "unexpected closing tag </%s>", name);
+
+ root->cur = root->cur->parent;
+ return NULL;
+}
+
+// checks for circular entity references, returns non-zero if no circular
+// references are found, zero otherwise
+int ezxml_ent_ok(char *name, char *s, char **ent)
+{
+ int i;
+
+ for (; ; s++) {
+ while (*s && *s != '&') s++; // find next entity reference
+ if (! *s) return 1;
+ if (! strncmp(s + 1, name, strlen(name))) return 0; // circular ref.
+ for (i = 0; ent[i] && strncmp(ent[i], s + 1, strlen(ent[i])); i += 2);
+ if (ent[i] && ! ezxml_ent_ok(name, ent[i + 1], ent)) return 0;
+ }
+}
+
+// called when the parser finds a processing instruction
+void ezxml_proc_inst(ezxml_root_t root, char *s, size_t len)
+{
+ int i = 0, j = 1;
+ char *target = s;
+
+ s[len] = '\0'; // null terminate instruction
+ if (*(s += strcspn(s, EZXML_WS))) {
+ *s = '\0'; // null terminate target
+ s += strspn(s + 1, EZXML_WS) + 1; // skip whitespace after target
+ }
+
+ if (! strcmp(target, "xml")) { // <?xml ... ?>
+ if ((s = strstr(s, "standalone")) && ! strncmp(s + strspn(s + 10,
+ EZXML_WS "='\"") + 10, "yes", 3)) root->standalone = 1;
+ return;
+ }
+
+ if (! root->pi[0]) *(root->pi = malloc(sizeof(char **))) = NULL; //first pi
+
+ while (root->pi[i] && strcmp(target, root->pi[i][0])) i++; // find target
+ if (! root->pi[i]) { // new target
+ root->pi = realloc(root->pi, sizeof(char **) * (i + 2));
+ root->pi[i] = malloc(sizeof(char *) * 3);
+ root->pi[i][0] = target;
+ root->pi[i][1] = (char *)(root->pi[i + 1] = NULL); // terminate pi list
+ root->pi[i][2] = _strdup(""); // empty document position list
+ }
+
+ while (root->pi[i][j]) j++; // find end of instruction list for this target
+ root->pi[i] = realloc(root->pi[i], sizeof(char *) * (j + 3));
+ root->pi[i][j + 2] = realloc(root->pi[i][j + 1], j + 1);
+ strcpy(root->pi[i][j + 2] + j - 1, (root->xml.name) ? ">" : "<");
+ root->pi[i][j + 1] = NULL; // null terminate pi list for this target
+ root->pi[i][j] = s; // set instruction
+}
+
+// called when the parser finds an internal doctype subset
+short ezxml_internal_dtd(ezxml_root_t root, char *s, size_t len)
+{
+ char q, *c, *t, *n = NULL, *v, **ent, **pe;
+ int i, j;
+
+ pe = memcpy(malloc(sizeof(EZXML_NIL)), EZXML_NIL, sizeof(EZXML_NIL));
+
+ for (s[len] = '\0'; s; ) {
+ while (*s && *s != '<' && *s != '%') s++; // find next declaration
+
+ if (! *s) break;
+ else if (! strncmp(s, "<!ENTITY", 8)) { // parse entity definitions
+ c = s += strspn(s + 8, EZXML_WS) + 8; // skip white space separator
+ n = s + strspn(s, EZXML_WS "%"); // find name
+ *(s = n + strcspn(n, EZXML_WS)) = ';'; // append ; to name
+
+ v = s + strspn(s + 1, EZXML_WS) + 1; // find value
+ if ((q = *(v++)) != '"' && q != '\'') { // skip externals
+ s = strchr(s, '>');
+ continue;
+ }
+
+ for (i = 0, ent = (*c == '%') ? pe : root->ent; ent[i]; i++);
+ ent = realloc(ent, (i + 3) * sizeof(char *)); // space for next ent
+ if (*c == '%') pe = ent;
+ else root->ent = ent;
+
+ *(++s) = '\0'; // null terminate name
+ if ((s = strchr(v, q))) *(s++) = '\0'; // null terminate value
+ ent[i + 1] = ezxml_decode(v, pe, '%'); // set value
+ ent[i + 2] = NULL; // null terminate entity list
+ if (! ezxml_ent_ok(n, ent[i + 1], ent)) { // circular reference
+ if (ent[i + 1] != v) free(ent[i + 1]);
+ ezxml_err(root, v, "circular entity declaration &%s", n);
+ break;
+ }
+ else ent[i] = n; // set entity name
+ }
+ else if (! strncmp(s, "<!ATTLIST", 9)) { // parse default attributes
+ t = s + strspn(s + 9, EZXML_WS) + 9; // skip whitespace separator
+ if (! *t) { ezxml_err(root, t, "unclosed <!ATTLIST"); break; }
+ if (*(s = t + strcspn(t, EZXML_WS ">")) == '>') continue;
+ else *s = '\0'; // null terminate tag name
+ for (i = 0; root->attr[i] && strcmp(n, root->attr[i][0]); i++);
+
+ while (*(n = s + 1 + strspn(s + 1, EZXML_WS)) && *n != '>') {
+ if (*(s = n + strcspn(n, EZXML_WS))) *s = '\0'; // attr name
+ else { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
+
+ s += strspn(s + 1, EZXML_WS) + 1; // find next token
+ c = (strncmp(s, "CDATA", 5)) ? "*" : " "; // is it cdata?
+ if (! strncmp(s, "NOTATION", 8))
+ s += strspn(s + 8, EZXML_WS) + 8;
+ s = (*s == '(') ? strchr(s, ')') : s + strcspn(s, EZXML_WS);
+ if (! s) { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
+
+ s += strspn(s, EZXML_WS ")"); // skip white space separator
+ if (! strncmp(s, "#FIXED", 6))
+ s += strspn(s + 6, EZXML_WS) + 6;
+ if (*s == '#') { // no default value
+ s += strcspn(s, EZXML_WS ">") - 1;
+ if (*c == ' ') continue; // cdata is default, nothing to do
+ v = NULL;
+ }
+ else if ((*s == '"' || *s == '\'') && // default value
+ (s = strchr(v = s + 1, *s))) *s = '\0';
+ else { ezxml_err(root, t, "malformed <!ATTLIST"); break; }
+
+ if (! root->attr[i]) { // new tag name
+ root->attr = (! i) ? malloc(2 * sizeof(char **))
+ : realloc(root->attr,
+ (i + 2) * sizeof(char **));
+ root->attr[i] = malloc(2 * sizeof(char *));
+ root->attr[i][0] = t; // set tag name
+ root->attr[i][1] = (char *)(root->attr[i + 1] = NULL);
+ }
+
+ for (j = 1; root->attr[i][j]; j += 3); // find end of list
+ root->attr[i] = realloc(root->attr[i],
+ (j + 4) * sizeof(char *));
+
+ root->attr[i][j + 3] = NULL; // null terminate list
+ root->attr[i][j + 2] = c; // is it cdata?
+ root->attr[i][j + 1] = (v) ? ezxml_decode(v, root->ent, *c)
+ : NULL;
+ root->attr[i][j] = n; // attribute name
+ }
+ }
+ else if (! strncmp(s, "<!--", 4)) s = strstr(s + 4, "-->"); // comments
+ else if (! strncmp(s, "<?", 2)) { // processing instructions
+ if ((s = strstr(c = s + 2, "?>")))
+ ezxml_proc_inst(root, c, s++ - c);
+ }
+ else if (*s == '<') s = strchr(s, '>'); // skip other declarations
+ else if (*(s++) == '%' && ! root->standalone) break;
+ }
+
+ free(pe);
+ return ! *root->err;
+}
+
+// Converts a UTF-16 string to UTF-8. Returns a new string that must be freed
+// or NULL if no conversion was needed.
+char *ezxml_str2utf8(char **s, size_t *len)
+{
+ char *u;
+ size_t l = 0, sl, max = *len;
+ long c, d;
+ int b, be = (**s == '\xFE') ? 1 : (**s == '\xFF') ? 0 : -1;
+
+ if (be == -1) return NULL; // not UTF-16
+
+ u = malloc(max);
+ for (sl = 2; sl < *len - 1; sl += 2) {
+ c = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF) //UTF-16BE
+ : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF); //UTF-16LE
+ if (c >= 0xD800 && c <= 0xDFFF && (sl += 2) < *len - 1) { // high-half
+ d = (be) ? (((*s)[sl] & 0xFF) << 8) | ((*s)[sl + 1] & 0xFF)
+ : (((*s)[sl + 1] & 0xFF) << 8) | ((*s)[sl] & 0xFF);
+ c = (((c & 0x3FF) << 10) | (d & 0x3FF)) + 0x10000;
+ }
+
+ while (l + 6 > max) u = realloc(u, max += EZXML_BUFSIZE);
+ if (c < 0x80) u[l++] = (char)c; // US-ASCII subset
+ else { // multi-byte UTF-8 sequence
+ for (b = 0, d = c; d; d /= 2) b++; // bits in c
+ b = (b - 2) / 5; // bytes in payload
+ u[l++] = (char)((0xFF << (7 - b)) | (c >> (6 * b))); // head
+ while (b) u[l++] = (char)(0x80 | ((c >> (6 * --b)) & 0x3F)); // payload
+ }
+ }
+ return *s = realloc(u, *len = l);
+}
+
+// frees a tag attribute list
+void ezxml_free_attr(char **attr) {
+ int i = 0;
+ char *m;
+
+ if (! attr || attr == EZXML_NIL) return; // nothing to free
+ while (attr[i]) i += 2; // find end of attribute list
+ m = attr[i + 1]; // list of which names and values are malloced
+ for (i = 0; m[i]; i++) {
+ if (m[i] & EZXML_NAMEM) free(attr[i * 2]);
+ if (m[i] & EZXML_TXTM) free(attr[(i * 2) + 1]);
+ }
+ free(m);
+ free(attr);
+}
+
+// parse the given xml string and return an ezxml structure
+ezxml_t ezxml_parse_str(char *s, size_t len)
+{
+ ezxml_root_t root = (ezxml_root_t)ezxml_new(NULL);
+ char q, e, *d, **attr, **a = NULL; // initialize a to avoid compile warning
+ int l, i, j;
+
+ root->m = s;
+ if (! len) return ezxml_err(root, NULL, "root tag missing");
+ root->u = ezxml_str2utf8(&s, &len); // convert utf-16 to utf-8
+ root->e = (root->s = s) + len; // record start and end of work area
+
+ e = s[len - 1]; // save end char
+ s[len - 1] = '\0'; // turn end char into null terminator
+
+ while (*s && *s != '<') s++; // find first tag
+ if (! *s) return ezxml_err(root, s, "root tag missing");
+
+ for (; ; ) {
+ attr = (char **)EZXML_NIL;
+ d = ++s;
+
+ if (isalpha(*s) || *s == '_' || *s == ':' || *s < '\0') { // new tag
+ if (! root->cur)
+ return ezxml_err(root, d, "markup outside of root element");
+
+ s += strcspn(s, EZXML_WS "/>");
+ while (isspace(*s)) *(s++) = '\0'; // null terminate tag name
+
+ if (*s && *s != '/' && *s != '>') // find tag in default attr list
+ for (i = 0; (a = root->attr[i]) && strcmp(a[0], d); i++);
+
+ for (l = 0; *s && *s != '/' && *s != '>'; l += 2) { // new attrib
+ attr = (l) ? realloc(attr, (l + 4) * sizeof(char *))
+ : malloc(4 * sizeof(char *)); // allocate space
+ attr[l + 3] = (l) ? realloc(attr[l + 1], (l / 2) + 2)
+ : malloc(2); // mem for list of maloced vals
+ strcpy(attr[l + 3] + (l / 2), " "); // value is not malloced
+ attr[l + 2] = NULL; // null terminate list
+ attr[l + 1] = ""; // temporary attribute value
+ attr[l] = s; // set attribute name
+
+ s += strcspn(s, EZXML_WS "=/>");
+ if (*s == '=' || isspace(*s)) {
+ *(s++) = '\0'; // null terminate tag attribute name
+ q = *(s += strspn(s, EZXML_WS "="));
+ if (q == '"' || q == '\'') { // attribute value
+ attr[l + 1] = ++s;
+ while (*s && *s != q) s++;
+ if (*s) *(s++) = '\0'; // null terminate attribute val
+ else {
+ ezxml_free_attr(attr);
+ return ezxml_err(root, d, "missing %c", q);
+ }
+
+ for (j = 1; a && a[j] && strcmp(a[j], attr[l]); j +=3);
+ attr[l + 1] = ezxml_decode(attr[l + 1], root->ent,
+ (char)((a && a[j]) ? *a[j + 2] : ' '));
+ if (attr[l + 1] < d || attr[l + 1] > s)
+ attr[l + 3][l / 2] = EZXML_TXTM; // value malloced
+ }
+ }
+ while (isspace(*s)) s++;
+ }
+
+ if (*s == '/') { // self closing tag
+ *(s++) = '\0';
+ if ((*s && *s != '>') || (! *s && e != '>')) {
+ if (l) ezxml_free_attr(attr);
+ return ezxml_err(root, d, "missing >");
+ }
+ ezxml_open_tag(root, d, attr);
+ ezxml_close_tag(root, d, s);
+ }
+ else if ((q = *s) == '>' || (! *s && e == '>')) { // open tag
+ *s = '\0'; // temporarily null terminate tag name
+ ezxml_open_tag(root, d, attr);
+ *s = q;
+ }
+ else {
+ if (l) ezxml_free_attr(attr);
+ return ezxml_err(root, d, "missing >");
+ }
+ }
+ else if (*s == '/') { // close tag
+ s += strcspn(d = s + 1, EZXML_WS ">") + 1;
+ if (! (q = *s) && e != '>') return ezxml_err(root, d, "missing >");
+ *s = '\0'; // temporarily null terminate tag name
+ if (ezxml_close_tag(root, d, s)) return &root->xml;
+ if (isspace(*s = q)) s += strspn(s, EZXML_WS);
+ }
+ else if (! strncmp(s, "!--", 3)) { // xml comment
+ if (! (s = strstr(s + 3, "--")) || (*(s += 2) != '>' && *s) ||
+ (! *s && e != '>')) return ezxml_err(root, d, "unclosed <!--");
+ }
+ else if (! strncmp(s, "![CDATA[", 8)) { // cdata
+ if ((s = strstr(s, "]]>")))
+ ezxml_char_content(root, d + 8, (s += 2) - d - 10, 'c');
+ else return ezxml_err(root, d, "unclosed <![CDATA[");
+ }
+ else if (! strncmp(s, "!DOCTYPE", 8)) { // dtd
+ for (l = 0; *s && ((! l && *s != '>') || (l && (*s != ']' ||
+ *(s + strspn(s + 1, EZXML_WS) + 1) != '>')));
+ l = (*s == '[') ? 1 : l) s += strcspn(s + 1, "[]>") + 1;
+ if (! *s && e != '>')
+ return ezxml_err(root, d, "unclosed <!DOCTYPE");
+ d = (l) ? strchr(d, '[') + 1 : d;
+ if (l && ! ezxml_internal_dtd(root, d, s++ - d)) return &root->xml;
+ }
+ else if (*s == '?') { // <?...?> processing instructions
+ do { s = strchr(s, '?'); } while (s && *(++s) && *s != '>');
+ if (! s || (! *s && e != '>'))
+ return ezxml_err(root, d, "unclosed <?");
+ else ezxml_proc_inst(root, d + 1, s - d - 2);
+ }
+ else return ezxml_err(root, d, "unexpected <");
+
+ if (! s || ! *s) break;
+ *s = '\0';
+ d = ++s;
+ if (*s && *s != '<') { // tag character content
+ while (*s && *s != '<') s++;
+ if (*s) ezxml_char_content(root, d, s - d, '&');
+ else break;
+ }
+ else if (! *s) break;
+ }
+
+ if (! root->cur) return &root->xml;
+ else if (! root->cur->name) return ezxml_err(root, d, "root tag missing");
+ else return ezxml_err(root, d, "unclosed tag <%s>", root->cur->name);
+}
+
+// Wrapper for ezxml_parse_str() that accepts a file stream. Reads the entire
+// stream into memory and then parses it. For xml files, use ezxml_parse_file()
+// or ezxml_parse_fd()
+ezxml_t ezxml_parse_fp(FILE *fp)
+{
+ ezxml_root_t root;
+ size_t l, len = 0;
+ char *s;
+
+ if (! (s = malloc(EZXML_BUFSIZE))) return NULL;
+ do {
+ len += (l = fread((s + len), 1, EZXML_BUFSIZE, fp));
+ if (l == EZXML_BUFSIZE) s = realloc(s, len + EZXML_BUFSIZE);
+ } while (s && l == EZXML_BUFSIZE);
+
+ if (! s) return NULL;
+ root = (ezxml_root_t)ezxml_parse_str(s, len);
+ root->len = SIZE_MAX; // so we know to free s in ezxml_free()
+ return &root->xml;
+}
+
+// Encodes ampersand sequences appending the results to *dst, reallocating *dst
+// if length excedes max. a is non-zero for attribute encoding. Returns *dst
+char *ezxml_ampencode(const char *s, size_t len, char **dst, size_t *dlen,
+ size_t *max, short a)
+{
+ const char *e;
+
+ for (e = s + len; s != e; s++) {
+ while (*dlen + 10 > *max) *dst = realloc(*dst, *max += EZXML_BUFSIZE);
+
+ switch (*s) {
+ case '\0': return *dst;
+ case '&': *dlen += sprintf(*dst + *dlen, "&amp;"); break; //!!!!!!!!!!!!!!
+ case '<': *dlen += sprintf(*dst + *dlen, "&lt;"); break; //!!!!!!!!!!!!!!
+ case '>': *dlen += sprintf(*dst + *dlen, "&gt;"); break; //!!!!!!!!!!!!!!
+ case '"': *dlen += sprintf(*dst + *dlen, (a) ? "&quot;" : "\""); break; //!!!!!!!!!!!!!!
+// case '\n': *dlen += sprintf(*dst + *dlen, (a) ? "&#xA;" : "\n"); break; //!!!!!!!!!!!!!!
+ case '\t': *dlen += sprintf(*dst + *dlen, (a) ? "&#x9;" : "\t"); break; //!!!!!!!!!!!!!!
+// case '\r': *dlen += sprintf(*dst + *dlen, "&#xD;"); break; //!!!!!!!!!!!!!!
+ default: (*dst)[(*dlen)++] = *s;
+ }
+ }
+ return *dst;
+}
+
+// Recursively converts each tag to xml appending it to *s. Reallocates *s if
+// its length excedes max. start is the location of the previous tag in the
+// parent tag's character content. Returns *s.
+char *ezxml_toxml_r(ezxml_t xml, char **s, size_t *len, size_t *max,
+ size_t start, char ***attr)
+{
+ int i, j;
+ char *txt = (xml->parent) ? xml->parent->txt : "";
+ size_t off = 0;
+
+ // parent character content up to this tag
+ *s = ezxml_ampencode(txt + start, xml->off - start, s, len, max, 0);
+
+ while (*len + strlen(xml->name) + 4 > *max) // reallocate s
+ *s = realloc(*s, *max += EZXML_BUFSIZE);
+
+ *len += sprintf(*s + *len, "<%s", xml->name); // open tag //!!!!!!!!!!!!!!
+ for (i = 0; xml->attr[i]; i += 2) { // tag attributes
+ if (ezxml_attr(xml, xml->attr[i]) != xml->attr[i + 1]) continue;
+ while (*len + strlen(xml->attr[i]) + 7 > *max) // reallocate s
+ *s = realloc(*s, *max += EZXML_BUFSIZE);
+
+ *len += sprintf(*s + *len, " %s=\"", xml->attr[i]); //!!!!!!!!!!!!!!
+ ezxml_ampencode(xml->attr[i + 1], SIZE_MAX, s, len, max, 1);
+ *len += sprintf(*s + *len, "\""); //!!!!!!!!!!!!!!
+ }
+
+ for (i = 0; attr[i] && strcmp(attr[i][0], xml->name); i++);
+ for (j = 1; attr[i] && attr[i][j]; j += 3) { // default attributes
+ if (! attr[i][j + 1] || ezxml_attr(xml, attr[i][j]) != attr[i][j + 1])
+ continue; // skip duplicates and non-values
+ while (*len + strlen(attr[i][j]) + 7 > *max) // reallocate s
+ *s = realloc(*s, *max += EZXML_BUFSIZE);
+
+ *len += sprintf(*s + *len, " %s=\"", attr[i][j]); //!!!!!!!!!!!!!!
+ ezxml_ampencode(attr[i][j + 1], SIZE_MAX, s, len, max, 1);
+ *len += sprintf(*s + *len, "\""); //!!!!!!!!!!!!!!
+ }
+ if (xml->attr != EZXML_NIL && xml->child == NULL && xml->txt[0] == 0)
+ *len += sprintf(*s + *len, "/>"); //!!!!!!!!!!!!!!
+ else
+ {
+ *len += sprintf(*s + *len, ">"); //!!!!!!!!!!!!!!
+
+ *s = (xml->child) ? ezxml_toxml_r(xml->child, s, len, max, 0, attr) //child
+ : ezxml_ampencode(xml->txt, SIZE_MAX, s, len, max, 0); //data
+
+ while (*len + strlen(xml->name) + 4 > *max) // reallocate s
+ *s = realloc(*s, *max += EZXML_BUFSIZE);
+
+ *len += sprintf(*s + *len, "</%s>", xml->name); // close tag //!!!!!!!!!!!!!!
+ }
+
+ while (txt[off] && off < xml->off) off++; // make sure off is within bounds
+ return (xml->ordered) ? ezxml_toxml_r(xml->ordered, s, len, max, off, attr)
+ : ezxml_ampencode(txt + off, SIZE_MAX, s, len, max, 0);
+}
+
+// Converts an ezxml structure back to xml. Returns a string of xml data that
+// must be freed.
+char *ezxml_toxml(ezxml_t xml, int addhdr)
+{
+ ezxml_t p = (xml) ? xml->parent : NULL, o = (xml) ? xml->ordered : NULL;
+ ezxml_root_t root = (ezxml_root_t)xml;
+ size_t len, max = EZXML_BUFSIZE;
+ char *s, *t, *n;
+ int i, j, k;
+
+ s = strcpy(malloc(max), addhdr ? "<?xml version=\"1.0\" encoding=\"utf-8\"?>" : "");
+ len = strlen(s);
+
+ if (! xml || ! xml->name) return realloc(s, len + 1);
+ while (root->xml.parent) root = (ezxml_root_t)root->xml.parent; // root tag
+
+ for (i = 0; ! p && root->pi[i]; i++) { // pre-root processing instructions
+ for (k = 2; root->pi[i][k - 1]; k++);
+ for (j = 1; (n = root->pi[i][j]); j++) {
+ if (root->pi[i][k][j - 1] == '>') continue; // not pre-root
+ while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
+ s = realloc(s, max += EZXML_BUFSIZE);
+ len += sprintf(s + len, "<?%s%s%s?>\n", t, *n ? " " : "", n); //!!!!!!!!!!!!!!
+ }
+ }
+
+ xml->parent = xml->ordered = NULL;
+ s = ezxml_toxml_r(xml, &s, &len, &max, 0, root->attr);
+ xml->parent = p;
+ xml->ordered = o;
+
+ for (i = 0; ! p && root->pi[i]; i++) { // post-root processing instructions
+ for (k = 2; root->pi[i][k - 1]; k++);
+ for (j = 1; (n = root->pi[i][j]); j++) {
+ if (root->pi[i][k][j - 1] == '<') continue; // not post-root
+ while (len + strlen(t = root->pi[i][0]) + strlen(n) + 7 > max)
+ s = realloc(s, max += EZXML_BUFSIZE);
+ len += sprintf(s + len, "\n<?%s%s%s?>", t, *n ? " " : "", n); //!!!!!!!!!!!!!!
+ }
+ }
+ return realloc(s, len + 1);
+}
+
+// free the memory allocated for the ezxml structure
+void ezxml_free(ezxml_t xml)
+{
+ ezxml_root_t root = (ezxml_root_t)xml;
+ int i, j;
+ char **a, *s;
+
+ if (! xml) return;
+ ezxml_free(xml->child);
+ ezxml_free(xml->ordered);
+
+ if (! xml->parent) { // free root tag allocations
+ for (i = 10; root->ent[i]; i += 2) // 0 - 9 are default entites (<>&"')
+ if ((s = root->ent[i + 1]) < root->s || s > root->e) free(s);
+ free(root->ent); // free list of general entities
+
+ for (i = 0; (a = root->attr[i]); i++) {
+ for (j = 1; a[j++]; j += 2) // free malloced attribute values
+ if (a[j] && (a[j] < root->s || a[j] > root->e)) free(a[j]);
+ free(a);
+ }
+ if (root->attr[0]) free(root->attr); // free default attribute list
+
+ for (i = 0; root->pi[i]; i++) {
+ for (j = 1; root->pi[i][j]; j++);
+ free(root->pi[i][j + 1]);
+ free(root->pi[i]);
+ }
+ if (root->pi[0]) free(root->pi); // free processing instructions
+
+ if (root->len == SIZE_MAX) free(root->m); // malloced xml data
+ if (root->u) free(root->u); // utf8 conversion
+ }
+
+ ezxml_free_attr(xml->attr); // tag attributes
+ if ((xml->flags & EZXML_TXTM)) free(xml->txt); // character content
+ if ((xml->flags & EZXML_NAMEM)) free(xml->name); // tag name
+ free(xml);
+}
+
+// return parser error message or empty string if none
+const char *ezxml_error(ezxml_t xml)
+{
+ while (xml && xml->parent) xml = xml->parent; // find root tag
+ return (xml) ? ((ezxml_root_t)xml)->err : "";
+}
+
+// returns a new empty ezxml structure with the given root tag name
+ezxml_t ezxml_new(const char *name)
+{
+ static char *ent[] = { "lt;", "&#60;", "gt;", "&#62;", "quot;", "&#34;",
+ "apos;", "&#39;", "amp;", "&#38;", NULL };
+ ezxml_root_t root = (ezxml_root_t)memset(malloc(sizeof(struct ezxml_root)),
+ '\0', sizeof(struct ezxml_root));
+ root->xml.name = (char *)name;
+ root->cur = &root->xml;
+ strcpy(root->err, root->xml.txt = "");
+ root->ent = memcpy(malloc(sizeof(ent)), ent, sizeof(ent));
+ root->attr = root->pi = (char ***)(root->xml.attr = EZXML_NIL);
+ return &root->xml;
+}
+
+// inserts an existing tag into an ezxml structure
+ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off)
+{
+ ezxml_t cur, prev, head;
+
+ xml->next = xml->sibling = xml->ordered = NULL;
+ xml->off = off;
+ xml->parent = dest;
+
+ if ((head = dest->child)) { // already have sub tags
+ if (head->off <= off) { // not first subtag
+ for (cur = head; cur->ordered && cur->ordered->off <= off;
+ cur = cur->ordered);
+ xml->ordered = cur->ordered;
+ cur->ordered = xml;
+ }
+ else { // first subtag
+ xml->ordered = head;
+ dest->child = xml;
+ }
+
+ for (cur = head, prev = NULL; cur && strcmp(cur->name, xml->name);
+ prev = cur, cur = cur->sibling); // find tag type
+ if (cur && cur->off <= off) { // not first of type
+ while (cur->next && cur->next->off <= off) cur = cur->next;
+ xml->next = cur->next;
+ cur->next = xml;
+ }
+ else { // first tag of this type
+ if (prev && cur) prev->sibling = cur->sibling; // remove old first
+ xml->next = cur; // old first tag is now next
+ for (cur = head, prev = NULL; cur && cur->off <= off;
+ prev = cur, cur = cur->sibling); // new sibling insert point
+ xml->sibling = cur;
+ if (prev) prev->sibling = xml;
+ }
+ }
+ else dest->child = xml; // only sub tag
+
+ return xml;
+}
+
+// Adds a child tag. off is the offset of the child tag relative to the start
+// of the parent tag's character content. Returns the child tag.
+ezxml_t ezxml_add_child(ezxml_t xml, const char *name, size_t off)
+{
+ ezxml_t child;
+
+ if (! xml) return NULL;
+ child = (ezxml_t)memset(malloc(sizeof(struct ezxml)), '\0',
+ sizeof(struct ezxml));
+ child->name = (char *)name;
+ child->attr = EZXML_NIL;
+ child->txt = "";
+
+ return ezxml_insert(child, xml, off);
+}
+
+// sets the character content for the given tag and returns the tag
+ezxml_t ezxml_set_txt(ezxml_t xml, const char *txt)
+{
+ if (! xml) return NULL;
+ if (xml->flags & EZXML_TXTM) free(xml->txt); // existing txt was malloced
+ xml->flags &= ~EZXML_TXTM;
+ xml->txt = (char *)txt;
+ return xml;
+}
+
+// Sets the given tag attribute or adds a new attribute if not found. A value
+// of NULL will remove the specified attribute. Returns the tag given.
+ezxml_t ezxml_set_attr(ezxml_t xml, const char *name, const char *value)
+{
+ int l = 0, c;
+
+ if (! xml) return NULL;
+ while (xml->attr[l] && strcmp(xml->attr[l], name)) l += 2;
+ if (! xml->attr[l]) { // not found, add as new attribute
+ if (! value) return xml; // nothing to do
+ if (xml->attr == EZXML_NIL) { // first attribute
+ xml->attr = malloc(4 * sizeof(char *));
+ xml->attr[1] = _strdup(""); // empty list of malloced names/vals
+ }
+ else xml->attr = realloc(xml->attr, (l + 4) * sizeof(char *));
+
+ xml->attr[l] = (char *)name; // set attribute name
+ xml->attr[l + 2] = NULL; // null terminate attribute list
+ xml->attr[l + 3] = realloc(xml->attr[l + 1],
+ (c = (int)strlen(xml->attr[l + 1])) + 2);
+ strcpy(xml->attr[l + 3] + c, " "); // set name/value as not malloced
+ if (xml->flags & EZXML_DUP) xml->attr[l + 3][c] = (unsigned char)EZXML_NAMEM;
+ }
+ else if (xml->flags & EZXML_DUP) free((char *)name); // name was strduped
+
+ for (c = l; xml->attr[c]; c += 2); // find end of attribute list
+ if (xml->attr[c + 1][l / 2] & EZXML_TXTM) free(xml->attr[l + 1]); //old val
+ if (xml->flags & EZXML_DUP) xml->attr[c + 1][l / 2] |= EZXML_TXTM;
+ else xml->attr[c + 1][l / 2] &= ~EZXML_TXTM;
+
+ if (value) xml->attr[l + 1] = (char *)value; // set attribute value
+ else { // remove attribute
+ if (xml->attr[c + 1][l / 2] & EZXML_NAMEM) free(xml->attr[l]);
+ memmove(xml->attr + l, xml->attr + l + 2, (c - l + 2) * sizeof(char*));
+ xml->attr = realloc(xml->attr, (c + 2) * sizeof(char *));
+ memmove(xml->attr[c + 1] + (l / 2), xml->attr[c + 1] + (l / 2) + 1,
+ (c / 2) - (l / 2)); // fix list of which name/vals are malloced
+ }
+ xml->flags &= ~EZXML_DUP; // clear strdup() flag
+ return xml;
+}
+
+// sets a flag for the given tag and returns the tag
+ezxml_t ezxml_set_flag(ezxml_t xml, short flag)
+{
+ if (xml) xml->flags |= flag;
+ return xml;
+}
+
+// removes a tag along with its subtags without freeing its memory
+ezxml_t ezxml_cut(ezxml_t xml)
+{
+ ezxml_t cur;
+
+ if (! xml) return NULL; // nothing to do
+ if (xml->next) xml->next->sibling = xml->sibling; // patch sibling list
+
+ if (xml->parent) { // not root tag
+ cur = xml->parent->child; // find head of subtag list
+ if (cur == xml) xml->parent->child = xml->ordered; // first subtag
+ else { // not first subtag
+ while (cur->ordered != xml) cur = cur->ordered;
+ cur->ordered = cur->ordered->ordered; // patch ordered list
+
+ cur = xml->parent->child; // go back to head of subtag list
+ if (strcmp(cur->name, xml->name)) { // not in first sibling list
+ while (strcmp(cur->sibling->name, xml->name))
+ cur = cur->sibling;
+ if (cur->sibling == xml) { // first of a sibling list
+ cur->sibling = (xml->next) ? xml->next
+ : cur->sibling->sibling;
+ }
+ else cur = cur->sibling; // not first of a sibling list
+ }
+
+ while (cur->next && cur->next != xml) cur = cur->next;
+ if (cur->next) cur->next = cur->next->next; // patch next list
+ }
+ }
+ xml->ordered = xml->sibling = xml->next = NULL;
+ return xml;
+}
diff --git a/protocols/MSN/src/ezxml.h b/protocols/MSN/src/ezxml.h
new file mode 100644
index 0000000000..37a0385541
--- /dev/null
+++ b/protocols/MSN/src/ezxml.h
@@ -0,0 +1,165 @@
+/* ezxml.h
+ *
+ * Copyright 2004-2006 Aaron Voisine <aaron@voisine.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _EZXML_H
+#define _EZXML_H
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define EZXML_BUFSIZE 1024 // size of internal memory buffers
+#define EZXML_NAMEM 0x80 // name is malloced
+#define EZXML_TXTM 0x40 // txt is malloced
+#define EZXML_DUP 0x20 // attribute name and value are strduped
+
+typedef struct ezxml *ezxml_t;
+struct ezxml {
+ char *name; // tag name
+ char **attr; // tag attributes { name, value, name, value, ... NULL }
+ char *txt; // tag character content, empty string if none
+ size_t off; // tag offset from start of parent tag character content
+ ezxml_t next; // next tag with same name in this section at this depth
+ ezxml_t sibling; // next tag with different name in same section and depth
+ ezxml_t ordered; // next tag, same section and depth, in original order
+ ezxml_t child; // head of sub tag list, NULL if none
+ ezxml_t parent; // parent tag, NULL if current tag is root tag
+ short flags; // additional information
+};
+
+// Given a string of xml data and its length, parses it and creates an ezxml
+// structure. For efficiency, modifies the data by adding null terminators
+// and decoding ampersand sequences. If you don't want this, copy the data and
+// pass in the copy. Returns NULL on failure.
+ezxml_t ezxml_parse_str(char *s, size_t len);
+
+// A wrapper for ezxml_parse_str() that accepts a file descriptor. First
+// attempts to mem map the file. Failing that, reads the file into memory.
+// Returns NULL on failure.
+ezxml_t ezxml_parse_fd(int fd);
+
+// a wrapper for ezxml_parse_fd() that accepts a file name
+ezxml_t ezxml_parse_file(const char *file);
+
+// Wrapper for ezxml_parse_str() that accepts a file stream. Reads the entire
+// stream into memory and then parses it. For xml files, use ezxml_parse_file()
+// or ezxml_parse_fd()
+ezxml_t ezxml_parse_fp(FILE *fp);
+
+// returns the first child tag (one level deeper) with the given name or NULL
+// if not found
+ezxml_t ezxml_child(ezxml_t xml, const char *name);
+
+// returns the next tag of the same name in the same section and depth or NULL
+// if not found
+#define ezxml_next(xml) ((xml) ? xml->next : NULL)
+
+// Returns the Nth tag with the same name in the same section at the same depth
+// or NULL if not found. An index of 0 returns the tag given.
+ezxml_t ezxml_idx(ezxml_t xml, int idx);
+
+// returns the name of the given tag
+#define ezxml_name(xml) ((xml) ? xml->name : NULL)
+
+// returns the given tag's character content or empty string if none
+#define ezxml_txt(xml) ((xml) ? xml->txt : "")
+
+// returns the value of the requested tag attribute, or NULL if not found
+const char *ezxml_attr(ezxml_t xml, const char *attr);
+
+// Traverses the ezxml sturcture to retrieve a specific subtag. Takes a
+// variable length list of tag names and indexes. The argument list must be
+// terminated by either an index of -1 or an empty string tag name. Example:
+// title = ezxml_get(library, "shelf", 0, "book", 2, "title", -1);
+// This retrieves the title of the 3rd book on the 1st shelf of library.
+// Returns NULL if not found.
+ezxml_t ezxml_get(ezxml_t xml, ...);
+
+// Converts an ezxml structure back to xml. Returns a string of xml data that
+// must be freed.
+char *ezxml_toxml(ezxml_t xml, int addhdr);
+
+// returns a NULL terminated array of processing instructions for the given
+// target
+const char **ezxml_pi(ezxml_t xml, const char *target);
+
+// frees the memory allocated for an ezxml structure
+void ezxml_free(ezxml_t xml);
+
+// returns parser error message or empty string if none
+const char *ezxml_error(ezxml_t xml);
+
+// returns a new empty ezxml structure with the given root tag name
+ezxml_t ezxml_new(const char *name);
+
+// wrapper for ezxml_new() that strdup()s name
+#define ezxml_new_d(name) ezxml_set_flag(ezxml_new(strdup(name)), EZXML_NAMEM)
+
+// Adds a child tag. off is the offset of the child tag relative to the start
+// of the parent tag's character content. Returns the child tag.
+ezxml_t ezxml_add_child(ezxml_t xml, const char *name, size_t off);
+
+// wrapper for ezxml_add_child() that strdup()s name
+#define ezxml_add_child_d(xml, name, off) \
+ ezxml_set_flag(ezxml_add_child(xml, strdup(name), off), EZXML_NAMEM)
+
+// sets the character content for the given tag and returns the tag
+ezxml_t ezxml_set_txt(ezxml_t xml, const char *txt);
+
+// wrapper for ezxml_set_txt() that strdup()s txt
+#define ezxml_set_txt_d(xml, txt) \
+ ezxml_set_flag(ezxml_set_txt(xml, strdup(txt)), EZXML_TXTM)
+
+// Sets the given tag attribute or adds a new attribute if not found. A value
+// of NULL will remove the specified attribute. Returns the tag given.
+ezxml_t ezxml_set_attr(ezxml_t xml, const char *name, const char *value);
+
+// Wrapper for ezxml_set_attr() that strdup()s name/value. Value cannot be NULL
+#define ezxml_set_attr_d(xml, name, value) \
+ ezxml_set_attr(ezxml_set_flag(xml, EZXML_DUP), strdup(name), strdup(value))
+
+// sets a flag for the given tag and returns the tag
+ezxml_t ezxml_set_flag(ezxml_t xml, short flag);
+
+// removes a tag along with its subtags without freeing its memory
+ezxml_t ezxml_cut(ezxml_t xml);
+
+// inserts an existing tag into an ezxml structure
+ezxml_t ezxml_insert(ezxml_t xml, ezxml_t dest, size_t off);
+
+// Moves an existing tag to become a subtag of dest at the given offset from
+// the start of dest's character content. Returns the moved tag.
+#define ezxml_move(xml, dest, off) ezxml_insert(ezxml_cut(xml), dest, off)
+
+// removes a tag along with all its subtags
+#define ezxml_remove(xml) ezxml_free(ezxml_cut(xml))
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _EZXML_H
diff --git a/protocols/MSN/src/msn.cpp b/protocols/MSN/src/msn.cpp
new file mode 100644
index 0000000000..21e199fb77
--- /dev/null
+++ b/protocols/MSN/src/msn.cpp
@@ -0,0 +1,151 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+#include "version.h"
+
+HINSTANCE hInst;
+
+int hLangpack;
+TIME_API tmi;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Initialization routines
+
+void MsnLinks_Init(void);
+void MsnLinks_Destroy(void);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Global variables
+
+int avsPresent = -1;
+
+static const PLUGININFOEX pluginInfo =
+{
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __AUTHOREMAIL,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE,
+ // {97724AF9-F3FB-47d3-A3BF-EAA935C74E6D}
+ {0x97724af9, 0xf3fb, 0x47d3, {0xa3, 0xbf, 0xea, 0xa9, 0x35, 0xc7, 0x4e, 0x6d}}
+};
+
+int MSN_GCEventHook(WPARAM wParam, LPARAM lParam);
+int MSN_GCMenuHook(WPARAM wParam, LPARAM lParam);
+
+/////////////////////////////////////////////////////////////////////////////
+// Protocol instances
+static int sttCompareProtocols(const CMsnProto *p1, const CMsnProto *p2)
+{
+ return _tcscmp(p1->m_tszUserName, p2->m_tszUserName);
+}
+
+OBJLIST<CMsnProto> g_Instances(1, sttCompareProtocols);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Main DLL function
+
+extern "C" BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason,LPVOID lpvReserved)
+{
+ if (fdwReason == DLL_PROCESS_ATTACH) {
+ hInst = hinstDLL;
+ DisableThreadLibraryCalls(hinstDLL);
+ }
+ return TRUE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnModulesLoaded - finalizes plugin's configuration on load
+
+static int OnModulesLoaded(WPARAM wParam, LPARAM lParam)
+{
+ avsPresent = ServiceExists(MS_AV_SETMYAVATART) != 0;
+
+ MsnLinks_Init();
+
+ return 0;
+}
+
+static CMsnProto* msnProtoInit(const char* pszProtoName, const TCHAR* tszUserName)
+{
+ CMsnProto *ppro = new CMsnProto(pszProtoName, tszUserName);
+ g_Instances.insert(ppro);
+ return ppro;
+}
+
+static int msnProtoUninit(CMsnProto* ppro)
+{
+ g_Instances.remove(ppro);
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Performs a primary set of actions upon plugin loading
+
+extern "C" int __declspec(dllexport) Load(void)
+{
+ mir_getTMI(&tmi);
+ mir_getLP(&pluginInfo);
+
+ HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded);
+
+ PROTOCOLDESCRIPTOR pd = { sizeof(pd) };
+ pd.szName = "MSN";
+ pd.fnInit = (pfnInitProto)msnProtoInit;
+ pd.fnUninit = (pfnUninitProto)msnProtoUninit;
+ pd.type = PROTOTYPE_PROTOCOL;
+ CallService(MS_PROTO_REGISTERMODULE, 0, (LPARAM)&pd);
+
+ MsnInitIcons();
+ MSN_InitContactMenu();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Unload a plugin
+
+extern "C" int __declspec(dllexport) Unload(void)
+{
+ MSN_RemoveContactMenus();
+ MsnLinks_Destroy();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MirandaPluginInfoEx - returns an information about a plugin
+
+extern "C" __declspec(dllexport) const PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion)
+{
+ return &pluginInfo;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MirandaInterfaces - returns the protocol interface to the core
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_PROTOCOL, MIID_LAST };
diff --git a/protocols/MSN/src/msn_auth.cpp b/protocols/MSN/src/msn_auth.cpp
new file mode 100644
index 0000000000..9474b71dfc
--- /dev/null
+++ b/protocols/MSN/src/msn_auth.cpp
@@ -0,0 +1,485 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2007-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+#include "des.h"
+
+static const char defaultPassportUrl[] = "https://login.live.com/RST2.srf";
+
+static const char authPacket[] =
+"<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+"<s:Envelope"
+ " xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\""
+ " xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\""
+ " xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\""
+ " xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\""
+ " xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\""
+ " xmlns:wsa=\"http://www.w3.org/2005/08/addressing\""
+ " xmlns:wssc=\"http://schemas.xmlsoap.org/ws/2005/02/sc\""
+ " xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+ "<s:Header>"
+ "<wsa:Action s:mustUnderstand=\"1\">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>"
+ "<wsa:To s:mustUnderstand=\"1\">HTTPS://login.live.com:443//RST2.srf</wsa:To>"
+ "<wsa:MessageID>%u</wsa:MessageID>"
+ "<ps:AuthInfo xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"PPAuthInfo\">"
+ "<ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>"
+ "<ps:BinaryVersion>5</ps:BinaryVersion>"
+ "<ps:UIVersion>1</ps:UIVersion>"
+ "<ps:Cookies />"
+ "<ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>"
+ "</ps:AuthInfo>"
+ "<wsse:Security>"
+ "<wsse:UsernameToken wsu:Id=\"user\">"
+ "<wsse:Username>%s</wsse:Username>"
+ "<wsse:Password>%s</wsse:Password>"
+ "</wsse:UsernameToken>"
+ "<wsu:Timestamp Id=\"Timestamp\">"
+ "<wsu:Created>%s</wsu:Created>"
+ "<wsu:Expires>%s</wsu:Expires>"
+ "</wsu:Timestamp>"
+ "</wsse:Security>"
+ "</s:Header>"
+ "<s:Body>"
+ "<ps:RequestMultipleSecurityTokens xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\" Id=\"RSTS\">"
+ "<wst:RequestSecurityToken Id=\"RST0\">"
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
+ "<wsp:AppliesTo>"
+ "<wsa:EndpointReference>"
+ "<wsa:Address>http://Passport.NET/tb</wsa:Address>"
+ "</wsa:EndpointReference>"
+ "</wsp:AppliesTo>"
+ "</wst:RequestSecurityToken>"
+ "<wst:RequestSecurityToken Id=\"RST1\">"
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
+ "<wsp:AppliesTo>"
+ "<wsa:EndpointReference>"
+ "<wsa:Address>messengerclear.live.com</wsa:Address>"
+ "</wsa:EndpointReference>"
+ "</wsp:AppliesTo>"
+ "<wsp:PolicyReference URI=\"MBI_KEY_OLD\" />"
+ "</wst:RequestSecurityToken>"
+ "<wst:RequestSecurityToken Id=\"RST2\">"
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
+ "<wsp:AppliesTo>"
+ "<wsa:EndpointReference>"
+ "<wsa:Address>messenger.msn.com</wsa:Address>"
+ "</wsa:EndpointReference>"
+ "</wsp:AppliesTo>"
+ "<wsp:PolicyReference URI=\"?id=507\" />"
+ "</wst:RequestSecurityToken>"
+ "<wst:RequestSecurityToken Id=\"RST3\">"
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
+ "<wsp:AppliesTo>"
+ "<wsa:EndpointReference>"
+ "<wsa:Address>messengersecure.live.com</wsa:Address>"
+ "</wsa:EndpointReference>"
+ "</wsp:AppliesTo>"
+ "<wsp:PolicyReference URI=\"MBI_SSL\" />"
+ "</wst:RequestSecurityToken>"
+ "<wst:RequestSecurityToken Id=\"RST4\">"
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
+ "<wsp:AppliesTo>"
+ "<wsa:EndpointReference>"
+ "<wsa:Address>contacts.msn.com</wsa:Address>"
+ "</wsa:EndpointReference>"
+ "</wsp:AppliesTo>"
+ "<wsp:PolicyReference URI=\"MBI\" />"
+ "</wst:RequestSecurityToken>"
+ "<wst:RequestSecurityToken Id=\"RST5\">"
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
+ "<wsp:AppliesTo>"
+ "<wsa:EndpointReference>"
+ "<wsa:Address>storage.msn.com</wsa:Address>"
+ "</wsa:EndpointReference>"
+ "</wsp:AppliesTo>"
+ "<wsp:PolicyReference URI=\"MBI\" />"
+ "</wst:RequestSecurityToken>"
+ "<wst:RequestSecurityToken Id=\"RST6\">"
+ "<wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
+ "<wsp:AppliesTo>"
+ "<wsa:EndpointReference>"
+ "<wsa:Address>sup.live.com</wsa:Address>"
+ "</wsa:EndpointReference>"
+ "</wsp:AppliesTo>"
+ "<wsp:PolicyReference URI=\"MBI\" />"
+ "</wst:RequestSecurityToken>"
+ "</ps:RequestMultipleSecurityTokens>"
+ "</s:Body>"
+"</s:Envelope>";
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Performs the MSN Passport login via TLS
+
+int CMsnProto::MSN_GetPassportAuth(void)
+{
+ int retVal = -1;
+
+ char szPassword[100];
+ db_get_static(NULL, m_szModuleName, "Password", szPassword, sizeof(szPassword));
+ szPassword[16] = 0;
+ char* szEncPassword = HtmlEncode(szPassword);
+
+ time_t ts = time(NULL);
+
+ TCHAR szTs1[64], szTs2[64];
+
+ tmi.printTimeStamp(UTC_TIME_HANDLE, ts, _T("I"), szTs1, SIZEOF(szTs1), 0);
+ tmi.printTimeStamp(UTC_TIME_HANDLE, ts + 20 * 60, _T("I"), szTs2, SIZEOF(szTs2), 0);
+
+ char *szTs1A = mir_t2a(szTs1), *szTs2A = mir_t2a(szTs2);
+
+ const size_t len = sizeof(authPacket) + 2048;
+ char* szAuthInfo = (char*)alloca(len);
+ mir_snprintf(szAuthInfo, len, authPacket, int(ts), MyOptions.szEmail, szEncPassword, szTs1A, szTs2A);
+
+ mir_free(szTs2A);
+ mir_free(szTs1A);
+ mir_free(szEncPassword);
+
+ char* szPassportHost = (char*)mir_alloc(256);
+ if (db_get_static(NULL, m_szModuleName, "MsnPassportHost", szPassportHost, 256))
+ strcpy(szPassportHost, defaultPassportUrl);
+
+ bool defaultUrlAllow = strcmp(szPassportHost, defaultPassportUrl) != 0;
+ char *tResult = NULL;
+
+ while (retVal == -1)
+ {
+ unsigned status;
+
+ tResult = getSslResult(&szPassportHost, szAuthInfo, NULL, status);
+ if (tResult == NULL)
+ {
+ if (defaultUrlAllow)
+ {
+ strcpy(szPassportHost, defaultPassportUrl);
+ defaultUrlAllow = false;
+ continue;
+ }
+ else
+ {
+ retVal = 4;
+ break;
+ }
+ }
+
+ switch (status)
+ {
+ case 200:
+ {
+ const char *errurl = NULL;
+ ezxml_t xml = ezxml_parse_str(tResult, strlen(tResult));
+
+ ezxml_t tokr = ezxml_get(xml, "S:Body", 0,
+ "wst:RequestSecurityTokenResponseCollection", 0,
+ "wst:RequestSecurityTokenResponse", -1);
+
+ while (tokr != NULL)
+ {
+ ezxml_t toks = ezxml_get(tokr, "wst:RequestedSecurityToken", 0,
+ "wsse:BinarySecurityToken", -1);
+
+ const char* addr = ezxml_txt(ezxml_get(tokr, "wsp:AppliesTo", 0,
+ "wsa:EndpointReference", 0, "wsa:Address", -1));
+
+ if (strcmp(addr, "http://Passport.NET/tb") == 0)
+ {
+ ezxml_t node = ezxml_get(tokr, "wst:RequestedSecurityToken", 0, "EncryptedData", -1);
+ free(hotAuthToken);
+ hotAuthToken = ezxml_toxml(node, 0);
+
+ node = ezxml_get(tokr, "wst:RequestedProofToken", 0, "wst:BinarySecret", -1);
+ replaceStr(hotSecretToken, ezxml_txt(node));
+ }
+ else if (strcmp(addr, "messengerclear.live.com") == 0)
+ {
+ ezxml_t node = ezxml_get(tokr, "wst:RequestedProofToken", 0,
+ "wst:BinarySecret", -1);
+ if (toks)
+ {
+ replaceStr(authStrToken, ezxml_txt(toks));
+ replaceStr(authSecretToken, ezxml_txt(node));
+ retVal = 0;
+ }
+ else
+ {
+ errurl = ezxml_txt(ezxml_get(tokr, "S:Fault", 0, "psf:pp", 0, "psf:flowurl", -1));
+ }
+ }
+ else if (strcmp(addr, "messenger.msn.com") == 0 && toks)
+ {
+ const char* tok = ezxml_txt(toks);
+ char* ch = (char*)strchr(tok, '&');
+ *ch = 0;
+ replaceStr(tAuthToken, tok+2);
+ replaceStr(pAuthToken, ch+3);
+ *ch = '&';
+ }
+ else if (strcmp(addr, "contacts.msn.com") == 0 && toks)
+ {
+ replaceStr(authContactToken, ezxml_txt(toks));
+ }
+ else if (strcmp(addr, "messengersecure.live.com") == 0 && toks)
+ {
+ replaceStr(oimSendToken, ezxml_txt(toks));
+ }
+ else if (strcmp(addr, "storage.msn.com") == 0 && toks)
+ {
+ replaceStr(authStorageToken, ezxml_txt(toks));
+ }
+
+ tokr = ezxml_next(tokr);
+ }
+
+ if (retVal != 0)
+ {
+ if (errurl)
+ {
+ debugLogA("Starting URL: '%s'", errurl);
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)errurl);
+ }
+
+ ezxml_t tokf = ezxml_get(xml, "S:Body", 0, "S:Fault", 0, "S:Detail", -1);
+ ezxml_t tokrdr = ezxml_child(tokf, "psf:redirectUrl");
+ if (tokrdr != NULL)
+ {
+ strcpy(szPassportHost, ezxml_txt(tokrdr));
+ debugLogA("Redirected to '%s'", szPassportHost);
+ }
+ else
+ {
+ const char* szFault = ezxml_txt(ezxml_get(tokf, "psf:error", 0, "psf:value", -1));
+ retVal = strcmp(szFault, "0x80048821") == 0 ? 3 : (tokf ? 5 : 7);
+ if (retVal != 3 && defaultUrlAllow)
+ {
+ strcpy(szPassportHost, defaultPassportUrl);
+ defaultUrlAllow = false;
+ retVal = -1;
+ }
+ else if (retVal != 3 && retVal != 7)
+ {
+ char err[512];
+ mir_snprintf(err, sizeof(err), "Unknown Authentication error: %s", szFault);
+ MSN_ShowError(err);
+ }
+ }
+ }
+
+ ezxml_free(xml);
+ break;
+ }
+ default:
+ if (defaultUrlAllow)
+ {
+ strcpy(szPassportHost, defaultPassportUrl);
+ defaultUrlAllow = false;
+ }
+ else
+ retVal = 6;
+ }
+ mir_free(tResult);
+ }
+
+ if (retVal != 0)
+ {
+ if (!Miranda_Terminated())
+ {
+ switch (retVal)
+ {
+ case 3:
+ MSN_ShowError("Your username or password is incorrect");
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD);
+ break;
+
+ case 5:
+ break;
+
+ default:
+ MSN_ShowError("Unable to contact MS Passport servers check proxy/firewall settings");
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER);
+ break;
+ }
+ }
+ }
+ else
+ setString("MsnPassportHost", szPassportHost);
+
+ mir_free(szPassportHost);
+ debugLogA("MSN_CheckRedirector exited with errorCode = %d", retVal);
+ return retVal;
+}
+
+static void derive_key(BYTE* der, unsigned char* key, size_t keylen, unsigned char* data, size_t datalen)
+{
+ BYTE hash1[MIR_SHA1_HASH_SIZE];
+ BYTE hash2[MIR_SHA1_HASH_SIZE];
+ BYTE hash3[MIR_SHA1_HASH_SIZE];
+ BYTE hash4[MIR_SHA1_HASH_SIZE];
+
+ const size_t buflen = MIR_SHA1_HASH_SIZE + datalen;
+ BYTE* buf = (BYTE*)alloca(buflen);
+
+ mir_hmac_sha1(hash1, key, keylen, data, datalen);
+ mir_hmac_sha1(hash3, key, keylen, hash1, MIR_SHA1_HASH_SIZE);
+
+ memcpy(buf, hash1, MIR_SHA1_HASH_SIZE);
+ memcpy(buf + MIR_SHA1_HASH_SIZE, data, datalen);
+ mir_hmac_sha1(hash2, key, keylen, buf, buflen);
+
+ memcpy(buf, hash3, MIR_SHA1_HASH_SIZE);
+ memcpy(buf + MIR_SHA1_HASH_SIZE, data, datalen);
+ mir_hmac_sha1(hash4, key, keylen, buf, buflen);
+
+ memcpy(der, hash2, MIR_SHA1_HASH_SIZE);
+ memcpy(der + MIR_SHA1_HASH_SIZE, hash4, 4);
+}
+
+typedef struct tag_MsgrUsrKeyHdr
+{
+ unsigned size;
+ unsigned cryptMode;
+ unsigned cipherType;
+ unsigned hashType;
+ unsigned ivLen;
+ unsigned hashLen;
+ unsigned long cipherLen;
+} MsgrUsrKeyHdr;
+
+static const MsgrUsrKeyHdr userKeyHdr =
+{
+ sizeof(MsgrUsrKeyHdr),
+ 1, // CRYPT_MODE_CBC
+ 0x6603, // CALG_3DES
+ 0x8004, // CALG_SHA1
+ 8, // sizeof(ivBytes)
+ MIR_SHA1_HASH_SIZE,
+ 72 // sizeof(cipherBytes);
+};
+
+
+static unsigned char* PKCS5_Padding(char* in, size_t &len)
+{
+ const size_t nlen = ((len >> 3) + 1) << 3;
+ unsigned char* res = (unsigned char*)mir_alloc(nlen);
+ memcpy(res, in, len);
+
+ const unsigned char pad = 8 - (len & 7);
+ memset(res + len, pad, pad);
+
+ len = nlen;
+ return res;
+}
+
+
+char* CMsnProto::GenerateLoginBlob(char* challenge)
+{
+ unsigned key1len;
+ BYTE *key1 = (BYTE*)mir_base64_decode(authSecretToken, &key1len);
+
+ BYTE key2[MIR_SHA1_HASH_SIZE+4];
+ BYTE key3[MIR_SHA1_HASH_SIZE+4];
+
+ static const unsigned char encdata1[] = "WS-SecureConversationSESSION KEY HASH";
+ static const unsigned char encdata2[] = "WS-SecureConversationSESSION KEY ENCRYPTION";
+
+ derive_key(key2, key1, key1len, (unsigned char*)encdata1, sizeof(encdata1) - 1);
+ derive_key(key3, key1, key1len, (unsigned char*)encdata2, sizeof(encdata2) - 1);
+
+ size_t chllen = strlen(challenge);
+
+ BYTE hash[MIR_SHA1_HASH_SIZE];
+ mir_hmac_sha1(hash, key2, MIR_SHA1_HASH_SIZE+4, (BYTE*)challenge, chllen);
+
+ unsigned char* newchl = PKCS5_Padding(challenge, chllen);
+
+ const size_t pktsz = sizeof(MsgrUsrKeyHdr) + MIR_SHA1_HASH_SIZE + 8 + chllen;
+ unsigned char* userKey = (unsigned char*)alloca(pktsz);
+
+ unsigned char* p = userKey;
+ memcpy(p, &userKeyHdr, sizeof(MsgrUsrKeyHdr));
+ ((MsgrUsrKeyHdr*)p)->cipherLen = (int)chllen;
+ p += sizeof(MsgrUsrKeyHdr);
+
+ unsigned char iv[8];
+ CallService(MS_UTILS_GETRANDOM, sizeof(iv), (LPARAM)iv);
+
+ memcpy(p, iv, sizeof(iv));
+ p += sizeof(iv);
+
+ memcpy(p, hash, sizeof(hash));
+ p += MIR_SHA1_HASH_SIZE;
+
+ des3_context ctxd;
+ memset(&ctxd, 0, sizeof(ctxd));
+ des3_set_3keys(&ctxd, key3);
+ des3_cbc_encrypt(&ctxd, iv, newchl, p, (int)chllen);
+
+ mir_free(newchl);
+
+ return mir_base64_encode(userKey, (unsigned)pktsz);
+}
+
+
+CMStringA CMsnProto::HotmailLogin(const char* url)
+{
+ unsigned char nonce[24];
+ CallService(MS_UTILS_GETRANDOM, sizeof(nonce), (LPARAM)nonce);
+
+ const size_t hotSecretlen = strlen(hotSecretToken);
+ unsigned key1len;
+ BYTE *key1 = (BYTE*)mir_base64_decode(hotSecretToken, &key1len);
+
+ static const unsigned char encdata[] = "WS-SecureConversation";
+ const size_t data1len = sizeof(nonce) + sizeof(encdata) - 1;
+
+ unsigned char* data1 = (unsigned char*)alloca(data1len);
+ memcpy(data1, encdata, sizeof(encdata) - 1);
+ memcpy(data1 + sizeof(encdata) - 1, nonce, sizeof(nonce));
+
+ unsigned char key2[MIR_SHA1_HASH_SIZE+4];
+ derive_key(key2, key1, key1len, data1, data1len);
+
+ CMStringA result;
+ result.Format("%s&da=%s&nonce=", url, ptrA(mir_urlEncode(hotAuthToken)));
+
+ ptrA noncenc(mir_base64_encode(nonce, sizeof(nonce)));
+ result.Append(ptrA(mir_urlEncode(noncenc)));
+
+ BYTE hash[MIR_SHA1_HASH_SIZE];
+ mir_hmac_sha1(hash, key2, sizeof(key2), (BYTE*)result.GetString(), result.GetLength());
+ ptrA szHash(mir_base64_encode(hash, sizeof(hash)));
+ result.AppendFormat("&hash=%s", ptrA(mir_urlEncode(szHash)));
+ return result;
+}
+
+void CMsnProto::FreeAuthTokens(void)
+{
+ mir_free(pAuthToken);
+ mir_free(tAuthToken);
+ mir_free(oimSendToken);
+ mir_free(authStrToken);
+ mir_free(authSecretToken);
+ mir_free(authContactToken);
+ mir_free(authStorageToken);
+ mir_free(hotSecretToken);
+ free(hotAuthToken);
+}
diff --git a/protocols/MSN/src/msn_avatar.cpp b/protocols/MSN/src/msn_avatar.cpp
new file mode 100644
index 0000000000..ba3b0da6ce
--- /dev/null
+++ b/protocols/MSN/src/msn_avatar.cpp
@@ -0,0 +1,122 @@
+/*
+Plugin for Miranda NG for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-14 Miranda NG Team
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+void CMsnProto::AvatarQueue_Init()
+{
+ hevAvatarQueue = ::CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ ForkThread(&CMsnProto::MSN_AvatarsThread, 0);
+}
+
+void CMsnProto::AvatarQueue_Uninit()
+{
+ ::CloseHandle(hevAvatarQueue);
+}
+
+void CMsnProto::pushAvatarRequest(MCONTACT hContact, LPCSTR pszUrl)
+{
+ ProtoBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, 0);
+
+ if (pszUrl != NULL && *pszUrl != 0) {
+ mir_cslock lck(csAvatarQueue);
+
+ for (int i=0; i < lsAvatarQueue.getCount(); i++)
+ if (lsAvatarQueue[i]->hContact == hContact)
+ return;
+
+ lsAvatarQueue.insert(new AvatarQueueEntry(hContact, pszUrl));
+ SetEvent(hevAvatarQueue);
+ }
+}
+
+bool CMsnProto::loadHttpAvatar(AvatarQueueEntry *p)
+{
+ NETLIBHTTPHEADER nlbhHeaders[1];
+ nlbhHeaders[0].szName = "User-Agent";
+ nlbhHeaders[0].szValue = (char*)MSN_USER_AGENT;
+
+ NETLIBHTTPREQUEST nlhr = { sizeof(nlhr) };
+ nlhr.requestType = REQUEST_GET;
+ nlhr.flags = NLHRF_HTTP11 | NLHRF_REDIRECT;
+ nlhr.szUrl = p->pszUrl;
+ nlhr.headers = (NETLIBHTTPHEADER*)&nlbhHeaders;
+ nlhr.headersCount = 1;
+
+ NETLIBHTTPREQUEST *nlhrReply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)m_hNetlibUser, (LPARAM)&nlhr);
+ if (nlhrReply == NULL)
+ return false;
+
+ if (nlhrReply->resultCode != 200 || nlhrReply->dataLength == 0) {
+LBL_Error:
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply);
+ return false;
+ }
+
+ const TCHAR *szExt;
+ int fmt = ProtoGetBufferFormat(nlhrReply->pData, &szExt);
+ if (fmt == PA_FORMAT_UNKNOWN)
+ goto LBL_Error;
+
+ PROTO_AVATAR_INFORMATIONT AI = { sizeof(AI) };
+ AI.format = fmt;
+ AI.hContact = p->hContact;
+ MSN_GetAvatarFileName(AI.hContact, AI.filename, SIZEOF(AI.filename), szExt);
+ _tremove(AI.filename);
+
+ int fileId = _topen(AI.filename, _O_CREAT | _O_TRUNC | _O_WRONLY | O_BINARY, _S_IREAD | _S_IWRITE);
+ if (fileId == -1)
+ goto LBL_Error;
+
+ _write(fileId, nlhrReply->pData, (unsigned)nlhrReply->dataLength);
+ _close(fileId);
+
+ ProtoBroadcastAck(p->hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &AI, 0);
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply);
+ return true;
+}
+
+void __cdecl CMsnProto::MSN_AvatarsThread(void*)
+{
+ while(true) {
+ if (WaitForSingleObject(hevAvatarQueue, INFINITE) != WAIT_OBJECT_0)
+ break;
+
+ if ( Miranda_Terminated())
+ break;
+
+ AvatarQueueEntry *p = NULL;
+ {
+ mir_cslock lck(csAvatarQueue);
+ if (lsAvatarQueue.getCount() > 0) {
+ p = lsAvatarQueue[0];
+ lsAvatarQueue.remove(0);
+ }
+ }
+
+ if (p == NULL)
+ continue;
+
+ if ( !loadHttpAvatar(p))
+ ProtoBroadcastAck(p->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, 0, 0);
+ delete p;
+ }
+}
diff --git a/protocols/MSN/src/msn_chat.cpp b/protocols/MSN/src/msn_chat.cpp
new file mode 100644
index 0000000000..04b17072d5
--- /dev/null
+++ b/protocols/MSN/src/msn_chat.cpp
@@ -0,0 +1,469 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#include "msn_global.h"
+#include "msn_proto.h"
+#include <m_history.h>
+
+MCONTACT CMsnProto::MSN_GetChatInernalHandle(MCONTACT hContact)
+{
+ MCONTACT result = hContact;
+ if ( isChatRoom(hContact)) {
+ DBVARIANT dbv;
+ if (getString(hContact, "ChatRoomID", &dbv) == 0) {
+ result = (MCONTACT)(-atol(dbv.pszVal));
+ db_free(&dbv);
+ }
+ }
+ return result;
+}
+
+int CMsnProto::MSN_ChatInit(ThreadData *info)
+{
+ InterlockedIncrement(&sttChatID);
+ _ltot(sttChatID, info->mChatID, 10);
+
+ TCHAR szName[512];
+ mir_sntprintf(szName, SIZEOF(szName), _T("%s %s%s"),
+ m_tszUserName, TranslateT("Chat #"), info->mChatID);
+
+ GCSESSION gcw = { sizeof(gcw) };
+ gcw.iType = GCW_CHATROOM;
+ gcw.pszModule = m_szModuleName;
+ gcw.ptszName = szName;
+ gcw.ptszID = info->mChatID;
+ CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM)&gcw);
+
+ GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_ADDGROUP };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.ptszStatus = TranslateT("Me");
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+
+ gcd.iType = GC_EVENT_JOIN;
+ gce.ptszUID = mir_a2t(MyOptions.szEmail);
+ gce.ptszNick = GetContactNameT(NULL);
+ gce.time = 0;
+ gce.bIsMe = TRUE;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+
+ gcd.iType = GC_EVENT_ADDGROUP;
+ gce.ptszStatus = TranslateT("Others");
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+
+ gcd.iType = GC_EVENT_CONTROL;
+ CallServiceSync(MS_GC_EVENT, SESSION_INITDONE, (LPARAM)&gce);
+ CallServiceSync(MS_GC_EVENT, SESSION_ONLINE, (LPARAM)&gce);
+ CallServiceSync(MS_GC_EVENT, WINDOW_VISIBLE, (LPARAM)&gce);
+
+ mir_free((TCHAR*)gce.ptszUID);
+ return 0;
+}
+
+void CMsnProto::MSN_ChatStart(ThreadData* info)
+{
+ if (info->mChatID[0] != 0)
+ return;
+
+ MSN_StartStopTyping(info, false);
+
+ MSN_ChatInit(info);
+
+ // add all participants onto the list
+ GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_JOIN };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.ptszStatus = TranslateT("Others");
+ gce.time = time(NULL);
+ gce.bIsMe = FALSE;
+
+ for (int j=0; j < info->mJoinedContactsWLID.getCount(); j++)
+ {
+ MCONTACT hContact = MSN_HContactFromEmail(info->mJoinedContactsWLID[j]);
+ TCHAR *wlid = mir_a2t(info->mJoinedContactsWLID[j]);
+
+ gce.ptszNick = GetContactNameT(hContact);
+ gce.ptszUID = wlid;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+
+ mir_free(wlid);
+ }
+}
+
+void CMsnProto::MSN_KillChatSession(const TCHAR* id)
+{
+ GCDEST gcd = { m_szModuleName, id, GC_EVENT_CONTROL };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_REMOVECONTACT;
+ CallServiceSync(MS_GC_EVENT, SESSION_OFFLINE, (LPARAM)&gce);
+ CallServiceSync(MS_GC_EVENT, SESSION_TERMINATE, (LPARAM)&gce);
+}
+
+static void ChatInviteUser(ThreadData* info, const char* email)
+{
+ if (info->mJoinedContactsWLID.getCount())
+ {
+ for (int j=0; j < info->mJoinedContactsWLID.getCount(); j++)
+ {
+ if (_stricmp(info->mJoinedContactsWLID[j], email) == 0)
+ return;
+ }
+
+ info->sendPacket("CAL", email);
+ info->proto->MSN_ChatStart(info);
+ }
+}
+
+static void ChatInviteSend(HANDLE hItem, HWND hwndList, STRLIST &str, CMsnProto *ppro)
+{
+ if (hItem == NULL)
+ hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_ROOT, 0);
+
+ while (hItem)
+ {
+ if (IsHContactGroup(hItem))
+ {
+ HANDLE hItemT = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
+ if (hItemT) ChatInviteSend(hItemT, hwndList, str, ppro);
+ }
+ else
+ {
+ int chk = SendMessage(hwndList, CLM_GETCHECKMARK, (WPARAM)hItem, 0);
+ if (chk)
+ {
+ if (IsHContactInfo(hItem))
+ {
+ TCHAR buf[128] = _T("");
+ SendMessage(hwndList, CLM_GETITEMTEXT, (WPARAM)hItem, (LPARAM)buf);
+
+ if (buf[0]) str.insert(mir_t2a(buf));
+ }
+ else
+ {
+ MsnContact *msc = ppro->Lists_Get((MCONTACT)hItem);
+ if (msc) str.insertn(msc->email);
+ }
+ }
+ }
+ hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXT, (LPARAM)hItem);
+ }
+}
+
+
+static void ChatValidateContact(MCONTACT hItem, HWND hwndList, CMsnProto* ppro)
+{
+ if (!ppro->MSN_IsMyContact(hItem) || ppro->isChatRoom(hItem) || ppro->MSN_IsMeByContact(hItem))
+ SendMessage(hwndList, CLM_DELETEITEM, (WPARAM)hItem, 0);
+}
+
+static void ChatPrepare(MCONTACT hItem, HWND hwndList, CMsnProto* ppro)
+{
+ if (hItem == NULL)
+ hItem = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_ROOT, 0);
+
+ while (hItem)
+ {
+ MCONTACT hItemN = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXT, (LPARAM)hItem);
+
+ if (IsHContactGroup(hItem)) {
+ MCONTACT hItemT = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
+ if (hItemT)
+ ChatPrepare(hItemT, hwndList, ppro);
+ }
+ else if (IsHContactContact(hItem))
+ ChatValidateContact(hItem, hwndList, ppro);
+
+ hItem = hItemN;
+ }
+}
+
+INT_PTR CALLBACK DlgInviteToChat(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ InviteChatParam *param = (InviteChatParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ param = (InviteChatParam*)lParam;
+
+// WindowSetIcon(hwndDlg, "msn");
+ break;
+
+ case WM_CLOSE:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ case WM_NCDESTROY:
+// WindowFreeIcon(hwndDlg);
+ delete param;
+ break;
+
+ case WM_NOTIFY:
+ {
+ NMCLISTCONTROL* nmc = (NMCLISTCONTROL*)lParam;
+ if (nmc->hdr.idFrom == IDC_CCLIST)
+ {
+ switch (nmc->hdr.code)
+ {
+ case CLN_NEWCONTACT:
+ if (param && (nmc->flags & (CLNF_ISGROUP | CLNF_ISINFO)) == 0)
+ ChatValidateContact((MCONTACT)nmc->hItem, nmc->hdr.hwndFrom, param->ppro);
+ break;
+
+ case CLN_LISTREBUILT:
+ if (param)
+ ChatPrepare(NULL, nmc->hdr.hwndFrom, param->ppro);
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_COMMAND:
+ {
+ switch (LOWORD(wParam))
+ {
+ case IDC_ADDSCR:
+ if (param->ppro->msnLoggedIn)
+ {
+ TCHAR email[MSN_MAX_EMAIL_LEN];
+ GetDlgItemText(hwndDlg, IDC_EDITSCR, email, SIZEOF(email));
+
+ CLCINFOITEM cii = {0};
+ cii.cbSize = sizeof(cii);
+ cii.flags = CLCIIF_CHECKBOX | CLCIIF_BELOWCONTACTS;
+ cii.pszText = _tcslwr(email);
+
+ HANDLE hItem = (HANDLE)SendDlgItemMessage(hwndDlg, IDC_CCLIST, CLM_ADDINFOITEM, 0, (LPARAM)&cii);
+ SendDlgItemMessage(hwndDlg, IDC_CCLIST, CLM_SETCHECKMARK, (LPARAM)hItem, 1);
+ }
+ break;
+
+ case IDOK:
+ {
+ char tEmail[MSN_MAX_EMAIL_LEN] = "";
+ ThreadData *info = NULL;
+ if (param->id)
+ info = param->ppro->MSN_GetThreadByChatId(param->id);
+ else if (param->hContact)
+ {
+ if (!param->ppro->MSN_IsMeByContact(param->hContact, tEmail))
+ info = param->ppro->MSN_GetThreadByContact(tEmail);
+ }
+
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_CCLIST);
+ STRLIST *cont = new STRLIST;
+ ChatInviteSend(NULL, hwndList, *cont, param->ppro);
+
+ if (info)
+ {
+ for (int i = 0; i < cont->getCount(); ++i)
+ ChatInviteUser(info, (*cont)[i]);
+ delete cont;
+ }
+ else
+ {
+ if (tEmail[0]) cont->insertn(tEmail);
+ param->ppro->MsgQueue_Add("chat", 'X', NULL, 0, NULL, 0, cont);
+ if (param->ppro->msnLoggedIn)
+ param->ppro->msnNsThread->sendPacket("XFR", "SB");
+ }
+ }
+
+ EndDialog(hwndDlg, IDOK);
+ break;
+
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+ }
+ }
+ break;
+ }
+ return FALSE;
+}
+
+int CMsnProto::MSN_GCEventHook(WPARAM, LPARAM lParam)
+{
+ GCHOOK *gch = (GCHOOK*)lParam;
+ if (!gch)
+ return 1;
+
+ if (_stricmp(gch->pDest->pszModule, m_szModuleName)) return 0;
+
+ switch (gch->pDest->iType)
+ {
+ case GC_SESSION_TERMINATE:
+ {
+ ThreadData* thread = MSN_GetThreadByChatId(gch->pDest->ptszID);
+ if (thread != NULL)
+ thread->sendTerminate();
+ break;
+ }
+
+ case GC_USER_MESSAGE:
+ if (gch->ptszText && gch->ptszText[0])
+ {
+ ThreadData* thread = MSN_GetThreadByChatId(gch->pDest->ptszID);
+ if (thread)
+ {
+ TCHAR* pszMsg = UnEscapeChatTags(NEWTSTR_ALLOCA(gch->ptszText));
+ rtrimt(pszMsg); // remove the ending linebreak
+ thread->sendMessage('N', NULL, NETID_MSN, UTF8(pszMsg), 0);
+
+ DBVARIANT dbv;
+ int bError = getTString("Nick", &dbv);
+
+ GCDEST gcd = { m_szModuleName, gch->pDest->ptszID, GC_EVENT_MESSAGE };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.ptszNick = bError ? _T("") : dbv.ptszVal;
+ gce.ptszUID = mir_a2t(MyOptions.szEmail);
+ gce.time = time(NULL);
+ gce.ptszText = gch->ptszText;
+ gce.bIsMe = TRUE;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+
+ mir_free((void*)gce.ptszUID);
+ if (!bError)
+ db_free(&dbv);
+ }
+ }
+ break;
+
+ case GC_USER_CHANMGR:
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_CHATROOM_INVITE), NULL, DlgInviteToChat,
+ LPARAM(new InviteChatParam(gch->pDest->ptszID, NULL, this)));
+ break;
+
+ case GC_USER_PRIVMESS:
+ {
+ char *email = mir_t2a(gch->ptszUID);
+ MCONTACT hContact = MSN_HContactFromEmail(email);
+ CallService(MS_MSG_SENDMESSAGE, hContact, 0);
+ mir_free(email);
+ break;
+ }
+
+ case GC_USER_LOGMENU:
+ switch(gch->dwData)
+ {
+ case 10:
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_CHATROOM_INVITE), NULL, DlgInviteToChat,
+ LPARAM(new InviteChatParam(gch->pDest->ptszID, NULL, this)));
+ break;
+
+ case 20:
+ MSN_KillChatSession(gch->pDest->ptszID);
+ break;
+ }
+ break;
+
+ case GC_USER_NICKLISTMENU:
+ {
+ char *email = mir_t2a(gch->ptszUID);
+ MCONTACT hContact = MSN_HContactFromEmail(email);
+ mir_free(email);
+
+ switch(gch->dwData)
+ {
+ case 10:
+ CallService(MS_USERINFO_SHOWDIALOG, hContact, 0);
+ break;
+
+ case 20:
+ CallService(MS_HISTORY_SHOWCONTACTHISTORY, hContact, 0);
+ break;
+
+ case 110:
+ MSN_KillChatSession(gch->pDest->ptszID);
+ break;
+ }
+ break;
+ }
+/* haven't implemented in chat.dll
+ case GC_USER_TYPNOTIFY:
+ {
+ int chatID = atoi(p);
+ ThreadData* thread = MSN_GetThreadByContact((HANDLE)-chatID);
+ for (int j=0; j < thread->mJoinedCount; j++)
+ {
+ if ((long)thread->mJoinedContacts[j] > 0)
+ CallService(MS_PROTO_SELFISTYPING, (WPARAM) thread->mJoinedContacts[j], (LPARAM) PROTOTYPE_SELFTYPING_ON);
+ }
+ break;
+ }
+*/
+ }
+
+ return 0;
+}
+
+int CMsnProto::MSN_GCMenuHook(WPARAM, LPARAM lParam)
+{
+ GCMENUITEMS *gcmi= (GCMENUITEMS*) lParam;
+
+ if (gcmi == NULL || _stricmp(gcmi->pszModule, m_szModuleName)) return 0;
+
+ if (gcmi->Type == MENU_ON_LOG)
+ {
+ static const struct gc_item Items[] =
+ {
+ { LPGENT("&Invite user..."), 10, MENU_ITEM, FALSE },
+ { LPGENT("&Leave chat session"), 20, MENU_ITEM, FALSE }
+ };
+ gcmi->nItems = SIZEOF(Items);
+ gcmi->Item = (gc_item*)Items;
+ }
+ else if (gcmi->Type == MENU_ON_NICKLIST)
+ {
+ char* email = mir_t2a(gcmi->pszUID);
+ if (!_stricmp(MyOptions.szEmail, email))
+ {
+ static const struct gc_item Items[] =
+ {
+ { LPGENT("User &details"), 10, MENU_ITEM, FALSE },
+ { LPGENT("User &history"), 20, MENU_ITEM, FALSE },
+ { _T(""), 100, MENU_SEPARATOR, FALSE },
+ { LPGENT("&Leave chat session"), 110, MENU_ITEM, FALSE }
+ };
+ gcmi->nItems = SIZEOF(Items);
+ gcmi->Item = (gc_item*)Items;
+ }
+ else
+ {
+ static const struct gc_item Items[] =
+ {
+ { LPGENT("User &details"), 10, MENU_ITEM, FALSE },
+ { LPGENT("User &history"), 20, MENU_ITEM, FALSE }
+ };
+ gcmi->nItems = SIZEOF(Items);
+ gcmi->Item = (gc_item*)Items;
+ }
+ mir_free(email);
+ }
+
+ return 0;
+}
diff --git a/protocols/MSN/src/msn_commands.cpp b/protocols/MSN/src/msn_commands.cpp
new file mode 100644
index 0000000000..c3bfaf9ee6
--- /dev/null
+++ b/protocols/MSN/src/msn_commands.cpp
@@ -0,0 +1,1753 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Starts a file sending thread
+
+void MSN_ConnectionProc(HANDLE hNewConnection, DWORD /* dwRemoteIP */, void* extra)
+{
+ CMsnProto *proto = (CMsnProto*)extra;
+
+ proto->debugLogA("File transfer connection accepted");
+
+ NETLIBCONNINFO connInfo = { sizeof(connInfo) };
+ CallService(MS_NETLIB_GETCONNECTIONINFO, (WPARAM)hNewConnection, (LPARAM)&connInfo);
+
+ ThreadData* T = proto->MSN_GetThreadByPort(connInfo.wPort);
+ if (T != NULL && T->s == NULL) {
+ T->s = hNewConnection;
+ ReleaseSemaphore(T->hWaitEvent, 1, NULL);
+ }
+ else {
+ proto->debugLogA("There's no registered file transfers for incoming port #%u, connection closed", connInfo.wPort);
+ Netlib_CloseHandle(hNewConnection);
+ }
+}
+
+void CMsnProto::MSN_SetMirVer(MCONTACT hContact, DWORD dwValue, bool always)
+{
+ static const char* MirVerStr[] =
+ {
+ "MSN 4.x-5.x",
+ "MSN 6.0",
+ "MSN 6.1",
+ "MSN 6.2",
+ "MSN 7.0",
+ "MSN 7.5",
+ "WLM 8.0",
+ "WLM 8.1",
+ "WLM 8.5",
+ "WLM 9.0 Beta",
+ "WLM 2009",
+ "WLM 2011",
+ "WLM 2012",
+ "WLM Unknown",
+ };
+
+ LPCSTR szVersion;
+
+ if (dwValue == 0)
+ szVersion = "Windows Phone";
+ else if (dwValue & 0x1)
+ szVersion = "MSN Mobile";
+ else if (dwValue & 0x200)
+ szVersion = "Webmessenger";
+ else if (dwValue == 0x800800)
+ szVersion = "Yahoo";
+ else if (dwValue == 0x800)
+ szVersion = "LCS";
+ else if (dwValue == 0x50000000)
+ szVersion = "Miranda IM 0.5.x (MSN v.0.5.x)";
+ else if (dwValue == 0x30000024)
+ szVersion = "Miranda IM 0.4.x (MSN v.0.4.x)";
+ else if (always || getByte(hContact, "StdMirVer", 0)) {
+ unsigned wlmId = min(dwValue >> 28 & 0xff, SIZEOF(MirVerStr) - 1);
+ szVersion = MirVerStr[wlmId];
+ }
+ else
+ return;
+
+ setString(hContact, "MirVer", szVersion);
+ setByte(hContact, "StdMirVer", 1);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Processes various invitations
+
+void CMsnProto::MSN_InviteMessage(ThreadData* info, char* msgBody, char* email, char* nick)
+{
+ MimeHeaders tFileInfo;
+ tFileInfo.readFromBuffer(msgBody);
+
+ const char* Appname = tFileInfo["Application-Name"];
+ const char* AppGUID = tFileInfo["Application-GUID"];
+ const char* Invcommand = tFileInfo["Invitation-Command"];
+ const char* Invcookie = tFileInfo["Invitation-Cookie"];
+ const char* Appfile = tFileInfo["Application-File"];
+ const char* Appfilesize = tFileInfo["Application-FileSize"];
+ const char* IPAddress = tFileInfo["IP-Address"];
+ const char* IPAddressInt = tFileInfo["IP-Address-Internal"];
+ const char* Port = tFileInfo["Port"];
+ const char* PortX = tFileInfo["PortX"];
+ const char* PortXInt = tFileInfo["PortX-Internal"];
+ const char* AuthCookie = tFileInfo["AuthCookie"];
+ const char* SessionID = tFileInfo["Session-ID"];
+ const char* SessionProtocol = tFileInfo["Session-Protocol"];
+ // const char* Connectivity = tFileInfo["Connectivity"];
+
+ if (AppGUID != NULL) {
+ if (!strcmp(AppGUID, "{02D3C01F-BF30-4825-A83A-DE7AF41648AA}")) {
+ MSN_ShowPopup(info->getContactHandle(),
+ TranslateT("Contact tried to open an audio conference (not currently supported)"), MSN_ALLOW_MSGBOX);
+ return;
+ }
+ }
+
+ if (Invcommand && (strcmp(Invcommand, "CANCEL") == 0)) {
+ delete info->mMsnFtp;
+ info->mMsnFtp = NULL;
+ }
+
+ if (Appname != NULL && Appfile != NULL && Appfilesize != NULL) // receive first
+ {
+ filetransfer* ft = info->mMsnFtp = new filetransfer(this);
+
+ ft->std.hContact = MSN_HContactFromEmail(email, nick, true, true);
+ mir_free(ft->std.tszCurrentFile);
+ ft->std.tszCurrentFile = mir_utf8decodeT(Appfile);
+ ft->std.totalBytes = ft->std.currentFileSize = _atoi64(Appfilesize);
+ ft->std.totalFiles = 1;
+ ft->szInvcookie = mir_strdup(Invcookie);
+ ft->p2p_dest = mir_strdup(email);
+
+ TCHAR tComment[40];
+ mir_sntprintf(tComment, SIZEOF(tComment), TranslateT("%I64u bytes"), ft->std.currentFileSize);
+
+ PROTORECVFILET pre = { 0 };
+ pre.flags = PREF_TCHAR;
+ pre.fileCount = 1;
+ pre.timestamp = time(NULL);
+ pre.tszDescription = tComment;
+ pre.ptszFiles = &ft->std.tszCurrentFile;
+ pre.lParam = (LPARAM)ft;
+ ProtoChainRecvFile(ft->std.hContact, &pre);
+ return;
+ }
+
+ // receive Second
+ if (IPAddress != NULL && Port != NULL && AuthCookie != NULL) {
+ ThreadData* newThread = new ThreadData;
+
+ if (inet_addr(IPAddress) != MyConnection.extIP || !IPAddressInt)
+ mir_snprintf(newThread->mServer, sizeof(newThread->mServer), "%s:%s", IPAddress, Port);
+ else
+ mir_snprintf(newThread->mServer, sizeof(newThread->mServer), "%s:%u", IPAddressInt, atol(PortXInt) ^ 0x3141);
+
+ newThread->mType = SERVER_FILETRANS;
+
+ if (info->mMsnFtp == NULL) {
+ ThreadData* otherThread = MSN_GetOtherContactThread(info);
+ if (otherThread) {
+ info->mMsnFtp = otherThread->mMsnFtp;
+ otherThread->mMsnFtp = NULL;
+ }
+ }
+
+ newThread->mMsnFtp = info->mMsnFtp; info->mMsnFtp = NULL;
+ strcpy(newThread->mCookie, AuthCookie);
+
+ newThread->startThread(&CMsnProto::MSNServerThread, this);
+ return;
+ }
+
+ // send 1
+ if (Invcommand != NULL && Invcookie != NULL && Port == NULL && AuthCookie == NULL && SessionID == NULL) {
+ msnftp_startFileSend(info, Invcommand, Invcookie);
+ return;
+ }
+
+ // netmeeting send 1
+ if (Appname == NULL && SessionID != NULL && SessionProtocol != NULL) {
+ if (!_stricmp(Invcommand, "ACCEPT")) {
+ ShellExecuteA(NULL, "open", "conf.exe", NULL, NULL, SW_SHOW);
+ Sleep(3000);
+
+ char command[1024];
+ int nBytes = mir_snprintf(command, sizeof(command),
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Invitation-Command: ACCEPT\r\n"
+ "Invitation-Cookie: %s\r\n"
+ "Session-ID: {1A879604-D1B8-11D7-9066-0003FF431510}\r\n"
+ "Launch-Application: TRUE\r\n"
+ "IP-Address: %s\r\n\r\n",
+ Invcookie, MyConnection.GetMyExtIPStr());
+ info->sendPacket("MSG", "N %d\r\n%s", nBytes, command);
+ }
+ return;
+ }
+
+ // netmeeting receive 1
+ if (Appname != NULL && !_stricmp(Appname, "NetMeeting")) {
+ char command[1024];
+ int nBytes;
+
+ TCHAR text[512], *tszEmail = mir_a2t(email);
+ mir_sntprintf(text, SIZEOF(text), TranslateT("Accept NetMeeting request from %s?"), tszEmail);
+ mir_free(tszEmail);
+
+ if (MessageBox(NULL, text, TranslateT("MSN Protocol"), MB_YESNO | MB_ICONQUESTION) == IDYES) {
+ nBytes = mir_snprintf(command, sizeof(command),
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Invitation-Command: ACCEPT\r\n"
+ "Invitation-Cookie: %s\r\n"
+ "Session-ID: {A2ED5ACF-F784-4B47-A7D4-997CD8F643CC}\r\n"
+ "Session-Protocol: SM1\r\n"
+ "Launch-Application: TRUE\r\n"
+ "Request-Data: IP-Address:\r\n"
+ "IP-Address: %s\r\n\r\n",
+ Invcookie, MyConnection.GetMyExtIPStr());
+ }
+ else {
+ nBytes = mir_snprintf(command, sizeof(command),
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Invitation-Command: CANCEL\r\n"
+ "Invitation-Cookie: %s\r\n"
+ "Cancel-Code: REJECT\r\n\r\n",
+ Invcookie);
+ }
+ info->sendPacket("MSG", "N %d\r\n%s", nBytes, command);
+ return;
+ }
+
+ if (IPAddress != NULL && Port == NULL && SessionID != NULL && SessionProtocol == NULL) { // netmeeting receive 2
+ char ipaddr[256];
+ mir_snprintf(ipaddr, sizeof(ipaddr), "callto://%s", IPAddress);
+ ShellExecuteA(NULL, "open", ipaddr, NULL, NULL, SW_SHOW);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Processes custom smiley messages
+
+void CMsnProto::MSN_CustomSmiley(const char* msgBody, char* email, char* nick, int iSmileyType)
+{
+ MCONTACT hContact = MSN_HContactFromEmail(email, nick, true, true);
+
+ char smileyList[500] = "";
+
+ const char *tok1 = msgBody, *tok2;
+ char *smlp = smileyList;
+ char lastsml[50];
+
+ unsigned iCount = 0;
+ bool parseSmiley = true;
+
+ for (;;) {
+ tok2 = strchr(tok1, '\t');
+ if (tok2 == NULL) break;
+
+ size_t sz = tok2 - tok1;
+ if (parseSmiley) {
+ sz = min(sz, sizeof(lastsml)-1);
+ memcpy(lastsml, tok1, sz);
+ lastsml[sz] = 0;
+
+ memcpy(smlp, tok1, sz); smlp += sz;
+ *(smlp++) = '\n'; *smlp = 0;
+ ++iCount;
+ }
+ else {
+ filetransfer* ft = new filetransfer(this);
+ ft->std.hContact = hContact;
+
+ ft->p2p_object = (char*)mir_alloc(sz + 1);
+ memcpy(ft->p2p_object, tok1, sz);
+ ft->p2p_object[sz] = 0;
+
+ size_t slen = strlen(lastsml);
+ ptrA buf(mir_base64_encode((PBYTE)lastsml, (unsigned)slen));
+ ptrA smileyName(mir_urlEncode(buf));
+ int rlen = lstrlenA(buf);
+
+ TCHAR path[MAX_PATH];
+ MSN_GetCustomSmileyFileName(hContact, path, SIZEOF(path), smileyName, iSmileyType);
+ ft->std.tszCurrentFile = mir_tstrdup(path);
+
+ if (p2p_IsDlFileOk(ft))
+ delete ft;
+ else {
+ debugLogA("Custom Smiley p2p invite for object : %s", ft->p2p_object);
+ p2p_invite(iSmileyType, ft, email);
+ Sleep(3000);
+ }
+ }
+ parseSmiley = !parseSmiley;
+ tok1 = tok2 + 1;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_ReceiveMessage - receives message or a file from the server
+/////////////////////////////////////////////////////////////////////////////////////////
+
+
+void CMsnProto::MSN_ReceiveMessage(ThreadData* info, char* cmdString, char* params)
+{
+ union {
+ char* tWords[6];
+ struct { char *fromEmail, *fromNick, *strMsgBytes; } data;
+ struct { char *fromEmail, *fromNetId, *toEmail, *toNetId, *typeId, *strMsgBytes; } datau;
+ };
+
+ if (sttDivideWords(params, SIZEOF(tWords), tWords) < 3) {
+ debugLogA("Invalid %.3s command, ignoring", cmdString);
+ return;
+ }
+
+ int msgBytes;
+ char *nick, *email;
+ bool ubmMsg = strncmp(cmdString, "UBM", 3) == 0;
+ bool sentMsg = false;
+
+ if (ubmMsg) {
+ msgBytes = atol(datau.strMsgBytes);
+ nick = datau.fromEmail;
+ email = datau.fromEmail;
+ }
+ else {
+ msgBytes = atol(data.strMsgBytes);
+ nick = data.fromNick;
+ email = data.fromEmail;
+ UrlDecode(nick);
+ }
+ stripBBCode(nick);
+ stripColorCode(nick);
+
+ char* msg = (char*)alloca(msgBytes+1);
+
+ HReadBuffer buf(info, 0);
+ BYTE* msgb = buf.surelyRead(msgBytes);
+ if (msgb == NULL) return;
+
+ memcpy(msg, msgb, msgBytes);
+ msg[msgBytes] = 0;
+
+ debugLogA("Message:\n%s", msg);
+
+ MimeHeaders tHeader;
+ char* msgBody = tHeader.readFromBuffer(msg);
+
+ const char* tMsgId = tHeader["Message-ID"];
+
+ // Chunked message
+ char* newbody = NULL;
+ if (tMsgId) {
+ int idx;
+ const char* tChunks = tHeader["Chunks"];
+ if (tChunks)
+ idx = addCachedMsg(tMsgId, msg, 0, msgBytes, atol(tChunks), true);
+ else
+ idx = addCachedMsg(tMsgId, msgBody, 0, strlen(msgBody), 0, true);
+
+ size_t newsize;
+ if (!getCachedMsg(idx, newbody, newsize)) return;
+ msgBody = tHeader.readFromBuffer(newbody);
+ }
+
+ // message from the server (probably)
+ if (!ubmMsg && strchr(email, '@') == NULL && _stricmp(email, "Hotmail"))
+ return;
+
+ const char* tContentType = tHeader["Content-Type"];
+ if (tContentType == NULL)
+ return;
+
+ if (!_strnicmp(tContentType, "text/x-clientcaps", 17)) {
+ MimeHeaders tFileInfo;
+ tFileInfo.readFromBuffer(msgBody);
+ info->firstMsgRecv = true;
+
+ MCONTACT hContact = MSN_HContactFromEmail(email);
+ const char* mirver = tFileInfo["Client-Name"];
+ if (hContact != NULL && mirver != NULL) {
+ setString(hContact, "MirVer", mirver);
+ delSetting(hContact, "StdMirVer");
+ }
+ }
+ else if (!ubmMsg && !info->firstMsgRecv) {
+ info->firstMsgRecv = true;
+ MsnContact *cont = Lists_Get(email);
+ if (cont && cont->hContact != NULL)
+ MSN_SetMirVer(cont->hContact, cont->cap1, true);
+ }
+
+ if (!_strnicmp(tContentType, "text/plain", 10)) {
+ MCONTACT hContact = MSN_HContactFromEmail(email, nick, true, true);
+
+ const char* p = tHeader["X-MMS-IM-Format"];
+ bool isRtl = p != NULL && strstr(p, "RL=1") != NULL;
+
+ if (info->mJoinedContactsWLID.getCount() > 1)
+ MSN_ChatStart(info);
+ else {
+ char *szEmail;
+ parseWLID(NEWSTR_ALLOCA(email), NULL, &szEmail, NULL);
+ sentMsg = _stricmp(szEmail, MyOptions.szEmail) == 0;
+ if (sentMsg)
+ hContact = ubmMsg ? MSN_HContactFromEmail(datau.toEmail, nick) : info->getContactHandle();
+ }
+
+ const char* tP4Context = tHeader["P4-Context"];
+ if (tP4Context) {
+ size_t newlen = strlen(msgBody) + strlen(tP4Context) + 4;
+ char* newMsgBody = (char*)mir_alloc(newlen);
+ mir_snprintf(newMsgBody, newlen, "[%s] %s", tP4Context, msgBody);
+ mir_free(newbody);
+ msgBody = newbody = newMsgBody;
+ }
+
+ if (info->mChatID[0]) {
+ GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_MESSAGE };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.ptszUID = mir_a2t(email);
+ gce.ptszNick = GetContactNameT(hContact);
+ gce.time = time(NULL);
+ gce.bIsMe = FALSE;
+
+ TCHAR* p = mir_utf8decodeT(msgBody);
+ gce.ptszText = EscapeChatTags(p);
+ mir_free(p);
+
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+ mir_free((void*)gce.ptszText);
+ mir_free((void*)gce.ptszUID);
+ }
+ else if (hContact) {
+ if (!sentMsg) {
+ CallService(MS_PROTO_CONTACTISTYPING, WPARAM(hContact), 0);
+
+ PROTORECVEVENT pre = { 0 };
+ pre.szMessage = (char*)msgBody;
+ pre.flags = PREF_UTF + (isRtl ? PREF_RTL : 0);
+ pre.timestamp = (DWORD)time(NULL);
+ pre.lParam = 0;
+ ProtoChainRecvMsg(hContact, &pre);
+ }
+ else {
+ bool haveWnd = MSN_MsgWndExist(hContact);
+
+ DBEVENTINFO dbei = { 0 };
+ dbei.cbSize = sizeof(dbei);
+ dbei.eventType = EVENTTYPE_MESSAGE;
+ dbei.flags = DBEF_SENT | DBEF_UTF | (haveWnd ? 0 : DBEF_READ) | (isRtl ? DBEF_RTL : 0);
+ dbei.szModule = m_szModuleName;
+ dbei.timestamp = time(NULL);
+ dbei.cbBlob = (unsigned)strlen(msgBody) + 1;
+ dbei.pBlob = (PBYTE)msgBody;
+ db_event_add(hContact, &dbei);
+ }
+ }
+ }
+ else if (!_strnicmp(tContentType, "text/x-msmsgsprofile", 20)) {
+ replaceStr(msnExternalIP, tHeader["ClientIP"]);
+ abchMigrated = atol(tHeader["ABCHMigrated"]);
+ langpref = atol(tHeader["lang_preference"]);
+ emailEnabled = atol(tHeader["EmailEnabled"]);
+
+ if (!MSN_RefreshContactList()) {
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER);
+ info->sendTerminate();
+ }
+ else {
+ MSN_SetServerStatus(m_iDesiredStatus);
+ MSN_EnableMenuItems(true);
+ }
+ }
+ else if (!_strnicmp(tContentType, "text/x-msmsgscontrol", 20)) {
+ const char* tTypingUser = tHeader["TypingUser"];
+
+ if (tTypingUser != NULL && info->mChatID[0] == 0 && _stricmp(email, MyOptions.szEmail)) {
+ MCONTACT hContact = MSN_HContactFromEmail(tTypingUser, tTypingUser);
+ CallService(MS_PROTO_CONTACTISTYPING, hContact, 7);
+ }
+ }
+ else if (!_strnicmp(tContentType, "text/x-msnmsgr-datacast", 23)) {
+ if (info->mJoinedContactsWLID.getCount()) {
+ MCONTACT tContact;
+
+ if (info->mChatID[0]) {
+ GC_INFO gci = { 0 };
+ gci.Flags = GCF_HCONTACT;
+ gci.pszModule = m_szModuleName;
+ gci.pszID = info->mChatID;
+ CallServiceSync(MS_GC_GETINFO, 0, (LPARAM)&gci);
+ tContact = gci.hContact;
+ }
+ else tContact = info->getContactHandle();
+
+ MimeHeaders tFileInfo;
+ tFileInfo.readFromBuffer(msgBody);
+
+ const char* id = tFileInfo["ID"];
+ if (id != NULL) {
+ switch (atol(id)) {
+ case 1: // Nudge
+ NotifyEventHooks(hMSNNudge, (WPARAM)tContact, 0);
+ break;
+
+ case 2: // Wink
+ break;
+
+ case 4: // Action Message
+ break;
+ }
+ }
+ }
+ }
+ else if (!_strnicmp(tContentType, "text/x-msmsgsemailnotification", 30))
+ sttNotificationMessage(msgBody, false);
+ else if (!_strnicmp(tContentType, "text/x-msmsgsinitialemailnotification", 37))
+ sttNotificationMessage(msgBody, true);
+ else if (!_strnicmp(tContentType, "text/x-msmsgsactivemailnotification", 35))
+ sttNotificationMessage(msgBody, false);
+ else if (!_strnicmp(tContentType, "text/x-msmsgsinitialmdatanotification", 37))
+ sttNotificationMessage(msgBody, true);
+ else if (!_strnicmp(tContentType, "text/x-msmsgsoimnotification", 28))
+ sttNotificationMessage(msgBody, false);
+ else if (!_strnicmp(tContentType, "text/x-msmsgsinvite", 19))
+ MSN_InviteMessage(info, msgBody, email, nick);
+ else if (!_strnicmp(tContentType, "application/x-msnmsgrp2p", 24)) {
+ const char* dest = tHeader["P2P-Dest"];
+ if (dest) {
+ char *szEmail, *szInst;
+ parseWLID(NEWSTR_ALLOCA(dest), NULL, &szEmail, &szInst);
+
+ if (stricmp(szEmail, MyOptions.szEmail) == 0) {
+ const char* src = tHeader["P2P-Src"];
+ if (src == NULL) src = email;
+
+ if (szInst == NULL)
+ p2p_processMsg(info, msgBody, src);
+ else if (stricmp(szInst, MyOptions.szMachineGuidP2P) == 0)
+ p2p_processMsgV2(info, msgBody, src);
+ }
+ }
+ }
+ else if (!_strnicmp(tContentType, "text/x-mms-emoticon", 19))
+ MSN_CustomSmiley(msgBody, email, nick, MSN_APPID_CUSTOMSMILEY);
+ else if (!_strnicmp(tContentType, "text/x-mms-animemoticon", 23))
+ MSN_CustomSmiley(msgBody, email, nick, MSN_APPID_CUSTOMANIMATEDSMILEY);
+
+ mir_free(newbody);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Process Yahoo Find
+
+void CMsnProto::MSN_ProcessYFind(char* buf, size_t len)
+{
+ if (buf == NULL) return;
+ ezxml_t xmli = ezxml_parse_str(buf, len);
+
+ ezxml_t dom = ezxml_child(xmli, "d");
+ const char* szDom = ezxml_attr(dom, "n");
+
+ ezxml_t cont = ezxml_child(dom, "c");
+ const char* szCont = ezxml_attr(cont, "n");
+
+ char szEmail[128];
+ mir_snprintf(szEmail, sizeof(szEmail), "%s@%s", szCont, szDom);
+
+ const char *szNetId = ezxml_attr(cont, "t");
+ if (msnSearchId != NULL) {
+ if (szNetId != NULL) {
+ TCHAR* szEmailT = mir_utf8decodeT(szEmail);
+ PROTOSEARCHRESULT isr = { 0 };
+ isr.cbSize = sizeof(isr);
+ isr.flags = PSR_TCHAR;
+ isr.id = szEmailT;
+ isr.nick = szEmailT;
+ isr.email = szEmailT;
+
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, msnSearchId, (LPARAM)&isr);
+ mir_free(szEmailT);
+ }
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, msnSearchId, 0);
+
+ msnSearchId = NULL;
+ }
+ else {
+ if (szNetId != NULL) {
+ int netId = atol(szNetId);
+ MCONTACT hContact = MSN_HContactFromEmail(szEmail, szEmail, true, false);
+ if (MSN_AddUser(hContact, szEmail, netId, LIST_FL)) {
+ MSN_AddUser(hContact, szEmail, netId, LIST_PL + LIST_REMOVE);
+ MSN_AddUser(hContact, szEmail, netId, LIST_BL + LIST_REMOVE);
+ MSN_AddUser(hContact, szEmail, netId, LIST_AL);
+ db_unset(hContact, "CList", "Hidden");
+ }
+ MSN_SetContactDb(hContact, szEmail);
+ }
+ }
+
+ ezxml_free(xmli);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Process user addition
+
+void CMsnProto::MSN_ProcessAdd(char* buf, size_t len)
+{
+ if (buf == NULL) return;
+
+ ezxml_t xmli = ezxml_parse_str(buf, len);
+ ezxml_t dom = ezxml_child(xmli, "d");
+ while (dom != NULL) {
+ const char* szDom = ezxml_attr(dom, "n");
+ ezxml_t cont = ezxml_child(dom, "c");
+ while (cont != NULL) {
+ const char* szCont = ezxml_attr(cont, "n");
+ const char* szNick = ezxml_attr(cont, "f");
+ int listId = atol(ezxml_attr(cont, "l"));
+ int netId = atol(ezxml_attr(cont, "t"));
+
+ char szEmail[128];
+ mir_snprintf(szEmail, sizeof(szEmail), "%s@%s", szCont, szDom);
+
+ UrlDecode((char*)szNick);
+
+ if (listId == LIST_FL) {
+ MCONTACT hContact = MSN_HContactFromEmail(szEmail, szNick, true, false);
+ MSN_SetContactDb(hContact, szEmail);
+ }
+
+ if (listId == LIST_RL)
+ MSN_SharingFindMembership(true);
+ else
+ MSN_AddUser(NULL, szEmail, netId, listId);
+
+ MsnContact* msc = Lists_Get(szEmail);
+ if (msc == NULL) {
+ Lists_Add(listId, netId, szEmail);
+ msc = Lists_Get(szEmail);
+ }
+
+ if (listId == LIST_RL) {
+ if ((msc->list & (LIST_AL | LIST_BL)) == 0) {
+ MSN_AddAuthRequest(szEmail, szNick, msc->invite);
+ msc->netId = netId;
+ }
+ else MSN_AddUser(NULL, szEmail, netId, LIST_PL + LIST_REMOVE);
+ }
+
+ cont = ezxml_next(cont);
+ }
+ dom = ezxml_next(dom);
+ }
+ ezxml_free(xmli);
+}
+
+void CMsnProto::MSN_ProcessRemove(char* buf, size_t len)
+{
+ ezxml_t xmli = ezxml_parse_str(buf, len);
+ ezxml_t dom = ezxml_child(xmli, "d");
+ while (dom != NULL) {
+ const char* szDom = ezxml_attr(dom, "n");
+ ezxml_t cont = ezxml_child(dom, "c");
+ while (cont != NULL) {
+ const char* szCont = ezxml_attr(cont, "n");
+ int listId = atol(ezxml_attr(cont, "l"));
+
+ char szEmail[128];
+ mir_snprintf(szEmail, sizeof(szEmail), "%s@%s", szCont, szDom);
+ Lists_Remove(listId, szEmail);
+
+ MsnContact* msc = Lists_Get(szEmail);
+ if (msc == NULL || (msc->list & (LIST_RL | LIST_FL | LIST_LL)) == 0) {
+ if (msc->hContact && _stricmp(szEmail, MyOptions.szEmail)) {
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)msc->hContact, 0);
+ msc->hContact = NULL;
+ }
+ }
+
+ cont = ezxml_next(cont);
+ }
+ dom = ezxml_next(dom);
+ }
+ ezxml_free(xmli);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_HandleCommands - process commands from the server
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CMsnProto::MSN_ProcessStatusMessage(char* buf, unsigned len, const char* wlid)
+{
+ MCONTACT hContact = MSN_HContactFromEmail(wlid);
+ if (hContact == NULL) return;
+
+ ezxml_t xmli = ezxml_parse_str(buf, len);
+
+ char* szEmail;
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+
+ // Add endpoints
+ for (ezxml_t endp = ezxml_child(xmli, "EndpointData"); endp; endp = ezxml_next(endp)) {
+ const char *id = ezxml_attr(endp, "id");
+ const char *caps = ezxml_txt(ezxml_child(endp, "Capabilities"));
+ char* end = NULL;
+ unsigned cap1 = caps ? strtoul(caps, &end, 10) : 0;
+ unsigned cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0;
+
+ Lists_AddPlace(szEmail, id, cap1, cap2);
+ }
+
+ // Process status message info
+ const char* szStatMsg = ezxml_txt(ezxml_child(xmli, "PSM"));
+ if (*szStatMsg) {
+ stripBBCode((char*)szStatMsg);
+ stripColorCode((char*)szStatMsg);
+ db_set_utf(hContact, "CList", "StatusMsg", szStatMsg);
+ }
+ else db_unset(hContact, "CList", "StatusMsg");
+
+ {
+ ptrT tszStatus(mir_utf8decodeT(szStatMsg));
+ ProtoBroadcastAck(hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, NULL, tszStatus);
+ }
+
+ // Process current media info
+ const char* szCrntMda = ezxml_txt(ezxml_child(xmli, "CurrentMedia"));
+ if (!*szCrntMda) {
+ delSetting(hContact, "ListeningTo");
+ ezxml_free(xmli);
+ return;
+ }
+
+ // Get parts separeted by "\\0"
+ char *parts[16];
+ unsigned pCount;
+
+ char* p = (char*)szCrntMda;
+ for (pCount = 0; pCount < SIZEOF(parts); ++pCount) {
+ parts[pCount] = p;
+
+ char* p1 = strstr(p, "\\0");
+ if (p1 == NULL) break;
+
+ *p1 = '\0';
+ p = p1 + 2;
+ }
+
+ // Now let's mount the final string
+ if (pCount <= 4) {
+ delSetting(hContact, "ListeningTo");
+ ezxml_free(xmli);
+ return;
+ }
+
+ // Check if there is any info in the string
+ bool foundUsefullInfo = false;
+ for (unsigned i = 4; i < pCount; i++) {
+ if (parts[i][0] != '\0') {
+ foundUsefullInfo = true;
+ break;
+ }
+ }
+ if (!foundUsefullInfo) {
+ delSetting(hContact, "ListeningTo");
+ ezxml_free(xmli);
+ return;
+ }
+
+ if (!ServiceExists(MS_LISTENINGTO_GETPARSEDTEXT) ||
+ !ServiceExists(MS_LISTENINGTO_OVERRIDECONTACTOPTION) ||
+ !CallService(MS_LISTENINGTO_OVERRIDECONTACTOPTION, 0, hContact))
+ {
+ // User contact options
+ char *format = mir_strdup(parts[3]);
+ char *unknown = NULL;
+ if (ServiceExists(MS_LISTENINGTO_GETUNKNOWNTEXT))
+ unknown = mir_utf8encodeT((TCHAR *)CallService(MS_LISTENINGTO_GETUNKNOWNTEXT, 0, 0));
+
+ for (unsigned i = 4; i < pCount; i++) {
+ char part[16];
+ size_t lenPart = mir_snprintf(part, sizeof(part), "{%d}", i - 4);
+ if (parts[i][0] == '\0' && unknown != NULL)
+ parts[i] = unknown;
+ size_t lenPartsI = strlen(parts[i]);
+ for (p = strstr(format, part); p; p = strstr(p + lenPartsI, part)) {
+ if (lenPart < lenPartsI) {
+ int loc = p - format;
+ format = (char *)mir_realloc(format, strlen(format) + (lenPartsI - lenPart) + 1);
+ p = format + loc;
+ }
+ memmove(p + lenPartsI, p + lenPart, strlen(p + lenPart) + 1);
+ memmove(p, parts[i], lenPartsI);
+ }
+ }
+
+ setStringUtf(hContact, "ListeningTo", format);
+ mir_free(unknown);
+ mir_free(format);
+ }
+ else {
+ // Use user options
+ LISTENINGTOINFO lti = { 0 };
+ lti.cbSize = sizeof(LISTENINGTOINFO);
+
+ lti.ptszTitle = mir_utf8decodeT(parts[4]);
+ if (pCount > 5) lti.ptszArtist = mir_utf8decodeT(parts[5]);
+ if (pCount > 6) lti.ptszAlbum = mir_utf8decodeT(parts[6]);
+ if (pCount > 7) lti.ptszTrack = mir_utf8decodeT(parts[7]);
+ if (pCount > 8) lti.ptszYear = mir_utf8decodeT(parts[8]);
+ if (pCount > 9) lti.ptszGenre = mir_utf8decodeT(parts[9]);
+ if (pCount > 10) lti.ptszLength = mir_utf8decodeT(parts[10]);
+ if (pCount > 11) lti.ptszPlayer = mir_utf8decodeT(parts[11]);
+ else lti.ptszPlayer = mir_utf8decodeT(parts[0]);
+ if (pCount > 12) lti.ptszType = mir_utf8decodeT(parts[12]);
+ else lti.ptszType = mir_utf8decodeT(parts[1]);
+
+ TCHAR *cm = (TCHAR *)CallService(MS_LISTENINGTO_GETPARSEDTEXT, (WPARAM)_T("%title% - %artist%"), (LPARAM)&lti);
+ setTString(hContact, "ListeningTo", cm);
+
+ mir_free(cm);
+
+ mir_free(lti.ptszArtist);
+ mir_free(lti.ptszAlbum);
+ mir_free(lti.ptszTitle);
+ mir_free(lti.ptszTrack);
+ mir_free(lti.ptszYear);
+ mir_free(lti.ptszGenre);
+ mir_free(lti.ptszLength);
+ mir_free(lti.ptszPlayer);
+ mir_free(lti.ptszType);
+ }
+ ezxml_free(xmli);
+}
+
+void CMsnProto::MSN_ProcessPage(char* buf, unsigned len)
+{
+ if (buf == NULL) return;
+ ezxml_t xmlnot = ezxml_parse_str(buf, len);
+
+ ezxml_t xmlbdy = ezxml_get(xmlnot, "MSG", 0, "BODY", -1);
+ const char* szMsg = ezxml_txt(ezxml_child(xmlbdy, "TEXT"));
+ const char* szTel = ezxml_attr(ezxml_child(xmlnot, "FROM"), "name");
+
+ if (szTel && *szMsg) {
+ PROTORECVEVENT pre = { 0 };
+ pre.szMessage = (char*)szMsg;
+ pre.flags = PREF_UTF /*+ ((isRtl) ? PREF_RTL : 0)*/;
+ pre.timestamp = time(NULL);
+ ProtoChainRecvMsg(MSN_HContactFromEmail(szTel, szTel, true, true), &pre);
+ }
+ ezxml_free(xmlnot);
+}
+
+void CMsnProto::MSN_ProcessNotificationMessage(char* buf, unsigned len)
+{
+ if (buf == NULL) return;
+ ezxml_t xmlnot = ezxml_parse_str(buf, len);
+
+ if (strcmp(ezxml_attr(xmlnot, "siteid"), "0") == 0) {
+ ezxml_free(xmlnot);
+ return;
+ }
+
+ ezxml_t xmlmsg = ezxml_child(xmlnot, "MSG");
+ ezxml_t xmlact = ezxml_child(xmlmsg, "ACTION");
+ ezxml_t xmlbdy = ezxml_child(xmlmsg, "BODY");
+ ezxml_t xmltxt = ezxml_child(xmlbdy, "TEXT");
+
+ if (xmltxt != NULL) {
+ char fullurl[1024];
+ size_t sz = 0;
+
+ const char* acturl = ezxml_attr(xmlact, "url");
+ if (acturl == NULL || strstr(acturl, "://") == NULL)
+ sz += mir_snprintf(fullurl + sz, sizeof(fullurl)-sz, "%s", ezxml_attr(xmlnot, "siteurl"));
+
+ sz += mir_snprintf(fullurl + sz, sizeof(fullurl)-sz, "%s", acturl);
+ if (sz != 0 && fullurl[sz - 1] != '?')
+ sz += mir_snprintf(fullurl + sz, sizeof(fullurl)-sz, "?");
+
+ mir_snprintf(fullurl + sz, sizeof(fullurl)-sz, "notification_id=%s&message_id=%s",
+ ezxml_attr(xmlnot, "id"), ezxml_attr(xmlmsg, "id"));
+
+ SkinPlaySound(alertsoundname);
+
+ TCHAR* alrt = mir_utf8decodeT(ezxml_txt(xmltxt));
+ MSN_ShowPopup(TranslateT("MSN Alert"), alrt, MSN_ALERT_POPUP | MSN_ALLOW_MSGBOX, fullurl);
+ mir_free(alrt);
+ }
+ else if (xmlbdy) {
+ const char *txt = ezxml_txt(xmlbdy);
+ if (strstr(txt, "ABCHInternal")) {
+ MSN_SharingFindMembership(true);
+ MSN_ABFind("ABFindContactsPaged", NULL, true);
+ MSN_StoreGetProfile();
+ }
+ }
+ ezxml_free(xmlnot);
+}
+
+void CMsnProto::MSN_InitSB(ThreadData* info, const char* szEmail)
+{
+ MsnContact *cont = Lists_Get(szEmail);
+
+ if (cont->netId == NETID_MSN)
+ info->sendCaps();
+
+ bool typing = false;
+
+ for (int i = 3; --i;) {
+ MsgQueueEntry E;
+ while (MsgQueue_GetNext(szEmail, E)) {
+ if (E.msgType == 'X') ;
+ else if (E.msgType == 2571)
+ typing = E.flags != 0;
+ else if (E.msgSize == 0) {
+ info->sendMessage(E.msgType, NULL, 1, E.message, E.flags);
+ ProtoBroadcastAck(cont->hContact, ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)E.seq, 0);
+ }
+ else {
+ if (E.msgType == 'D' && !info->mBridgeInit /*&& strchr(data.flags, ':')*/) {
+ info->mBridgeInit = true;
+
+// P2PV2_Header hdrdata(E.message);
+// P2PV2_Header tHdr;
+// tHdr.mID = hdrdata.mID;
+// p2p_sendMsg(info, E.wlid, 0, tHdr, NULL, 0);
+ }
+ info->sendRawMessage(E.msgType, E.message, E.msgSize);
+ }
+
+ mir_free(E.message);
+ mir_free(E.wlid);
+
+ if (E.ft != NULL)
+ info->mMsnFtp = E.ft;
+ }
+ mir_free(info->mInitialContactWLID); info->mInitialContactWLID = NULL;
+ }
+
+ if (typing)
+ MSN_StartStopTyping(info, true);
+
+ if (getByte("EnableDeliveryPopup", 0))
+ MSN_ShowPopup(cont->hContact, info->mCaller ?
+ TranslateT("Chat session established by my request") :
+ TranslateT("Chat session established by contact request"), 0);
+
+ PROTO_AVATAR_INFORMATIONT ai = { sizeof(ai), cont->hContact };
+ GetAvatarInfo(GAIF_FORCE, (LPARAM)&ai);
+}
+
+int CMsnProto::MSN_HandleCommands(ThreadData* info, char* cmdString)
+{
+ char* params = "";
+ int trid = -1;
+
+ if (cmdString[3]) {
+ if (isdigit((BYTE)cmdString[4])) {
+ trid = strtol(cmdString + 4, &params, 10);
+ switch (*params) {
+ case ' ': case '\0': case '\t': case '\n':
+ while (*params == ' ' || *params == '\t')
+ params++;
+ break;
+
+ default:
+ params = cmdString + 4;
+ }
+ }
+ else params = cmdString + 4;
+ }
+
+ switch((*(PDWORD)cmdString & 0x00FFFFFF) | 0x20000000) {
+ case ' KCA': //********* ACK: section 8.7 Instant Messages
+ ReleaseSemaphore(info->hWaitEvent, 1, NULL);
+
+ if (info->mJoinedContactsWLID.getCount() > 0 && MyOptions.SlowSend)
+ ProtoBroadcastAck(info->getContactHandle(), ACKTYPE_MESSAGE, ACKRESULT_SUCCESS, (HANDLE)trid, 0);
+ break;
+
+ case ' YQF': //********* FQY: Find Yahoo User
+ char* tWords[1];
+ if (sttDivideWords(params, 1, tWords) != 1)
+ debugLogA("Invalid %.3s command, ignoring", cmdString);
+ else {
+ size_t len = atol(tWords[0]);
+ MSN_ProcessYFind((char*)HReadBuffer(info, 0).surelyRead(len), len);
+ }
+ break;
+
+ case ' LDA': //********* ADL: Add to the list
+ {
+ char* tWords[1];
+ if (sttDivideWords(params, 1, tWords) != 1) {
+LBL_InvalidCommand:
+ debugLogA("Invalid %.3s command, ignoring", cmdString);
+ break;
+ }
+
+ if (strcmp(tWords[0], "OK") != 0) {
+ size_t len = atol(tWords[0]);
+ MSN_ProcessAdd((char*)HReadBuffer(info, 0).surelyRead(len), len);
+ }
+ }
+ break;
+
+ case ' SBS':
+ break;
+
+ case ' SNA': //********* ANS: section 8.4 Getting Invited to a Switchboard Session
+ break;
+
+ case ' PRP':
+ break;
+
+ case ' PLB': //********* BLP: section 7.6 List Retrieval And Property Management
+ break;
+
+ case ' EYB': //********* BYE: section 8.5 Session Participant Changes
+ {
+ union {
+ char* tWords[2];
+ // modified for chat, orginally param2 = junk
+ // param 2: quit due to idle = "1", normal quit = nothing
+ struct { char *userEmail, *isIdle; } data;
+ };
+
+ sttDivideWords(params, 2, tWords);
+ UrlDecode(data.userEmail);
+
+ if (strchr(data.userEmail, ';')) {
+ if (info->mJoinedContactsWLID.getCount() == 1)
+ p2p_clearThreadSessions((MCONTACT)info->mJoinedContactsWLID[0], info->mType);
+ info->contactLeft(data.userEmail);
+ break;
+ }
+
+ MCONTACT hContact = MSN_HContactFromEmail(data.userEmail);
+
+ if (getByte("EnableSessionPopup", 0))
+ MSN_ShowPopup(hContact, TranslateT("Contact left channel"), 0);
+
+ // modified for chat
+ {
+ GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_QUIT };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.ptszNick = GetContactNameT(hContact);
+ gce.ptszUID = mir_a2t(data.userEmail);
+ gce.time = time(NULL);
+ gce.bIsMe = FALSE;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+ mir_free((void*)gce.ptszUID);
+ }
+
+ int personleft = info->contactLeft(data.userEmail);
+
+ int temp_status = getWord(hContact, "Status", ID_STATUS_OFFLINE);
+ if (temp_status == ID_STATUS_INVISIBLE && MSN_GetThreadByContact(data.userEmail) == NULL)
+ setWord(hContact, "Status", ID_STATUS_OFFLINE);
+
+ // see if the session is quit due to idleness
+ if (info->mChatID[0] && personleft == 1) {
+ if (!strcmp(data.isIdle, "1")) {
+ GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_INFORMATION };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.bIsMe = FALSE;
+ gce.time = time(NULL);
+ gce.ptszText = TranslateT("This conversation has been inactive, participants will be removed.");
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+ gce.ptszText = TranslateT("To resume the conversation, please quit this session and start a new chat session.");
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+ }
+ else {
+ if (!Miranda_Terminated() && MessageBox(NULL,
+ TranslateT("There is only 1 person left in the chat, do you want to switch back to standard message window?"),
+ TranslateT("MSN Chat"), MB_YESNO | MB_ICONQUESTION) == IDYES) {
+ // kill chat dlg and open srmm dialog
+ MSN_KillChatSession(info->mChatID);
+
+ // open up srmm dialog when quit while 1 person left
+ MCONTACT hContact = info->getContactHandle();
+ if (hContact) CallServiceSync(MS_MSG_SENDMESSAGE, hContact, 0);
+ }
+ }
+ }
+ // this is not in chat session, quit the session when everyone left
+ else if (info->mJoinedContactsWLID.getCount() < 1)
+ return 1;
+
+ }
+ break;
+
+ case ' LAC': //********* CAL: section 8.3 Inviting Users to a Switchboard Session
+ break;
+
+ case ' GHC': //********* CHG: section 7.7 Client States
+ {
+ int oldStatus = m_iStatus;
+ int newStatus = MSNStatusToMiranda(params);
+ if (oldStatus <= ID_STATUS_OFFLINE) {
+ isConnectSuccess = true;
+ int count = -1;
+ for (;;) {
+ MsnContact *msc = Lists_GetNext(count);
+ if (msc == NULL) break;
+
+ if (msc->netId == NETID_MOB)
+ setWord(msc->hContact, "Status", ID_STATUS_ONTHEPHONE);
+ }
+ }
+ if (newStatus != ID_STATUS_IDLE) {
+ m_iStatus = newStatus;
+ ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, newStatus);
+ debugLogA("Status change acknowledged: %s", params);
+ MSN_RemoveEmptyGroups();
+ }
+ if (newStatus == ID_STATUS_OFFLINE) return 1;
+ }
+ break;
+ case ' LHC': //********* CHL: Query from Server on MSNP7
+ {
+ char* authChallengeInfo;
+ if (sttDivideWords(params, 1, &authChallengeInfo) != 1)
+ goto LBL_InvalidCommand;
+
+ char dgst[64];
+ MSN_MakeDigest(authChallengeInfo, dgst);
+ info->sendPacket("QRY", "%s 32\r\n%s", msnProductID, dgst);
+ }
+ break;
+ case ' RVC': //********* CVR: MSNP8
+ break;
+
+ case ' NLF': //********* FLN: section 7.9 Notification Messages
+ {
+ union {
+ char* tWords[2];
+ struct { char *userEmail, *netId; } data;
+ };
+
+ int tArgs = sttDivideWords(params, 2, tWords);
+ if (tArgs < 2)
+ goto LBL_InvalidCommand;
+
+ MCONTACT hContact = MSN_HContactFromEmail(data.userEmail);
+ if (hContact != NULL) {
+ setWord(hContact, "Status", MSN_GetThreadByContact(data.userEmail) ? ID_STATUS_INVISIBLE : ID_STATUS_OFFLINE);
+ setDword(hContact, "IdleTS", 0);
+ ForkThread(&CMsnProto::MsgQueue_AllClearThread, mir_strdup(data.userEmail));
+ }
+ }
+ break;
+ case ' NLI':
+ case ' NLN': //********* ILN/NLN: section 7.9 Notification Messages
+ {
+ union {
+ char* tWords[5];
+ struct { char *userStatus, *wlid, *userNick, *objid, *cmdstring; } data;
+ };
+
+ int tArgs = sttDivideWords(params, 5, tWords);
+ if (tArgs < 3)
+ goto LBL_InvalidCommand;
+
+ UrlDecode(data.userNick);
+ stripBBCode(data.userNick);
+ stripColorCode(data.userNick);
+
+ bool isMe = false;
+ char* szEmail, *szNet;
+ parseWLID(NEWSTR_ALLOCA(data.wlid), &szNet, &szEmail, NULL);
+ if (!stricmp(szEmail, MyOptions.szEmail) && !strcmp(szNet, "1")) {
+ isMe = true;
+ int newStatus = MSNStatusToMiranda(params);
+ if (newStatus != m_iStatus && newStatus != ID_STATUS_IDLE) {
+ int oldMode = m_iStatus;
+ m_iDesiredStatus = m_iStatus = newStatus;
+ ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldMode, m_iStatus);
+ }
+ }
+
+ WORD lastStatus = ID_STATUS_OFFLINE;
+
+ MsnContact *cont = Lists_Get(szEmail);
+
+ MCONTACT hContact = NULL;
+ if (!cont && !isMe) {
+ hContact = MSN_HContactFromEmail(data.wlid, data.userNick, true, true);
+ cont = Lists_Get(szEmail);
+ }
+ if (cont) hContact = cont->hContact;
+
+ if (hContact != NULL) {
+ setStringUtf(hContact, "Nick", data.userNick);
+ lastStatus = getWord(hContact, "Status", ID_STATUS_OFFLINE);
+ if (lastStatus == ID_STATUS_OFFLINE || lastStatus == ID_STATUS_INVISIBLE)
+ db_unset(hContact, "CList", "StatusMsg");
+
+ int newStatus = MSNStatusToMiranda(params);
+ setWord(hContact, "Status", newStatus != ID_STATUS_IDLE ? newStatus : ID_STATUS_AWAY);
+ setDword(hContact, "IdleTS", newStatus != ID_STATUS_IDLE ? 0 : time(NULL));
+ }
+
+ if (tArgs > 3 && tArgs <= 5 && cont) {
+ UrlDecode(data.cmdstring);
+
+ char* end = NULL;
+ cont->cap1 = strtoul(data.objid, &end, 10);
+ cont->cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0;
+
+ if (lastStatus == ID_STATUS_OFFLINE) {
+ DBVARIANT dbv;
+ bool always = getString(hContact, "MirVer", &dbv) != 0;
+ if (!always) db_free(&dbv);
+ MSN_SetMirVer(hContact, cont->cap1, always);
+ }
+
+ if (data.cmdstring[0] && strcmp(data.cmdstring, "0")) {
+ char *pszUrl, *pszAvatarHash = MSN_GetAvatarHash(data.cmdstring, &pszUrl);
+ if (pszAvatarHash == NULL)
+ goto remove;
+
+ setString(hContact, "PictContext", data.cmdstring);
+ setString(hContact, "AvatarHash", pszAvatarHash);
+ if (pszUrl)
+ setString(hContact, "AvatarUrl", pszUrl);
+ else
+ delSetting(hContact, "AvatarUrl");
+
+ if (hContact != NULL) {
+ char szSavedHash[64] = "";
+ db_get_static(hContact, m_szModuleName, "AvatarSavedHash", szSavedHash, sizeof(szSavedHash));
+ if (stricmp(szSavedHash, pszAvatarHash))
+ pushAvatarRequest(hContact, pszUrl);
+ else {
+ char szSavedContext[64];
+ int result = db_get_static(hContact, m_szModuleName, "PictSavedContext", szSavedContext, sizeof(szSavedContext));
+ if (result || strcmp(szSavedContext, data.cmdstring))
+ pushAvatarRequest(hContact, pszUrl);
+ }
+ }
+ mir_free(pszAvatarHash);
+ mir_free(pszUrl);
+ }
+ else {
+remove:
+ delSetting(hContact, "AvatarHash");
+ delSetting(hContact, "AvatarSavedHash");
+ delSetting(hContact, "AvatarUrl");
+ delSetting(hContact, "PictContext");
+ delSetting(hContact, "PictSavedContext");
+
+ ProtoBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_STATUS, NULL, 0);
+ }
+ }
+ else if (lastStatus == ID_STATUS_OFFLINE)
+ delSetting(hContact, "MirVer");
+
+ }
+ break;
+ case ' ORI': //********* IRO: section 8.4 Getting Invited to a Switchboard Session
+ {
+ union {
+ char* tWords[5];
+ struct { char *strThisContact, *totalContacts, *userEmail, *userNick, *flags; } data;
+ };
+
+ int tNumTokens = sttDivideWords(params, 5, tWords);
+ if (tNumTokens < 4)
+ goto LBL_InvalidCommand;
+
+ info->contactJoined(data.userEmail);
+
+ if (!strchr(data.userEmail, ';')) {
+ UrlDecode(data.userNick);
+ MCONTACT hContact = MSN_HContactFromEmail(data.userEmail, data.userNick, true, true);
+
+ if (tNumTokens == 5 && strcmp(data.flags, "0:0")) {
+ MsnContact *cont = Lists_Get(data.userEmail);
+ if (cont) {
+ char* end = NULL;
+ cont->cap1 = strtoul(data.flags, &end, 10);
+ cont->cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0;
+ }
+ }
+
+ int temp_status = getWord(hContact, "Status", ID_STATUS_OFFLINE);
+ if (temp_status == ID_STATUS_OFFLINE && Lists_IsInList(LIST_FL, data.userEmail))
+ setWord(hContact, "Status", ID_STATUS_INVISIBLE);
+
+ // only start the chat session after all the IRO messages has been recieved
+ if (info->mJoinedContactsWLID.getCount() > 1 && !strcmp(data.strThisContact, data.totalContacts))
+ MSN_ChatStart(info);
+ }
+ }
+ break;
+
+ case ' IOJ': //********* JOI: section 8.5 Session Participant Changes
+ {
+ union {
+ char* tWords[3];
+ struct { char *userEmail, *userNick, *flags; } data;
+ };
+
+ int tNumTokens = sttDivideWords(params, 3, tWords);
+ if (tNumTokens < 2)
+ goto LBL_InvalidCommand;
+
+ UrlDecode(data.userEmail);
+
+ if (strchr(data.userEmail, ';')) {
+ info->contactJoined(data.userEmail);
+ break;
+ }
+
+ if (_stricmp(MyOptions.szEmail, data.userEmail) == 0) {
+ if (!info->mCaller) {
+ if (info->mJoinedContactsWLID.getCount() == 1) {
+ MSN_InitSB(info, info->mJoinedContactsWLID[0]);
+ }
+ else {
+ info->sendCaps();
+ if (info->mInitialContactWLID && MsgQueue_CheckContact(info->mInitialContactWLID))
+ msnNsThread->sendPacket("XFR", "SB");
+ mir_free(info->mInitialContactWLID); info->mInitialContactWLID = NULL;
+ }
+ break;
+ }
+
+ const char* wlid;
+ do {
+ wlid = MsgQueue_GetNextRecipient();
+ }
+ while (wlid != NULL && MSN_GetUnconnectedThread(wlid) != NULL);
+
+ //can happen if both parties send first message at the same time
+ if (wlid == NULL) {
+ debugLogA("USR (SB) internal: thread created for no reason");
+ return 1;
+ }
+
+ if (strcmp(wlid, "chat") == 0) {
+ MsgQueueEntry E;
+ MsgQueue_GetNext(wlid, E);
+
+ for (int i = 0; i < E.cont->getCount(); ++i)
+ info->sendPacket("CAL", (*E.cont)[i]);
+
+ MSN_ChatStart(info);
+
+ delete E.cont;
+ mir_free(E.wlid);
+ break;
+ }
+
+ char* szEmail;
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+
+ info->mInitialContactWLID = mir_strdup(szEmail);
+ info->sendPacket("CAL", szEmail);
+ break;
+ }
+
+ UrlDecode(data.userNick);
+ stripBBCode(data.userNick);
+ stripColorCode(data.userNick);
+
+ MCONTACT hContact = MSN_HContactFromEmail(data.userEmail, data.userNick, true, true);
+ if (tNumTokens == 3) {
+ MsnContact *cont = Lists_Get(data.userEmail);
+ if (cont) {
+ char* end = NULL;
+ cont->cap1 = strtoul(data.flags, &end, 10);
+ cont->cap2 = end && *end == ':' ? strtoul(end + 1, NULL, 10) : 0;
+ }
+ }
+
+ mir_utf8decode(data.userNick, NULL);
+ debugLogA("New contact in channel %s %s", data.userEmail, data.userNick);
+
+ if (info->contactJoined(data.userEmail) <= 1)
+ MSN_InitSB(info, data.userEmail);
+ else {
+ bool chatCreated = info->mChatID[0] != 0;
+
+ info->sendCaps();
+
+ if (chatCreated) {
+ GCDEST gcd = { m_szModuleName, info->mChatID, GC_EVENT_JOIN };
+ GCEVENT gce = { sizeof(gce), &gcd };
+ gce.dwFlags = GCEF_ADDTOLOG;
+ gce.ptszNick = GetContactNameT(hContact);
+ gce.ptszUID = mir_a2t(data.userEmail);
+ gce.ptszStatus = TranslateT("Others");
+ gce.time = time(NULL);
+ gce.bIsMe = FALSE;
+ CallServiceSync(MS_GC_EVENT, 0, (LPARAM)&gce);
+ mir_free((void*)gce.ptszUID);
+ }
+ else MSN_ChatStart(info);
+ }
+ }
+ break;
+
+ case ' GSM': //********* MSG: sections 8.7 Instant Messages, 8.8 Receiving an Instant Message
+ MSN_ReceiveMessage(info, cmdString, params);
+ break;
+
+ case ' MBU':
+ MSN_ReceiveMessage(info, cmdString, params);
+ break;
+
+ case ' KAN': //********* NAK: section 8.7 Instant Messages
+ if (info->mJoinedContactsWLID.getCount() > 0 && MyOptions.SlowSend)
+ ProtoBroadcastAck(info->getContactHandle(),
+ ACKTYPE_MESSAGE, ACKRESULT_FAILED,
+ (HANDLE)trid, (LPARAM)Translate("Message delivery failed"));
+ debugLogA("Message send failed (trid=%d)", trid);
+ break;
+
+ case ' TON': //********* NOT: notification message
+ MSN_ProcessNotificationMessage((char*)HReadBuffer(info, 0).surelyRead(trid), trid);
+ break;
+
+ case ' GPI': //********* IPG: mobile page
+ MSN_ProcessPage((char*)HReadBuffer(info, 0).surelyRead(trid), trid);
+ break;
+
+ case ' FCG': //********* GCF:
+ HReadBuffer(info, 0).surelyRead(atol(params));
+ break;
+
+ case ' TUO': //********* OUT: sections 7.10 Connection Close, 8.6 Leaving a Switchboard Session
+ if (!_stricmp(params, "OTH")) {
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_OTHERLOCATION);
+ debugLogA("You have been disconnected from the MSN server because you logged on from another location using the same MSN passport.");
+ }
+
+ if (!_stricmp(params, "MIG")) // ignore it
+ break;
+
+ return 1;
+
+ case ' YRQ': //********* QRY:
+ break;
+
+ case ' GNQ': //********* QNG: reply to PNG
+ msnPingTimeout = trid;
+ if (info->mType == SERVER_NOTIFICATION && hKeepAliveThreadEvt != NULL)
+ SetEvent(hKeepAliveThreadEvt);
+ break;
+
+ case ' LMR': //********* RML: Remove from the list
+ {
+ char* tWords[1];
+ if (sttDivideWords(params, 1, tWords) != 1)
+ goto LBL_InvalidCommand;
+
+ if (strcmp(tWords[0], "OK") != 0) {
+ size_t len = atol(tWords[0]);
+ MSN_ProcessRemove((char*)HReadBuffer(info, 0).surelyRead(len), len);
+ }
+ }
+ break;
+
+ case ' GNR': //********* RNG: section 8.4 Getting Invited to a Switchboard Session
+ //note: unusual message encoding: trid==sessionid
+ {
+ union {
+ char* tWords[8];
+ struct { char *newServer, *security, *authChallengeInfo, *callerEmail, *callerNick,
+ *type, *srcUrl, *genGateway; } data;
+ };
+
+ if (sttDivideWords(params, 8, tWords) != 8)
+ goto LBL_InvalidCommand;
+
+ UrlDecode(data.newServer); UrlDecode(data.callerEmail);
+ UrlDecode(data.callerNick);
+ stripBBCode(data.callerNick);
+ stripColorCode(data.callerNick);
+
+ if (strcmp(data.security, "CKI")) {
+ debugLogA("Unknown security package in RNG command: %s", data.security);
+ break;
+ }
+
+ ThreadData* newThread = new ThreadData;
+ strcpy(newThread->mServer, data.newServer);
+ newThread->gatewayType = atol(data.genGateway) != 0;
+ newThread->mType = SERVER_SWITCHBOARD;
+ newThread->mInitialContactWLID = mir_strdup(data.callerEmail);
+ MSN_HContactFromEmail(data.callerEmail, data.callerNick, true, true);
+ mir_snprintf(newThread->mCookie, sizeof(newThread->mCookie), "%s %d", data.authChallengeInfo, trid);
+
+ ReleaseSemaphore(newThread->hWaitEvent, MSN_PACKETS_COMBINE, NULL);
+
+ debugLogA("Opening caller's switchboard server '%s'...", data.newServer);
+ newThread->startThread(&CMsnProto::MSNServerThread, this);
+ }
+ break;
+
+ case ' XBU': // UBX : MSNP11+ User Status Message
+ {
+ union {
+ char* tWords[2];
+ struct { char *wlid, *datalen; } data;
+ };
+
+ if (sttDivideWords(params, 2, tWords) != 2)
+ goto LBL_InvalidCommand;
+
+ int len = atol(data.datalen);
+ if (len < 0 || len > 4000)
+ goto LBL_InvalidCommand;
+
+ MSN_ProcessStatusMessage((char*)HReadBuffer(info, 0).surelyRead(len), len, data.wlid);
+ }
+ break;
+
+ case ' NBU': // UBN : MSNP13+ File sharing, P2P Bootstrap, TURN setup.
+ {
+ union {
+ char* tWords[3];
+ struct { char *email, *typeId, *datalen; } data;
+ };
+
+ if (sttDivideWords(params, 3, tWords) != 3)
+ goto LBL_InvalidCommand;
+
+ int len = atol(data.datalen);
+ if (len < 0 || len > 4000)
+ goto LBL_InvalidCommand;
+
+ HReadBuffer buf(info, 0);
+ char* msgBody = (char*)buf.surelyRead(len);
+
+ char *szEmail = data.email;
+ if (strstr(data.email, sttVoidUid))
+ parseWLID(NEWSTR_ALLOCA(data.email), NULL, &szEmail, NULL);
+
+ switch (atol(data.typeId)) {
+ case 1:
+ // File sharing stuff
+ // sttProcessFileSharing(msgBody, len, hContact);
+ break;
+
+ case 3:
+ // P2P Bootstrap
+ p2p_processSIP(info, msgBody, NULL, szEmail);
+ break;
+
+ case 4:
+ case 8:
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_OTHERLOCATION);
+ debugLogA("You have been disconnected from the MSN server because you logged on from another location using the same MSN passport.");
+ break;
+
+ case 6:
+ MSN_SharingFindMembership(true);
+ MSN_ABFind("ABFindContactsPaged", NULL, true);
+ break;
+
+ case 10:
+ // TURN setup
+ p2p_processSIP(info, msgBody, NULL, szEmail);
+ break;
+ }
+ }
+ break;
+
+ case ' NUU': // UUN : MSNP13+ File sharing, P2P Bootstrap, TURN setup.
+ break;
+
+ case ' RSU': //********* USR: sections 7.3 Authentication, 8.2 Switchboard Connections and Authentication
+ if (info->mType == SERVER_SWITCHBOARD) { //(section 8.2)
+ union {
+ char* tWords[3];
+ struct { char *status, *userHandle, *friendlyName; } data;
+ };
+
+ if (sttDivideWords(params, 3, tWords) != 3)
+ goto LBL_InvalidCommand;
+
+ UrlDecode(data.userHandle); UrlDecode(data.friendlyName);
+
+ if (strcmp(data.status, "OK")) {
+ debugLogA("Unknown status to USR command (SB): '%s'", data.status);
+ break;
+ }
+
+ info->sendPacket("CAL", MyOptions.szEmail);
+ }
+ else { //dispatch or notification server (section 7.3)
+ union {
+ char* tWords[4];
+ struct { char *security, *sequence, *authChallengeInfo, *nonce; } data;
+ };
+
+ if (sttDivideWords(params, 4, tWords) != 4)
+ goto LBL_InvalidCommand;
+
+ if (!strcmp(data.security, "SSO")) {
+ if (MSN_GetPassportAuth()) {
+ m_iDesiredStatus = ID_STATUS_OFFLINE;
+ return 1;
+ }
+
+ char* sec = GenerateLoginBlob(data.nonce);
+ info->sendPacket("USR", "SSO S %s %s %s", authStrToken ? authStrToken : "", sec, MyOptions.szMachineGuid);
+ mir_free(sec);
+
+ ForkThread(&CMsnProto::msn_keepAliveThread, NULL);
+ ForkThread(&CMsnProto::MSNConnDetectThread, NULL);
+ }
+ else if (!strcmp(data.security, "OK")) {
+ }
+ else {
+ debugLogA("Unknown security package '%s'", data.security);
+
+ if (info->mType == SERVER_NOTIFICATION)
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPROTOCOL);
+ return 1;
+ }
+ }
+ break;
+
+ case ' SFR': // RFS: Refresh Contact List
+ if (!MSN_RefreshContactList()) {
+ MSN_ShowError("Cannot retrieve contact list");
+ return 1;
+ }
+ break;
+
+ case ' XUU': // UUX: MSNP11 addition
+ {
+ char* tWords[1];
+ if (sttDivideWords(params, SIZEOF(tWords), tWords) != SIZEOF(tWords))
+ goto LBL_InvalidCommand;
+
+ int len = atol(tWords[0]);
+ if (len < 0 || len > 4000)
+ goto LBL_InvalidCommand;
+
+ HReadBuffer(info, 0).surelyRead(len);
+ }
+ break;
+
+ case ' REV': //******** VER: section 7.1 Protocol Versioning
+ {
+ char* protocol1;
+ if (sttDivideWords(params, 1, &protocol1) != 1)
+ goto LBL_InvalidCommand;
+
+ if (MyOptions.szEmail[0] == 0) {
+ MSN_ShowError("You must specify your e-mail in Options/Network/MSN");
+ return 1;
+ }
+
+ OSVERSIONINFOEX osvi = {0};
+ osvi.dwOSVersionInfoSize = sizeof(osvi);
+ GetVersionEx((LPOSVERSIONINFO)&osvi);
+
+ info->sendPacket("CVR","0x0409 %s %d.%d.%d i386 MSNMSGR %s msmsgs %s",
+ osvi.dwPlatformId >= 2 ? "winnt" : "win", osvi.dwMajorVersion,
+ osvi.dwMinorVersion, osvi.wServicePackMajor,
+ msnProductVer, MyOptions.szEmail);
+
+ info->sendPacket("USR", "SSO I %s", MyOptions.szEmail);
+ }
+ break;
+ case ' RFX': //******** XFR: sections 7.4 Referral, 8.1 Referral to Switchboard
+ {
+ union {
+ char* tWords[7];
+ struct { char *type, *newServer, *security, *authChallengeInfo,
+ *type2, *srcUrl, *genGateway; } data;
+ };
+
+ int numWords = sttDivideWords(params, 7, tWords);
+ if (numWords < 3)
+ goto LBL_InvalidCommand;
+
+ if (!strcmp(data.type, "NS")) { //notification server
+ UrlDecode(data.newServer);
+ ThreadData* newThread = new ThreadData;
+ strcpy(newThread->mServer, data.newServer);
+ newThread->mType = SERVER_NOTIFICATION;
+ newThread->mTrid = info->mTrid;
+ newThread->mIsMainThread = true;
+ usingGateway |= (*data.security == 'G');
+ info->mIsMainThread = false;
+
+ debugLogA("Switching to notification server '%s'...", data.newServer);
+ newThread->startThread(&CMsnProto::MSNServerThread, this);
+ return 1; //kill the old thread
+ }
+
+ if (!strcmp(data.type, "SB")) { //switchboard server
+ UrlDecode(data.newServer);
+
+ if (numWords < 4)
+ goto LBL_InvalidCommand;
+
+ if (strcmp(data.security, "CKI")) {
+ debugLogA("Unknown XFR SB security package '%s'", data.security);
+ break;
+ }
+
+ ThreadData* newThread = new ThreadData;
+ strcpy(newThread->mServer, data.newServer);
+ newThread->gatewayType = data.genGateway && atol(data.genGateway) != 0;
+ newThread->mType = SERVER_SWITCHBOARD;
+ newThread->mCaller = 1;
+ strcpy(newThread->mCookie, data.authChallengeInfo);
+
+ debugLogA("Opening switchboard server '%s'...", data.newServer);
+ newThread->startThread(&CMsnProto::MSNServerThread, this);
+ }
+ else debugLogA("Unknown referral server: %s", data.type);
+ }
+ break;
+
+ default:
+ debugLogA("Unrecognised message: %s", cmdString);
+ break;
+ }
+
+ return 0;
+}
diff --git a/protocols/MSN/src/msn_contact.cpp b/protocols/MSN/src/msn_contact.cpp
new file mode 100644
index 0000000000..fc3bef21a1
--- /dev/null
+++ b/protocols/MSN/src/msn_contact.cpp
@@ -0,0 +1,272 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+MCONTACT CMsnProto::MSN_HContactFromEmail(const char* wlid, const char* msnNick, bool addIfNeeded, bool temporary)
+{
+ MCONTACT hContact = NULL;
+
+ char* szEmail;
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+
+ MsnContact *msc = Lists_Get(szEmail);
+ if (msc && msc->hContact) hContact = msc->hContact;
+
+ if (hContact == NULL && addIfNeeded) {
+ hContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD, 0, 0);
+ CallService(MS_PROTO_ADDTOCONTACT, hContact, (LPARAM)m_szModuleName);
+ setString(hContact, "e-mail", szEmail);
+ setStringUtf(hContact, "Nick", msnNick ? msnNick : wlid);
+ if (temporary)
+ db_set_b(hContact, "CList", "NotOnList", 1);
+
+ Lists_Add(0, NETID_MSN, wlid, hContact);
+ }
+
+ return hContact;
+}
+
+
+void CMsnProto::MSN_SetContactDb(MCONTACT hContact, const char *szEmail)
+{
+ MsnContact *cont = Lists_Get(szEmail);
+ const int listId = cont->list;
+
+ if (listId & LIST_FL)
+ {
+ if (db_get_b(hContact, "CList", "NotOnList", 0) == 1)
+ {
+ db_unset(hContact, "CList", "NotOnList");
+ db_unset(hContact, "CList", "Hidden");
+ }
+
+ if (listId & (LIST_BL | LIST_AL))
+ {
+ WORD tApparentMode = getWord(hContact, "ApparentMode", 0);
+ if ((listId & LIST_BL) && tApparentMode == 0)
+ setWord(hContact, "ApparentMode", ID_STATUS_OFFLINE);
+ else if ((listId & LIST_AL) && tApparentMode != 0)
+ delSetting(hContact, "ApparentMode");
+ }
+
+ if (cont->netId == NETID_MOB)
+ {
+ setWord(hContact, "Status", ID_STATUS_ONTHEPHONE);
+ setString(hContact, "MirVer", "SMS");
+ }
+ }
+ if (listId & LIST_LL)
+ setByte(hContact, "LocalList", 1);
+ else
+ delSetting(hContact, "LocalList");
+
+}
+
+
+void CMsnProto::AddDelUserContList(const char* email, const int list, const int netId, const bool del)
+{
+ char buf[512];
+ size_t sz;
+
+ if (list < LIST_RL)
+ {
+ const char* dom = strchr(email, '@');
+ if (dom == NULL)
+ {
+ sz = mir_snprintf(buf, sizeof(buf),
+ "<ml><t><c n=\"%s\" l=\"%d\"/></t></ml>",
+ email, list);
+ }
+ else
+ {
+ *(char*)dom = 0;
+ sz = mir_snprintf(buf, sizeof(buf),
+ "<ml><d n=\"%s\"><c n=\"%s\" l=\"%d\" t=\"%d\"/></d></ml>",
+ dom+1, email, list, netId);
+ *(char*)dom = '@';
+ }
+ msnNsThread->sendPacket(del ? "RML" : "ADL", "%d\r\n%s", sz, buf);
+ }
+
+ if (del)
+ Lists_Remove(list, email);
+ else
+ Lists_Add(list, netId, email);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_AddUser - adds a e-mail address to one of the MSN server lists
+
+bool CMsnProto::MSN_AddUser(MCONTACT hContact, const char* email, int netId, int flags, const char *msg)
+{
+ bool needRemove = (flags & LIST_REMOVE) != 0;
+ bool leaveHotmail = (flags & LIST_REMOVENH) == LIST_REMOVENH;
+ flags &= 0xFF;
+
+ if (needRemove != Lists_IsInList(flags, email))
+ return true;
+
+
+ bool res = false;
+ if (flags == LIST_FL)
+ {
+ if (needRemove)
+ {
+ if (hContact == NULL)
+ {
+ hContact = MSN_HContactFromEmail(email);
+ if (hContact == NULL) return false;
+ }
+
+ char id[MSN_GUID_LEN];
+ if (!db_get_static(hContact, m_szModuleName, "ID", id, sizeof(id)))
+ {
+ int netId = Lists_GetNetId(email);
+ if (leaveHotmail)
+ res = MSN_ABAddRemoveContact(id, netId, false);
+ else
+ res = MSN_ABAddDelContactGroup(id , NULL, "ABContactDelete");
+ if (res) AddDelUserContList(email, flags, netId, true);
+
+ delSetting(hContact, "GroupID");
+ delSetting(hContact, "ID");
+ MSN_RemoveEmptyGroups();
+ }
+ }
+ else
+ {
+ DBVARIANT dbv = {0};
+ if (!strcmp(email, MyOptions.szEmail))
+ getStringUtf("Nick", &dbv);
+
+ unsigned res1 = MSN_ABContactAdd(email, dbv.pszVal, netId, msg, false);
+ if (netId == NETID_MSN && res1 == 2)
+ {
+ netId = NETID_LCS;
+ res = MSN_ABContactAdd(email, dbv.pszVal, netId, msg, false) == 0;
+ }
+ else if (netId == NETID_MSN && res1 == 3)
+ {
+ char szContactID[100];
+ hContact = MSN_HContactFromEmail(email);
+ if (db_get_static(hContact, m_szModuleName, "ID", szContactID, sizeof(szContactID)) == 0)
+ {
+ MSN_ABAddRemoveContact(szContactID, netId, true);
+ res = true;
+ }
+ }
+
+ else
+ res = (res1 == 0);
+
+ if (res)
+ {
+ DBVARIANT dbv;
+ if (!db_get_utf(hContact, "CList", "Group", &dbv))
+ {
+ MSN_MoveContactToGroup(hContact, dbv.pszVal);
+ db_free(&dbv);
+ }
+
+ char szContactID[100];
+ if (db_get_static(hContact, m_szModuleName, "ID", szContactID, sizeof(szContactID)) == 0)
+ MSN_ABFind("ABFindByContacts", szContactID);
+
+ MSN_SharingFindMembership(true);
+ AddDelUserContList(email, flags, netId, false);
+ }
+ else
+ {
+ if (netId == 1 && strstr(email, "@yahoo.com") != 0)
+ MSN_FindYahooUser(email);
+ }
+ db_free(&dbv);
+ }
+ }
+ else if (flags == LIST_LL)
+ {
+ if (needRemove)
+ Lists_Remove(LIST_LL, email);
+ else
+ Lists_Add(LIST_LL, NETID_MSN, email);
+ }
+ else
+ {
+ if (netId == 0) netId = Lists_GetNetId(email);
+ res = MSN_SharingAddDelMember(email, flags, netId, needRemove ? "DeleteMember" : "AddMember");
+// if (res || (flags & LIST_RL))
+ AddDelUserContList(email, flags, netId, needRemove);
+ if ((flags & LIST_BL) && !needRemove)
+ {
+ ThreadData* thread = MSN_GetThreadByContact(email, SERVER_SWITCHBOARD);
+ if (thread) thread->sendTerminate();
+ }
+ if ((flags & LIST_PL) && needRemove)
+ {
+ MSN_AddUser(hContact, email, netId, LIST_RL);
+ }
+ }
+ return res;
+}
+
+
+void CMsnProto::MSN_FindYahooUser(const char* email)
+{
+ const char* dom = strchr(email, '@');
+ if (dom)
+ {
+ char buf[512];
+ size_t sz;
+
+ *(char*)dom = '\0';
+ sz = mir_snprintf(buf, sizeof(buf), "<ml><d n=\"%s\"><c n=\"%s\"/></d></ml>", dom+1, email);
+ *(char*)dom = '@';
+ msnNsThread->sendPacket("FQY", "%d\r\n%s", sz, buf);
+ }
+}
+
+bool CMsnProto::MSN_RefreshContactList(void)
+{
+ Lists_Wipe();
+ Lists_Populate();
+
+ if (!MSN_SharingFindMembership()) return false;
+
+ if (m_iDesiredStatus == ID_STATUS_OFFLINE) return false;
+
+ if (!MSN_ABFind("ABFindContactsPaged", NULL)) return false;
+
+ if (m_iDesiredStatus == ID_STATUS_OFFLINE) return false;
+
+ MSN_CleanupLists();
+
+ if (m_iDesiredStatus == ID_STATUS_OFFLINE) return false;
+
+ msnLoggedIn = true;
+
+ MSN_CreateContList();
+ MSN_StoreGetProfile();
+ return true;
+}
diff --git a/protocols/MSN/src/msn_errors.cpp b/protocols/MSN/src/msn_errors.cpp
new file mode 100644
index 0000000000..177122d4e0
--- /dev/null
+++ b/protocols/MSN/src/msn_errors.cpp
@@ -0,0 +1,93 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+int CMsnProto::MSN_HandleErrors(ThreadData* info, char* cmdString)
+{
+ int errorCode, packetID = -1;
+ sscanf(cmdString, "%d %d", &errorCode, &packetID);
+
+ debugLogA("Server error:%s", cmdString);
+
+ switch(errorCode) {
+ case ERR_INTERNAL_SERVER:
+ MSN_ShowError("MSN Services are temporarily unavailable, please try to connect later");
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER);
+ return 1;
+
+ case ERR_SERVER_BUSY:
+ case ERR_SERVER_UNAVAILABLE:
+ MSN_ShowError("MSN Services are too busy, please try to connect later");
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NOSERVER);
+ return 1;
+
+ case ERR_NOT_ALLOWED_WHEN_OFFLINE:
+ MSN_ShowError("MSN protocol does not allow you to communicate with others when you are invisible");
+ return 0;
+
+ case ERR_LIST_FULL:
+ MSN_ShowError("MSN plugin cannot add a new contact because the contact list is full");
+ return 0;
+
+ case ERR_ALREADY_THERE:
+ MSN_ShowError("User is already in your contact list");
+ return 0;
+
+ case ERR_CONTACT_LIST_FAILED:
+ case ERR_LIST_UNAVAILABLE:
+ char* tWords[3];
+ if (sttDivideWords(cmdString, 3, tWords) == 3)
+ HReadBuffer(info, 0).surelyRead(atol(tWords[2]));
+ return 0;
+
+ case ERR_NOT_ONLINE:
+ if (info->mInitialContactWLID)
+ ProtoBroadcastAck(MSN_HContactFromEmail(info->mInitialContactWLID), ACKTYPE_MESSAGE, ACKRESULT_FAILED,
+ (HANDLE)999999, (LPARAM)Translate("User not online"));
+ else
+ MSN_ShowError("User not online");
+
+ return 1;
+
+ case ERR_NOT_EXPECTED:
+ MSN_ShowError("Your MSN account e-mail is unverified. Goto http://www.passport.com and verify the primary e-mail first");
+ return 0;
+
+ case ERR_AUTHENTICATION_FAILED:
+ if (info->mType != SERVER_SWITCHBOARD)
+ {
+ MSN_ShowError("Your username or password is incorrect");
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD);
+ }
+ return 1;
+
+ default:
+ debugLogA("Unprocessed error: %s", cmdString);
+ if (errorCode >= 500) //all these errors look fatal-ish
+ MSN_ShowError("Unrecognised error %d. The server has closed our connection", errorCode);
+
+ break;
+ }
+ return 0;
+}
diff --git a/protocols/MSN/src/msn_ftold.cpp b/protocols/MSN/src/msn_ftold.cpp
new file mode 100644
index 0000000000..db1ba3de66
--- /dev/null
+++ b/protocols/MSN/src/msn_ftold.cpp
@@ -0,0 +1,395 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+
+void CMsnProto::msnftp_sendAcceptReject(filetransfer *ft, bool acc)
+{
+ ThreadData* thread = MSN_GetThreadByContact(ft->p2p_dest);
+ if (thread == NULL) return;
+
+ if (acc)
+ {
+ thread->sendPacket("MSG",
+ "U %d\r\nMIME-Version: 1.0\r\n"
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Invitation-Command: ACCEPT\r\n"
+ "Invitation-Cookie: %s\r\n"
+ "Launch-Application: FALSE\r\n"
+ "Request-Data: IP-Address:\r\n\r\n",
+ 172+4+strlen(ft->szInvcookie), ft->szInvcookie);
+ }
+ else
+ {
+ thread->sendPacket("MSG",
+ "U %d\r\nMIME-Version: 1.0\r\n"
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Invitation-Command: CANCEL\r\n"
+ "Invitation-Cookie: %s\r\n"
+ "Cancel-Code: REJECT\r\n\r\n",
+ 172-33+4+strlen(ft->szInvcookie), ft->szInvcookie);
+ }
+}
+
+void CMsnProto::msnftp_invite(filetransfer *ft)
+{
+ bool isOffline;
+ ThreadData* thread = MSN_StartSB(ft->p2p_dest, isOffline);
+ if (isOffline) return;
+ if (thread != NULL) thread->mMsnFtp = ft;
+
+ TCHAR* pszFiles = _tcsrchr(ft->std.ptszFiles[0], '\\');
+ if (pszFiles)
+ pszFiles++;
+ else
+ pszFiles = *ft->std.ptszFiles;
+
+ char msg[1024];
+ mir_snprintf(msg, SIZEOF(msg),
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Application-Name: File Transfer\r\n"
+ "Application-GUID: {5D3E02AB-6190-11d3-BBBB-00C04F795683}\r\n"
+ "Invitation-Command: INVITE\r\n"
+ "Invitation-Cookie: %i\r\n"
+ "Application-File: %s\r\n"
+ "Application-FileSize: %I64u\r\n\r\n",
+ MSN_GenRandom(), UTF8(pszFiles), ft->std.currentFileSize);
+
+ if (thread == NULL)
+ MsgQueue_Add(ft->p2p_dest, 'S', msg, -1, ft);
+ else
+ thread->sendMessage('S', NULL, NETID_MSN, msg, MSG_DISABLE_HDR);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN File Transfer Protocol commands processing
+
+int CMsnProto::MSN_HandleMSNFTP(ThreadData *info, char *cmdString)
+{
+ char* params = "";
+ filetransfer* ft = info->mMsnFtp;
+
+ if (cmdString[3])
+ params = cmdString+4;
+
+ switch((*(PDWORD)cmdString&0x00FFFFFF)|0x20000000)
+ {
+ case ' EYB': //********* BYE
+ {
+ ft->complete();
+ return 1;
+ }
+ case ' LIF': //********* FIL
+ {
+ char filesize[30];
+ if (sscanf(params, "%s", filesize) < 1)
+ goto LBL_InvalidCommand;
+
+ info->mCaller = 1;
+ info->send("TFR\r\n", 5);
+ break;
+ }
+ case ' RFT': //********* TFR
+ {
+ char* sendpacket = (char*)alloca(2048);
+ filetransfer* ft = info->mMsnFtp;
+
+ info->mCaller = 3;
+
+ while (ft->std.currentFileProgress < ft->std.currentFileSize)
+ {
+ if (ft->bCanceled)
+ {
+ sendpacket[0] = 0x01;
+ sendpacket[1] = 0x00;
+ sendpacket[2] = 0x00;
+ info->send(sendpacket, 3);
+ return 0;
+ }
+
+ int wPlace = 0;
+ sendpacket[wPlace++] = 0x00;
+ unsigned __int64 packetLen = ft->std.currentFileSize - ft->std.currentFileProgress;
+ if (packetLen > 2045) packetLen = 2045;
+
+ sendpacket[wPlace++] = (char)(packetLen & 0x00ff);
+ sendpacket[wPlace++] = (char)((packetLen & 0xff00) >> 8);
+ _read(ft->fileId, &sendpacket[wPlace], packetLen);
+
+ info->send(&sendpacket[0], packetLen+3);
+
+ ft->std.totalProgress += packetLen;
+ ft->std.currentFileProgress += packetLen;
+
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+ }
+
+ ft->complete();
+ break;
+ }
+ case ' RSU': //********* USR
+ {
+ char email[130],authcookie[14];
+ if (sscanf(params,"%129s %13s",email,authcookie) < 2)
+ {
+ debugLogA("Invalid USR OK command, ignoring");
+ break;
+ }
+
+ char tCommand[30];
+ mir_snprintf(tCommand, sizeof(tCommand), "FIL %i\r\n", info->mMsnFtp->std.totalBytes);
+ info->send(tCommand, strlen(tCommand));
+ break;
+ }
+ case ' REV': //********* VER
+ {
+ char protocol1[7];
+ if (sscanf(params, "%6s", protocol1) < 1)
+ {
+LBL_InvalidCommand:
+ debugLogA("Invalid %.3s command, ignoring", cmdString);
+ break;
+ }
+
+ if (strcmp(protocol1, "MSNFTP") != 0)
+ {
+ int tempInt;
+ int tFieldCount = sscanf(params, "%d %6s", &tempInt, protocol1);
+ if (tFieldCount != 2 || strcmp(protocol1, "MSNFTP") != 0)
+ {
+ debugLogA("Another side requested the unknown protocol (%s), closing thread", params);
+ return 1;
+ }
+ }
+
+ if (info->mCaller == 0) //receive
+ {
+ char tCommand[MSN_MAX_EMAIL_LEN + 50];
+ mir_snprintf(tCommand, sizeof(tCommand), "USR %s %s\r\n", MyOptions.szEmail, info->mCookie);
+ info->send(tCommand, strlen(tCommand));
+ }
+ else if (info->mCaller == 2) //send
+ {
+ static const char sttCommand[] = "VER MSNFTP\r\n";
+ info->send(sttCommand, strlen(sttCommand));
+ }
+ break;
+ }
+ default: // receiving file
+ {
+ HReadBuffer tBuf(info, int(cmdString - info->mData));
+
+ for (;;)
+ {
+ if (ft->bCanceled)
+ { info->send("CCL\r\n", 5);
+ ft->close();
+ return 1;
+ }
+
+ BYTE* p = tBuf.surelyRead(3);
+ if (p == NULL)
+ {
+LBL_Error:
+ ft->close();
+ MSN_ShowError("file transfer is canceled by remote host");
+ return 1;
+ }
+
+ BYTE tIsTransitionFinished = *p++;
+ WORD dataLen = *p++;
+ dataLen |= (*p++ << 8);
+
+ if (tIsTransitionFinished)
+ {
+LBL_Success:
+ static const char sttCommand[] = "BYE 16777989\r\n";
+ info->send(sttCommand, strlen(sttCommand));
+ return 1;
+ }
+
+ p = tBuf.surelyRead(dataLen);
+ if (p == NULL)
+ goto LBL_Error;
+
+ _write(ft->fileId, p, dataLen);
+ ft->std.totalProgress += dataLen;
+ ft->std.currentFileProgress += dataLen;
+
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+
+ if (ft->std.currentFileProgress == ft->std.totalBytes)
+ {
+ ft->complete();
+ goto LBL_Success;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// ft_startFileSend - sends a file using the old f/t protocol
+
+void __cdecl CMsnProto::msnftp_sendFileThread(void* arg)
+{
+ ThreadData* info = (ThreadData*)arg;
+
+ debugLogA("Waiting for an incoming connection to '%s'...", info->mServer);
+
+ switch(WaitForSingleObject(info->hWaitEvent, 60000))
+ {
+ case WAIT_TIMEOUT:
+ case WAIT_FAILED:
+ debugLogA("Incoming connection timed out, closing file transfer");
+ return;
+ }
+
+ info->mBytesInData = 0;
+
+ for (;;)
+ {
+ int recvResult = info->recv(info->mData+info->mBytesInData, 1000 - info->mBytesInData);
+ if (recvResult == SOCKET_ERROR || !recvResult)
+ break;
+
+ info->mBytesInData += recvResult;
+
+ //pull off each line for parsing
+ if (info->mCaller == 3 && info->mType == SERVER_FILETRANS)
+ {
+ if (MSN_HandleMSNFTP(info, info->mData))
+ break;
+ }
+ else // info->mType!=SERVER_FILETRANS
+ {
+ for (;;)
+ {
+ char* peol = strchr(info->mData,'\r');
+ if (peol == NULL)
+ break;
+
+ if (info->mBytesInData < peol - info->mData + 2)
+ break; //wait for full line end
+
+ char msg[sizeof(info->mData)];
+ memcpy(msg, info->mData, peol - info->mData); msg[peol - info->mData] = 0;
+ if (*++peol != '\n')
+ debugLogA("Dodgy line ending to command: ignoring");
+ else
+ peol++;
+
+ info->mBytesInData -= peol - info->mData;
+ memmove(info->mData, peol, info->mBytesInData);
+
+ debugLogA("RECV:%s", msg);
+
+ if (!isalnum(msg[0]) || !isalnum(msg[1]) || !isalnum(msg[2]) || (msg[3] && msg[3]!=' '))
+ {
+ debugLogA("Invalid command name");
+ continue;
+ }
+
+ if (MSN_HandleMSNFTP(info, msg))
+ break;
+ }
+ }
+
+ if (info->mBytesInData == sizeof(info->mData))
+ {
+ debugLogA("sizeof(data) is too small: the longest line won't fit");
+ break;
+ }
+ }
+
+ debugLogA("Closing file transfer thread");
+}
+
+void CMsnProto::msnftp_startFileSend(ThreadData* info, const char* Invcommand, const char* Invcookie)
+{
+ if (_stricmp(Invcommand, "ACCEPT"))
+ return;
+
+ NETLIBBIND nlb = {0};
+ HANDLE sb = NULL;
+
+ filetransfer* ft = info->mMsnFtp; info->mMsnFtp = NULL;
+ if (ft != NULL && MyConnection.extIP)
+ {
+ nlb.cbSize = sizeof(nlb);
+ nlb.pfnNewConnectionV2 = MSN_ConnectionProc;
+ nlb.pExtra = this;
+
+ sb = (HANDLE)CallService(MS_NETLIB_BINDPORT, (WPARAM)m_hNetlibUser, (LPARAM)&nlb);
+ if (sb == NULL)
+ debugLogA("Unable to bind the port for incoming transfers");
+ }
+
+ char hostname[256] = "";
+ gethostname(hostname, sizeof(hostname));
+ PHOSTENT he = gethostbyname(hostname);
+
+ const PIN_ADDR addr = (PIN_ADDR)he->h_addr_list[0];
+ if (addr)
+ strcpy(hostname, inet_ntoa(*addr));
+ else
+ hostname[0] = 0;
+
+ char command[1024];
+ int nBytes = mir_snprintf(command, sizeof(command),
+ "MIME-Version: 1.0\r\n"
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Invitation-Command: %s\r\n"
+ "Invitation-Cookie: %s\r\n"
+ "IP-Address: %s\r\n"
+ "IP-Address-Internal: %s\r\n"
+ "Port: %i\r\n"
+ "PortX: %i\r\n"
+ "PortX-Internal: %i\r\n"
+ "AuthCookie: %i\r\n"
+ "Launch-Application: FALSE\r\n"
+ "Request-Data: IP-Address:\r\n\r\n",
+ sb && MyConnection.extIP ? "ACCEPT" : "CANCEL",
+ Invcookie, MyConnection.GetMyExtIPStr(), hostname,
+ nlb.wExPort, nlb.wExPort ^ 0x3141, nlb.wPort ^ 0x3141,
+ MSN_GenRandom());
+
+ info->sendPacket("MSG", "N %d\r\n%s", nBytes, command);
+
+ if (sb)
+ {
+ ThreadData* newThread = new ThreadData;
+ newThread->mType = SERVER_FILETRANS;
+ newThread->mCaller = 2;
+ newThread->mMsnFtp = ft;
+ newThread->mIncomingBoundPort = sb;
+ newThread->mIncomingPort = nlb.wPort;
+ newThread->startThread(&CMsnProto::msnftp_sendFileThread, this);
+ }
+ else
+ delete ft;
+}
diff --git a/protocols/MSN/src/msn_global.h b/protocols/MSN/src/msn_global.h
new file mode 100644
index 0000000000..d1efa2736b
--- /dev/null
+++ b/protocols/MSN/src/msn_global.h
@@ -0,0 +1,884 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2011 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <windows.h>
+#include <commctrl.h>
+
+#include <ctype.h>
+#include <malloc.h>
+#include <process.h>
+#include <stdio.h>
+#include <time.h>
+
+#include <direct.h>
+#include <io.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#include <newpluginapi.h>
+
+#include <m_clist.h>
+#include <m_clistint.h>
+#include <m_clui.h>
+#include <m_contacts.h>
+#include <m_idle.h>
+#include <m_icolib.h>
+#include <m_message.h>
+#include <m_options.h>
+#include <m_protocols.h>
+#include <m_protomod.h>
+#include <m_protosvc.h>
+#include <m_protoint.h>
+#include <m_skin.h>
+#include <m_system.h>
+#include <m_system_cpp.h>
+#include <m_userinfo.h>
+#include <m_utils.h>
+#include <win2k.h>
+#include <m_database.h>
+#include <m_langpack.h>
+#include <m_netlib.h>
+#include <m_popup.h>
+#include <m_chat.h>
+#include <m_avatars.h>
+#include <m_timezones.h>
+#include <m_extraicons.h>
+#include <m_nudge.h>
+#include <m_string.h>
+
+#include "m_proto_listeningto.h"
+#include "m_folders.h"
+#include "m_metacontacts.h"
+
+#include "ezxml.h"
+
+#include "resource.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN error codes
+
+#define ERR_SYNTAX_ERROR 200
+#define ERR_INVALID_PARAMETER 201
+#define ERR_INVALID_USER 205
+#define ERR_FQDN_MISSING 206
+#define ERR_ALREADY_LOGIN 207
+#define ERR_INVALID_USERNAME 208
+#define ERR_INVALID_FRIENDLY_NAME 209
+#define ERR_LIST_FULL 210
+#define ERR_ALREADY_THERE 215
+#define ERR_NOT_ON_LIST 216
+#define ERR_NOT_ONLINE 217
+#define ERR_ALREADY_IN_THE_MODE 218
+#define ERR_ALREADY_IN_OPPOSITE_LIST 219
+#define ERR_CONTACT_LIST_FAILED 241
+#define ERR_SWITCHBOARD_FAILED 280
+#define ERR_NOTIFY_XFR_FAILED 281
+#define ERR_REQUIRED_FIELDS_MISSING 300
+#define ERR_NOT_LOGGED_IN 302
+#define ERR_INTERNAL_SERVER 500
+#define ERR_DB_SERVER 501
+#define ERR_LIST_UNAVAILABLE 508
+#define ERR_FILE_OPERATION 510
+#define ERR_MEMORY_ALLOC 520
+#define ERR_SERVER_BUSY 600
+#define ERR_SERVER_UNAVAILABLE 601
+#define ERR_PEER_NS_DOWN 602
+#define ERR_DB_CONNECT 603
+#define ERR_SERVER_GOING_DOWN 604
+#define ERR_CREATE_CONNECTION 707
+#define ERR_INVALID_LOCALE 710
+#define ERR_BLOCKING_WRITE 711
+#define ERR_SESSION_OVERLOAD 712
+#define ERR_USER_TOO_ACTIVE 713
+#define ERR_TOO_MANY_SESSIONS 714
+#define ERR_NOT_EXPECTED 715
+#define ERR_BAD_FRIEND_FILE 717
+#define ERR_AUTHENTICATION_FAILED 911
+#define ERR_NOT_ALLOWED_WHEN_OFFLINE 913
+#define ERR_NOT_ACCEPTING_NEW_USERS 920
+#define ERR_EMAIL_ADDRESS_NOT_VERIFIED 924
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Global definitions
+
+#define MSN_MAX_EMAIL_LEN 128
+#define MSN_GUID_LEN 40
+
+#define MSN_PACKETS_COMBINE 7
+#define MSN_DEFAULT_PORT 1863
+#define MSN_DEFAULT_GATEWAY_PORT 80
+const char MSN_DEFAULT_LOGIN_SERVER[] = "messenger.hotmail.com";
+const char MSN_DEFAULT_GATEWAY[] = "gateway.messenger.hotmail.com";
+const char MSN_USER_AGENT[] = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1)";
+
+#define MSN_BLOCK "/BlockCommand"
+#define MSN_INVITE "/InviteCommand"
+#define MSN_NETMEETING "/NetMeeting"
+#define MSN_VIEW_PROFILE "/ViewProfile"
+
+#define MS_GOTO_INBOX "/GotoInbox"
+#define MS_EDIT_PROFILE "/EditProfile"
+#define MS_EDIT_ALERTS "/EditAlerts"
+#define MS_SET_NICKNAME_UI "/SetNicknameUI"
+
+extern const char sttVoidUid[];
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN plugin functions
+
+struct CMsnProto;
+
+#define MSN_ALLOW_MSGBOX 1
+#define MSN_ALLOW_ENTER 2
+#define MSN_HOTMAIL_POPUP 4
+#define MSN_SHOW_ERROR 8
+#define MSN_ALERT_POPUP 16
+
+void HtmlDecode(char* str);
+char* HtmlEncode(const char* str);
+bool txtParseParam (const char* szData, const char* presearch, const char* start, const char* finish, char* param, const int size);
+void stripBBCode(char* src);
+void stripColorCode(char* src);
+void parseWLID(char* wlid, char** net, char** email, char** inst);
+
+char* GetGlobalIp(void);
+
+template <class chartype> void UrlDecode(chartype* str);
+
+void __cdecl MSN_ConnectionProc(HANDLE hNewConnection, DWORD dwRemoteIP, void*);
+
+char* MSN_GetAvatarHash(char* szContext, char** pszUrl = NULL);
+bool MSN_MsgWndExist(MCONTACT hContact);
+
+#define MSN_SendNickname(a) MSN_SendNicknameUtf(UTF8(a))
+
+unsigned MSN_GenRandom(void);
+
+void MSN_InitContactMenu(void);
+void MSN_RemoveContactMenus(void);
+
+HANDLE GetIconHandle(int iconId);
+HICON LoadIconEx(const char* name, bool big = false);
+void ReleaseIconEx(const char* name, bool big = false);
+
+void MsnInitIcons(void);
+
+int sttDivideWords(char* parBuffer, int parMinItems, char** parDest);
+void MSN_MakeDigest(const char* chl, char* dgst);
+char* getNewUuid(void);
+
+TCHAR* EscapeChatTags(const TCHAR* pszText);
+TCHAR* UnEscapeChatTags(TCHAR* str_in);
+
+void overrideStr(TCHAR*& dest, const TCHAR* src, bool unicode, const TCHAR* def = NULL);
+
+char* arrayToHex(BYTE* data, size_t datasz);
+
+inline unsigned short _htons(unsigned short s)
+{
+ return s>>8|s<<8;
+}
+
+inline unsigned long _htonl(unsigned long s)
+{
+ return s<<24|(s&0xff00)<<8|((s>>8)&0xff00)|s>>24;
+}
+
+inline unsigned __int64 _htonl64(unsigned __int64 s)
+{
+ return (unsigned __int64)_htonl(s & 0xffffffff) << 32 | _htonl(s >> 32);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup interface
+
+typedef struct _tag_PopupData
+{
+ unsigned flags;
+ char* url;
+ TCHAR* title;
+ TCHAR* text;
+ CMsnProto* proto;
+} PopupData;
+
+struct STRLIST : public LIST<char>
+{
+ static int compare(const char* p1, const char* p2)
+ { return _stricmp(p1, p2); }
+
+ STRLIST() : LIST<char>(2, compare) {}
+ ~STRLIST() { destroy(); }
+
+ void destroy( void )
+ {
+ for (int i=0; i < count; i++)
+ mir_free(items[i]);
+
+ List_Destroy((SortedList*)this);
+ }
+
+ int insertn(const char* p) { return insert(mir_strdup(p)); }
+
+ int remove(int idx)
+ {
+ mir_free(items[idx]);
+ return List_Remove((SortedList*)this, idx);
+ }
+
+ int remove(const char* p)
+ {
+ int idx;
+ return List_GetIndex((SortedList*)this, (char*)p, &idx) == 1 ? remove(idx) : -1;
+ }
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MIME headers processing
+
+class MimeHeaders
+{
+public:
+
+ MimeHeaders();
+ MimeHeaders(unsigned);
+ ~MimeHeaders();
+
+ void clear(void);
+ char* decodeMailBody(char* msgBody);
+ const char* find(const char* fieldName);
+ char* flipStr(const char* src, size_t len, char* dest);
+ size_t getLength(void);
+ char* readFromBuffer(char* src);
+ char* writeToBuffer(char* dest);
+
+ void addString(const char* name, const char* szValue, unsigned flags = 0);
+ void addLong(const char* name, long lValue, unsigned flags = 0);
+ void addULong(const char* name, unsigned lValue);
+ void addBool(const char* name, bool lValue);
+
+ const char* operator[](const char* fieldName) { return find(fieldName); }
+
+ static wchar_t* decode(const char* val);
+
+private:
+ typedef struct tag_MimeHeader
+ {
+ const char* name;
+ const char* value;
+ unsigned flags;
+ } MimeHeader;
+
+ unsigned mCount;
+ unsigned mAllocCount;
+ MimeHeader* mVals;
+
+ unsigned allocSlot(void);
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// File transfer helper
+
+struct ThreadData;
+
+struct HReadBuffer
+{
+ HReadBuffer(ThreadData* info, int iStart = 0);
+ ~HReadBuffer();
+
+ BYTE* surelyRead(size_t parBytes);
+
+ ThreadData* owner;
+ BYTE* buffer;
+ size_t totalDataSize;
+ size_t startOffset;
+};
+
+enum TInfoType
+{
+ SERVER_NOTIFICATION,
+ SERVER_SWITCHBOARD,
+ SERVER_FILETRANS,
+ SERVER_P2P_DIRECT
+};
+
+
+struct filetransfer
+{
+ filetransfer(CMsnProto* prt);
+ ~filetransfer(void);
+
+ void close(void);
+ void complete(void);
+ int create(void);
+ int openNext(void);
+
+ CMsnProto* proto;
+
+ PROTOFILETRANSFERSTATUS std;
+
+ bool bCanceled; // flag to interrupt a transfer
+ bool bCompleted; // was a FT ever completed?
+ bool bAccepted; // was a FT ever completed?
+
+ int fileId; // handle of file being transferring (r/w)
+
+ HANDLE hLockHandle;
+
+ ThreadData *info;
+ TInfoType tType;
+ TInfoType tTypeReq;
+ time_t ts;
+ clock_t nNotify;
+ unsigned cf;
+
+ bool p2p_waitack; // wait for ack
+ bool p2p_isV2; // P2P V2
+
+ unsigned p2p_sessionid; // session id
+ unsigned p2p_acksessid; // acknowledged session id
+ unsigned p2p_sendmsgid; // send message id
+ unsigned p2p_byemsgid; // bye message id
+ unsigned p2p_ackID; // number of ack's state
+ unsigned p2p_appID; // application id: 1 = avatar, 2 = file transfer
+ unsigned p2p_type; // application id: 1 = avatar, 2 = file transfer, 3 = custom emoticon
+ char* p2p_branch; // header Branch: field
+ char* p2p_callID; // header Call-ID: field
+ char* p2p_dest; // destination e-mail address
+ char* p2p_object; // MSN object for a transfer
+
+ //---- receiving a file
+ char* szInvcookie; // cookie for receiving
+
+ unsigned __int64 lstFilePtr;
+};
+
+struct directconnection
+{
+ directconnection(const char* CallID, const char* Wlid);
+ ~directconnection();
+
+ char* calcHashedNonce(UUID* nonce);
+ char* mNonceToText(void);
+ char* mNonceToHash(void) { return calcHashedNonce(mNonce); }
+ void xNonceToBin(UUID* nonce);
+
+ UUID* mNonce;
+ char* xNonce;
+
+ char* callId;
+ char* wlid;
+
+ time_t ts;
+
+ bool useHashedNonce;
+ bool bAccepted;
+
+ CMsnProto* proto;
+};
+
+
+#pragma pack(1)
+
+typedef struct _tag_HFileContext
+{
+ unsigned len;
+ unsigned ver;
+ unsigned __int64 dwSize;
+ unsigned type;
+ wchar_t wszFileName[MAX_PATH];
+ char unknown[30];
+ unsigned id;
+ char unknown2[64];
+} HFileContext;
+
+struct P2PB_Header
+{
+ virtual char* parseMsg(char *buf) = 0;
+ virtual char* createMsg(char *buf, const char* wlid, CMsnProto *ppro) = 0;
+ virtual bool isV2Hdr(void) = 0;
+ virtual void logHeader(CMsnProto *ppro) = 0;
+};
+
+struct P2P_Header : P2PB_Header
+{
+ unsigned mSessionID;
+ unsigned mID;
+ unsigned __int64 mOffset;
+ unsigned __int64 mTotalSize;
+ unsigned mPacketLen;
+ unsigned mFlags;
+ unsigned mAckSessionID;
+ unsigned mAckUniqueID;
+ unsigned __int64 mAckDataSize;
+
+ P2P_Header() { memset(&mSessionID, 0, 48); }
+ P2P_Header(char *buf) { parseMsg(buf); }
+
+ char* parseMsg(char *buf) { memcpy(&mSessionID, buf, 48); return buf + 48; }
+ char* createMsg(char *buf, const char* wlid, CMsnProto *ppro);
+ bool isV2Hdr(void) { return false; }
+ void logHeader(CMsnProto *ppro);
+} ;
+
+struct P2PV2_Header : P2PB_Header
+{
+ unsigned mSessionID;
+ unsigned mID;
+ const char* mCap;
+ unsigned __int64 mRemSize;
+ unsigned mPacketLen;
+ unsigned mPacketNum;
+ unsigned mAckUniqueID;
+ unsigned char mOpCode;
+ unsigned char mTFCode;
+
+ P2PV2_Header() { memset(&mSessionID, 0, ((char*)&mTFCode - (char*)&mSessionID) + sizeof(mTFCode)); }
+ P2PV2_Header(char *buf) { parseMsg(buf); }
+
+ char* parseMsg(char *buf);
+ char* createMsg(char *buf, const char* wlid, CMsnProto *ppro);
+ bool isV2Hdr(void) { return true; }
+ void logHeader(CMsnProto *ppro);
+};
+
+#pragma pack()
+
+bool p2p_IsDlFileOk(filetransfer* ft);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Thread handling functions and datatypes
+
+#define MSG_DISABLE_HDR 1
+#define MSG_REQUIRE_ACK 2
+#define MSG_RTL 4
+#define MSG_OFFLINE 8
+
+struct CMsnProto;
+typedef void (__cdecl CMsnProto::*MsnThreadFunc)(void*);
+
+struct ThreadData
+{
+ ThreadData();
+ ~ThreadData();
+
+ STRLIST mJoinedContactsWLID;
+ STRLIST mJoinedIdentContactsWLID;
+ char* mInitialContactWLID;
+
+ TInfoType mType; // thread type
+ MsnThreadFunc mFunc; // thread entry point
+ char mServer[80]; // server name
+
+ HANDLE s; // NetLib connection for the thread
+ HANDLE mIncomingBoundPort; // Netlib listen for the thread
+ HANDLE hWaitEvent;
+ WORD mIncomingPort;
+ TCHAR mChatID[10];
+ bool mIsMainThread;
+ clock_t mWaitPeriod;
+
+ CMsnProto* proto;
+
+ //----| for gateways |----------------------------------------------------------------
+ char mSessionID[50]; // Gateway session ID
+ char mGatewayIP[80]; // Gateway IP address
+ int mGatewayTimeout;
+ bool sessionClosed;
+ bool termPending;
+ bool gatewayType;
+
+ //----| for switchboard servers only |------------------------------------------------
+ bool firstMsgRecv;
+ int mCaller;
+ char mCookie[130]; // for switchboard servers only
+ LONG mTrid; // current message ID
+ UINT mTimerId; // typing notifications timer id
+
+ //----| for file transfers only |-----------------------------------------------------
+ filetransfer* mMsnFtp; // file transfer block
+ bool mBridgeInit;
+
+ //----| internal data buffer |--------------------------------------------------------
+ int mBytesInData; // bytes available in data buffer
+ char mData[8192]; // data buffer for connection
+
+ //----| methods |---------------------------------------------------------------------
+ void applyGatewayData(HANDLE hConn, bool isPoll);
+ void getGatewayUrl(char* dest, int destlen, bool isPoll);
+ void processSessionData(const char* xMsgr, const char* xHost);
+ void startThread(MsnThreadFunc , CMsnProto *prt);
+
+ int send(const char data[], size_t datalen);
+ int recv(char* data, size_t datalen);
+
+ void resetTimeout(bool term = false);
+ bool isTimeout(void);
+
+ void sendTerminate(void);
+ void sendCaps(void);
+ int sendMessage(int msgType, const char* email, int netId, const char* msg, int parFlags);
+ int sendRawMessage(int msgType, const char* data, int datLen);
+ int sendPacket(const char* cmd, const char* fmt, ...);
+
+ int contactJoined(const char* email);
+ int contactLeft(const char* email);
+ MCONTACT getContactHandle(void);
+};
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN P2P session support
+
+#define MSN_APPID_AVATAR 1
+#define MSN_APPID_AVATAR2 12
+#define MSN_APPID_FILE 2
+#define MSN_APPID_WEBCAM 4
+#define MSN_APPID_MEDIA_SHARING 35
+#define MSN_APPID_IMAGE 33
+
+#define MSN_APPID_CUSTOMSMILEY 3
+#define MSN_APPID_CUSTOMANIMATEDSMILEY 4
+
+#define MSN_TYPEID_FTPREVIEW 0
+#define MSN_TYPEID_FTNOPREVIEW 1
+#define MSN_TYPEID_CUSTOMSMILEY 2
+#define MSN_TYPEID_DISPLAYPICT 3
+#define MSN_TYPEID_BKGNDSHARING 4
+#define MSN_TYPEID_BKGNDIMG 5
+#define MSN_TYPEID_WINK 8
+
+
+
+inline bool IsChatHandle(MCONTACT hContact) { return (INT_PTR)hContact < 0; }
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Message queue
+
+#define MSGQUE_RAW 1
+
+struct MsgQueueEntry
+{
+ char* wlid;
+ char* message;
+ filetransfer* ft;
+ STRLIST* cont;
+ int msgType;
+ int msgSize;
+ int seq;
+ int allocatedToThread;
+ time_t ts;
+ int flags;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Avatars' queue
+
+struct AvatarQueueEntry
+{
+ MCONTACT hContact;
+ char *pszUrl;
+
+ __forceinline AvatarQueueEntry(MCONTACT _contact, LPCSTR _url) :
+ hContact(_contact),
+ pszUrl( mir_strdup(_url))
+ {}
+
+ __forceinline ~AvatarQueueEntry()
+ { mir_free(pszUrl);
+ }
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// User lists
+
+template< class T > int CompareId(const T* p1, const T* p2)
+{
+ return _stricmp(p1->id, p2->id);
+}
+
+struct ServerGroupItem
+{
+ char* id;
+ char* name; // in UTF8
+};
+
+struct MsnPlace
+{
+ char *id;
+ unsigned cap1;
+ unsigned cap2;
+ unsigned p2pMsgId;
+ unsigned short p2pPktNum;
+
+ ~MsnPlace() { mir_free(id); }
+};
+
+struct MsnContact
+{
+ char *email;
+ char *invite;
+ char *nick;
+ MCONTACT hContact;
+ int list;
+ int netId;
+ int p2pMsgId;
+ unsigned cap1;
+ unsigned cap2;
+
+ OBJLIST<MsnPlace> places;
+
+ MsnContact() : places(1, CompareId) {}
+ ~MsnContact() { mir_free(email); mir_free(nick); mir_free(invite); }
+};
+
+#define cap_OnlineViaMobile 0x00000001
+#define cap_OnlineMSN8User 0x00000002
+#define cap_SupportsGifInk 0x00000004
+#define cap_SupportsIsfInk 0x00000008
+#define cap_WebCamDetected 0x00000010
+#define cap_SupportsChunking 0x00000020
+#define cap_MobileEnabled 0x00000040
+#define cap_WebWatchEnabled 0x00000080
+#define cap_SupportsActivities 0x00000100
+#define cap_OnlineViaWebIM 0x00000200
+#define cap_MobileDevice 0x00000400
+#define cap_OnlineViaTGW 0x00000800
+#define cap_HasSpace 0x00001000
+#define cap_IsMceUser 0x00002000
+#define cap_SupportsDirectIM 0x00004000
+#define cap_SupportsWinks 0x00008000
+#define cap_SupportsSharedSearch 0x00010000
+#define cap_IsBot 0x00020000
+#define cap_SupportsVoiceIM 0x00040000
+#define cap_SupportsSChannel 0x00080000
+#define cap_SupportsSipInvite 0x00100000
+#define cap_SupportsMultipartyMedia 0x00200000
+#define cap_SupportsSDrive 0x00400000
+#define cap_SupportsPageModeMessaging 0x00800000
+#define cap_HasOneCare 0x01000000
+#define cap_SupportsTurn 0x02000000
+#define cap_SupportsP2PBootstrap 0x04000000
+#define cap_UsingAlias 0x08000000
+
+#define capex_IsSmsOnly 0x00000001
+#define capex_SupportsVoiceOverMsnp 0x00000002
+#define capex_SupportsUucpSipStack 0x00000004
+#define capex_SupportsApplicationMsg 0x00000008
+#define capex_RTCVideoEnabled 0x00000010
+#define capex_SupportsPeerToPeerV2 0x00000020
+#define capex_IsAuthWebIMUser 0x00000040
+#define capex_Supports1On1ViaGroup 0x00000080
+#define capex_SupportsOfflineIM 0x00000100
+#define capex_SupportsSharingVideo 0x00000200
+#define capex_SupportsNudges 0x00000400
+#define capex_CircleVoiceIMEnabled 0x00000800
+#define capex_SharingEnabled 0x00001000
+#define capex_MobileSuspendIMFanoutDisable 0x00002000
+#define capex_SupportsP2PMixerRelay 0x00008000
+#define capex_ConvWindowFileTransfer 0x00020000
+#define capex_VideoCallSupports16x9 0x00040000
+#define capex_SupportsP2PEnveloping 0x00080000
+#define capex_YahooIMDisabled 0x00400000
+#define capex_SIPTunnelVersion2 0x00800000
+#define capex_VoiceClipSupportsWMAFormat 0x01000000
+#define capex_VoiceClipSupportsCircleIM 0x02000000
+#define capex_SupportsSocialNewsObjectTypes 0x04000000
+#define capex_CustomEmoticonsCapable 0x08000000
+#define capex_SupportsUTF8MoodMessages 0x10000000
+#define capex_FTURNCapable 0x20000000
+#define capex_SupportsP4Activity 0x40000000
+#define capex_SupportsChats 0x80000000
+
+#define NETID_UNKNOWN 0x0000
+#define NETID_MSN 0x0001
+#define NETID_LCS 0x0002
+#define NETID_MOB 0x0004
+#define NETID_MOBNET 0x0008
+#define NETID_CIRCLE 0x0009
+#define NETID_TMPCIRCLE 0x000A
+#define NETID_CID 0x000B
+#define NETID_CONNECT 0x000D
+#define NETID_REMOTE 0x000E
+#define NETID_SMTP 0x0010
+#define NETID_YAHOO 0x0020
+
+#define LIST_FL 0x0001
+#define LIST_AL 0x0002
+#define LIST_BL 0x0004
+#define LIST_RL 0x0008
+#define LIST_PL 0x0010
+#define LIST_LL 0x0080
+
+#define LIST_REMOVE 0x0100
+#define LIST_REMOVENH 0x0300
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN plugin options
+
+typedef struct _tag_MYOPTIONS
+{
+ bool EnableSounds;
+
+ bool ShowErrorsAsPopups;
+ bool SlowSend;
+ bool ManageServer;
+
+ char szEmail[MSN_MAX_EMAIL_LEN];
+ char szMachineGuid[MSN_GUID_LEN];
+ char szMachineGuidP2P[MSN_GUID_LEN];
+}
+MYOPTIONS;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Windows error class
+
+struct TWinErrorCode
+{
+ WINAPI TWinErrorCode();
+ WINAPI ~TWinErrorCode();
+
+ char* WINAPI getText();
+
+ long mErrorCode;
+ char* mErrorText;
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// External variables
+
+#define MSN_NUM_MODES 9
+
+const char msnProtChallenge[] = "C1BX{V4W}Q3*10SM";
+const char msnProductID[] = "PROD0120PW!CCV9@";
+const char msnAppID[] = "AAD9B99B-58E6-4F23-B975-D9EC1F9EC24A";
+const char msnStoreAppId[] = "Messenger Client 9.0";
+const char msnProductVer[] = "14.0.8117.0416";
+const char msnProtID[] = "MSNP18";
+
+extern HINSTANCE hInst;
+
+///////////////////////////////////////////////////////////////////////////////
+// UTF8 encode helper
+
+class UTFEncoder
+{
+private:
+ char* m_body;
+
+public:
+ UTFEncoder(const char* pSrc) :
+ m_body(mir_utf8encode(pSrc)) {}
+
+ UTFEncoder(const wchar_t* pSrc) :
+ m_body(mir_utf8encodeW(pSrc)) {}
+
+ ~UTFEncoder() { mir_free(m_body); }
+ const char* str() const { return m_body; }
+};
+
+#define UTF8(A) UTFEncoder(A).str()
+
+
+typedef enum _tag_ConEnum
+{
+ conUnknown,
+ conDirect,
+ conUnknownNAT,
+ conIPRestrictNAT,
+ conPortRestrictNAT,
+ conSymmetricNAT,
+ conFirewall,
+ conISALike
+} ConEnum;
+
+#pragma pack(1)
+typedef struct _tag_UDPProbePkt
+{
+ unsigned char version;
+ unsigned char serviceCode;
+ unsigned short clientPort;
+ unsigned clientIP;
+ unsigned short discardPort;
+ unsigned short testPort;
+ unsigned testIP;
+ unsigned trId;
+} UDPProbePkt;
+#pragma pack()
+
+extern const char* conStr[];
+
+typedef struct _tag_MyConnectionType
+{
+ unsigned intIP;
+ unsigned extIP;
+ ConEnum udpConType;
+ ConEnum tcpConType;
+ unsigned weight;
+ bool upnpNAT;
+ bool icf;
+
+ const IN_ADDR GetMyExtIP(void) { return *((PIN_ADDR)&extIP); }
+ const char* GetMyExtIPStr(void) { return inet_ntoa(GetMyExtIP()); }
+ const char* GetMyUdpConStr(void) { return conStr[udpConType]; }
+ void SetUdpCon(const char* str);
+ void CalculateWeight(void);
+} MyConnectionType;
+
+struct chunkedmsg
+{
+ char* id;
+ char* msg;
+ size_t size;
+ size_t recvsz;
+ bool bychunk;
+
+ chunkedmsg(const char* tid, const size_t totsz, const bool bychunk);
+ ~chunkedmsg();
+
+ void add(const char* msg, size_t offset, size_t portion);
+ bool get(char*& tmsg, size_t& tsize);
+};
+
+struct DeleteParam
+{
+ CMsnProto *proto;
+ MCONTACT hContact;
+};
+
+INT_PTR CALLBACK DlgDeleteContactUI(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+
+struct InviteChatParam
+{
+ TCHAR* id;
+ MCONTACT hContact;
+ CMsnProto* ppro;
+
+ InviteChatParam(const TCHAR* id, MCONTACT hContact, CMsnProto* ppro)
+ : id(mir_tstrdup(id)), hContact(hContact), ppro(ppro) {}
+
+ ~InviteChatParam()
+ { mir_free(id); }
+};
+
+INT_PTR CALLBACK DlgInviteToChat(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam); \ No newline at end of file
diff --git a/protocols/MSN/src/msn_http.cpp b/protocols/MSN/src/msn_http.cpp
new file mode 100644
index 0000000000..ae2f18cdf2
--- /dev/null
+++ b/protocols/MSN/src/msn_http.cpp
@@ -0,0 +1,130 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+static ThreadData* FindThreadConn(HANDLE hConn)
+{
+ ThreadData* res = NULL;
+ for (int i = 0; i < g_Instances.getCount() && res == NULL; ++i)
+ res = g_Instances[i].MSN_GetThreadByConnection(hConn);
+
+ return res;
+}
+
+//=======================================================================================
+// Fake function - it does nothing but confirms successful session initialization
+//=======================================================================================
+
+int msn_httpGatewayInit(HANDLE hConn, NETLIBOPENCONNECTION* nloc, NETLIBHTTPREQUEST* nlhr)
+{
+ NETLIBHTTPPROXYINFO nlhpi = {0};
+ nlhpi.cbSize = sizeof(nlhpi);
+ nlhpi.szHttpGetUrl = NULL;
+ nlhpi.szHttpPostUrl = "messenger.hotmail.com";
+ nlhpi.flags = NLHPIF_HTTP11;
+ nlhpi.combinePackets = MSN_PACKETS_COMBINE;
+ return CallService(MS_NETLIB_SETHTTPPROXYINFO, (WPARAM)hConn, (LPARAM)&nlhpi);
+}
+
+//=======================================================================================
+// Prepares the szHttpPostUrl. If it's the very first send (mSessionID is void), this
+// function generates the initial URL depending on a thread type
+//=======================================================================================
+
+int msn_httpGatewayWrapSend(HANDLE hConn, PBYTE buf, int len, int flags, MIRANDASERVICE pfnNetlibSend)
+{
+ ThreadData* T = FindThreadConn(hConn);
+ if (T != NULL)
+ {
+ if (T->sessionClosed)
+ return SOCKET_ERROR;
+
+ T->applyGatewayData(hConn, len == 0);
+ }
+
+ NETLIBBUFFER tBuf = { (char*)buf, len, flags };
+ return pfnNetlibSend((LPARAM)hConn, WPARAM(&tBuf));
+}
+
+//=======================================================================================
+// Processes the results of the command execution. Parses HTTP headers to get the next
+// SessionID & gateway IP values
+//=======================================================================================
+
+PBYTE msn_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST* nlhr, PBYTE buf, int len, int *outBufLen, void *(*NetlibRealloc)(void *, size_t))
+{
+ *outBufLen = len;
+
+ ThreadData* T = FindThreadConn(nlhr->nlc);
+ if (T == NULL) return buf;
+
+ bool isSessionClosed = true;
+ bool isMsnPacket = false;
+
+ if (nlhr->resultCode == 200)
+ {
+ char *xMsgr = NULL, *xHost = NULL;
+
+ for (int i=0; i < nlhr->headersCount; i++)
+ {
+ NETLIBHTTPHEADER& tHeader = nlhr->headers[i];
+ if (_stricmp(tHeader.szName, "X-MSN-Messenger") == 0)
+ xMsgr = tHeader.szValue;
+ else if (_stricmp(tHeader.szName, "X-MSN-Host") == 0)
+ xHost = tHeader.szValue;
+
+ }
+
+ if (xMsgr)
+ {
+ isMsnPacket = true;
+
+ if (strstr(xMsgr, "Session=close") == 0)
+ isSessionClosed = false;
+
+ T->processSessionData(xMsgr, xHost);
+ T->applyGatewayData(nlhr->nlc, false);
+ }
+ }
+
+ T->sessionClosed |= isSessionClosed;
+ if (isSessionClosed && buf == NULL)
+ {
+ *outBufLen = 0;
+ buf = (PBYTE)mir_alloc(1);
+ *buf = 0;
+ }
+ else if (buf == NULL && len == 0)
+ {
+ *outBufLen = 1;
+ buf = (PBYTE)mir_alloc(1);
+ *buf = 0;
+ }
+ else if (!isMsnPacket)
+ {
+ *outBufLen = 0;
+ *buf = 0;
+ }
+ return buf;
+}
diff --git a/protocols/MSN/src/msn_libstr.cpp b/protocols/MSN/src/msn_libstr.cpp
new file mode 100644
index 0000000000..48296b36fb
--- /dev/null
+++ b/protocols/MSN/src/msn_libstr.cpp
@@ -0,0 +1,319 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+
+static TCHAR* a2tf(const TCHAR* str, bool unicode)
+{
+ if (str == NULL)
+ return NULL;
+
+ return unicode ? mir_tstrdup(str) : mir_a2t((char*)str);
+}
+
+void overrideStr(TCHAR*& dest, const TCHAR* src, bool unicode, const TCHAR* def)
+{
+ mir_free(dest);
+ dest = NULL;
+
+ if (src != NULL)
+ dest = a2tf(src, unicode);
+ else if (def != NULL)
+ dest = mir_tstrdup(def);
+}
+
+char* arrayToHex(BYTE* data, size_t datasz)
+{
+ char *res = (char*)mir_alloc(2 * datasz + 1);
+ bin2hex(data, datasz, res);
+ return res;
+}
+
+bool txtParseParam (const char* szData, const char* presearch, const char* start, const char* finish, char* param, const int size)
+{
+ const char *cp, *cp1;
+ int len;
+
+ if (szData == NULL) return false;
+
+ if (presearch != NULL) {
+ cp1 = strstr(szData, presearch);
+ if (cp1 == NULL) return false;
+ }
+ else cp1 = szData;
+
+ cp = strstr(cp1, start);
+ if (cp == NULL) return false;
+ cp += strlen(start);
+ while (*cp == ' ') ++cp;
+
+ if (finish) {
+ cp1 = strstr(cp, finish);
+ if (cp1 == NULL) return FALSE;
+ while (*(cp1-1) == ' ' && cp1 > cp) --cp1;
+ }
+ else cp1 = strchr(cp, '\0');
+
+ len = min(cp1 - cp, size - 1);
+ memmove(param, cp, len);
+ param[len] = 0;
+
+ return true;
+}
+
+void parseWLID(char* wlid, char** net, char** email, char** inst)
+{
+ char *col = strchr(wlid, ':');
+ if (col && strncmp(wlid, "tel:", 4)) {
+ *col = 0;
+ if (net) *net = wlid;
+ if (email) *email = col + 1;
+ ++col;
+ }
+ else {
+ if (net) *net = NULL;
+ if (email) *email = wlid;
+ }
+
+ col = strchr(wlid, ';');
+ if (col) {
+ *col = 0;
+ if (inst) *inst = col + 1;
+ }
+ else if (inst)
+ *inst = NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// UrlDecode - converts URL chars like %20 into printable characters
+
+static int SingleHexToDecimal(char c)
+{
+ if (c >= '0' && c <= '9') return c-'0';
+ if (c >= 'a' && c <= 'f') return c-'a'+10;
+ if (c >= 'A' && c <= 'F') return c-'A'+10;
+ return -1;
+}
+
+template void UrlDecode(char* str);
+template void UrlDecode(wchar_t* str);
+
+template <class chartype> void UrlDecode(chartype* str)
+{
+ chartype* s = str, *d = str;
+
+ while(*s) {
+ if (*s == '%') {
+ int digit1 = SingleHexToDecimal(s[1]);
+ if (digit1 != -1) {
+ int digit2 = SingleHexToDecimal(s[2]);
+ if (digit2 != -1) {
+ s += 3;
+ *d++ = (char)((digit1 << 4) | digit2);
+ continue;
+ }
+ }
+ }
+ *d++ = *s++;
+ }
+
+ *d = 0;
+}
+
+void HtmlDecode(char *str)
+{
+ if (str == NULL)
+ return;
+
+ char* p, *q;
+ for (p = q = str; *p != '\0'; p++, q++) {
+ if (*p == '&') {
+ if (!strncmp(p, "&amp;", 5)) { *q = '&'; p += 4; }
+ else if (!strncmp(p, "&apos;", 6)) { *q = '\''; p += 5; }
+ else if (!strncmp(p, "&gt;", 4)) { *q = '>'; p += 3; }
+ else if (!strncmp(p, "&lt;", 4)) { *q = '<'; p += 3; }
+ else if (!strncmp(p, "&quot;", 6)) { *q = '"'; p += 5; }
+ else { *q = *p; }
+ }
+ else *q = *p;
+ }
+ *q = '\0';
+}
+
+char* HtmlEncode(const char *str)
+{
+ char* s, *p, *q;
+ int c;
+
+ if (str == NULL)
+ return NULL;
+
+ for (c=0,p=(char*)str; *p!='\0'; p++) {
+ switch (*p) {
+ case '&': c += 5; break;
+ case '\'': c += 6; break;
+ case '>': c += 4; break;
+ case '<': c += 4; break;
+ case '"': c += 6; break;
+ default: c++; break;
+ }
+ }
+
+ if ((s=(char*)mir_alloc(c+1)) != NULL) {
+ for (p=(char*)str,q=s; *p!='\0'; p++) {
+ switch (*p) {
+ case '&': strcpy(q, "&amp;"); q += 5; break;
+ case '\'': strcpy(q, "&apos;"); q += 6; break;
+ case '>': strcpy(q, "&gt;"); q += 4; break;
+ case '<': strcpy(q, "&lt;"); q += 4; break;
+ case '"': strcpy(q, "&quot;"); q += 6; break;
+ default: *q = *p; q++; break;
+ }
+ }
+ *q = '\0';
+ }
+
+ return s;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void stripBBCode(char* src)
+{
+ bool tag = false;
+ char *ps = src;
+ char *pd = src;
+
+ while (*ps != 0) {
+ if (!tag && *ps == '[') {
+ char ch = ps[1];
+ if (ch == '/') ch = ps[2];
+ tag = ch == 'b' || ch == 'u' || ch == 'i' || ch == 'c' || ch == 'a' || ch == 's';
+ }
+ if (!tag) *(pd++) = *ps;
+ else tag = *ps != ']';
+ ++ps;
+ }
+ *pd = 0;
+}
+
+void stripColorCode(char* src)
+{
+ unsigned char* ps = (unsigned char*)src;
+ unsigned char* pd = (unsigned char*)src;
+
+ while (*ps != 0) {
+ if (ps[0] == 0xc2 && ps[1] == 0xb7) {
+ char ch = ps[2];
+ switch (ch) {
+ case '#':
+ case '&':
+ case '\'':
+ case '@':
+ case '0':
+ ps += 3;
+ continue;
+
+ case '$':
+ if (isdigit(ps[3])) {
+ ps += 3;
+ if (isdigit(ps[1]))
+ ps += 2;
+ else
+ ++ps;
+
+ if (ps[0] == ',' && isdigit(ps[1])) {
+ ps += 2;
+ if (isdigit(ps[1]))
+ ps += 2;
+ else
+ ++ps;
+ }
+ continue;
+ }
+ else if (ps[3] == '#') {
+ ps += 4;
+ for (int i=0; i<6; ++i)
+ if (isxdigit(*ps)) ++ps;
+ else break;
+ continue;
+ }
+ break;
+ }
+ }
+ *(pd++) = *(ps++);
+ }
+ *pd = 0;
+}
+
+// Process a string, and double all % characters, according to chat.dll's restrictions
+// Returns a pointer to the new string (old one is not freed)
+TCHAR* EscapeChatTags(const TCHAR* pszText)
+{
+ int nChars = 0;
+ for (const TCHAR* p = pszText; (p = _tcschr(p, '%')) != NULL; p++)
+ nChars++;
+
+ if (nChars == 0)
+ return mir_tstrdup(pszText);
+
+ TCHAR *pszNewText = (TCHAR*)mir_alloc(sizeof(TCHAR)*(_tcslen(pszText) + 1 + nChars));
+ if (pszNewText == NULL)
+ return mir_tstrdup(pszText);
+
+ const TCHAR *s = pszText;
+ TCHAR *d = pszNewText;
+ while (*s) {
+ if (*s == '%')
+ *d++ = '%';
+ *d++ = *s++;
+ }
+ *d = 0;
+ return pszNewText;
+}
+
+TCHAR* UnEscapeChatTags(TCHAR* str_in)
+{
+ TCHAR *s = str_in, *d = str_in;
+ while (*s) {
+ if ((*s == '%' && s[1] == '%') || (*s == '\n' && s[1] == '\n'))
+ s++;
+ *d++ = *s++;
+ }
+ *d = 0;
+ return str_in;
+}
+
+char* getNewUuid(void)
+{
+ UUID id;
+ UuidCreate(&id);
+
+ BYTE *p;
+ UuidToStringA(&id, &p);
+ size_t len = strlen((char*)p) + 3;
+ char *result = (char*)mir_alloc(len);
+ mir_snprintf(result, len, "{%s}", p);
+ _strupr(result);
+ RpcStringFreeA(&p);
+ return result;
+}
diff --git a/protocols/MSN/src/msn_links.cpp b/protocols/MSN/src/msn_links.cpp
new file mode 100644
index 0000000000..4c3adb0a1b
--- /dev/null
+++ b/protocols/MSN/src/msn_links.cpp
@@ -0,0 +1,171 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2008-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+#include <m_addcontact.h>
+
+#include "m_assocmgr.h"
+
+static HANDLE hServiceParseLink;
+
+static MCONTACT GetContact(TCHAR *arg, TCHAR **pemail, CMsnProto *proto)
+{
+ TCHAR* email = NULL;
+ do
+ {
+ TCHAR *tok = _tcschr(arg, '&'); /* next token */
+ if (tok != NULL) *tok++ = '\0';
+
+ if (_tcsnicmp(arg, _T("contact="), 8) == 0)
+ {
+ arg += 8;
+ UrlDecode(arg);
+ email = arg;
+ }
+ arg = tok;
+ }
+ while(arg != NULL);
+
+ if (email == NULL || email[0] == '\0')
+ {
+ if (pemail) *pemail = NULL;
+ return NULL;
+ }
+ if (pemail) *pemail = email;
+ MCONTACT hContact = proto->MSN_HContactFromEmail(UTF8(email), NULL, true, true);
+ return hContact;
+}
+
+/*
+ add user: msnim:add?contact=netpassport@emailaddress.com
+ send message: msnim:chat?contact=netpassport@emailaddress.com
+ voice chat: msnim:voice?contact=netpassport@emailaddress.com
+ video chat: msnim:video?contact=netpassport@emailaddress.com
+*/
+
+static INT_PTR ServiceParseMsnimLink(WPARAM, LPARAM lParam)
+{
+ if (lParam == 0) return 1; /* sanity check */
+
+ TCHAR *arg = (TCHAR*)lParam;
+
+ /* skip leading prefix */
+ arg = _tcschr(arg, ':');
+ if (arg == NULL) return 1; /* parse failed */
+
+ for (++arg; *arg == '/'; ++arg) {}
+
+ arg = NEWTSTR_ALLOCA(arg);
+
+ if (g_Instances.getCount() == 0) return 0;
+
+ CMsnProto *proto = &g_Instances[0];
+ for (int i = 0; i < g_Instances.getCount(); ++i)
+ {
+ if (g_Instances[i].m_iStatus > ID_STATUS_OFFLINE)
+ {
+ proto = &g_Instances[i];
+ break;
+ }
+ }
+ if (proto == NULL) return 1;
+
+
+ /* add a contact to the list */
+ if(_tcsnicmp(arg, _T("add?"), 4) == 0)
+ {
+ arg += 4;
+
+ TCHAR *email;
+ MCONTACT hContact = GetContact(arg, &email, proto);
+ if (email == NULL) return 1;
+
+ /* does not yet check if email is current user */
+ if (hContact == NULL)
+ {
+ PROTOSEARCHRESULT psr = { sizeof(psr) };
+ psr.flags = PSR_TCHAR;
+ psr.nick = email;
+ psr.email = email;
+
+ ADDCONTACTSTRUCT acs = {0};
+ acs.handleType = HANDLE_SEARCHRESULT;
+ acs.szProto = proto->m_szModuleName;
+ acs.psr = &psr;
+ CallService(MS_ADDCONTACT_SHOW, 0, (LPARAM)&acs);
+ }
+ return 0;
+ }
+ /* send a message to a contact */
+ /* "voice" and "video" not yet implemented, perform same action as "chat" */
+ else if(_tcsnicmp(arg, _T("chat?"), 5) == 0)
+ {
+ arg += 5;
+
+ MCONTACT hContact = GetContact(arg, NULL, proto);
+
+ if (hContact != NULL)
+ {
+ CallService(MS_MSG_SENDMESSAGE, hContact, 0);
+ return 0;
+ }
+ }
+ else if(_tcsnicmp(arg, _T("voice?"), 6) == 0)
+ {
+ arg += 6;
+
+ MCONTACT hContact = GetContact(arg, NULL, proto);
+
+ if (hContact != NULL)
+ {
+ CallService(MS_MSG_SENDMESSAGE, hContact, 0);
+ return 0;
+ }
+ }
+ else if(_tcsnicmp(arg, _T("video?"), 6) == 0)
+ {
+ arg += 6;
+
+ MCONTACT hContact = GetContact(arg, NULL, proto);
+
+ if (hContact != NULL)
+ {
+ CallService(MS_MSG_SENDMESSAGE, hContact, 0);
+ return 0;
+ }
+ }
+ return 1; /* parse failed */
+}
+
+void MsnLinks_Init(void)
+{
+ static const char szService[] = "MSN/ParseMsnimLink";
+
+ hServiceParseLink = CreateServiceFunction(szService, ServiceParseMsnimLink);
+ AssocMgr_AddNewUrlTypeT("msnim:", TranslateT("MSN Link Protocol"), hInst, IDI_MSN, szService, 0);
+}
+
+void MsnLinks_Destroy(void)
+{
+ DestroyServiceFunction(hServiceParseLink);
+ CallService(MS_ASSOCMGR_REMOVEURLTYPE, 0, (LPARAM)"msnim:");
+}
diff --git a/protocols/MSN/src/msn_lists.cpp b/protocols/MSN/src/msn_lists.cpp
new file mode 100644
index 0000000000..57a227a22f
--- /dev/null
+++ b/protocols/MSN/src/msn_lists.cpp
@@ -0,0 +1,623 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+#include "m_smileyadd.h"
+
+void CMsnProto::Lists_Uninit(void)
+{
+ Lists_Wipe();
+}
+
+void CMsnProto::Lists_Wipe(void)
+{
+ mir_cslock lck(csLists);
+ contList.destroy();
+}
+
+bool CMsnProto::Lists_IsInList(int list, const char* email)
+{
+ mir_cslock lck(csLists);
+
+ MsnContact* p = contList.find((MsnContact*)&email);
+ bool res = p != NULL;
+ if (res && list != -1)
+ res &= ((p->list & list) == list);
+ return res;
+}
+
+MsnContact* CMsnProto::Lists_Get(const char* email)
+{
+ mir_cslock lck(csLists);
+ return contList.find((MsnContact*)&email);
+}
+
+MsnContact* CMsnProto::Lists_Get(MCONTACT hContact)
+{
+ mir_cslock lck(csLists);
+
+ for (int i = 0; i < contList.getCount(); ++i)
+ if (contList[i].hContact == hContact)
+ return &contList[i];
+
+ return NULL;
+}
+
+MsnPlace* CMsnProto::Lists_GetPlace(const char* wlid)
+{
+ mir_cslock lck(csLists);
+
+ char *szEmail, *szInst;
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, &szInst);
+
+ if (szInst == NULL)
+ szInst = (char*)sttVoidUid;
+
+ MsnPlace* pl = NULL;
+ MsnContact* p = contList.find((MsnContact*)&szEmail);
+ if (p)
+ pl = p->places.find((MsnPlace*)&szInst);
+
+ return pl;
+}
+
+MsnPlace* CMsnProto::Lists_AddPlace(const char* email, const char* id, unsigned cap1, unsigned cap2)
+{
+ mir_cslock lck(csLists);
+
+ MsnPlace* pl = NULL;
+ MsnContact* p = contList.find((MsnContact*)&email);
+ if (p) {
+ pl = p->places.find((MsnPlace*)&id);
+ if (!pl) {
+ pl = new MsnPlace;
+
+ pl->id = mir_strdup(id);
+ pl->cap1 = cap1;
+ pl->cap2 = cap2;
+ pl->p2pMsgId = 0;
+ pl->p2pPktNum = 0;
+ p->places.insert(pl);
+ }
+ }
+
+ return pl;
+}
+
+MsnContact* CMsnProto::Lists_GetNext(int& i)
+{
+ MsnContact* p = NULL;
+
+ mir_cslock lck(csLists);
+
+ while (p == NULL && ++i < contList.getCount())
+ if (contList[i].hContact)
+ p = &contList[i];
+
+ return p;
+}
+
+int CMsnProto::Lists_GetMask(const char* email)
+{
+ mir_cslock lck(csLists);
+
+ MsnContact* p = contList.find((MsnContact*)&email);
+ return p ? p->list : 0;
+}
+
+int CMsnProto::Lists_GetNetId(const char* email)
+{
+ if (email[0] == 0) return NETID_UNKNOWN;
+
+ mir_cslock lck(csLists);
+
+ MsnContact* p = contList.find((MsnContact*)&email);
+ return p ? p->netId : NETID_UNKNOWN;
+}
+
+unsigned CMsnProto::p2p_getMsgId(const char* wlid, int inc)
+{
+ mir_cslock lck(csLists);
+ MsnPlace* p = Lists_GetPlace(wlid);
+
+ unsigned res = p && p->p2pMsgId ? p->p2pMsgId : MSN_GenRandom();
+ if (p)
+ p->p2pMsgId = res + inc;
+
+ return res;
+}
+
+unsigned CMsnProto::p2p_getPktNum(const char* wlid)
+{
+ mir_cslock lck(csLists);
+
+ MsnPlace* p = Lists_GetPlace(wlid);
+ return p ? p->p2pPktNum++ : 0;
+}
+
+int CMsnProto::Lists_Add(int list, int netId, const char* email, MCONTACT hContact, const char* nick, const char* invite)
+{
+ mir_cslock lck(csLists);
+
+ MsnContact* p = contList.find((MsnContact*)&email);
+ if (p == NULL) {
+ p = new MsnContact;
+ p->list = list;
+ p->netId = netId;
+ p->email = _strlwr(mir_strdup(email));
+ p->invite = mir_strdup(invite);
+ p->nick = mir_strdup(nick);
+ p->hContact = hContact;
+ p->p2pMsgId = 0;
+ contList.insert(p);
+ }
+ else {
+ p->list |= list;
+ if (invite) replaceStr(p->invite, invite);
+ if (hContact) p->hContact = hContact;
+ if (list & LIST_FL) p->netId = netId;
+ if (p->netId == NETID_UNKNOWN && netId != NETID_UNKNOWN)
+ p->netId = netId;
+ }
+ return p->list;
+}
+
+void CMsnProto::Lists_Remove(int list, const char* email)
+{
+ mir_cslock lck(csLists);
+
+ int i = contList.getIndex((MsnContact*)&email);
+ if (i != -1) {
+ MsnContact &p = contList[i];
+ p.list &= ~list;
+ if (list & LIST_PL) { mir_free(p.invite); p.invite = NULL; }
+ if (p.list == 0 && p.hContact == NULL)
+ contList.remove(i);
+ }
+}
+
+
+void CMsnProto::Lists_Populate(void)
+{
+ MCONTACT hContact = db_find_first(m_szModuleName);
+ while (hContact) {
+ MCONTACT hNext = db_find_next(hContact, m_szModuleName);
+ char szEmail[MSN_MAX_EMAIL_LEN] = "";
+ if (db_get_static(hContact, m_szModuleName, "wlid", szEmail, sizeof(szEmail)))
+ db_get_static(hContact, m_szModuleName, "e-mail", szEmail, sizeof(szEmail));
+ if (szEmail[0]) {
+ bool localList = getByte(hContact, "LocalList", 0) != 0;
+ if (localList)
+ Lists_Add(LIST_LL, NETID_MSN, szEmail, hContact);
+ else
+ Lists_Add(0, NETID_UNKNOWN, szEmail, hContact);
+ }
+ else CallService(MS_DB_CONTACT_DELETE, hContact, 0);
+ hContact = hNext;
+ }
+}
+
+void CMsnProto::MSN_CleanupLists(void)
+{
+ for (int i = contList.getCount(); i--;) {
+ MsnContact& p = contList[i];
+ if (p.list & LIST_FL)
+ MSN_SetContactDb(p.hContact, p.email);
+
+ if (p.list & LIST_PL) {
+ if (p.list & (LIST_AL | LIST_BL))
+ MSN_AddUser(NULL, p.email, p.netId, LIST_PL + LIST_REMOVE);
+ else
+ MSN_AddAuthRequest(p.email, p.nick, p.invite);
+ }
+
+ if (p.hContact && !(p.list & (LIST_LL | LIST_FL | LIST_PL)) && p.list != LIST_RL) {
+ int count = db_event_count(p.hContact);
+ if (count) {
+ TCHAR text[256];
+ TCHAR *sze = mir_a2t(p.email);
+ mir_sntprintf(text, SIZEOF(text), TranslateT("Contact %s has been removed from the server.\nWould you like to keep it as \"Local Only\" contact to preserve history?"), sze);
+ mir_free(sze);
+
+ TCHAR title[128];
+ mir_sntprintf(title, SIZEOF(title), TranslateT("%s protocol"), m_tszUserName);
+
+ if (MessageBox(NULL, text, title, MB_YESNO | MB_ICONQUESTION | MB_SETFOREGROUND) == IDYES) {
+ MSN_AddUser(p.hContact, p.email, 0, LIST_LL);
+ setByte(p.hContact, "LocalList", 1);
+ continue;
+ }
+ }
+
+ if (!(p.list & (LIST_LL | LIST_FL))) {
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)p.hContact, 0);
+ p.hContact = NULL;
+ }
+ }
+
+ if (p.list & (LIST_LL | LIST_FL) && p.hContact) {
+ TCHAR path[MAX_PATH];
+ MSN_GetCustomSmileyFileName(p.hContact, path, SIZEOF(path), "", 0);
+ if (path[0]) {
+ SMADD_CONT cont;
+ cont.cbSize = sizeof(SMADD_CONT);
+ cont.hContact = p.hContact;
+ cont.type = 0;
+ cont.path = path;
+
+ CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, (LPARAM)&cont);
+ }
+ }
+ }
+}
+
+void CMsnProto::MSN_CreateContList(void)
+{
+ bool *used = (bool*)mir_calloc(contList.getCount()*sizeof(bool));
+
+ char cxml[8192];
+
+ size_t sz = mir_snprintf(cxml, sizeof(cxml), "<ml l=\"1\">");
+ {
+ mir_cslock lck(csLists);
+
+ for (int i = 0; i < contList.getCount(); i++) {
+ if (used[i]) continue;
+
+ const char* lastds = strchr(contList[i].email, '@');
+ bool newdom = true;
+
+ for (int j = 0; j < contList.getCount(); j++) {
+ if (used[j]) continue;
+
+ const MsnContact& C = contList[j];
+ if (C.list == LIST_RL || C.list == LIST_PL || C.list == LIST_LL) {
+ used[j] = true;
+ continue;
+ }
+
+ const char *dom = strchr(C.email, '@');
+ if (dom == NULL && lastds == NULL) {
+ if (sz == 0) sz = mir_snprintf(cxml + sz, sizeof(cxml), "<ml l=\"1\">");
+ if (newdom) {
+ sz += mir_snprintf(cxml + sz, sizeof(cxml) - sz, "<t>");
+ newdom = false;
+ }
+
+ sz += mir_snprintf(cxml + sz, sizeof(cxml) - sz, "<c n=\"%s\" l=\"%d\"/>", C.email, C.list & ~(LIST_RL | LIST_LL));
+ used[j] = true;
+ }
+ else if (dom != NULL && lastds != NULL && _stricmp(lastds, dom) == 0) {
+ if (sz == 0) sz = mir_snprintf(cxml, sizeof(cxml), "<ml l=\"1\">");
+ if (newdom) {
+ sz += mir_snprintf(cxml + sz, sizeof(cxml) - sz, "<d n=\"%s\">", lastds + 1);
+ newdom = false;
+ }
+
+ *(char*)dom = 0;
+ sz += mir_snprintf(cxml + sz, sizeof(cxml) - sz, "<c n=\"%s\" l=\"%d\" t=\"%d\"/>", C.email, C.list & ~(LIST_RL | LIST_LL), C.netId);
+ *(char*)dom = '@';
+ used[j] = true;
+ }
+
+ if (used[j] && sz > 7400) {
+ sz += mir_snprintf(cxml + sz, sizeof(cxml) - sz, "</%c></ml>", lastds ? 'd' : 't');
+ msnNsThread->sendPacket("ADL", "%d\r\n%s", sz, cxml);
+ sz = 0;
+ newdom = true;
+ }
+ }
+ if (!newdom)
+ sz += mir_snprintf(cxml + sz, sizeof(cxml) - sz, lastds ? "</d>" : "</t>");
+ }
+ }
+
+ if (sz) {
+ sz += mir_snprintf(cxml + sz, sizeof(cxml) - sz, "</ml>");
+ msnNsThread->sendPacket("ADL", "%d\r\n%s", sz, cxml);
+ }
+
+ mir_free(used);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN Server List Manager dialog procedure
+
+static void AddPrivacyListEntries(HWND hwndList, CMsnProto *proto)
+{
+ CLCINFOITEM cii = { 0 };
+ cii.cbSize = sizeof(cii);
+ cii.flags = CLCIIF_BELOWCONTACTS;
+
+ // Delete old info
+ HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_ROOT, 0);
+ while (hItem) {
+ HANDLE hItemNext = (HANDLE)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXT, (LPARAM)hItem);
+
+ if (IsHContactInfo(hItem))
+ SendMessage(hwndList, CLM_DELETEITEM, (WPARAM)hItem, 0);
+
+ hItem = hItemNext;
+ }
+
+ // Add new info
+ for (int i = 0; i < proto->contList.getCount(); ++i) {
+ MsnContact &cont = proto->contList[i];
+ if (!(cont.list & (LIST_FL | LIST_LL))) {
+ cii.pszText = (TCHAR*)cont.email;
+ HANDLE hItem = (HANDLE)SendMessage(hwndList, CLM_ADDINFOITEMA, 0, (LPARAM)&cii);
+
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(0, (cont.list & LIST_LL) ? 1 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(1, (cont.list & LIST_FL) ? 2 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(2, (cont.list & LIST_AL) ? 3 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(3, (cont.list & LIST_BL) ? 4 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(4, (cont.list & LIST_RL) ? 5 : 0));
+ }
+ }
+}
+
+static void SetContactIcons(MCONTACT hItem, HWND hwndList, CMsnProto* proto)
+{
+ if (!proto->MSN_IsMyContact(hItem)) {
+ SendMessage(hwndList, CLM_DELETEITEM, (WPARAM)hItem, 0);
+ return;
+ }
+
+ char szEmail[MSN_MAX_EMAIL_LEN];
+ if (db_get_static(hItem, proto->m_szModuleName, "e-mail", szEmail, sizeof(szEmail))) {
+ SendMessage(hwndList, CLM_DELETEITEM, (WPARAM)hItem, 0);
+ return;
+ }
+
+ DWORD dwMask = proto->Lists_GetMask(szEmail);
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(0, (dwMask & LIST_LL) ? 1 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(1, (dwMask & LIST_FL) ? 2 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(2, (dwMask & LIST_AL) ? 3 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(3, (dwMask & LIST_BL) ? 4 : 0));
+ SendMessage(hwndList, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(4, (dwMask & LIST_RL) ? 5 : 0));
+}
+
+static void SetAllContactIcons(MCONTACT hItem, HWND hwndList, CMsnProto* proto)
+{
+ if (hItem == NULL)
+ hItem = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_ROOT, 0);
+
+ while (hItem) {
+ MCONTACT hItemN = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXT, (LPARAM)hItem);
+
+ if (IsHContactGroup(hItem)) {
+ MCONTACT hItemT = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
+ if (hItemT)
+ SetAllContactIcons(hItemT, hwndList, proto);
+ }
+ else if (IsHContactContact(hItem))
+ SetContactIcons(hItem, hwndList, proto);
+
+ hItem = hItemN;
+ }
+}
+
+static void SaveListItem(MCONTACT hContact, const char* szEmail, int list, int iPrevValue, int iNewValue, CMsnProto* proto)
+{
+ if (iPrevValue == iNewValue)
+ return;
+
+ if (iNewValue == 0) {
+ if (list & LIST_FL) {
+ DeleteParam param = { proto, hContact };
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_DELETECONTACT), NULL, DlgDeleteContactUI, (LPARAM)&param);
+ return;
+ }
+
+ list |= LIST_REMOVE;
+ }
+
+ proto->MSN_AddUser(hContact, szEmail, proto->Lists_GetNetId(szEmail), list);
+}
+
+static void SaveSettings(MCONTACT hItem, HWND hwndList, CMsnProto* proto)
+{
+ if (hItem == NULL)
+ hItem = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_ROOT, 0);
+
+ while (hItem) {
+ if (IsHContactGroup(hItem)) {
+ MCONTACT hItemT = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_CHILD, (LPARAM)hItem);
+ if (hItemT)
+ SaveSettings(hItemT, hwndList, proto);
+ }
+ else {
+ char szEmail[MSN_MAX_EMAIL_LEN];
+
+ if (IsHContactContact(hItem)) {
+ if (db_get_static(hItem, proto->m_szModuleName, "e-mail", szEmail, sizeof(szEmail)))
+ continue;
+ }
+ else if (IsHContactInfo(hItem)) {
+ TCHAR buf[MSN_MAX_EMAIL_LEN];
+ SendMessage(hwndList, CLM_GETITEMTEXT, (WPARAM)hItem, (LPARAM)buf);
+ WideCharToMultiByte(CP_ACP, 0, buf, -1, szEmail, sizeof(szEmail), 0, 0);
+
+ }
+
+ int dwMask = proto->Lists_GetMask(szEmail);
+ SaveListItem(hItem, szEmail, LIST_LL, (dwMask & LIST_LL) ? 1 : 0, SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(0, 0)), proto);
+ SaveListItem(hItem, szEmail, LIST_FL, (dwMask & LIST_FL) ? 2 : 0, SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(1, 0)), proto);
+ SaveListItem(hItem, szEmail, LIST_AL, (dwMask & LIST_AL) ? 3 : 0, SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(2, 0)), proto);
+ SaveListItem(hItem, szEmail, LIST_BL, (dwMask & LIST_BL) ? 4 : 0, SendMessage(hwndList, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(3, 0)), proto);
+
+ int newMask = proto->Lists_GetMask(szEmail);
+ int xorMask = newMask ^ dwMask;
+
+ if (xorMask && newMask & (LIST_FL | LIST_LL)) {
+ MCONTACT hContact = IsHContactInfo(hItem) ? proto->MSN_HContactFromEmail(szEmail, szEmail, true, false) : hItem;
+ proto->MSN_SetContactDb(hContact, szEmail);
+ }
+
+ if (xorMask & (LIST_FL | LIST_LL) && !(newMask & (LIST_FL | LIST_LL))) {
+ if (!IsHContactInfo(hItem)) {
+ CallService(MS_DB_CONTACT_DELETE, (WPARAM)hItem, 0);
+ MsnContact* msc = proto->Lists_Get(szEmail);
+ if (msc) msc->hContact = NULL;
+ }
+ }
+ }
+ hItem = (MCONTACT)SendMessage(hwndList, CLM_GETNEXTITEM, CLGN_NEXT, (LPARAM)hItem);
+ }
+}
+
+INT_PTR CALLBACK DlgProcMsnServLists(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ CMsnProto *proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ HIMAGELIST hIml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_MASK | ILC_COLOR32, 5, 5);
+
+ HICON hIcon = LoadSkinnedIcon(SKINICON_OTHER_SMALLDOT);
+ ImageList_AddIcon(hIml, hIcon);
+ Skin_ReleaseIcon(hIcon);
+
+ hIcon = LoadIconEx("list_lc");
+ ImageList_AddIcon(hIml, hIcon);
+ SendDlgItemMessage(hwndDlg, IDC_ICON_LC, STM_SETICON, (WPARAM)hIcon, 0);
+
+ hIcon = LoadIconEx("list_fl");
+ ImageList_AddIcon(hIml, hIcon);
+ SendDlgItemMessage(hwndDlg, IDC_ICON_FL, STM_SETICON, (WPARAM)hIcon, 0);
+
+ hIcon = LoadIconEx("list_al");
+ ImageList_AddIcon(hIml, hIcon);
+ SendDlgItemMessage(hwndDlg, IDC_ICON_AL, STM_SETICON, (WPARAM)hIcon, 0);
+
+ hIcon = LoadIconEx("list_bl");
+ ImageList_AddIcon(hIml, hIcon);
+ SendDlgItemMessage(hwndDlg, IDC_ICON_BL, STM_SETICON, (WPARAM)hIcon, 0);
+
+ hIcon = LoadIconEx("list_rl");
+ ImageList_AddIcon(hIml, hIcon);
+ SendDlgItemMessage(hwndDlg, IDC_ICON_RL, STM_SETICON, (WPARAM)hIcon, 0);
+
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST);
+
+ SendMessage(hwndList, CLM_SETEXTRAIMAGELIST, 0, (LPARAM)hIml);
+ SendMessage(hwndList, CLM_SETEXTRACOLUMNS, 5, 0);
+
+ EnableWindow(hwndList, ((CMsnProto*)lParam)->msnLoggedIn);
+ }
+ return TRUE;
+
+// case WM_SETFOCUS:
+// SetFocus(GetDlgItem(hwndDlg ,IDC_LIST));
+// break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_LISTREFRESH)
+ {
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST);
+ SendMessage(hwndList, CLM_AUTOREBUILD, 0, 0);
+
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ EnableWindow(hwndList, proto->msnLoggedIn);
+ }
+ break;
+
+ case WM_NOTIFY:
+ {
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ NMCLISTCONTROL* nmc = (NMCLISTCONTROL*)lParam;
+ if (nmc->hdr.idFrom == 0 && nmc->hdr.code == (unsigned)PSN_APPLY)
+ {
+ HWND hwndList = GetDlgItem(hwndDlg, IDC_LIST);
+ SaveSettings(NULL, hwndList, proto);
+ SendMessage(hwndList, CLM_AUTOREBUILD, 0, 0);
+ EnableWindow(hwndList, proto->msnLoggedIn);
+ }
+ else if (nmc->hdr.idFrom == IDC_LIST)
+ {
+ switch (nmc->hdr.code)
+ {
+ case CLN_NEWCONTACT:
+ if ((nmc->flags & (CLNF_ISGROUP | CLNF_ISINFO)) == 0)
+ SetContactIcons((MCONTACT)nmc->hItem, nmc->hdr.hwndFrom, proto);
+ break;
+
+ case CLN_LISTREBUILT:
+ AddPrivacyListEntries(nmc->hdr.hwndFrom, proto);
+ SetAllContactIcons(NULL, nmc->hdr.hwndFrom, proto);
+ break;
+
+ case NM_CLICK:
+ HANDLE hItem;
+ DWORD hitFlags;
+ int iImage;
+
+ // Make sure we have an extra column, also we can't change RL list
+ if (nmc->iColumn == -1 || nmc->iColumn == 4)
+ break;
+
+ // Find clicked item
+ hItem = (HANDLE)SendMessage(nmc->hdr.hwndFrom, CLM_HITTEST, (WPARAM)&hitFlags, MAKELPARAM(nmc->pt.x,nmc->pt.y));
+
+ // Nothing was clicked
+ if (hItem == NULL || !(IsHContactContact(hItem) || IsHContactInfo(hItem)))
+ break;
+
+ // It was not our extended icon
+ if (!(hitFlags & CLCHT_ONITEMEXTRA))
+ break;
+
+ // Get image in clicked column (0=none, 1=LL, 2=FL, 3=AL, 4=BL, 5=RL)
+ iImage = SendMessage(nmc->hdr.hwndFrom, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nmc->iColumn, 0));
+ iImage = iImage ? 0 : nmc->iColumn + 1;
+
+ SendMessage(nmc->hdr.hwndFrom, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nmc->iColumn, iImage));
+ if (iImage && SendMessage(nmc->hdr.hwndFrom, CLM_GETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nmc->iColumn ^ 1, 0)) != EMPTY_EXTRA_ICON)
+ if (nmc->iColumn == 2 || nmc->iColumn == 3)
+ SendMessage(nmc->hdr.hwndFrom, CLM_SETEXTRAIMAGE, (WPARAM)hItem, MAKELPARAM(nmc->iColumn ^ 1, 0));
+
+ // Activate Apply button
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ }
+ }
+ break;
+
+ case WM_DESTROY:
+ HIMAGELIST hIml=(HIMAGELIST)SendDlgItemMessage(hwndDlg,IDC_LIST,CLM_GETEXTRAIMAGELIST,0,0);
+ ImageList_Destroy(hIml);
+ ReleaseIconEx("list_fl");
+ ReleaseIconEx("list_al");
+ ReleaseIconEx("list_bl");
+ ReleaseIconEx("list_rl");
+ break;
+ }
+
+ return FALSE;
+}
diff --git a/protocols/MSN/src/msn_mail.cpp b/protocols/MSN/src/msn_mail.cpp
new file mode 100644
index 0000000000..407375bf17
--- /dev/null
+++ b/protocols/MSN/src/msn_mail.cpp
@@ -0,0 +1,424 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2007-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+static const char oimRecvUrl[] = "https://rsi.hotmail.com/rsi/rsi.asmx";
+static const char mailReqHdr[] =
+ "SOAPAction: \"http://www.hotmail.msn.com/ws/2004/09/oim/rsi/%s\"\r\n";
+
+ezxml_t CMsnProto::oimRecvHdr(const char* service, ezxml_t& tbdy, char*& httphdr)
+{
+ ezxml_t xmlp = ezxml_new("soap:Envelope");
+ ezxml_set_attr(xmlp, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ ezxml_set_attr(xmlp, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
+ ezxml_set_attr(xmlp, "xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/");
+
+ ezxml_t hdr = ezxml_add_child(xmlp, "soap:Header", 0);
+ ezxml_t cook = ezxml_add_child(hdr, "PassportCookie", 0);
+ ezxml_set_attr(cook, "xmlns", "http://www.hotmail.msn.com/ws/2004/09/oim/rsi");
+ ezxml_t tcook = ezxml_add_child(cook, "t", 0);
+ ezxml_set_txt(tcook, tAuthToken ? tAuthToken : "");
+ ezxml_t pcook = ezxml_add_child(cook, "p", 0);
+ ezxml_set_txt(pcook, pAuthToken ? pAuthToken : "");
+
+ ezxml_t bdy = ezxml_add_child(xmlp, "soap:Body", 0);
+
+ tbdy = ezxml_add_child(bdy, service, 0);
+ ezxml_set_attr(tbdy, "xmlns", "http://www.hotmail.msn.com/ws/2004/09/oim/rsi");
+
+ size_t hdrsz = strlen(service) + sizeof(mailReqHdr) + 20;
+ httphdr = (char*)mir_alloc(hdrsz);
+
+ mir_snprintf(httphdr, hdrsz, mailReqHdr, service);
+
+ return xmlp;
+}
+
+
+void CMsnProto::getOIMs(ezxml_t xmli)
+{
+ ezxml_t toki = ezxml_child(xmli, "M");
+ if (toki == NULL) return;
+
+ char* getReqHdr;
+ ezxml_t reqmsg;
+ ezxml_t xmlreq = oimRecvHdr("GetMessage", reqmsg, getReqHdr);
+
+ ezxml_t reqmid = ezxml_add_child(reqmsg, "messageId", 0);
+ ezxml_t reqmrk = ezxml_add_child(reqmsg, "alsoMarkAsRead", 0);
+ ezxml_set_txt(reqmrk, "false");
+
+ char* delReqHdr;
+ ezxml_t delmsg;
+ ezxml_t xmldel = oimRecvHdr("DeleteMessages", delmsg, delReqHdr);
+ ezxml_t delmids = ezxml_add_child(delmsg, "messageIds", 0);
+
+ while (toki != NULL)
+ {
+ const char* szId = ezxml_txt(ezxml_child(toki, "I"));
+ const char* szEmail = ezxml_txt(ezxml_child(toki, "E"));
+
+ ezxml_set_txt(reqmid, szId);
+ char* szData = ezxml_toxml(xmlreq, true);
+
+ unsigned status;
+ char* url = (char*)mir_strdup(oimRecvUrl);
+
+ char* tResult = getSslResult(&url, szData, getReqHdr, status);
+
+ free(szData);
+ mir_free(url);
+
+ if (tResult != NULL && status == 200)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ ezxml_t body = getSoapResponse(xmlm, "GetMessage");
+
+ MimeHeaders mailInfo;
+ const char* mailbody = mailInfo.readFromBuffer((char*)ezxml_txt(body));
+
+ time_t evtm = time(NULL);
+ const char* arrTime = mailInfo["X-OriginalArrivalTime"];
+ if (arrTime != NULL)
+ {
+ char szTime[32], *p;
+ txtParseParam(arrTime, "FILETIME", "[", "]", szTime, sizeof(szTime));
+
+ unsigned filetimeLo = strtoul(szTime, &p, 16);
+ if (*p == ':')
+ {
+ unsigned __int64 filetime = strtoul(p+1, &p, 16);
+ filetime <<= 32;
+ filetime |= filetimeLo;
+ filetime /= 10000000;
+#ifndef __GNUC__
+ filetime -= 11644473600ui64;
+#else
+ filetime -= 11644473600ull;
+#endif
+ evtm = (time_t)filetime;
+ }
+ }
+
+ PROTORECVEVENT pre = {0};
+ pre.szMessage = mailInfo.decodeMailBody((char*)mailbody);
+ pre.flags = PREF_UTF /*+ ((isRtl) ? PREF_RTL : 0)*/;
+ pre.timestamp = evtm;
+ ProtoChainRecvMsg( MSN_HContactFromEmail(szEmail), &pre);
+ mir_free(pre.szMessage);
+
+ ezxml_t delmid = ezxml_add_child(delmids, "messageId", 0);
+ ezxml_set_txt(delmid, szId);
+
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ toki = ezxml_next(toki);
+ }
+ ezxml_free(xmlreq);
+ mir_free(getReqHdr);
+
+ if (ezxml_child(delmids, "messageId") != NULL)
+ {
+ char* szData = ezxml_toxml(xmldel, true);
+
+ unsigned status;
+ char* url = (char*)mir_strdup(oimRecvUrl);
+
+ char* tResult = getSslResult(&url, szData, delReqHdr, status);
+
+ mir_free(url);
+ mir_free(tResult);
+ free(szData);
+ }
+ ezxml_free(xmldel);
+ mir_free(delReqHdr);
+}
+
+
+void CMsnProto::getMetaData(void)
+{
+ char* getReqHdr;
+ ezxml_t reqbdy;
+ ezxml_t xmlreq = oimRecvHdr("GetMetadata", reqbdy, getReqHdr);
+
+ char* szData = ezxml_toxml(xmlreq, true);
+ ezxml_free(xmlreq);
+
+ unsigned status;
+ char* url = (char*)mir_strdup(oimRecvUrl);
+
+ char* tResult = getSslResult(&url, szData, getReqHdr, status);
+
+ mir_free(url);
+ free(szData);
+ mir_free(getReqHdr);
+
+ if (tResult != NULL && status == 200)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ ezxml_t xmli = ezxml_get(xmlm, "s:Body", 0, "GetMetadataResponse", 0, "MD", -1);
+ if (!xmli)
+ xmli = ezxml_get(xmlm, "soap:Body", 0, "GetMetadataResponse", 0, "MD", -1);
+
+ getOIMs(xmli);
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+}
+
+
+void CMsnProto::processMailData(char* mailData)
+{
+ if (strcmp(mailData, "too-large") == 0)
+ {
+ getMetaData();
+ }
+ else
+ {
+ ezxml_t xmli = ezxml_parse_str(mailData, strlen(mailData));
+
+ ezxml_t toke = ezxml_child(xmli, "E");
+
+ const char* szIU = ezxml_txt(ezxml_child(toke, "IU"));
+ if (*szIU) mUnreadMessages = atol(szIU);
+
+ const char* szOU = ezxml_txt(ezxml_child(toke, "OU"));
+ if (*szOU) mUnreadJunkEmails = atol(szOU);
+
+ getOIMs(xmli);
+
+ ezxml_free(xmli);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Processes e-mail notification
+
+void CMsnProto::sttNotificationMessage(char* msgBody, bool isInitial)
+{
+ TCHAR tBuffer[512];
+ TCHAR tBuffer2[512];
+ int UnreadMessages = mUnreadMessages;
+ int UnreadJunkEmails = mUnreadJunkEmails;
+ bool ShowPopup = isInitial;
+
+ MimeHeaders tFileInfo;
+ tFileInfo.readFromBuffer(msgBody);
+
+ const char* From = tFileInfo["From"];
+ const char* Subject = tFileInfo["Subject"];
+ const char* Fromaddr = tFileInfo["From-Addr"];
+ const char* MsgDelta = tFileInfo["Message-Delta"];
+ const char* SrcFolder = tFileInfo["Src-Folder"];
+ const char* DestFolder = tFileInfo["Dest-Folder"];
+ const char* InboxUnread = tFileInfo["Inbox-Unread"];
+ const char* FoldersUnread = tFileInfo["Folders-Unread"];
+
+ if (InboxUnread != NULL)
+ mUnreadMessages = atol(InboxUnread);
+ if (FoldersUnread != NULL)
+ mUnreadJunkEmails = atol(FoldersUnread);
+
+ if (MsgDelta != NULL)
+ {
+ int iDelta = atol(MsgDelta);
+ if (SrcFolder && strcmp(SrcFolder, "ACTIVE") == 0)
+ mUnreadMessages -= iDelta;
+ else if (DestFolder && strcmp(DestFolder, "ACTIVE") == 0)
+ mUnreadMessages += iDelta;
+ if (SrcFolder && strcmp(SrcFolder, "HM_BuLkMail_") == 0)
+ mUnreadJunkEmails -= iDelta;
+ else if (DestFolder && strcmp(DestFolder, "HM_BuLkMail_") == 0)
+ mUnreadJunkEmails += iDelta;
+
+ if (mUnreadJunkEmails < 0) mUnreadJunkEmails = 0;
+ if (mUnreadMessages < 0) mUnreadMessages = 0;
+ }
+
+ if (From != NULL && Subject != NULL && Fromaddr != NULL)
+ {
+ if (DestFolder != NULL && SrcFolder == NULL)
+ {
+ mUnreadMessages += strcmp(DestFolder, "ACTIVE") == 0;
+ mUnreadJunkEmails += strcmp(DestFolder, "HM_BuLkMail_") == 0;
+ }
+
+ wchar_t* mimeFromW = tFileInfo.decode(From);
+ wchar_t* mimeSubjectW = tFileInfo.decode(Subject);
+
+
+ mir_sntprintf(tBuffer2, SIZEOF(tBuffer2), TranslateT("Subject: %s"), mimeSubjectW);
+
+
+
+ TCHAR* msgtxt = _stricmp(From, Fromaddr) ?
+ TranslateT("Hotmail from %s (%S)") : TranslateT("Hotmail from %s");
+
+ mir_sntprintf(tBuffer, SIZEOF(tBuffer), msgtxt, mimeFromW, Fromaddr);
+ mir_free(mimeFromW);
+ mir_free(mimeSubjectW);
+ ShowPopup = true;
+ }
+ else
+ {
+ const char* MailData = tFileInfo["Mail-Data"];
+ if (MailData != NULL) processMailData((char*)MailData);
+
+ mir_sntprintf(tBuffer, SIZEOF(tBuffer), m_tszUserName);
+ mir_sntprintf(tBuffer2, SIZEOF(tBuffer2), TranslateT("Unread mail is available: %d in Inbox and %d in other folders."), mUnreadMessages, mUnreadJunkEmails);
+ }
+
+ if (UnreadMessages == mUnreadMessages && UnreadJunkEmails == mUnreadJunkEmails && !isInitial)
+ return;
+
+ ShowPopup &= mUnreadMessages != 0 || (mUnreadJunkEmails != 0 && !getByte("DisableHotmailJunk", 0));
+
+ MCONTACT hContact = MSN_HContactFromEmail(MyOptions.szEmail);
+ if (hContact)
+ {
+ CallService(MS_CLIST_REMOVEEVENT, hContact, (LPARAM) 1);
+ displayEmailCount(hContact);
+
+ if (ShowPopup && !getByte("DisableHotmailTray", 1))
+ {
+ CLISTEVENT cle = {0};
+
+ cle.cbSize = sizeof(cle);
+ cle.hContact = hContact;
+ cle.hDbEvent = (HANDLE) 1;
+ cle.flags = CLEF_URGENT | CLEF_TCHAR;
+ cle.hIcon = LoadSkinnedIcon(SKINICON_OTHER_SENDEMAIL);
+ cle.ptszTooltip = tBuffer2;
+ char buf[64];
+ mir_snprintf(buf, SIZEOF(buf), "%s%s", m_szModuleName, MS_GOTO_INBOX);
+ cle.pszService = buf;
+
+ CallService(MS_CLIST_ADDEVENT, hContact, (LPARAM)&cle);
+ }
+ }
+
+ ProtoBroadcastAck(NULL, ACKTYPE_EMAIL, ACKRESULT_STATUS, NULL, 0);
+
+ // Disable to notify receiving hotmail
+ if (ShowPopup && !getByte("DisableHotmail", 0))
+ {
+ SkinPlaySound(mailsoundname);
+
+ const char *msgurl = tFileInfo["Message-URL"];
+ if (msgurl)
+ {
+ const char *p = strchr(msgurl, '&'); if (p) *(char*)p = 0;
+ p = strstr(msgurl, "getmsg"); if (p) msgurl = p;
+ }
+ else
+ msgurl = "inbox";
+
+ char szUrl[256];
+ mir_snprintf(szUrl, sizeof(szUrl), "http://mail.live.com?rru=%s", msgurl);
+
+ MSN_ShowPopup(tBuffer, tBuffer2,
+ MSN_ALLOW_ENTER | MSN_ALLOW_MSGBOX | MSN_HOTMAIL_POPUP,
+ szUrl);
+ }
+
+ if (!getByte("RunMailerOnHotmail", 0) || !ShowPopup || isInitial)
+ return;
+
+ char mailerpath[MAX_PATH];
+ if (!db_get_static(NULL, m_szModuleName, "MailerPath", mailerpath, sizeof(mailerpath)))
+ {
+ if (mailerpath[0])
+ {
+ char* tParams = NULL;
+ char* tCmd = mailerpath;
+
+ if (*tCmd == '\"')
+ {
+ ++tCmd;
+ char* tEndPtr = strchr(tCmd, '\"');
+ if (tEndPtr != NULL)
+ {
+ *tEndPtr = 0;
+ tParams = tEndPtr+1;
+ }
+ }
+
+ if (tParams == NULL)
+ {
+ tParams = strchr(tCmd, ' ');
+ tParams = tParams ? tParams + 1 : strchr(tCmd, '\0');
+ }
+
+ while (*tParams == ' ') ++tParams;
+
+ debugLogA("Running mailer \"%s\" with params \"%s\"", tCmd, tParams);
+ ShellExecuteA(NULL, "open", tCmd, tParams, NULL, TRUE);
+ }
+ }
+}
+
+static void TruncUtf8(char *str, size_t sz)
+{
+ size_t len = strlen(str);
+ if (sz > len) sz = len;
+
+ size_t cntl = 0, cnt = 0;
+ for (;;)
+ {
+ unsigned char p = (unsigned char)str[cnt];
+
+ if (p >= 0xE0) cnt += 3;
+ else if (p >= 0xC0) cnt += 2;
+ else if (p != 0) ++cnt;
+ else break;
+
+ if (cnt <= sz) cntl = cnt;
+ else break;
+ }
+ str[cntl] = 0;
+}
+
+void CMsnProto::displayEmailCount(MCONTACT hContact)
+{
+ if (!emailEnabled || getByte("DisableHotmailCL", 0)) return;
+
+ TCHAR* name = GetContactNameT(hContact);
+ if (name == NULL) return;
+
+ TCHAR* ch = name-1;
+ do
+ {
+ ch = _tcschr(ch+1, '[');
+ }
+ while (ch && !_istdigit(ch[1]));
+ if (ch) *ch = 0;
+ rtrimt(name);
+
+ TCHAR szNick[128];
+ mir_sntprintf(szNick, SIZEOF(szNick),
+ getByte("DisableHotmailJunk", 0) ? _T("%s [%d]") : _T("%s [%d][%d]"), name, mUnreadMessages, mUnreadJunkEmails);
+
+ nickChg = true;
+ db_set_ts(hContact, "CList", "MyHandle", szNick);
+ nickChg = false;
+}
diff --git a/protocols/MSN/src/msn_menu.cpp b/protocols/MSN/src/msn_menu.cpp
new file mode 100644
index 0000000000..84ef664f0f
--- /dev/null
+++ b/protocols/MSN/src/msn_menu.cpp
@@ -0,0 +1,460 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+static HGENMENU hBlockMenuItem, hLiveSpaceMenuItem, hNetmeetingMenuItem, hChatInviteMenuItem, hOpenInboxMenuItem;
+
+HANDLE hNetMeeting, hBlockCom, hSendHotMail, hInviteChat, hViewProfile;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Block command callback function
+
+INT_PTR CMsnProto::MsnBlockCommand(WPARAM hContact, LPARAM)
+{
+ if (msnLoggedIn) {
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ db_get_static(hContact, m_szModuleName, "e-mail", tEmail, sizeof(tEmail));
+
+ if (Lists_IsInList(LIST_BL, tEmail))
+ delSetting(hContact, "ApparentMode");
+ else
+ setWord(hContact, "ApparentMode", ID_STATUS_OFFLINE);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGotoInbox - goes to the Inbox folder at the live.com
+
+INT_PTR CMsnProto::MsnGotoInbox(WPARAM, LPARAM)
+{
+ MCONTACT hContact = MSN_HContactFromEmail(MyOptions.szEmail);
+ if (hContact) CallService(MS_CLIST_REMOVEEVENT, hContact, (LPARAM) 1);
+
+ MsnInvokeMyURL(true, "http://mail.live.com?rru=inbox");
+ return 0;
+}
+
+INT_PTR CMsnProto::MsnSendHotmail(WPARAM hContact, LPARAM)
+{
+ char szEmail[MSN_MAX_EMAIL_LEN];
+ if (MSN_IsMeByContact(hContact, szEmail))
+ MsnGotoInbox(0, 0);
+ else if (msnLoggedIn)
+ MsnInvokeMyURL(true, CMStringA().Format("http://mail.live.com?rru=compose?to=%s", ptrA(mir_urlEncode(szEmail))));
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSetupAlerts - goes to the alerts section at the live.com
+
+INT_PTR CMsnProto::MsnSetupAlerts(WPARAM, LPARAM)
+{
+ MsnInvokeMyURL(false, "http://alerts.live.com");
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnViewProfile - view a contact's profile
+
+INT_PTR CMsnProto::MsnViewProfile(WPARAM hContact, LPARAM)
+{
+ char buf[64], *cid;
+
+ if (hContact == NULL)
+ cid = mycid;
+ else {
+ cid = buf;
+ if (db_get_static(hContact, m_szModuleName, "CID", buf, 30))
+ return 0;
+ }
+
+ char tUrl[256];
+ mir_snprintf(tUrl, sizeof(tUrl), "http://cid-%I64X.profiles.live.com", _atoi64(cid));
+ MsnInvokeMyURL(false, tUrl);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnEditProfile - goes to the Profile section at the live.com
+
+INT_PTR CMsnProto::MsnEditProfile(WPARAM, LPARAM)
+{
+ MsnViewProfile(0, 0);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnInviteCommand - invite command callback function
+
+INT_PTR CMsnProto::MsnInviteCommand(WPARAM, LPARAM)
+{
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_CHATROOM_INVITE), NULL, DlgInviteToChat,
+ LPARAM(new InviteChatParam(NULL, NULL, this)));
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnRebuildContactMenu - gray or ungray the block menus according to contact's status
+
+int CMsnProto::OnPrebuildContactMenu(WPARAM hContact, LPARAM)
+{
+ if ( !MSN_IsMyContact(hContact))
+ return 0;
+
+ char szEmail[MSN_MAX_EMAIL_LEN];
+ bool isMe = MSN_IsMeByContact(hContact, szEmail);
+ if (szEmail[0]) {
+ int listId = Lists_GetMask(szEmail);
+ bool noChat = !(listId & LIST_FL) || isMe || isChatRoom(hContact);
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.flags = CMIM_NAME;
+ mi.pszName = ((listId & LIST_BL) ? LPGEN("&Unblock") : LPGEN("&Block"));
+ Menu_ModifyItem(hBlockMenuItem, &mi);
+ Menu_ShowItem(hBlockMenuItem, !noChat);
+
+ mi.pszName = isMe ? LPGEN("Open &Hotmail Inbox") : LPGEN("Send &Hotmail E-mail");
+ Menu_ModifyItem(hOpenInboxMenuItem, &mi);
+ Menu_ShowItem(hOpenInboxMenuItem, emailEnabled);
+
+ Menu_ShowItem(hNetmeetingMenuItem, !noChat);
+ Menu_ShowItem(hChatInviteMenuItem, !noChat);
+ }
+
+ return 0;
+}
+
+int CMsnProto::OnContactDoubleClicked(WPARAM hContact, LPARAM)
+{
+ if (emailEnabled && MSN_IsMeByContact(hContact)) {
+ MsnSendHotmail(hContact, 0);
+ return 1;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSendNetMeeting - Netmeeting callback function
+
+INT_PTR CMsnProto::MsnSendNetMeeting(WPARAM wParam, LPARAM)
+{
+ if (!msnLoggedIn) return 0;
+
+ MCONTACT hContact = MCONTACT(wParam);
+
+ char szEmail[MSN_MAX_EMAIL_LEN];
+ if (MSN_IsMeByContact(hContact, szEmail)) return 0;
+
+ ThreadData* thread = MSN_GetThreadByContact(szEmail);
+
+ if (thread == NULL) {
+ MessageBox(NULL, TranslateT("You must be talking to start Netmeeting"), TranslateT("MSN Protocol"), MB_OK | MB_ICONERROR);
+ return 0;
+ }
+
+ char msg[1024];
+
+ mir_snprintf(msg, sizeof(msg),
+ "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n\r\n"
+ "Application-Name: NetMeeting\r\n"
+ "Application-GUID: {44BBA842-CC51-11CF-AAFA-00AA00B6015C}\r\n"
+ "Session-Protocol: SM1\r\n"
+ "Invitation-Command: INVITE\r\n"
+ "Invitation-Cookie: %i\r\n"
+ "Session-ID: {1A879604-D1B8-11D7-9066-0003FF431510}\r\n\r\n",
+ MSN_GenRandom());
+
+ thread->sendMessage('N', NULL, 1, msg, MSG_DISABLE_HDR);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// SetNicknameCommand - sets nick name
+
+static INT_PTR CALLBACK DlgProcSetNickname(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ TranslateDialogDefault(hwndDlg);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ CMsnProto* proto = (CMsnProto*)lParam;
+
+ SendMessage(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)LoadIconEx("main", true));
+ SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)LoadIconEx("main"));
+ SendMessage(GetDlgItem(hwndDlg, IDC_NICKNAME), EM_LIMITTEXT, 129, 0);
+
+ DBVARIANT dbv;
+ if (!proto->getTString("Nick", &dbv)) {
+ SetDlgItemText(hwndDlg, IDC_NICKNAME, dbv.ptszVal);
+ db_free(&dbv);
+ }
+ return TRUE;
+ }
+ case WM_COMMAND:
+ switch(wParam)
+ {
+ case IDOK:
+ {
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ if (proto->msnLoggedIn)
+ {
+ TCHAR str[130];
+ GetDlgItemText(hwndDlg, IDC_NICKNAME, str, SIZEOF(str));
+ proto->MSN_SendNickname(str);
+ }
+ }
+
+ case IDCANCEL:
+ DestroyWindow(hwndDlg);
+ break;
+ }
+ break;
+
+ case WM_CLOSE:
+ DestroyWindow(hwndDlg);
+ break;
+
+ case WM_DESTROY:
+ ReleaseIconEx("main");
+ ReleaseIconEx("main", true);
+ break;
+ }
+ return FALSE;
+}
+
+INT_PTR CMsnProto::SetNicknameUI(WPARAM, LPARAM)
+{
+ HWND hwndSetNickname = CreateDialogParam (hInst, MAKEINTRESOURCE(IDD_SETNICKNAME),
+ NULL, DlgProcSetNickname, (LPARAM)this);
+
+ SetForegroundWindow(hwndSetNickname);
+ SetFocus(hwndSetNickname);
+ ShowWindow(hwndSetNickname, SW_SHOW);
+ return 0;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+// Menus initialization
+
+void CMsnProto::MsnInitMainMenu(void)
+{
+ char servicefunction[100];
+ strcpy(servicefunction, m_szModuleName);
+ char* tDest = servicefunction + strlen(servicefunction);
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+
+ HGENMENU hRoot = MO_GetProtoRootMenu(m_szModuleName);
+ if (hRoot == NULL) {
+ mi.popupPosition = 500085000;
+ mi.hParentMenu = HGENMENU_ROOT;
+ mi.flags = CMIF_ROOTPOPUP | CMIF_TCHAR | CMIF_KEEPUNTRANSLATED;
+ mi.icolibItem = GetIconHandle(IDI_MSN);
+ mi.ptszName = m_tszUserName;
+ hRoot = mainMenuRoot = Menu_AddProtoMenuItem(&mi);
+ }
+ else {
+ MsnRemoveMainMenus();
+ mainMenuRoot = NULL;
+ }
+
+ mi.flags = CMIF_CHILDPOPUP;
+ mi.hParentMenu = hRoot;
+ mi.pszService = servicefunction;
+
+ strcpy(tDest, MS_SET_NICKNAME_UI);
+ CreateProtoService(MS_SET_NICKNAME_UI, &CMsnProto::SetNicknameUI);
+ mi.position = 201001;
+ mi.icolibItem = GetIconHandle(IDI_MSN);
+ mi.pszName = LPGEN("Set &Nickname");
+ menuItemsMain[0] = Menu_AddProtoMenuItem(&mi);
+
+ strcpy(tDest, MSN_INVITE);
+ CreateProtoService(MSN_INVITE, &CMsnProto::MsnInviteCommand);
+ mi.position = 201002;
+ mi.icolibItem = GetIconHandle(IDI_INVITE);
+ mi.pszName = LPGEN("Create &Chat");
+ menuItemsMain[0] = Menu_AddProtoMenuItem(&mi);
+
+ strcpy(tDest, MS_GOTO_INBOX);
+ CreateProtoService(MS_GOTO_INBOX, &CMsnProto::MsnGotoInbox);
+ mi.position = 201003;
+ mi.icolibItem = GetIconHandle(IDI_INBOX);
+ mi.pszName = LPGEN("Display &Hotmail Inbox");
+ menuItemsMain[1] = Menu_AddProtoMenuItem(&mi);
+
+ strcpy(tDest, MS_EDIT_PROFILE);
+ CreateProtoService(MS_EDIT_PROFILE, &CMsnProto::MsnEditProfile);
+ mi.position = 201004;
+ mi.icolibItem = GetIconHandle(IDI_PROFILE);
+ mi.pszName = LPGEN("View &Profile");
+ menuItemsMain[2] = Menu_AddProtoMenuItem(&mi);
+
+ strcpy(tDest, MS_EDIT_ALERTS);
+ CreateProtoService(MS_EDIT_ALERTS, &CMsnProto::MsnSetupAlerts);
+ mi.position = 201004;
+ mi.icolibItem = GetIconHandle(IDI_PROFILE);
+ mi.pszName = LPGEN("Setup Live &Alerts");
+ menuItemsMain[3] = Menu_AddProtoMenuItem(&mi);
+
+ MSN_EnableMenuItems(m_iStatus >= ID_STATUS_ONLINE);
+}
+
+void CMsnProto::MsnRemoveMainMenus(void)
+{
+ if (mainMenuRoot)
+ CallService(MO_REMOVEMENUITEM, (WPARAM)mainMenuRoot, 0);
+}
+
+void CMsnProto::MSN_EnableMenuItems(bool bEnable)
+{
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.flags = CMIM_FLAGS;
+ if (!bEnable)
+ mi.flags |= CMIF_GRAYED;
+
+ for (int i=0; i < SIZEOF(menuItemsMain); i++)
+ if (menuItemsMain[i] != NULL)
+ Menu_ModifyItem(menuItemsMain[i], &mi);
+
+ if (bEnable)
+ Menu_ShowItem(menuItemsMain[1], emailEnabled);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////
+
+static CMsnProto* GetProtoInstanceByHContact(MCONTACT hContact)
+{
+ char* szProto = GetContactProto(hContact);
+ 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 MsnMenuBlockCommand(WPARAM wParam, LPARAM lParam)
+{
+ CMsnProto* ppro = GetProtoInstanceByHContact(wParam);
+ return (ppro) ? ppro->MsnBlockCommand(wParam, lParam) : 0;
+}
+
+static INT_PTR MsnMenuViewProfile(WPARAM wParam, LPARAM lParam)
+{
+ CMsnProto* ppro = GetProtoInstanceByHContact(wParam);
+ return (ppro) ? ppro->MsnViewProfile(wParam, lParam) : 0;
+}
+
+static INT_PTR MsnMenuSendNetMeeting(WPARAM wParam, LPARAM lParam)
+{
+ CMsnProto* ppro = GetProtoInstanceByHContact(wParam);
+ return (ppro) ? ppro->MsnSendNetMeeting(wParam, lParam) : 0;
+}
+
+static INT_PTR MsnMenuSendHotmail(WPARAM wParam, LPARAM lParam)
+{
+ CMsnProto* ppro = GetProtoInstanceByHContact(wParam);
+ return (ppro) ? ppro->MsnSendHotmail(wParam, lParam) : 0;
+}
+
+static int MSN_OnPrebuildContactMenu(WPARAM wParam, LPARAM lParam)
+{
+ CMsnProto* ppro = GetProtoInstanceByHContact(wParam);
+ if (ppro)
+ ppro->OnPrebuildContactMenu(wParam, lParam);
+ else {
+ Menu_ShowItem(hBlockMenuItem, false);
+ Menu_ShowItem(hLiveSpaceMenuItem, false);
+ Menu_ShowItem(hNetmeetingMenuItem, false);
+ Menu_ShowItem(hChatInviteMenuItem, false);
+ Menu_ShowItem(hOpenInboxMenuItem, false);
+ }
+
+ return 0;
+}
+
+void MSN_InitContactMenu(void)
+{
+ char servicefunction[100];
+ strcpy(servicefunction, "MSN");
+ char* tDest = servicefunction + strlen(servicefunction);
+
+ CLISTMENUITEM mi = { sizeof(mi) };
+ mi.pszService = servicefunction;
+
+ strcpy(tDest, MSN_BLOCK);
+ hBlockCom = CreateServiceFunction(servicefunction, MsnMenuBlockCommand);
+ mi.position = -500050000;
+ mi.icolibItem = GetIconHandle(IDI_MSNBLOCK);
+ mi.pszName = LPGEN("&Block");
+ hBlockMenuItem = Menu_AddContactMenuItem(&mi);
+
+ strcpy(tDest, MSN_VIEW_PROFILE);
+ hViewProfile = CreateServiceFunction(servicefunction, MsnMenuViewProfile);
+ mi.position = -500050003;
+ mi.icolibItem = GetIconHandle(IDI_PROFILE);
+ mi.pszName = LPGEN("View &Profile");
+ hLiveSpaceMenuItem = Menu_AddContactMenuItem(&mi);
+
+ strcpy(tDest, MSN_NETMEETING);
+ hNetMeeting = CreateServiceFunction(servicefunction, MsnMenuSendNetMeeting);
+ mi.flags = CMIF_NOTOFFLINE;
+ mi.position = -500050002;
+ mi.icolibItem = GetIconHandle(IDI_NETMEETING);
+ mi.pszName = LPGEN("&Start Netmeeting");
+ hNetmeetingMenuItem = Menu_AddContactMenuItem(&mi);
+
+ strcpy(tDest, "/SendHotmail");
+ hSendHotMail = CreateServiceFunction(servicefunction, MsnMenuSendHotmail);
+ mi.position = -2000010005;
+ mi.flags = CMIF_HIDDEN;
+ mi.icolibItem = LoadSkinnedIconHandle(SKINICON_OTHER_SENDEMAIL);
+ mi.pszName = LPGEN("Open &Hotmail Inbox");
+ hOpenInboxMenuItem = Menu_AddContactMenuItem(&mi);
+
+ HookEvent(ME_CLIST_PREBUILDCONTACTMENU, MSN_OnPrebuildContactMenu);
+}
+
+void MSN_RemoveContactMenus(void)
+{
+ CallService(MO_REMOVEMENUITEM, (WPARAM)hBlockMenuItem, 0);
+ CallService(MO_REMOVEMENUITEM, (WPARAM)hLiveSpaceMenuItem, 0);
+ CallService(MO_REMOVEMENUITEM, (WPARAM)hNetmeetingMenuItem, 0);
+ CallService(MO_REMOVEMENUITEM, (WPARAM)hChatInviteMenuItem, 0);
+ CallService(MO_REMOVEMENUITEM, (WPARAM)hOpenInboxMenuItem, 0);
+
+ DestroyServiceFunction(hNetMeeting);
+ DestroyServiceFunction(hBlockCom);
+ DestroyServiceFunction(hSendHotMail);
+ DestroyServiceFunction(hInviteChat);
+ DestroyServiceFunction(hViewProfile);
+}
diff --git a/protocols/MSN/src/msn_mime.cpp b/protocols/MSN/src/msn_mime.cpp
new file mode 100644
index 0000000000..6f9d15d587
--- /dev/null
+++ b/protocols/MSN/src/msn_mime.cpp
@@ -0,0 +1,536 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// constructors and destructor
+
+MimeHeaders::MimeHeaders() :
+ mCount(0),
+ mAllocCount(0),
+ mVals(NULL)
+{
+}
+
+MimeHeaders::MimeHeaders(unsigned iInitCount) :
+ mCount(0)
+{
+ mAllocCount = iInitCount;
+ mVals = (MimeHeader*)mir_alloc(iInitCount * sizeof(MimeHeader));
+}
+
+MimeHeaders::~MimeHeaders()
+{
+ clear();
+ mir_free(mVals);
+}
+
+void MimeHeaders::clear(void)
+{
+ for (unsigned i=0; i < mCount; i++)
+ {
+ MimeHeader& H = mVals[i];
+ if (H.flags & 1) mir_free((void*)H.name);
+ if (H.flags & 2) mir_free((void*)H.value);
+ }
+ mCount = 0;
+}
+
+unsigned MimeHeaders::allocSlot(void)
+{
+ if (++mCount >= mAllocCount)
+ {
+ mAllocCount += 10;
+ mVals = (MimeHeader*)mir_realloc(mVals, sizeof(MimeHeader) * mAllocCount);
+ }
+ return mCount - 1;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// add various values
+
+void MimeHeaders::addString(const char* name, const char* szValue, unsigned flags)
+{
+ if (szValue == NULL) return;
+
+ MimeHeader& H = mVals[allocSlot()];
+ H.name = name;
+ H.value = szValue;
+ H.flags = flags;
+}
+
+void MimeHeaders::addLong(const char* name, long lValue, unsigned flags)
+{
+ MimeHeader& H = mVals[allocSlot()];
+ H.name = name;
+
+ char szBuffer[20];
+ _ltoa(lValue, szBuffer, 10);
+ H.value = mir_strdup(szBuffer);
+ H.flags = 2 | flags;
+}
+
+void MimeHeaders::addULong(const char* name, unsigned lValue)
+{
+ MimeHeader& H = mVals[allocSlot()];
+ H.name = name;
+
+ char szBuffer[20];
+ _ultoa(lValue, szBuffer, 10);
+ H.value = mir_strdup(szBuffer);
+ H.flags = 2;
+}
+
+void MimeHeaders::addBool(const char* name, bool lValue)
+{
+ MimeHeader& H = mVals[allocSlot()];
+ H.name = name;
+ H.value = lValue ? "true" : "false";
+ H.flags = 0;
+}
+
+char* MimeHeaders::flipStr(const char* src, size_t len, char* dest)
+{
+ if (len == -1) len = strlen(src);
+
+ if (src == dest)
+ {
+ const unsigned b = (unsigned)len-- / 2;
+ for (unsigned i = 0; i < b; i++)
+ {
+ const char c = dest[i];
+ dest[i] = dest[len - i];
+ dest[len - i] = c;
+ }
+ ++len;
+ }
+ else
+ {
+ for (unsigned i = 0; i < len; i++)
+ dest[i] = src[len - 1 - i];
+ dest[len] = 0;
+ }
+
+ return dest + len;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// write all values to a buffer
+
+size_t MimeHeaders::getLength(void)
+{
+ size_t iResult = 0;
+ for (unsigned i=0; i < mCount; i++)
+ {
+ MimeHeader& H = mVals[i];
+ iResult += strlen(H.name) + strlen(H.value) + 4;
+ }
+
+ return iResult + (iResult ? 2 : 0);
+}
+
+char* MimeHeaders::writeToBuffer(char* dest)
+{
+ for (unsigned i=0; i < mCount; i++)
+ {
+ MimeHeader& H = mVals[i];
+ if (H.flags & 4)
+ {
+ dest = flipStr(H.name, -1, dest);
+
+ *(dest++) = ':';
+ *(dest++) = ' ';
+
+ dest = flipStr(H.value, -1, dest);
+
+ *(dest++) = '\r';
+ *(dest++) = '\n';
+ *dest = 0;
+ }
+ else
+ dest += sprintf(dest, "%s: %s\r\n", H.name, H.value); //!!!!!!!!!!!!
+ }
+
+ if (mCount)
+ {
+ *(dest++) = '\r';
+ *(dest++) = '\n';
+ *dest = 0;
+ }
+
+ return dest;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// read set of values from buffer
+
+char* MimeHeaders::readFromBuffer(char* src)
+{
+ clear();
+
+ while (*src)
+ {
+ char* peol = strchr(src, '\n');
+
+ if (peol == NULL)
+ return strchr(src, 0);
+ else if (peol == src)
+ return src + 1;
+ else if (peol == (src + 1) && *src == '\r')
+ return src + 2;
+
+ *peol = 0;
+
+ char* delim = strchr(src, ':');
+ if (delim)
+ {
+ *delim = 0;
+
+ MimeHeader& H = mVals[allocSlot()];
+
+ H.name = lrtrimp(src);
+ H.value = lrtrimp(delim + 1);
+ H.flags = 0;
+ }
+
+ src = peol + 1;
+ }
+
+ return src;
+}
+
+const char* MimeHeaders::find(const char* szFieldName)
+{
+ size_t i;
+ for (i = 0; i < mCount; i++)
+ {
+ MimeHeader& MH = mVals[i];
+ if (_stricmp(MH.name, szFieldName) == 0)
+ return MH.value;
+ }
+
+ const size_t len = strlen(szFieldName);
+ char* szFieldNameR = (char*)alloca(len + 1);
+ flipStr(szFieldName, len, szFieldNameR);
+
+ for (i = 0; i < mCount; i++)
+ {
+ MimeHeader& MH = mVals[i];
+ if (_stricmp(MH.name, szFieldNameR) == 0 && (MH.flags & 3) == 0)
+ {
+ strcpy((char*)MH.name, szFieldNameR);
+ flipStr(MH.value, -1, (char*)MH.value);
+ return MH.value;
+ }
+ }
+
+ return NULL;
+}
+
+static const struct _tag_cpltbl
+{
+ unsigned cp;
+ const char* mimecp;
+} cptbl[] =
+{
+ { 37, "IBM037" }, // IBM EBCDIC US-Canada
+ { 437, "IBM437" }, // OEM United States
+ { 500, "IBM500" }, // IBM EBCDIC International
+ { 708, "ASMO-708" }, // Arabic (ASMO 708)
+ { 720, "DOS-720" }, // Arabic (Transparent ASMO); Arabic (DOS)
+ { 737, "ibm737" }, // OEM Greek (formerly 437G); Greek (DOS)
+ { 775, "ibm775" }, // OEM Baltic; Baltic (DOS)
+ { 850, "ibm850" }, // OEM Multilingual Latin 1; Western European (DOS)
+ { 852, "ibm852" }, // OEM Latin 2; Central European (DOS)
+ { 855, "IBM855" }, // OEM Cyrillic (primarily Russian)
+ { 857, "ibm857" }, // OEM Turkish; Turkish (DOS)
+ { 858, "IBM00858" }, // OEM Multilingual Latin 1 + Euro symbol
+ { 860, "IBM860" }, // OEM Portuguese; Portuguese (DOS)
+ { 861, "ibm861" }, // OEM Icelandic; Icelandic (DOS)
+ { 862, "DOS-862" }, // OEM Hebrew; Hebrew (DOS)
+ { 863, "IBM863" }, // OEM French Canadian; French Canadian (DOS)
+ { 864, "IBM864" }, // OEM Arabic; Arabic (864)
+ { 865, "IBM865" }, // OEM Nordic; Nordic (DOS)
+ { 866, "cp866" }, // OEM Russian; Cyrillic (DOS)
+ { 869, "ibm869" }, // OEM Modern Greek; Greek, Modern (DOS)
+ { 870, "IBM870" }, // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC Multilingual Latin 2
+ { 874, "windows-874" }, // ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai (Windows)
+ { 875, "cp875" }, // IBM EBCDIC Greek Modern
+ { 932, "shift_jis" }, // ANSI/OEM Japanese; Japanese (Shift-JIS)
+ { 936, "gb2312" }, // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese Simplified (GB2312)
+ { 949, "ks_c_5601-1987" }, // ANSI/OEM Korean (Unified Hangul Code)
+ { 950, "big5" }, // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR, PRC); Chinese Traditional (Big5)
+ { 1026, "IBM1026" }, // IBM EBCDIC Turkish (Latin 5)
+ { 1047, "IBM01047" }, // IBM EBCDIC Latin 1/Open System
+ { 1140, "IBM01140" }, // IBM EBCDIC US-Canada (037 + Euro symbol); IBM EBCDIC (US-Canada-Euro)
+ { 1141, "IBM01141" }, // IBM EBCDIC Germany (20273 + Euro symbol); IBM EBCDIC (Germany-Euro)
+ { 1142, "IBM01142" }, // IBM EBCDIC Denmark-Norway (20277 + Euro symbol); IBM EBCDIC (Denmark-Norway-Euro)
+ { 1143, "IBM01143" }, // IBM EBCDIC Finland-Sweden (20278 + Euro symbol); IBM EBCDIC (Finland-Sweden-Euro)
+ { 1144, "IBM01144" }, // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC (Italy-Euro)
+ { 1145, "IBM01145" }, // IBM EBCDIC Latin America-Spain (20284 + Euro symbol); IBM EBCDIC (Spain-Euro)
+ { 1146, "IBM01146" }, // IBM EBCDIC United Kingdom (20285 + Euro symbol); IBM EBCDIC (UK-Euro)
+ { 1147, "IBM01147" }, // IBM EBCDIC France (20297 + Euro symbol); IBM EBCDIC (France-Euro)
+ { 1148, "IBM01148" }, // IBM EBCDIC International (500 + Euro symbol); IBM EBCDIC (International-Euro)
+ { 1149, "IBM01149" }, // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM EBCDIC (Icelandic-Euro)
+ { 1250, "windows-1250" }, // ANSI Central European; Central European (Windows)
+ { 1251, "windows-1251" }, // ANSI Cyrillic; Cyrillic (Windows)
+ { 1252, "windows-1252" }, // ANSI Latin 1; Western European (Windows)
+ { 1253, "windows-1253" }, // ANSI Greek; Greek (Windows)
+ { 1254, "windows-1254" }, // ANSI Turkish; Turkish (Windows)
+ { 1255, "windows-1255" }, // ANSI Hebrew; Hebrew (Windows)
+ { 1256, "windows-1256" }, // ANSI Arabic; Arabic (Windows)
+ { 1257, "windows-1257" }, // ANSI Baltic; Baltic (Windows)
+ { 1258, "windows-1258" }, // ANSI/OEM Vietnamese; Vietnamese (Windows)
+ { 20127, "us-ascii" }, // US-ASCII (7-bit)
+ { 20273, "IBM273" }, // IBM EBCDIC Germany
+ { 20277, "IBM277" }, // IBM EBCDIC Denmark-Norway
+ { 20278, "IBM278" }, // IBM EBCDIC Finland-Sweden
+ { 20280, "IBM280" }, // IBM EBCDIC Italy
+ { 20284, "IBM284" }, // IBM EBCDIC Latin America-Spain
+ { 20285, "IBM285" }, // IBM EBCDIC United Kingdom
+ { 20290, "IBM290" }, // IBM EBCDIC Japanese Katakana Extended
+ { 20297, "IBM297" }, // IBM EBCDIC France
+ { 20420, "IBM420" }, // IBM EBCDIC Arabic
+ { 20423, "IBM423" }, // IBM EBCDIC Greek
+ { 20424, "IBM424" }, // IBM EBCDIC Hebrew
+ { 20838, "IBM-Thai" }, // IBM EBCDIC Thai
+ { 20866, "koi8-r" }, // Russian (KOI8-R); Cyrillic (KOI8-R)
+ { 20871, "IBM871" }, // IBM EBCDIC Icelandic
+ { 20880, "IBM880" }, // IBM EBCDIC Cyrillic Russian
+ { 20905, "IBM905" }, // IBM EBCDIC Turkish
+ { 20924, "IBM00924" }, // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
+ { 20932, "EUC-JP" }, // Japanese (JIS 0208-1990 and 0121-1990)
+ { 21025, "cp1025" }, // IBM EBCDIC Cyrillic Serbian-Bulgarian
+ { 21866, "koi8-u" }, // Ukrainian (KOI8-U); Cyrillic (KOI8-U)
+ { 28591, "iso-8859-1" }, // ISO 8859-1 Latin 1; Western European (ISO)
+ { 28592, "iso-8859-2" }, // ISO 8859-2 Central European; Central European (ISO)
+ { 28593, "iso-8859-3" }, // ISO 8859-3 Latin 3
+ { 28594, "iso-8859-4" }, // ISO 8859-4 Baltic
+ { 28595, "iso-8859-5" }, // ISO 8859-5 Cyrillic
+ { 28596, "iso-8859-6" }, // ISO 8859-6 Arabic
+ { 28597, "iso-8859-7" }, // ISO 8859-7 Greek
+ { 28598, "iso-8859-8" }, // ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
+ { 28599, "iso-8859-9" }, // ISO 8859-9 Turkish
+ { 28603, "iso-8859-13" }, // ISO 8859-13 Estonian
+ { 28605, "iso-8859-15" }, // ISO 8859-15 Latin 9
+ { 38598, "iso-8859-8-i" }, // ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
+ { 50220, "iso-2022-jp" }, // ISO 2022 Japanese with no halfwidth Katakana; Japanese (JIS)
+ { 50221, "csISO2022JP" }, // ISO 2022 Japanese with halfwidth Katakana; Japanese (JIS-Allow 1 byte Kana)
+ { 50222, "iso-2022-jp" }, // ISO 2022 Japanese JIS X 0201-1989; Japanese (JIS-Allow 1 byte Kana - SO/SI)
+ { 50225, "iso-2022-kr" }, // ISO 2022 Korean
+ { 50227, "ISO-2022-CN" }, // ISO 2022 Simplified Chinese; Chinese Simplified (ISO 2022)
+ { 50229, "ISO-2022-CN-EXT" }, // ISO 2022 Traditional Chinese
+ { 51932, "euc-jp" }, // EUC Japanese
+ { 51936, "EUC-CN" }, // EUC Simplified Chinese; Chinese Simplified (EUC)
+ { 51949, "euc-kr" }, // EUC Korean
+ { 52936, "hz-gb-2312" }, // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
+ { 54936, "GB18030" }, // Windows XP and later: GB18030 Simplified Chinese (4 byte); Chinese Simplified (GB18030)
+};
+
+
+static unsigned FindCP(const char* mimecp)
+{
+ unsigned cp = CP_ACP;
+ for (unsigned i = 0; i < SIZEOF(cptbl); ++i)
+ {
+ if (_stricmp(mimecp, cptbl[i].mimecp) == 0)
+ {
+ cp = cptbl[i].cp;
+ break;
+ }
+ }
+ return cp;
+}
+
+
+static int SingleHexToDecimal(char c)
+{
+ if (c >= '0' && c <= '9') return c-'0';
+ if (c >= 'a' && c <= 'f') return c-'a'+10;
+ if (c >= 'A' && c <= 'F') return c-'A'+10;
+ return -1;
+}
+
+static void PQDecode(char* str)
+{
+ char* s = str, *d = str;
+
+ while(*s)
+ {
+ switch (*s)
+ {
+ case '=':
+ {
+ int digit1 = SingleHexToDecimal(s[1]);
+ if (digit1 != -1)
+ {
+ int digit2 = SingleHexToDecimal(s[2]);
+ if (digit2 != -1)
+ {
+ s += 3;
+ *d++ = (char)((digit1 << 4) | digit2);
+ }
+ }
+ break;
+ }
+
+ case '_':
+ *d++ = ' '; ++s;
+ break;
+
+ default:
+ *d++ = *s++;
+ break;
+ }
+ }
+ *d = 0;
+}
+
+static size_t utf8toutf16(char* str, wchar_t* res)
+{
+ wchar_t *dec = mir_utf8decodeW(str);
+ if (dec == NULL) dec = mir_a2u(str);
+ wcscpy(res, dec);
+ mir_free(dec);
+ return wcslen(res);
+}
+
+
+wchar_t* MimeHeaders::decode(const char* val)
+{
+ size_t ssz = strlen(val) * 2 + 1;
+ char* tbuf = (char*)alloca(ssz);
+ memcpy(tbuf, val, ssz);
+
+ wchar_t* res = (wchar_t*)mir_alloc(ssz * sizeof(wchar_t));
+ wchar_t* resp = res;
+
+ char *p = tbuf;
+ while (*p)
+ {
+ char *cp = strstr(p, "=?");
+ if (cp == NULL) break;
+ *cp = 0;
+
+ size_t sz = utf8toutf16(p, resp);
+ ssz -= sz; resp += sz;
+ cp += 2;
+
+ char *enc = strchr(cp, '?');
+ if (enc == NULL) break;
+ *(enc++) = 0;
+
+ char *fld = strchr(enc, '?');
+ if (fld == NULL) break;
+ *(fld++) = 0;
+
+ char *pe = strstr(fld, "?=");
+ if (pe == NULL) break;
+ *pe = 0;
+
+ switch (*enc)
+ {
+ case 'b':
+ case 'B':
+ {
+ char* dec = (char*)mir_base64_decode(fld, 0);
+ strcpy(fld, dec);
+ mir_free(dec);
+ break;
+ }
+
+ case 'q':
+ case 'Q':
+ PQDecode(fld);
+ break;
+ }
+
+ if (_stricmp(cp, "UTF-8") == 0)
+ {
+ sz = utf8toutf16(fld, resp);
+ ssz -= sz; resp += sz;
+ }
+ else
+ {
+ int sz = MultiByteToWideChar(FindCP(cp), 0, fld, -1, resp, (int)ssz);
+ if (sz == 0)
+ sz = MultiByteToWideChar(CP_ACP, 0, fld, -1, resp, (int)ssz);
+ ssz -= --sz; resp += sz;
+ }
+ p = pe + 2;
+ }
+
+ utf8toutf16(p, resp);
+
+ return res;
+}
+
+
+char* MimeHeaders::decodeMailBody(char* msgBody)
+{
+ char* res;
+ const char *val = find("Content-Transfer-Encoding");
+ if (val && _stricmp(val, "base64") == 0)
+ {
+ char *src = msgBody, *dst = msgBody;
+ while (*src != 0)
+ {
+ if (isspace(*src)) ++src;
+ else *(dst++) = *(src++);
+ }
+ *dst = 0;
+ res = (char*)mir_base64_decode(msgBody, 0);
+ }
+ else
+ {
+ res = mir_strdup(msgBody);
+ if (val && _stricmp(val, "quoted-printable") == 0)
+ PQDecode(res);
+ }
+ return res;
+}
+
+
+int sttDivideWords(char* parBuffer, int parMinItems, char** parDest)
+{
+ int i;
+ for (i=0; i < parMinItems; i++)
+ {
+ parDest[i] = parBuffer;
+
+ size_t tWordLen = strcspn(parBuffer, " \t");
+ if (tWordLen == 0)
+ return i;
+
+ parBuffer += tWordLen;
+ if (*parBuffer != '\0')
+ {
+ size_t tSpaceLen = strspn(parBuffer, " \t");
+ memset(parBuffer, 0, tSpaceLen);
+ parBuffer += tSpaceLen;
+ } }
+
+ return i;
+}
diff --git a/protocols/MSN/src/msn_misc.cpp b/protocols/MSN/src/msn_misc.cpp
new file mode 100644
index 0000000000..d370429c57
--- /dev/null
+++ b/protocols/MSN/src/msn_misc.cpp
@@ -0,0 +1,1293 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+#include "version.h"
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MirandaStatusToMSN - status helper functions
+
+const char* CMsnProto::MirandaStatusToMSN(int status)
+{
+ switch(status)
+ {
+ case ID_STATUS_OFFLINE: return "FLN";
+ case ID_STATUS_ONTHEPHONE:
+ case ID_STATUS_OUTTOLUNCH:
+ case ID_STATUS_NA:
+ case ID_STATUS_AWAY: return "AWY";
+ case ID_STATUS_DND:
+ case ID_STATUS_OCCUPIED: return "BSY";
+ case ID_STATUS_INVISIBLE: return "HDN";
+ case ID_STATUS_IDLE: return "IDL";
+ default: return "NLN";
+} }
+
+WORD CMsnProto::MSNStatusToMiranda(const char *status)
+{
+ switch((*(PDWORD)status&0x00FFFFFF) | 0x20000000)
+ {
+ case ' LDI': return ID_STATUS_IDLE;
+ case ' NLN': return ID_STATUS_ONLINE;
+ case ' NHP':
+ case ' NUL':
+ case ' BRB':
+ case ' YWA': return ID_STATUS_AWAY;
+ case ' YSB': return ID_STATUS_OCCUPIED;
+ case ' NDH': return ID_STATUS_INVISIBLE;
+ default: return ID_STATUS_OFFLINE;
+ }
+}
+
+char** CMsnProto::GetStatusMsgLoc(int status)
+{
+ static const int modes[MSN_NUM_MODES] =
+ {
+ ID_STATUS_ONLINE,
+ ID_STATUS_AWAY,
+ ID_STATUS_DND,
+ ID_STATUS_NA,
+ ID_STATUS_OCCUPIED,
+ ID_STATUS_FREECHAT,
+ ID_STATUS_INVISIBLE,
+ ID_STATUS_ONTHEPHONE,
+ ID_STATUS_OUTTOLUNCH,
+ };
+
+ for (int i=0; i < MSN_NUM_MODES; i++)
+ if (modes[i] == status) return &msnModeMsgs[i];
+
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_AddAuthRequest - adds the authorization event to the database
+
+void CMsnProto::MSN_AddAuthRequest(const char *email, const char *nick, const char *reason)
+{
+ //blob is: UIN=0(DWORD), hContact(DWORD), nick(ASCIIZ), ""(ASCIIZ), ""(ASCIIZ), email(ASCIIZ), ""(ASCIIZ)
+
+ MCONTACT hContact = MSN_HContactFromEmail(email, nick, true, true);
+
+ int emaillen = (int)strlen(email);
+
+ if (nick == NULL) nick = "";
+ int nicklen = (int)strlen(nick);
+
+ if (reason == NULL) reason = "";
+ int reasonlen = (int)strlen(reason);
+
+ PROTORECVEVENT pre = { 0 };
+ pre.flags = PREF_UTF;
+ pre.timestamp = (DWORD)time(NULL);
+ pre.lParam = sizeof(DWORD) + sizeof(HANDLE) + nicklen + emaillen + 5 + reasonlen;
+
+ char* pCurBlob = (char*)alloca(pre.lParam);
+ pre.szMessage = pCurBlob;
+
+ *(PDWORD)pCurBlob = 0; pCurBlob += sizeof(DWORD); // UID
+ *(PDWORD)pCurBlob = (DWORD)hContact; pCurBlob += sizeof(DWORD); // Contact Handle
+ strcpy(pCurBlob, nick); pCurBlob += nicklen + 1; // Nickname
+ *pCurBlob = '\0'; pCurBlob++; // First Name
+ *pCurBlob = '\0'; pCurBlob++; // Last Name
+ strcpy(pCurBlob, email); pCurBlob += emaillen + 1; // E-mail
+ strcpy(pCurBlob, reason); // Reason
+
+ ProtoChainRecv(hContact, PSR_AUTH, 0, (LPARAM)&pre);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CMsnProto::InitCustomFolders(void)
+{
+ if (InitCstFldRan) return;
+
+ TCHAR folder[MAX_PATH];
+ mir_sntprintf(folder, SIZEOF(folder), _T("%%miranda_avatarcache%%\\%S"), m_szModuleName);
+ hCustomSmileyFolder = FoldersRegisterCustomPathT(LPGEN("Custom Smileys"), m_szModuleName, folder, m_tszUserName);
+
+ InitCstFldRan = true;
+}
+
+
+char* MSN_GetAvatarHash(char* szContext, char** pszUrl)
+{
+ if (pszUrl)
+ *pszUrl = NULL;
+
+ if (szContext == NULL)
+ return NULL;
+
+ char *res = NULL;
+
+ ezxml_t xmli = ezxml_parse_str(NEWSTR_ALLOCA(szContext), strlen(szContext));
+ const char *szAvatarHash = ezxml_attr(xmli, "SHA1D");
+ if (szAvatarHash != NULL) {
+ unsigned hashLen;
+ mir_ptr<BYTE> hash((BYTE*)mir_base64_decode(szAvatarHash, &hashLen));
+ if (hash)
+ res = arrayToHex(hash, hashLen);
+
+ if (pszUrl) {
+ const char *pszUrlAttr;
+ for (int i=0; ; i++) {
+ char szSetting[20];
+ if (i == 0)
+ strcpy(szSetting, "Url");
+ else
+ mir_snprintf(szSetting, sizeof(szSetting), "Url%d", i);
+ pszUrlAttr = ezxml_attr(xmli, szSetting);
+ if (pszUrlAttr == NULL)
+ break;
+
+ if (pszUrlAttr[0] != 0) {
+ *pszUrl = mir_strdup(pszUrlAttr);
+ break;
+ }
+ }
+ }
+ }
+ ezxml_free(xmli);
+
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_GetAvatarFileName - gets a file name for an contact's avatar
+
+void CMsnProto::MSN_GetAvatarFileName(MCONTACT hContact, TCHAR* pszDest, size_t cbLen, const TCHAR *ext)
+{
+ size_t tPathLen = mir_sntprintf(pszDest, cbLen, _T("%s\\%S"), VARST(_T("%miranda_avatarcache%")), m_szModuleName);
+
+ if (_taccess(pszDest, 0))
+ CreateDirectoryTreeT(pszDest);
+
+ size_t tPathLen2 = tPathLen;
+ if (hContact != NULL) {
+ DBVARIANT dbv;
+ if (getString(hContact, "PictContext", &dbv) == 0) {
+ char* szAvatarHash = MSN_GetAvatarHash(dbv.pszVal);
+ if (szAvatarHash != NULL) {
+ TCHAR *sztAvatarHash = mir_a2t(szAvatarHash);
+ tPathLen += mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s."), sztAvatarHash);
+ mir_free(sztAvatarHash);
+ mir_free(szAvatarHash);
+ }
+ else {
+ delSetting(hContact, "PictContext");
+ if (cbLen) pszDest[0] = 0;
+ }
+ db_free(&dbv);
+ }
+ else if (cbLen)
+ pszDest[0] = 0;
+ }
+ else {
+ TCHAR *sztModuleName = mir_a2t(m_szModuleName);
+ tPathLen += mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s avatar."), sztModuleName);
+ mir_free(sztModuleName);
+ }
+
+ if (ext == NULL) {
+ mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("*"));
+
+ bool found = false;
+ _tfinddata_t c_file;
+ long hFile = _tfindfirst(pszDest, &c_file);
+ if (hFile > -1L) {
+ do {
+ if (_tcsrchr(c_file.name, '.')) {
+ mir_sntprintf(pszDest + tPathLen2, cbLen - tPathLen2, _T("\\%s"), c_file.name);
+ found = true;
+ }
+ }
+ while(_tfindnext(hFile, &c_file) == 0);
+ _findclose( hFile );
+ }
+
+ if (!found) pszDest[0] = 0;
+ }
+ else {
+ tPathLen--;
+ mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, ext);
+ }
+}
+
+int CMsnProto::MSN_SetMyAvatar(const TCHAR* sztFname, void* pData, size_t cbLen)
+{
+ mir_sha1_ctx sha1ctx;
+ BYTE sha1c[MIR_SHA1_HASH_SIZE], sha1d[MIR_SHA1_HASH_SIZE];
+
+ char *szFname = mir_utf8encodeT(sztFname);
+
+ mir_sha1_init(&sha1ctx);
+ mir_sha1_append(&sha1ctx, (BYTE*)pData, (int)cbLen);
+ mir_sha1_finish(&sha1ctx, sha1d);
+
+ ptrA szSha1d( mir_base64_encode((PBYTE)sha1d, sizeof(sha1d)));
+
+ mir_sha1_init(&sha1ctx);
+ ezxml_t xmlp = ezxml_new("msnobj");
+
+ mir_sha1_append(&sha1ctx, (PBYTE)"Creator", 7);
+ mir_sha1_append(&sha1ctx, (PBYTE)MyOptions.szEmail, (int)strlen(MyOptions.szEmail));
+ ezxml_set_attr(xmlp, "Creator", MyOptions.szEmail);
+
+ char szFileSize[20];
+ _ultoa((unsigned)cbLen, szFileSize, 10);
+ mir_sha1_append(&sha1ctx, (PBYTE)"Size", 4);
+ mir_sha1_append(&sha1ctx, (PBYTE)szFileSize, (int)strlen(szFileSize));
+ ezxml_set_attr(xmlp, "Size", szFileSize);
+
+ mir_sha1_append(&sha1ctx, (PBYTE)"Type", 4);
+ mir_sha1_append(&sha1ctx, (PBYTE)"3", 1); // MSN_TYPEID_DISPLAYPICT
+ ezxml_set_attr(xmlp, "Type", "3");
+
+ mir_sha1_append(&sha1ctx, (PBYTE)"Location", 8);
+ mir_sha1_append(&sha1ctx, (PBYTE)szFname, (int)strlen(szFname));
+ ezxml_set_attr(xmlp, "Location", szFname);
+
+ mir_sha1_append(&sha1ctx, (PBYTE)"Friendly", 8);
+ mir_sha1_append(&sha1ctx, (PBYTE)"AAA=", 4);
+ ezxml_set_attr(xmlp, "Friendly", "AAA=");
+
+ mir_sha1_append(&sha1ctx, (PBYTE)"SHA1D", 5);
+ mir_sha1_append(&sha1ctx, (PBYTE)(char*)szSha1d, (int)strlen(szSha1d));
+ ezxml_set_attr(xmlp, "SHA1D", szSha1d);
+
+ mir_sha1_finish(&sha1ctx, sha1c);
+
+ ptrA szSha1c( mir_base64_encode((PBYTE)sha1c, sizeof(sha1c)));
+
+ // ezxml_set_attr(xmlp, "SHA1C", szSha1c);
+
+ char* szBuffer = ezxml_toxml(xmlp, false);
+ ezxml_free(xmlp);
+ mir_free(szFname);
+ ptrA szEncodedBuffer(mir_urlEncode(szBuffer));
+ free(szBuffer);
+
+ const TCHAR *szExt;
+ int fmt = ProtoGetBufferFormat(pData, &szExt);
+ if (fmt == PA_FORMAT_UNKNOWN)
+ return fmt;
+
+ TCHAR szFileName[MAX_PATH];
+ MSN_GetAvatarFileName(NULL, szFileName, SIZEOF(szFileName), NULL);
+ _tremove(szFileName);
+
+ MSN_GetAvatarFileName(NULL, szFileName, SIZEOF(szFileName), szExt);
+
+ int fileId = _topen(szFileName, _O_CREAT | _O_TRUNC | _O_WRONLY | O_BINARY, _S_IREAD | _S_IWRITE);
+ if (fileId >= 0) {
+ _write(fileId, pData, (unsigned)cbLen);
+ _close(fileId);
+
+ char szAvatarHashdOld[41] = "";
+ db_get_static(NULL, m_szModuleName, "AvatarHash", szAvatarHashdOld, sizeof(szAvatarHashdOld));
+ char *szAvatarHash = arrayToHex(sha1d, sizeof(sha1d));
+ if (strcmp(szAvatarHashdOld, szAvatarHash)) {
+ setString("PictObject", szEncodedBuffer);
+ setString("AvatarHash", szAvatarHash);
+ }
+ mir_free(szAvatarHash);
+ }
+ else MSN_ShowError("Cannot set avatar. File '%s' could not be created/overwritten", szFileName);
+
+ return fmt;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_GetCustomSmileyFileName - gets a file name for an contact's custom smiley
+
+void CMsnProto::MSN_GetCustomSmileyFileName(MCONTACT hContact, TCHAR* pszDest, size_t cbLen, const char* SmileyName, int type)
+{
+ size_t tPathLen;
+
+ InitCustomFolders();
+
+ TCHAR* path = (TCHAR*)alloca(cbLen * sizeof(TCHAR));
+ if (hCustomSmileyFolder == NULL || FoldersGetCustomPathT(hCustomSmileyFolder, path, (int)cbLen, _T(""))) {
+ TCHAR *tmpPath = Utils_ReplaceVarsT(_T("%miranda_userdata%"));
+ TCHAR *tszModuleName = mir_a2t(m_szModuleName);
+ tPathLen = mir_sntprintf(pszDest, cbLen, _T("%s\\%s\\CustomSmiley"), tmpPath, tszModuleName);
+ mir_free(tszModuleName);
+ mir_free(tmpPath);
+ }
+ else {
+ _tcscpy(pszDest, path);
+ tPathLen = _tcslen(pszDest);
+ }
+
+ if (hContact != NULL)
+ {
+ DBVARIANT dbv = {0};
+ if (getTString(hContact, "e-mail", &dbv))
+ {
+ dbv.type = DBVT_ASCIIZ;
+ dbv.ptszVal = (TCHAR*)mir_alloc(11);
+ _ui64tot((UINT_PTR)hContact, dbv.ptszVal, 10);
+ }
+
+ tPathLen += mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s"), dbv.ptszVal);
+ db_free(&dbv);
+ }
+ else {
+ TCHAR *tszModuleName = mir_a2t(m_szModuleName);
+ tPathLen += mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s"), tszModuleName);
+ mir_free(tszModuleName);
+ }
+
+ bool exist = _taccess(pszDest, 0) == 0;
+
+ if (type == 0) {
+ if (!exist) pszDest[0] = 0;
+ return;
+ }
+
+ if (!exist)
+ CreateDirectoryTreeT(pszDest);
+
+ TCHAR *sztSmileyName = mir_a2t(SmileyName);
+ mir_sntprintf(pszDest + tPathLen, cbLen - tPathLen, _T("\\%s.%s"), sztSmileyName,
+ type == MSN_APPID_CUSTOMSMILEY ? _T("png") : _T("gif"));
+ mir_free(sztSmileyName);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_GoOffline - performs several actions when a server goes offline
+
+void CMsnProto::MSN_GoOffline(void)
+{
+ if (m_iStatus == ID_STATUS_OFFLINE) return;
+
+ msnLoggedIn = false;
+
+ if (mStatusMsgTS)
+ ForkThread(&CMsnProto::msn_storeProfileThread, NULL);
+
+ mir_free(msnPreviousUUX);
+ msnPreviousUUX = NULL;
+ msnSearchId = NULL;
+
+ if (!Miranda_Terminated())
+ MSN_EnableMenuItems(false);
+
+ MSN_FreeGroups();
+ MsgQueue_Clear();
+ clearCachedMsg();
+
+ if (!Miranda_Terminated()) {
+ int msnOldStatus = m_iStatus; m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+ ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)msnOldStatus, ID_STATUS_OFFLINE);
+ isIdle = false;
+
+ int count = -1;
+ for (;;) {
+ MsnContact *msc = Lists_GetNext(count);
+ if (msc == NULL) break;
+
+ if (ID_STATUS_OFFLINE != getWord(msc->hContact, "Status", ID_STATUS_OFFLINE)) {
+ setWord(msc->hContact, "Status", ID_STATUS_OFFLINE);
+ setDword(msc->hContact, "IdleTS", 0);
+ }
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_SendMessage - formats and sends a MSG packet through the server
+
+int ThreadData::sendMessage(int msgType, const char* email, int netId, const char* parMsg, int parFlags)
+{
+ char buf[2048];
+ int off;
+
+ off = mir_snprintf(buf, sizeof(buf), "MIME-Version: 1.0\r\n");
+
+ if ((parFlags & MSG_DISABLE_HDR) == 0) {
+ char tFontName[100], tFontStyle[3];
+ DWORD tFontColor;
+
+ strcpy(tFontName, "Arial");
+
+ if (proto->getByte("SendFontInfo", 1)) {
+ char* p;
+
+ DBVARIANT dbv;
+ if (!db_get_s(NULL, "SRMsg", "Font0", &dbv)) {
+ for (p = dbv.pszVal; *p; p++)
+ if (BYTE(*p) >= 128 || *p < 32)
+ break;
+
+ if (*p == 0) {
+ strncpy_s(tFontName, sizeof(tFontName), ptrA(mir_urlEncode(dbv.pszVal)), _TRUNCATE);
+ db_free(&dbv);
+ }
+ }
+
+ int tStyle = db_get_b(NULL, "SRMsg", "Font0Sty", 0);
+ p = tFontStyle;
+ if (tStyle & 1) *p++ = 'B';
+ if (tStyle & 2) *p++ = 'I';
+ *p = 0;
+
+ tFontColor = db_get_dw(NULL, "SRMsg", "Font0Col", 0);
+ }
+ else {
+ tFontColor = 0;
+ tFontStyle[0] = 0;
+ }
+
+ if (parFlags & MSG_OFFLINE)
+ off += mir_snprintf(buf + off, sizeof(buf) - off, "Dest-Agent: client\r\n");
+
+ off += mir_snprintf(buf + off, sizeof(buf) - off, "Content-Type: text/plain; charset=UTF-8\r\n");
+ off += mir_snprintf(buf + off, sizeof(buf) - off, "X-MMS-IM-Format: FN=%s; EF=%s; CO=%x; CS=0; PF=31%s\r\n\r\n",
+ tFontName, tFontStyle, tFontColor, (parFlags & MSG_RTL) ? ";RL=1" : "");
+ }
+
+ int seq;
+ if (netId == NETID_YAHOO || netId == NETID_MOB || (parFlags & MSG_OFFLINE))
+ seq = sendPacket("UUM", "%s %d %c %d\r\n%s%s", email, netId, msgType,
+ strlen(parMsg)+off, buf, parMsg);
+ else
+ seq = sendPacket("MSG", "%c %d\r\n%s%s", msgType,
+ strlen(parMsg)+off, buf, parMsg);
+
+ return seq;
+}
+
+void ThreadData::sendCaps(void)
+{
+ char mversion[100], capMsg[1000];
+ CallService(MS_SYSTEM_GETVERSIONTEXT, sizeof(mversion), (LPARAM)mversion);
+
+ mir_snprintf(capMsg, sizeof(capMsg),
+ "Content-Type: text/x-clientcaps\r\n\r\n"
+ "Client-Name: Miranda NG %s (MSN v.%s)\r\n",
+ mversion, __VERSION_STRING_DOTS);
+
+ sendMessage('U', NULL, 1, capMsg, MSG_DISABLE_HDR);
+}
+
+void ThreadData::sendTerminate(void)
+{
+ if (!termPending) {
+ sendPacket("OUT", NULL);
+ termPending = true;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_SendRawPacket - sends a packet accordingly to the MSN protocol
+
+int ThreadData::sendRawMessage(int msgType, const char* data, int datLen)
+{
+ if (data == NULL)
+ data = "";
+
+ if (datLen == -1)
+ datLen = (int)strlen(data);
+
+ char* buf = (char*)alloca(datLen + 100);
+
+ int thisTrid = InterlockedIncrement(&mTrid);
+ int nBytes = mir_snprintf(buf, 100, "MSG %d %c %d\r\nMIME-Version: 1.0\r\n",
+ thisTrid, msgType, datLen + 19);
+ memcpy(buf + nBytes, data, datLen);
+
+ send(buf, nBytes + datLen);
+
+ return thisTrid;
+}
+
+// Typing notifications support
+
+void CMsnProto::MSN_SendTyping(ThreadData* info, const char* email, int netId )
+{
+ char tCommand[1024];
+ mir_snprintf(tCommand, sizeof(tCommand),
+ "Content-Type: text/x-msmsgscontrol\r\n"
+ "TypingUser: %s\r\n\r\n\r\n", MyOptions.szEmail);
+
+ info->sendMessage(netId == NETID_MSN ? 'U' : '2', email, netId, tCommand, MSG_DISABLE_HDR);
+}
+
+
+static ThreadData* FindThreadTimer(UINT timerId)
+{
+ ThreadData* res = NULL;
+ for (int i = 0; i < g_Instances.getCount() && res == NULL; ++i)
+ res = g_Instances[i].MSN_GetThreadByTimer(timerId);
+
+ return res;
+}
+
+static VOID CALLBACK TypingTimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
+{
+ ThreadData* T = FindThreadTimer(idEvent);
+ if (T != NULL)
+ T->proto->MSN_SendTyping(T, NULL, 1);
+ else
+ KillTimer(NULL, idEvent);
+}
+
+
+void CMsnProto::MSN_StartStopTyping(ThreadData* info, bool start)
+{
+ if (start && info->mTimerId == 0) {
+ info->mTimerId = SetTimer(NULL, 0, 5000, TypingTimerProc);
+ MSN_SendTyping(info, NULL, 1);
+ }
+ else if (!start && info->mTimerId != 0) {
+ KillTimer(NULL, info->mTimerId);
+ info->mTimerId = 0;
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_SendStatusMessage - notify a server about the status message change
+
+// Helper to process texts
+static char * HtmlEncodeUTF8T(const TCHAR *src)
+{
+ if (src == NULL)
+ return mir_strdup("");
+
+ return HtmlEncode(UTF8(src));
+}
+
+void CMsnProto::MSN_SendStatusMessage(const char* msg)
+{
+ if (!msnLoggedIn)
+ return;
+
+ char* msgEnc = HtmlEncode(msg ? msg : "");
+
+ size_t sz;
+ char szMsg[2048];
+ if (msnCurrentMedia.cbSize == 0) {
+ sz = mir_snprintf(szMsg, sizeof(szMsg), "<Data><PSM>%s</PSM><CurrentMedia></CurrentMedia><MachineGuid>%s</MachineGuid>"
+ "<DDP></DDP><SignatureSound></SignatureSound><Scene></Scene><ColorScheme></ColorScheme></Data>",
+ msgEnc, MyOptions.szMachineGuid);
+ }
+ else {
+ char *szFormatEnc;
+ if (ServiceExists(MS_LISTENINGTO_GETPARSEDTEXT)) {
+ LISTENINGTOINFO lti = {0};
+ lti.cbSize = sizeof(lti);
+ if (msnCurrentMedia.ptszTitle != NULL) lti.ptszTitle = _T("{0}");
+ if (msnCurrentMedia.ptszArtist != NULL) lti.ptszArtist = _T("{1}");
+ if (msnCurrentMedia.ptszAlbum != NULL) lti.ptszAlbum = _T("{2}");
+ if (msnCurrentMedia.ptszTrack != NULL) lti.ptszTrack = _T("{3}");
+ if (msnCurrentMedia.ptszYear != NULL) lti.ptszYear = _T("{4}");
+ if (msnCurrentMedia.ptszGenre != NULL) lti.ptszGenre = _T("{5}");
+ if (msnCurrentMedia.ptszLength != NULL) lti.ptszLength = _T("{6}");
+ if (msnCurrentMedia.ptszPlayer != NULL) lti.ptszPlayer = _T("{7}");
+ if (msnCurrentMedia.ptszType != NULL) lti.ptszType = _T("{8}");
+
+ TCHAR *tmp = (TCHAR *)CallService(MS_LISTENINGTO_GETPARSEDTEXT, (WPARAM) _T("%title% - %artist%"), (LPARAM) &lti);
+ szFormatEnc = HtmlEncodeUTF8T(tmp);
+ mir_free(tmp);
+ }
+ else szFormatEnc = HtmlEncodeUTF8T(_T("{0} - {1}"));
+
+ char *szArtist = HtmlEncodeUTF8T(msnCurrentMedia.ptszArtist);
+ char *szAlbum = HtmlEncodeUTF8T(msnCurrentMedia.ptszAlbum);
+ char *szTitle = HtmlEncodeUTF8T(msnCurrentMedia.ptszTitle);
+ char *szTrack = HtmlEncodeUTF8T(msnCurrentMedia.ptszTrack);
+ char *szYear = HtmlEncodeUTF8T(msnCurrentMedia.ptszYear);
+ char *szGenre = HtmlEncodeUTF8T(msnCurrentMedia.ptszGenre);
+ char *szLength = HtmlEncodeUTF8T(msnCurrentMedia.ptszLength);
+ char *szPlayer = HtmlEncodeUTF8T(msnCurrentMedia.ptszPlayer);
+ char *szType = HtmlEncodeUTF8T(msnCurrentMedia.ptszType);
+
+ sz = mir_snprintf(szMsg, sizeof szMsg,
+ "<Data>"
+ "<PSM>%s</PSM>"
+ "<CurrentMedia>%s\\0%s\\01\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0%s\\0\\0</CurrentMedia>"
+ "<MachineGuid>%s</MachineGuid><DDP></DDP><SignatureSound></SignatureSound><Scene></Scene><ColorScheme></ColorScheme>"
+ "<DDP></DDP><SignatureSound></SignatureSound><Scene></Scene><ColorScheme></ColorScheme>"
+ "</Data>",
+ msgEnc, szPlayer, szType, szFormatEnc, szTitle, szArtist, szAlbum, szTrack, szYear, szGenre,
+ szLength, szPlayer, szType, MyOptions.szMachineGuid);
+
+ mir_free(szArtist);
+ mir_free(szAlbum);
+ mir_free(szTitle);
+ mir_free(szTrack);
+ mir_free(szYear);
+ mir_free(szGenre);
+ mir_free(szLength);
+ mir_free(szPlayer);
+ mir_free(szType);
+ }
+ mir_free(msgEnc);
+
+ if (msnPreviousUUX == NULL || strcmp(msnPreviousUUX, szMsg)) {
+ replaceStr(msnPreviousUUX, szMsg);
+ msnNsThread->sendPacket("UUX", "%d\r\n%s", sz, szMsg);
+ mStatusMsgTS = clock();
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_SendPacket - sends a packet accordingly to the MSN protocol
+
+int ThreadData::sendPacket(const char* cmd, const char* fmt,...)
+{
+ if (this == NULL) return 0;
+
+ size_t strsize = 512;
+ char* str = (char*)mir_alloc(strsize);
+
+ int thisTrid = 0;
+
+ if (fmt == NULL)
+ mir_snprintf(str, strsize, "%s", cmd);
+ else {
+ thisTrid = InterlockedIncrement(&mTrid);
+ if (fmt[0] == '\0')
+ mir_snprintf(str, strsize, "%s %d", cmd, thisTrid);
+ else {
+ va_list vararg;
+ va_start(vararg, fmt);
+
+ int paramStart = mir_snprintf(str, strsize, "%s %d ", cmd, thisTrid);
+ while (mir_vsnprintf(str+paramStart, strsize-paramStart-3, fmt, vararg) == -1)
+ str = (char*)mir_realloc(str, strsize += 512);
+
+ str[strsize-3] = 0;
+ va_end(vararg);
+ }
+ }
+
+ if (strchr(str, '\r') == NULL)
+ strcat(str,"\r\n");
+
+ int result = send(str, strlen(str));
+ mir_free(str);
+ return (result > 0) ? thisTrid : -1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_SetServerStatus - changes plugins status at the server
+
+void CMsnProto::MSN_SetServerStatus(int newStatus)
+{
+ debugLogA("Setting MSN server status %d, logged in = %d", newStatus, msnLoggedIn);
+
+ if (!msnLoggedIn)
+ return;
+
+ if (isIdle)
+ newStatus = ID_STATUS_IDLE;
+
+ const char* szStatusName = MirandaStatusToMSN(newStatus);
+
+ if (newStatus != ID_STATUS_OFFLINE) {
+ DBVARIANT msnObject = {0};
+ if (ServiceExists(MS_AV_SETMYAVATAR))
+ getString("PictObject", &msnObject);
+
+ // Capabilties: WLM 2009, Chunking, UUN Bootstrap
+ myFlags = 0xA0000000 | cap_SupportsChunking | cap_SupportsP2PBootstrap | cap_SupportsSipInvite;
+ if (getByte("MobileEnabled", 0) && getByte("MobileAllowed", 0))
+ myFlags |= cap_MobileEnabled;
+
+ unsigned myFlagsEx = capex_SupportsPeerToPeerV2;
+
+ char szMsg[256];
+ if (m_iStatus < ID_STATUS_ONLINE) {
+ int sz = mir_snprintf(szMsg, sizeof(szMsg),
+ "<EndpointData><Capabilities>%u:%u</Capabilities></EndpointData>", myFlags, myFlagsEx);
+ msnNsThread->sendPacket( "UUX", "%d\r\n%s", sz, szMsg );
+
+ msnNsThread->sendPacket("BLP", msnOtherContactsBlocked ? "BL" : "AL");
+
+ DBVARIANT dbv;
+ if (!getStringUtf("Nick", &dbv)) {
+ if (dbv.pszVal[0])
+ MSN_SetNicknameUtf(dbv.pszVal);
+ db_free(&dbv);
+ }
+ }
+
+ char *szPlace;
+ DBVARIANT dbv;
+ if (!getStringUtf("Place", &dbv))
+ szPlace = dbv.pszVal;
+ else {
+ TCHAR buf[128] = _T("Miranda");
+ DWORD buflen = SIZEOF(buf);
+ GetComputerName(buf, &buflen);
+ szPlace = mir_utf8encodeT(buf);
+ }
+
+ int sz = mir_snprintf(szMsg, sizeof(szMsg),
+ "<PrivateEndpointData>"
+ "<EpName>%s</EpName>"
+ "<Idle>%s</Idle>"
+ "<ClientType>1</ClientType>"
+ "<State>%s</State>"
+ "</PrivateEndpointData>",
+ szPlace, newStatus == ID_STATUS_IDLE ? "true" : "false", szStatusName);
+ msnNsThread->sendPacket("UUX", "%d\r\n%s", sz, szMsg);
+ mir_free(szPlace);
+
+ if (newStatus != ID_STATUS_IDLE) {
+ char** msgptr = GetStatusMsgLoc(newStatus);
+ if (msgptr != NULL)
+ MSN_SendStatusMessage(*msgptr);
+ }
+
+ msnNsThread->sendPacket("CHG", "%s %u:%u %s", szStatusName, myFlags, myFlagsEx, msnObject.pszVal ? msnObject.pszVal : "0");
+ db_free(&msnObject);
+ }
+ else msnNsThread->sendPacket("CHG", szStatusName);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Display Hotmail Inbox thread
+
+static const char postdataM[] = "ct=%u&bver=7&wa=wsignin1.0&ru=%s&pl=MBI";
+static const char postdataS[] = "ct=%u&bver=7&id=73625&ru=%s&js=yes&pl=%%3Fid%%3D73625";
+
+void CMsnProto::MsnInvokeMyURL(bool ismail, const char* url)
+{
+ char* hippy = NULL;
+ if (!url)
+ url = ismail ? "http://mail.live.com?rru=inbox" : "http://profile.live.com";
+
+ const char *postdata = ismail ? postdataM : postdataS;
+
+ char passport[256];
+ if (db_get_static(NULL, m_szModuleName, "MsnPassportHost", passport, 256))
+ strcpy(passport, "https://login.live.com/");
+
+ char *p = strchr(passport, '/');
+ if (p && p[1] == '/') p = strchr(p + 2, '/');
+ if (p)
+ *p = 0;
+
+ CMStringA post = HotmailLogin(CMStringA().Format(postdata, (unsigned)time(NULL), ptrA(mir_urlEncode(url))));
+ if (!post.IsEmpty()) {
+ CMStringA hippy(passport);
+ hippy.AppendFormat("/ppsecure/sha1auth.srf?lc=%d&token=%s", itoa(langpref, passport, 10), ptrA(mir_urlEncode(post)));
+
+ debugLogA("Starting URL: '%s'", hippy);
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)hippy.GetString());
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_ShowError - shows an error
+
+void CMsnProto::MSN_ShowError(const char* msgtext, ...)
+{
+ TCHAR tBuffer[4096];
+ va_list tArgs;
+
+ TCHAR *buf = Langpack_PcharToTchar(msgtext);
+
+ va_start(tArgs, msgtext);
+ mir_vsntprintf(tBuffer, SIZEOF(tBuffer), buf, tArgs);
+ va_end(tArgs);
+
+ mir_free(buf);
+
+ MSN_ShowPopup(m_tszUserName, tBuffer, MSN_ALLOW_MSGBOX | MSN_SHOW_ERROR, NULL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup plugin window proc
+
+LRESULT CALLBACK NullWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ PopupData *tData = (PopupData*)PUGetPluginData(hWnd);
+
+ switch (msg) {
+ case WM_COMMAND:
+ if (tData != NULL) {
+ if (tData->flags & MSN_HOTMAIL_POPUP) {
+ MCONTACT hContact = tData->proto->MSN_HContactFromEmail(tData->proto->MyOptions.szEmail, NULL);
+ if (hContact) CallService(MS_CLIST_REMOVEEVENT, hContact, (LPARAM) 1);
+ if (tData->flags & MSN_ALLOW_ENTER)
+ tData->proto->MsnInvokeMyURL(true, tData->url);
+ }
+ else if (tData->url != NULL)
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)tData->url);
+ }
+ PUDeletePopup(hWnd);
+ break;
+
+ case WM_CONTEXTMENU:
+ if (tData != NULL && tData->flags & MSN_HOTMAIL_POPUP) {
+ MCONTACT hContact = tData->proto->MSN_HContactFromEmail(tData->proto->MyOptions.szEmail, NULL);
+ if (hContact)
+ CallService(MS_CLIST_REMOVEEVENT, hContact, (LPARAM) 1);
+ }
+ PUDeletePopup(hWnd);
+ break;
+
+ case UM_FREEPLUGINDATA:
+ if (tData != NULL && tData != (PopupData*)CALLSERVICE_NOTFOUND) {
+ mir_free(tData->title);
+ mir_free(tData->text);
+ mir_free(tData->url);
+ mir_free(tData);
+ }
+ break;
+ }
+
+ return DefWindowProc(hWnd, msg, wParam, lParam);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// InitPopups - popup plugin support
+
+void CMsnProto::InitPopups(void)
+{
+ TCHAR desc[256];
+ char name[256];
+
+ POPUPCLASS ppc = { sizeof(ppc) };
+ ppc.flags = PCF_TCHAR;
+ ppc.PluginWindowProc = NullWindowProc;
+ ppc.hIcon = LoadIconEx("main");
+ ppc.ptszDescription = desc;
+ ppc.pszName = name;
+
+ ppc.colorBack = RGB(173, 206, 247);
+ ppc.colorText = GetSysColor(COLOR_WINDOWTEXT);
+ ppc.iSeconds = 3;
+ mir_sntprintf(desc, SIZEOF(desc), _T("%s/%s"), m_tszUserName, TranslateT("Hotmail"));
+ mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Hotmail");
+ hPopupHotmail = Popup_RegisterClass(&ppc);
+
+ ppc.colorBack = RGB(173, 206, 247);
+ ppc.colorText = GetSysColor(COLOR_WINDOWTEXT);
+ ppc.iSeconds = 3;
+ mir_sntprintf(desc, SIZEOF(desc), _T("%s/%s"), m_tszUserName, TranslateT("Notify"));
+ mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Notify");
+ hPopupNotify = Popup_RegisterClass(&ppc);
+
+ ppc.hIcon = (HICON)LoadImage(NULL, IDI_WARNING, IMAGE_ICON, 0, 0, LR_SHARED);
+ ppc.colorBack = RGB(191, 0, 0); //Red
+ ppc.colorText = RGB(255, 245, 225); //Yellow
+ ppc.iSeconds = 60;
+
+ mir_sntprintf(desc, SIZEOF(desc), _T("%s/%s"), m_tszUserName, TranslateT("Error"));
+ mir_snprintf(name, SIZEOF(name), "%s_%s", m_szModuleName, "Error");
+ hPopupError = Popup_RegisterClass(&ppc);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_ShowPopup - popup plugin support
+
+void CALLBACK sttMainThreadCallback(PVOID dwParam)
+{
+ PopupData* pud = (PopupData*)dwParam;
+
+ bool iserr = (pud->flags & MSN_SHOW_ERROR) != 0;
+ if ((iserr && !pud->proto->MyOptions.ShowErrorsAsPopups) || !ServiceExists(MS_POPUP_ADDPOPUPCLASS)) {
+ if (pud->flags & MSN_ALLOW_MSGBOX) {
+ TCHAR szMsg[MAX_SECONDLINE + MAX_CONTACTNAME];
+ mir_sntprintf(szMsg, SIZEOF(szMsg), _T("%s:\n%s"), pud->title, pud->text);
+ MessageBox(NULL, szMsg, TranslateT("MSN Protocol"),
+ MB_OK | (iserr ? MB_ICONERROR : MB_ICONINFORMATION));
+ }
+ mir_free(pud->title);
+ mir_free(pud->text);
+ mir_free(pud->url);
+ mir_free(pud);
+ return;
+ }
+
+ char name[256];
+
+ POPUPDATACLASS ppd = { sizeof(ppd) };
+ ppd.ptszTitle = pud->title;
+ ppd.ptszText = pud->text;
+ ppd.PluginData = pud;
+ ppd.pszClassName = name;
+
+ if (pud->flags & MSN_SHOW_ERROR)
+ mir_snprintf(name, SIZEOF(name), "%s_%s", pud->proto->m_szModuleName, "Error");
+ else if (pud->flags & (MSN_HOTMAIL_POPUP | MSN_ALERT_POPUP))
+ mir_snprintf(name, SIZEOF(name), "%s_%s", pud->proto->m_szModuleName, "Hotmail");
+ else
+ mir_snprintf(name, SIZEOF(name), "%s_%s", pud->proto->m_szModuleName, "Notify");
+
+ CallService(MS_POPUP_ADDPOPUPCLASS, 0, (LPARAM)&ppd);
+}
+
+void CMsnProto::MSN_ShowPopup(const TCHAR* nickname, const TCHAR* msg, int flags, const char* url, MCONTACT hContact)
+{
+ if (Miranda_Terminated()) return;
+
+ PopupData *pud = (PopupData*)mir_calloc(sizeof(PopupData));
+ pud->flags = flags;
+ pud->url = mir_strdup(url);
+ pud->title = mir_tstrdup(nickname);
+ pud->text = mir_tstrdup(msg);
+ pud->proto = this;
+
+ CallFunctionAsync(sttMainThreadCallback, pud);
+}
+
+
+void CMsnProto::MSN_ShowPopup(const MCONTACT hContact, const TCHAR* msg, int flags)
+{
+ const TCHAR* nickname = hContact ? GetContactNameT(hContact) : _T("Me");
+ MSN_ShowPopup(nickname, msg, flags, NULL, hContact);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// filetransfer class members
+
+filetransfer::filetransfer(CMsnProto* prt)
+{
+ memset(this, 0, sizeof(filetransfer));
+ fileId = -1;
+ std.cbSize = sizeof(std);
+ std.flags = PFTS_TCHAR;
+ proto = prt;
+
+ hLockHandle = CreateMutex(NULL, FALSE, NULL);
+}
+
+filetransfer::~filetransfer(void)
+{
+ if (p2p_sessionid)
+ proto->debugLogA("Destroying file transfer session %08X", p2p_sessionid);
+
+ WaitForSingleObject(hLockHandle, 2000);
+ CloseHandle(hLockHandle);
+
+ if (fileId != -1)
+ {
+ _close(fileId);
+ if (p2p_appID != MSN_APPID_FILE && !(std.flags & PFTS_SENDING))
+ proto->p2p_pictureTransferFailed(this);
+ }
+
+ if (!bCompleted && p2p_appID == MSN_APPID_FILE)
+ {
+ std.ptszFiles = NULL;
+ std.totalFiles = 0;
+ proto->ProtoBroadcastAck(std.hContact, ACKTYPE_FILE, ACKRESULT_FAILED, this, 0);
+ }
+
+ mir_free(p2p_branch);
+ mir_free(p2p_callID);
+ mir_free(p2p_dest);
+ mir_free(p2p_object);
+
+ mir_free(std.tszCurrentFile);
+ mir_free(std.tszWorkingDir);
+ if (std.ptszFiles != NULL)
+ {
+ for (int i=0; std.ptszFiles[i]; i++)
+ mir_free(std.ptszFiles[i]);
+ mir_free(std.ptszFiles);
+ }
+
+ mir_free(szInvcookie);
+}
+
+void filetransfer::close(void)
+{
+ if (fileId != -1) _close(fileId);
+ fileId = -1;
+}
+
+void filetransfer::complete(void)
+{
+ close();
+
+ bCompleted = true;
+ proto->ProtoBroadcastAck(std.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, this, 0);
+}
+
+int filetransfer::create(void)
+{
+ fileId = _topen(std.tszCurrentFile, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE);
+
+ if (fileId == -1)
+ proto->MSN_ShowError("Cannot create file '%s' during a file transfer", std.tszCurrentFile);
+ // else if (std.currentFileSize != 0)
+ // _chsize(fileId, std.currentFileSize);
+
+ return fileId;
+}
+
+int filetransfer::openNext(void)
+{
+ if (fileId != -1)
+ {
+ close();
+ ++std.currentFileNumber;
+ ++cf;
+ }
+
+ while (std.ptszFiles && std.ptszFiles[cf])
+ {
+ struct _stati64 statbuf;
+ if (_tstati64(std.ptszFiles[cf], &statbuf) == 0 && (statbuf.st_mode & _S_IFDIR) == 0)
+ break;
+
+ ++cf;
+ }
+
+ if (std.ptszFiles && std.ptszFiles[cf])
+ {
+ bCompleted = false;
+ replaceStrT(std.tszCurrentFile, std.ptszFiles[cf]);
+ fileId = _topen(std.tszCurrentFile, _O_BINARY | _O_RDONLY, _S_IREAD);
+ if (fileId != -1)
+ {
+ std.currentFileSize = _filelengthi64(fileId);
+ std.currentFileProgress = 0;
+
+ p2p_sendmsgid = 0;
+ p2p_byemsgid = 0;
+ tType = SERVER_NOTIFICATION;
+ bAccepted = false;
+
+ mir_free(p2p_branch); p2p_branch = NULL;
+ mir_free(p2p_callID); p2p_callID = NULL;
+ }
+ else
+ proto->MSN_ShowError("Unable to open file '%s' for the file transfer, error %d", std.tszCurrentFile, errno);
+ }
+
+ return fileId;
+}
+
+directconnection::directconnection(const char* CallID, const char* Wlid)
+{
+ memset(this, 0, sizeof(directconnection));
+
+ wlid = mir_strdup(Wlid);
+ callId = mir_strdup(CallID);
+ mNonce = (UUID*)mir_alloc(sizeof(UUID));
+ UuidCreate(mNonce);
+ ts = time(NULL);
+}
+
+directconnection::~directconnection()
+{
+ mir_free(wlid);
+ mir_free(callId);
+ mir_free(mNonce);
+ mir_free(xNonce);
+}
+
+
+char* directconnection::calcHashedNonce(UUID* nonce)
+{
+ mir_sha1_ctx sha1ctx;
+ BYTE sha[MIR_SHA1_HASH_SIZE];
+
+ mir_sha1_init(&sha1ctx);
+ mir_sha1_append(&sha1ctx, (BYTE*)nonce, sizeof(UUID));
+ mir_sha1_finish(&sha1ctx, sha);
+
+ char* p;
+ UuidToStringA((UUID*)&sha, (BYTE**)&p);
+ size_t len = strlen(p) + 3;
+ char* result = (char*)mir_alloc(len);
+ mir_snprintf(result, len, "{%s}", p);
+ _strupr(result);
+ RpcStringFreeA((BYTE**)&p);
+
+ return result;
+}
+
+char* directconnection::mNonceToText(void)
+{
+ char* p;
+ UuidToStringA(mNonce, (BYTE**)&p);
+ size_t len = strlen(p) + 3;
+ char* result = (char*)mir_alloc(len);
+ mir_snprintf(result, len, "{%s}", p);
+ _strupr(result);
+ RpcStringFreeA((BYTE**)&p);
+
+ return result;
+}
+
+
+void directconnection::xNonceToBin(UUID* nonce)
+{
+ size_t len = strlen(xNonce);
+ char *p = (char*)alloca(len);
+ strcpy(p, xNonce + 1);
+ p[len-2] = 0;
+ UuidFromStringA((BYTE*)p, nonce);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// TWinErrorCode class
+
+TWinErrorCode::TWinErrorCode() : mErrorText(NULL)
+{
+ mErrorCode = ::GetLastError();
+}
+
+TWinErrorCode::~TWinErrorCode()
+{
+ mir_free(mErrorText);
+}
+
+char* TWinErrorCode::getText()
+{
+ if (mErrorText == NULL)
+ return NULL;
+
+ int tBytes = 0;
+ mErrorText = (char*)mir_alloc(256);
+
+ if (tBytes == 0)
+ tBytes = FormatMessageA(
+ FORMAT_MESSAGE_FROM_SYSTEM, NULL,
+ mErrorCode, LANG_NEUTRAL, mErrorText, 256, NULL);
+
+ if (tBytes == 0)
+ tBytes = mir_snprintf(mErrorText, 256, "unknown Windows error code %d", mErrorCode);
+
+ *mErrorText = (char)tolower(*mErrorText);
+
+ if (mErrorText[tBytes-1] == '\n')
+ mErrorText[--tBytes] = 0;
+ if (mErrorText[tBytes-1] == '\r')
+ mErrorText[--tBytes] = 0;
+ if (mErrorText[tBytes-1] == '.')
+ mErrorText[tBytes-1] = 0;
+
+ return mErrorText;
+}
+
+bool CMsnProto::MSN_IsMyContact(MCONTACT hContact)
+{
+ const char* szProto = GetContactProto(hContact);
+ return szProto != NULL && strcmp(m_szModuleName, szProto) == 0;
+}
+
+bool CMsnProto::MSN_IsMeByContact(MCONTACT hContact, char* szEmail)
+{
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ char *emailPtr = szEmail ? szEmail : tEmail;
+
+ *emailPtr = 0;
+ if (db_get_static(hContact, m_szModuleName, "e-mail", emailPtr, sizeof(tEmail)))
+ return false;
+
+ return _stricmp(emailPtr, MyOptions.szEmail) == 0;
+}
+
+bool MSN_MsgWndExist(MCONTACT hContact)
+{
+ MessageWindowInputData msgWinInData =
+ { sizeof(MessageWindowInputData), hContact, MSG_WINDOW_UFLAG_MSG_BOTH };
+ MessageWindowData msgWinData = {0};
+ msgWinData.cbSize = sizeof(MessageWindowData);
+
+ bool res = CallService(MS_MSG_GETWINDOWDATA, (WPARAM)&msgWinInData, (LPARAM)&msgWinData) != 0;
+ res = res || msgWinData.hwndWindow;
+ if (res) {
+ msgWinInData.hContact = db_mc_getMeta(hContact);
+ if (msgWinInData.hContact != NULL) {
+ res = CallService(MS_MSG_GETWINDOWDATA, (WPARAM)&msgWinInData, (LPARAM)&msgWinData) != 0;
+ res |= (msgWinData.hwndWindow == NULL);
+ }
+ }
+ return res;
+}
+
+void MSN_MakeDigest(const char* chl, char* dgst)
+{
+ //Digest it
+ DWORD md5hash[4], md5hashOr[4];
+ mir_md5_state_t context;
+ mir_md5_init(&context);
+ mir_md5_append(&context, (BYTE*)chl, (int)strlen(chl));
+ mir_md5_append(&context, (BYTE*)msnProtChallenge, (int)strlen(msnProtChallenge));
+ mir_md5_finish(&context, (BYTE*)md5hash);
+
+ memcpy(md5hashOr, md5hash, sizeof(md5hash));
+
+ size_t i;
+ for (i=0; i < 4; i++)
+ md5hash[i] &= 0x7FFFFFFF;
+
+ char chlString[128];
+ mir_snprintf(chlString, sizeof(chlString), "%s%s00000000", chl, msnProductID);
+ chlString[(strlen(chl)+strlen(msnProductID)+7) & 0xF8] = 0;
+
+ LONGLONG high=0, low=0;
+ int* chlStringArray = (int*)chlString;
+ for (i=0; i < strlen(chlString) / 4; i += 2) {
+ LONGLONG temp = chlStringArray[i];
+
+ temp = (0x0E79A9C1 * temp) % 0x7FFFFFFF;
+ temp += high;
+ temp = md5hash[0] * temp + md5hash[1];
+ temp = temp % 0x7FFFFFFF;
+
+ high = chlStringArray[i + 1];
+ high = (high + temp) % 0x7FFFFFFF;
+ high = md5hash[2] * high + md5hash[3];
+ high = high % 0x7FFFFFFF;
+
+ low = low + high + temp;
+ }
+ high = (high + md5hash[1]) % 0x7FFFFFFF;
+ low = (low + md5hash[3]) % 0x7FFFFFFF;
+
+ md5hashOr[0] ^= high;
+ md5hashOr[1] ^= low;
+ md5hashOr[2] ^= high;
+ md5hashOr[3] ^= low;
+
+ char* str = arrayToHex((PBYTE)md5hashOr, sizeof(md5hashOr));
+ strcpy(dgst, str);
+ mir_free(str);
+}
+
+char* GetGlobalIp(void)
+{
+ NETLIBIPLIST* ihaddr = (NETLIBIPLIST*)CallService(MS_NETLIB_GETMYIP, 1, 0);
+ for (unsigned i = 0; i < ihaddr->cbNum; ++i)
+ if (strchr(ihaddr->szIp[i], ':'))
+ return mir_strdup(ihaddr->szIp[i]);
+
+ mir_free(ihaddr);
+ return NULL;
+}
diff --git a/protocols/MSN/src/msn_msgqueue.cpp b/protocols/MSN/src/msn_msgqueue.cpp
new file mode 100644
index 0000000000..c11887c53d
--- /dev/null
+++ b/protocols/MSN/src/msn_msgqueue.cpp
@@ -0,0 +1,190 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+//a few little functions to manage queuing send message requests until the
+//connection is established
+
+void CMsnProto::MsgQueue_Init(void)
+{
+ msgQueueSeq = 1;
+}
+
+void CMsnProto::MsgQueue_Uninit(void)
+{
+ MsgQueue_Clear();
+}
+
+int CMsnProto::MsgQueue_Add(const char* wlid, int msgType, const char* msg, int msgSize, filetransfer* ft, int flags, STRLIST *cnt)
+{
+ mir_cslock lck(csMsgQueue);
+
+ MsgQueueEntry* E = new MsgQueueEntry;
+ lsMessageQueue.insert(E);
+
+ int seq = msgQueueSeq++;
+
+ E->wlid = mir_strdup(wlid);
+ E->msgSize = msgSize;
+ E->msgType = msgType;
+ if (msgSize <= 0)
+ E->message = mir_strdup(msg);
+ else
+ memcpy(E->message = (char*)mir_alloc(msgSize), msg, msgSize);
+ E->ft = ft;
+ E->cont = cnt;
+ E->seq = seq;
+ E->flags = flags;
+ E->allocatedToThread = 0;
+ E->ts = time(NULL);
+ return seq;
+}
+
+// shall we create another session?
+const char* CMsnProto::MsgQueue_CheckContact(const char* wlid, time_t tsc)
+{
+ time_t ts = time(NULL);
+
+ mir_cslock lck(csMsgQueue);
+
+ for (int i=0; i < lsMessageQueue.getCount(); i++)
+ if (_stricmp(lsMessageQueue[i].wlid, wlid) == 0 && (tsc == 0 || (ts - lsMessageQueue[i].ts) < tsc))
+ return wlid;
+
+ return NULL;
+}
+
+//for threads to determine who they should connect to
+const char* CMsnProto::MsgQueue_GetNextRecipient(void)
+{
+ mir_cslock lck(csMsgQueue);
+
+ for (int i=0; i < lsMessageQueue.getCount(); i++)
+ {
+ MsgQueueEntry& E = lsMessageQueue[i];
+ if (!E.allocatedToThread)
+ {
+ E.allocatedToThread = 1;
+
+ const char *ret = E.wlid;
+ while(++i < lsMessageQueue.getCount())
+ if (_stricmp(lsMessageQueue[i].wlid, ret) == 0)
+ lsMessageQueue[i].allocatedToThread = 1;
+
+ return ret;
+ }
+ }
+
+ return NULL;
+}
+
+//deletes from list. Must mir_free() return value
+bool CMsnProto::MsgQueue_GetNext(const char* wlid, MsgQueueEntry& retVal)
+{
+ int i;
+
+ mir_cslock lck(csMsgQueue);
+ for (i = 0; i < lsMessageQueue.getCount(); i++)
+ if (_stricmp(lsMessageQueue[i].wlid, wlid) == 0)
+ break;
+
+ bool res = i != lsMessageQueue.getCount();
+ if (res)
+ {
+ retVal = lsMessageQueue[i];
+ lsMessageQueue.remove(i);
+ }
+
+ return res;
+}
+
+int CMsnProto::MsgQueue_NumMsg(const char* wlid)
+{
+ int res = 0;
+ mir_cslock lck(csMsgQueue);
+
+ for(int i=0; i < lsMessageQueue.getCount(); i++)
+ res += (_stricmp(lsMessageQueue[i].wlid, wlid) == 0);
+
+ return res;
+}
+
+void CMsnProto::MsgQueue_Clear(const char* wlid, bool msg)
+{
+ int i;
+
+ mir_cslockfull lck(csMsgQueue);
+ if (wlid == NULL)
+ {
+ for(i=0; i < lsMessageQueue.getCount(); i++)
+ {
+ const MsgQueueEntry& E = lsMessageQueue[i];
+ if (E.msgSize == 0)
+ {
+ MCONTACT hContact = MSN_HContactFromEmail(E.wlid);
+ ProtoBroadcastAck(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED,
+ (HANDLE)E.seq, (LPARAM)Translate("Message delivery failed"));
+ }
+ mir_free(E.message);
+ mir_free(E.wlid);
+ if (E.cont) delete E.cont;
+ }
+ lsMessageQueue.destroy();
+
+ msgQueueSeq = 1;
+ }
+ else
+ {
+ for(i=0; i < lsMessageQueue.getCount(); i++)
+ {
+ time_t ts = time(NULL);
+ const MsgQueueEntry& E = lsMessageQueue[i];
+ if (_stricmp(lsMessageQueue[i].wlid, wlid) == 0 && (!msg || E.msgSize == 0))
+ {
+ bool msgfnd = E.msgSize == 0 && E.ts < ts;
+ int seq = E.seq;
+
+ mir_free(E.message);
+ mir_free(E.wlid);
+ if (E.cont) delete E.cont;
+ lsMessageQueue.remove(i);
+
+ if (msgfnd) {
+ lck.unlock();
+ MCONTACT hContact = MSN_HContactFromEmail(wlid);
+ ProtoBroadcastAck(hContact, ACKTYPE_MESSAGE, ACKRESULT_FAILED, (HANDLE)seq,
+ (LPARAM)Translate("Message delivery failed"));
+ i = 0;
+ lck.lock();
+ }
+ }
+ }
+ }
+}
+
+void __cdecl CMsnProto::MsgQueue_AllClearThread(void* arg)
+{
+ MsgQueue_Clear((char*)arg);
+ mir_free(arg);
+}
diff --git a/protocols/MSN/src/msn_msgsplit.cpp b/protocols/MSN/src/msn_msgsplit.cpp
new file mode 100644
index 0000000000..ab0e3bb11d
--- /dev/null
+++ b/protocols/MSN/src/msn_msgsplit.cpp
@@ -0,0 +1,124 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2007-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+
+chunkedmsg::chunkedmsg(const char* tid, const size_t totsz, const bool tbychunk)
+ : size(totsz), recvsz(0), bychunk(tbychunk)
+{
+ id = mir_strdup(tid);
+ msg = tbychunk ? NULL : (char*)mir_alloc(totsz + 1);
+}
+
+chunkedmsg::~chunkedmsg()
+{
+ mir_free(id);
+ mir_free(msg);
+}
+
+void chunkedmsg::add(const char* tmsg, size_t offset, size_t portion)
+{
+ if (bychunk)
+ {
+ size_t oldsz = recvsz;
+ recvsz += portion;
+ msg = (char*)mir_realloc(msg, recvsz + 1);
+ memcpy( msg + oldsz, tmsg, portion );
+ --size;
+ }
+ else
+ {
+ size_t newsz = offset + portion;
+ if (newsz > size)
+ {
+ portion = size - offset;
+ newsz = size;
+ }
+ memcpy(msg + offset, tmsg, portion);
+ if (newsz > recvsz) recvsz = newsz;
+ }
+}
+
+bool chunkedmsg::get(char*& tmsg, size_t& tsize)
+{
+ bool alldata = bychunk ? size == 0 : recvsz == size;
+ if (alldata)
+ {
+ msg[recvsz] = 0;
+ tmsg = msg;
+ tsize = recvsz;
+ msg = NULL;
+ }
+
+ return alldata;
+}
+
+
+int CMsnProto::addCachedMsg(const char* id, const char* msg, const size_t offset,
+ const size_t portion, const size_t totsz, const bool bychunk)
+{
+ int idx = msgCache.getIndex((chunkedmsg*)&id);
+ if (idx == -1)
+ {
+ msgCache.insert(new chunkedmsg(id, totsz, bychunk));
+ idx = msgCache.getIndex((chunkedmsg*)&id);
+ }
+
+ msgCache[idx].add(msg, offset, portion);
+
+ return idx;
+}
+
+size_t CMsnProto::getCachedMsgSize(const char* id)
+{
+ int idx = msgCache.getIndex((chunkedmsg*)&id);
+ return idx != -1 ? msgCache[idx].size : 0;
+}
+
+bool CMsnProto::getCachedMsg(int idx, char*& msg, size_t& size)
+{
+ bool res = msgCache[idx].get(msg, size);
+ if (res)
+ msgCache.remove(idx);
+
+ return res;
+}
+
+bool CMsnProto::getCachedMsg(const char* id, char*& msg, size_t& size)
+{
+ int idx = msgCache.getIndex((chunkedmsg*)&id);
+ return idx != -1 && getCachedMsg(idx, msg, size);
+}
+
+
+void CMsnProto::clearCachedMsg(int idx)
+{
+ if (idx != -1)
+ msgCache.remove(idx);
+ else
+ msgCache.destroy();
+}
+
+void CMsnProto::CachedMsg_Uninit(void)
+{
+ clearCachedMsg();
+}
diff --git a/protocols/MSN/src/msn_natdetect.cpp b/protocols/MSN/src/msn_natdetect.cpp
new file mode 100644
index 0000000000..c6569ff817
--- /dev/null
+++ b/protocols/MSN/src/msn_natdetect.cpp
@@ -0,0 +1,496 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2007-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+#include <netfw.h>
+
+#ifndef CLSID_NetFwMgr
+#define MDEF_CLSID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
+ const CLSID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
+
+ MDEF_CLSID(CLSID_NetFwMgr, 0x304ce942, 0x6e39, 0x40d8, 0x94, 0x3a, 0xb9, 0x13, 0xc4, 0x0c, 0x9c, 0xd4);
+ MDEF_CLSID(IID_INetFwMgr, 0xf7898af5, 0xcac4, 0x4632, 0xa2, 0xec, 0xda ,0x06, 0xe5, 0x11, 0x1a, 0xf2);
+#endif
+
+
+MyConnectionType MyConnection;
+
+const char* conStr[] =
+{
+ "Unknown-Connect",
+ "Direct-Connect",
+ "Unknown-NAT",
+ "IP-Restrict-NAT",
+ "Port-Restrict-NAT",
+ "Symmetric-NAT",
+ "Firewall",
+ "ISALike"
+};
+
+
+void CMsnProto::DecryptEchoPacket(UDPProbePkt& pkt)
+{
+ pkt.clientPort ^= 0x3141;
+ pkt.discardPort ^= 0x3141;
+ pkt.testPort ^= 0x3141;
+ pkt.clientIP ^= 0x31413141;
+ pkt.testIP ^= 0x31413141;
+
+
+ IN_ADDR addr;
+ debugLogA("Echo packet: version: 0x%x service code: 0x%x transaction ID: 0x%x",
+ pkt.version, pkt.serviceCode, pkt.trId);
+ addr.S_un.S_addr = pkt.clientIP;
+ debugLogA("Echo packet: client port: %u client addr: %s",
+ pkt.clientPort, inet_ntoa(addr));
+ addr.S_un.S_addr = pkt.testIP;
+ debugLogA("Echo packet: discard port: %u test port: %u test addr: %s",
+ pkt.discardPort, pkt.testPort, inet_ntoa(addr));
+}
+
+
+static void DiscardExtraPackets(SOCKET s)
+{
+ Sleep(3000);
+
+ static const TIMEVAL tv = {0, 0};
+ unsigned buf;
+
+ for (;;)
+ {
+ if (Miranda_Terminated()) break;
+
+ fd_set fd;
+ FD_ZERO(&fd);
+ FD_SET(s, &fd);
+
+ if (select(1, &fd, NULL, NULL, &tv) == 1)
+ recv(s, (char*)&buf, sizeof(buf), 0);
+ else
+ break;
+ }
+}
+
+
+void CMsnProto::MSNatDetect(void)
+{
+ unsigned i;
+
+ PHOSTENT host = gethostbyname("echo.edge.messenger.live.com");
+ if (host == NULL)
+ {
+ debugLogA("P2PNAT could not find echo server \"echo.edge.messenger.live.com\"");
+ return;
+ }
+
+ SOCKADDR_IN addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = _htons(7001);
+ addr.sin_addr = *( PIN_ADDR )host->h_addr_list[0];
+
+ debugLogA("P2PNAT Detected echo server IP %d.%d.%d.%d",
+ addr.sin_addr.S_un.S_un_b.s_b1, addr.sin_addr.S_un.S_un_b.s_b2,
+ addr.sin_addr.S_un.S_un_b.s_b3, addr.sin_addr.S_un.S_un_b.s_b4);
+
+ SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ connect(s, (SOCKADDR*)&addr, sizeof(addr));
+
+ UDPProbePkt pkt = { 0 };
+ UDPProbePkt pkt2;
+
+ // Detect My IP
+ pkt.version = 2;
+ pkt.serviceCode = 4;
+ send(s, (char*)&pkt, sizeof(pkt), 0);
+
+ SOCKADDR_IN myaddr;
+ int szname = sizeof(myaddr);
+ getsockname(s, (SOCKADDR*)&myaddr, &szname);
+
+ MyConnection.intIP = myaddr.sin_addr.S_un.S_addr;
+ debugLogA("P2PNAT Detected IP facing internet %d.%d.%d.%d",
+ myaddr.sin_addr.S_un.S_un_b.s_b1, myaddr.sin_addr.S_un.S_un_b.s_b2,
+ myaddr.sin_addr.S_un.S_un_b.s_b3, myaddr.sin_addr.S_un.S_un_b.s_b4);
+
+ SOCKET s1 = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+
+ pkt.version = 2;
+ pkt.serviceCode = 1;
+ pkt.clientPort = 0x3141;
+ pkt.clientIP = 0x31413141;
+
+ UDPProbePkt rpkt = {0};
+
+ // NAT detection
+ for (i=0; i<4; ++i)
+ {
+ if (Miranda_Terminated()) break;
+
+ // Send echo request to server 1
+ debugLogA("P2PNAT Request 1 attempt %d sent", i);
+ sendto(s1, (char*)&pkt, sizeof(pkt), 0, (SOCKADDR*)&addr, sizeof(addr));
+
+ fd_set fd;
+ FD_ZERO(&fd);
+ FD_SET(s1, &fd);
+ TIMEVAL tv = {0, 200000 * (1 << i) };
+
+ if (select(1, &fd, NULL, NULL, &tv) == 1)
+ {
+ debugLogA("P2PNAT Request 1 attempt %d response", i);
+ recv(s1, (char*)&rpkt, sizeof(rpkt), 0);
+ pkt2 = rpkt;
+ DecryptEchoPacket(rpkt);
+ break;
+ }
+ else
+ debugLogA("P2PNAT Request 1 attempt %d timeout", i);
+ }
+
+ closesocket(s);
+
+ // Server did not respond
+ if (i >= 4)
+ {
+ MyConnection.udpConType = conFirewall;
+ closesocket(s1);
+ return;
+ }
+
+ MyConnection.extIP = rpkt.clientIP;
+
+ // Check if NAT not found
+ if (MyConnection.extIP == MyConnection.intIP)
+ {
+ if (msnExternalIP != NULL && inet_addr(msnExternalIP) != MyConnection.extIP)
+ MyConnection.udpConType = conISALike;
+ else
+ MyConnection.udpConType = conDirect;
+
+ closesocket(s1);
+ return;
+ }
+
+ // Detect UPnP NAT
+ NETLIBBIND nlb = {0};
+ nlb.cbSize = sizeof(nlb);
+ nlb.pfnNewConnectionV2 = MSN_ConnectionProc;
+ nlb.pExtra = this;
+
+ HANDLE sb = (HANDLE) CallService(MS_NETLIB_BINDPORT, (WPARAM)m_hNetlibUser, (LPARAM)&nlb);
+ if ( sb != NULL )
+ {
+ MyConnection.upnpNAT = htonl(nlb.dwExternalIP) == MyConnection.extIP;
+ Sleep(100);
+ Netlib_CloseHandle(sb);
+ }
+
+ DiscardExtraPackets(s1);
+
+ // Start IP Restricted NAT detection
+ UDPProbePkt rpkt2 = {0};
+ pkt2.serviceCode = 3;
+ SOCKADDR_IN addr2 = addr;
+ addr2.sin_addr.S_un.S_addr = rpkt.testIP;
+ addr2.sin_port = rpkt.discardPort;
+ for (i=0; i<4; ++i)
+ {
+ if (Miranda_Terminated()) break;
+
+ debugLogA("P2PNAT Request 2 attempt %d sent", i);
+ // Remove IP restriction for server 2
+ sendto(s1, NULL, 0, 0, (SOCKADDR*)&addr2, sizeof(addr2));
+ // Send echo request to server 1 for server 2
+ sendto(s1, (char*)&pkt2, sizeof(pkt2), 0, (SOCKADDR*)&addr, sizeof(addr));
+
+ fd_set fd;
+ FD_ZERO(&fd);
+ FD_SET(s1, &fd);
+ TIMEVAL tv = {0, 200000 * (1 << i) };
+
+ if (select(1, &fd, NULL, NULL, &tv) == 1)
+ {
+ debugLogA("P2PNAT Request 2 attempt %d response", i);
+ recv(s1, (char*)&rpkt2, sizeof(rpkt2), 0);
+ DecryptEchoPacket(rpkt2);
+ break;
+ }
+ else
+ debugLogA("P2PNAT Request 2 attempt %d timeout", i);
+ }
+
+ // Response recieved so it's an IP Restricted NAT (Restricted Cone NAT)
+ // (MSN does not detect Full Cone NAT and consider it as IP Restricted NAT)
+ if (i < 4)
+ {
+ MyConnection.udpConType = conIPRestrictNAT;
+ closesocket(s1);
+ return;
+ }
+
+ DiscardExtraPackets(s1);
+
+ // Symmetric NAT detection
+ addr2.sin_port = rpkt.testPort;
+ for (i=0; i<4; ++i)
+ {
+ if (Miranda_Terminated()) break;
+
+ debugLogA("P2PNAT Request 3 attempt %d sent", i);
+ // Send echo request to server 1
+ sendto(s1, (char*)&pkt, sizeof(pkt), 0, (SOCKADDR*)&addr2, sizeof(addr2));
+
+ fd_set fd;
+ FD_ZERO(&fd);
+ FD_SET(s1, &fd);
+ TIMEVAL tv = {1 << i, 0 };
+
+ if ( select(1, &fd, NULL, NULL, &tv) == 1 )
+ {
+ debugLogA("P2PNAT Request 3 attempt %d response", i);
+ recv(s1, (char*)&rpkt2, sizeof(rpkt2), 0);
+ DecryptEchoPacket(rpkt2);
+ break;
+ }
+ else
+ debugLogA("P2PNAT Request 3 attempt %d timeout", i);
+ }
+ if (i < 4)
+ {
+ // If ports different it's symmetric NAT
+ MyConnection.udpConType = rpkt.clientPort == rpkt2.clientPort ?
+ conPortRestrictNAT : conSymmetricNAT;
+ }
+ closesocket(s1);
+}
+
+
+static bool IsIcfEnabled(void)
+{
+ HRESULT hr;
+ VARIANT_BOOL fwEnabled = VARIANT_FALSE;
+
+ INetFwProfile* fwProfile = NULL;
+ INetFwMgr* fwMgr = NULL;
+ INetFwPolicy* fwPolicy = NULL;
+ INetFwAuthorizedApplication* fwApp = NULL;
+ INetFwAuthorizedApplications* fwApps = NULL;
+ BSTR fwBstrProcessImageFileName = NULL;
+ wchar_t *wszFileName = NULL;
+
+ hr = CoInitialize(NULL);
+ if (FAILED(hr)) return false;
+
+ // Create an instance of the firewall settings manager.
+ hr = CoCreateInstance(CLSID_NetFwMgr, NULL, CLSCTX_INPROC_SERVER,
+ IID_INetFwMgr, (void**)&fwMgr );
+ if (FAILED(hr)) goto error;
+
+ // Retrieve the local firewall policy.
+ hr = fwMgr->get_LocalPolicy(&fwPolicy);
+ if (FAILED(hr)) goto error;
+
+ // Retrieve the firewall profile currently in effect.
+ hr = fwPolicy->get_CurrentProfile(&fwProfile);
+ if (FAILED(hr)) goto error;
+
+ // Get the current state of the firewall.
+ hr = fwProfile->get_FirewallEnabled(&fwEnabled);
+ if (FAILED(hr)) goto error;
+
+ if (fwEnabled == VARIANT_FALSE) goto error;
+
+ // Retrieve the authorized application collection.
+ hr = fwProfile->get_AuthorizedApplications(&fwApps);
+ if (FAILED(hr)) goto error;
+
+ TCHAR szFileName[MAX_PATH];
+ GetModuleFileName(NULL, szFileName, SIZEOF(szFileName));
+
+ wszFileName = mir_t2u(szFileName);
+
+ // Allocate a BSTR for the process image file name.
+ fwBstrProcessImageFileName = SysAllocString(wszFileName);
+ if (FAILED(hr)) goto error;
+
+ // Attempt to retrieve the authorized application.
+ hr = fwApps->Item(fwBstrProcessImageFileName, &fwApp);
+ if (SUCCEEDED(hr))
+ {
+ // Find out if the authorized application is enabled.
+ fwApp->get_Enabled(&fwEnabled);
+ fwEnabled = ~fwEnabled;
+ }
+
+error:
+ // Free the BSTR.
+ SysFreeString(fwBstrProcessImageFileName);
+ mir_free(wszFileName);
+
+ // Release the authorized application instance.
+ if (fwApp != NULL) fwApp->Release();
+
+ // Release the authorized application collection.
+ if (fwApps != NULL) fwApps->Release();
+
+ // Release the firewall profile.
+ if (fwProfile != NULL) fwProfile->Release();
+
+ // Release the local firewall policy.
+ if (fwPolicy != NULL) fwPolicy->Release();
+
+ // Release the firewall settings manager.
+ if (fwMgr != NULL) fwMgr->Release();
+
+ CoUninitialize();
+
+ return fwEnabled != VARIANT_FALSE;
+}
+
+
+void CMsnProto::MSNConnDetectThread( void* )
+{
+ char parBuf[512] = "";
+
+ memset(&MyConnection, 0, sizeof(MyConnection));
+
+ MyConnection.icf = IsIcfEnabled();
+ bool portsMapped = getByte("NLSpecifyIncomingPorts", 0) != 0;
+
+ unsigned gethst = getByte("AutoGetHost", 1);
+ switch (gethst)
+ {
+ case 0:
+ debugLogA("P2PNAT User overwrote IP connection is guessed by user settings only");
+
+ // User specified host by himself so check if it matches MSN information
+ // if it does, move to connection type autodetection,
+ // if it does not, guess connection type from available info
+ db_get_static(NULL, m_szModuleName, "YourHost", parBuf, sizeof(parBuf));
+ if (msnExternalIP == NULL || strcmp(msnExternalIP, parBuf) != 0)
+ {
+ MyConnection.extIP = inet_addr(parBuf);
+ if (MyConnection.extIP == INADDR_NONE)
+ {
+ PHOSTENT myhost = gethostbyname(parBuf);
+ if (myhost != NULL)
+ MyConnection.extIP = ((PIN_ADDR)myhost->h_addr)->S_un.S_addr;
+ else
+ setByte("AutoGetHost", 1);
+ }
+ if (MyConnection.extIP != INADDR_NONE)
+ {
+ MyConnection.intIP = MyConnection.extIP;
+ MyConnection.udpConType = MyConnection.extIP ? (ConEnum)portsMapped : conUnknown;
+ MyConnection.CalculateWeight();
+ return;
+ }
+ else
+ MyConnection.extIP = 0;
+ }
+ break;
+
+ case 1:
+ if (msnExternalIP != NULL)
+ MyConnection.extIP = inet_addr(msnExternalIP);
+ else
+ {
+ gethostname(parBuf, sizeof(parBuf));
+ PHOSTENT myhost = gethostbyname(parBuf);
+ if (myhost != NULL)
+ MyConnection.extIP = ((PIN_ADDR)myhost->h_addr)->S_un.S_addr;
+ }
+ MyConnection.intIP = MyConnection.extIP;
+ break;
+
+ case 2:
+ MyConnection.udpConType = conUnknown;
+ MyConnection.CalculateWeight();
+ return;
+ }
+
+ if (getByte( "NLSpecifyOutgoingPorts", 0))
+ {
+ // User specified outgoing ports so the connection must be firewalled
+ // do not autodetect and guess connection type from available info
+ MyConnection.intIP = MyConnection.extIP;
+ MyConnection.udpConType = (ConEnum)portsMapped;
+ MyConnection.upnpNAT = false;
+ MyConnection.CalculateWeight();
+ return;
+ }
+
+ MSNatDetect();
+
+ // If user mapped incoming ports consider direct connection
+ if (portsMapped)
+ {
+ debugLogA("P2PNAT User manually mapped ports for incoming connection");
+ switch(MyConnection.udpConType)
+ {
+ case conUnknown:
+ case conFirewall:
+ case conISALike:
+ MyConnection.udpConType = conDirect;
+ break;
+
+ case conUnknownNAT:
+ case conPortRestrictNAT:
+ case conIPRestrictNAT:
+ case conSymmetricNAT:
+ MyConnection.upnpNAT = true;
+ break;
+ }
+ }
+
+ debugLogA("P2PNAT Connection %s found UPnP: %d ICF: %d", conStr[MyConnection.udpConType],
+ MyConnection.upnpNAT, MyConnection.icf);
+
+ MyConnection.CalculateWeight();
+}
+
+
+void MyConnectionType::SetUdpCon(const char* str)
+{
+ for (unsigned i=0; i<sizeof(conStr)/sizeof(char*); ++i)
+ {
+ if (strcmp(conStr[i], str) == 0)
+ {
+ udpConType = (ConEnum)i;
+ break;
+ }
+ }
+}
+
+
+void MyConnectionType::CalculateWeight(void)
+{
+ if (icf) weight = 0;
+ else if (udpConType == conDirect) weight = 6;
+ else if (udpConType >= conIPRestrictNAT && udpConType <= conSymmetricNAT)
+ weight = upnpNAT ? 5 : 2;
+ else if (udpConType == conUnknownNAT)
+ weight = upnpNAT ? 4 : 1;
+ else if (udpConType == conUnknown) weight = 1;
+ else if (udpConType == conFirewall) weight = 2;
+ else if (udpConType == conISALike) weight = 3;
+}
diff --git a/protocols/MSN/src/msn_opts.cpp b/protocols/MSN/src/msn_opts.cpp
new file mode 100644
index 0000000000..ae6276b6f3
--- /dev/null
+++ b/protocols/MSN/src/msn_opts.cpp
@@ -0,0 +1,684 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+#include <commdlg.h>
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Icons init
+
+static IconItem iconList[] =
+{
+ { LPGEN("Protocol icon"), "main", IDI_MSN },
+ { LPGEN("Hotmail Inbox"), "inbox", IDI_INBOX },
+ { LPGEN("Profile"), "profile", IDI_PROFILE },
+ { LPGEN("MSN Services"), "services", IDI_SERVICES },
+ { LPGEN("Block user"), "block", IDI_MSNBLOCK },
+ { LPGEN("Invite to chat"), "invite", IDI_INVITE },
+ { LPGEN("Start Netmeeting"), "netmeeting", IDI_NETMEETING },
+ { LPGEN("Contact list"), "list_fl", IDI_LIST_FL },
+ { LPGEN("Allowed list"), "list_al", IDI_LIST_AL },
+ { LPGEN("Blocked list"), "list_bl", IDI_LIST_BL },
+ { LPGEN("Relative list"), "list_rl", IDI_LIST_RL },
+ { LPGEN("Local list"), "list_lc", IDI_LIST_LC },
+};
+
+void MsnInitIcons(void)
+{
+ Icon_Register(hInst, "Protocols/MSN", iconList, SIZEOF(iconList), "MSN");
+}
+
+HICON LoadIconEx(const char* name, bool big)
+{
+ char szSettingName[100];
+ mir_snprintf(szSettingName, sizeof(szSettingName), "MSN_%s", name);
+ return Skin_GetIcon(szSettingName, big);
+}
+
+HANDLE GetIconHandle(int iconId)
+{
+ for (unsigned i=0; i < SIZEOF(iconList); i++)
+ if (iconList[i].defIconID == iconId)
+ return iconList[i].hIcolib;
+
+ return NULL;
+}
+
+void ReleaseIconEx(const char* name, bool big)
+{
+ char szSettingName[100];
+ mir_snprintf(szSettingName, sizeof(szSettingName), "MSN_%s", name);
+ Skin_ReleaseIcon(szSettingName, big);
+}
+
+INT_PTR CALLBACK DlgProcMsnServLists(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN Options dialog procedure
+
+static INT_PTR CALLBACK DlgProcMsnOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ CMsnProto* proto = (CMsnProto*)lParam;
+
+ SetDlgItemTextA(hwndDlg, IDC_HANDLE, proto->MyOptions.szEmail);
+
+ char tBuffer[MAX_PATH];
+ if (!db_get_static(NULL, proto->m_szModuleName, "Password", tBuffer, sizeof(tBuffer))) {
+ tBuffer[16] = 0;
+ SetDlgItemTextA(hwndDlg, IDC_PASSWORD, tBuffer);
+ }
+ SendDlgItemMessage(hwndDlg, IDC_PASSWORD, EM_SETLIMITTEXT, 16, 0);
+
+ HWND wnd = GetDlgItem(hwndDlg, IDC_HANDLE2);
+ DBVARIANT dbv;
+ if (!proto->getTString("Nick", &dbv)) {
+ SetWindowText(wnd, dbv.ptszVal);
+ db_free(&dbv);
+ }
+ EnableWindow(wnd, proto->msnLoggedIn);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MOBILESEND), proto->msnLoggedIn &&
+ proto->getByte("MobileEnabled", 0) && proto->getByte("MobileAllowed", 0));
+
+ CheckDlgButton(hwndDlg, IDC_MOBILESEND, proto->getByte("MobileAllowed", 0));
+ CheckDlgButton(hwndDlg, IDC_SENDFONTINFO, proto->getByte("SendFontInfo", 1));
+ CheckDlgButton(hwndDlg, IDC_MANAGEGROUPS, proto->getByte("ManageServer", 1));
+
+ int tValue = proto->getByte("RunMailerOnHotmail", 0);
+ CheckDlgButton(hwndDlg, IDC_RUN_APP_ON_HOTMAIL, tValue);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MAILER_APP), tValue);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ENTER_MAILER_APP), tValue);
+
+ if (!db_get_static(NULL, proto->m_szModuleName, "MailerPath", tBuffer, sizeof(tBuffer)))
+ SetDlgItemTextA(hwndDlg, IDC_MAILER_APP, tBuffer);
+
+ if (!proto->msnLoggedIn) {
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MANAGEGROUPS), FALSE);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_DISABLE_ANOTHER_CONTACTS), FALSE);
+ }
+ else CheckDlgButton(hwndDlg, IDC_DISABLE_ANOTHER_CONTACTS, proto->msnOtherContactsBlocked);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_NEWMSNACCOUNTLINK) {
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)"https://signup.live.com");
+ return TRUE;
+ }
+
+ if (HIWORD(wParam) == EN_CHANGE && (HWND)lParam == GetFocus()) {
+ switch(LOWORD(wParam)) {
+ case IDC_HANDLE: case IDC_PASSWORD: case IDC_HANDLE2:
+ case IDC_GATEWAYSERVER: case IDC_YOURHOST: case IDC_DIRECTSERVER:
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ }
+
+ if (HIWORD(wParam) == BN_CLICKED) {
+ switch(LOWORD(wParam)) {
+ case IDC_SENDFONTINFO:
+ case IDC_DISABLE_ANOTHER_CONTACTS:
+ case IDC_MOBILESEND:
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+
+ case IDC_MANAGEGROUPS:
+ if (IsDlgButtonChecked(hwndDlg, IDC_MANAGEGROUPS)) {
+ if (IDYES == MessageBox(hwndDlg,
+ TranslateT("Server groups import may change your contact list layout after next login. Do you want to upload your groups to the server?"),
+ TranslateT("MSN Protocol"), MB_YESNOCANCEL))
+ {
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ proto->MSN_UploadServerGroups(NULL);
+ }
+ }
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+
+ case IDC_RUN_APP_ON_HOTMAIL:
+ {
+ BOOL tIsChosen = IsDlgButtonChecked(hwndDlg, IDC_RUN_APP_ON_HOTMAIL);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_MAILER_APP), tIsChosen);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ENTER_MAILER_APP), tIsChosen);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ break;
+
+ case IDC_ENTER_MAILER_APP:
+ {
+ HWND tEditField = GetDlgItem(hwndDlg, IDC_MAILER_APP);
+
+ char szFile[MAX_PATH + 2];
+ GetWindowTextA(tEditField, szFile, sizeof(szFile));
+
+ size_t tSelectLen = 0;
+
+ if (szFile[0] == '\"') {
+ char* p = strchr(szFile+1, '\"');
+ if (p != NULL) {
+ *p = '\0';
+ memmove(szFile, szFile+1, strlen(szFile));
+ tSelectLen += 2;
+ goto LBL_Continue;
+ }
+ }
+
+ {
+ char* p = strchr(szFile, ' ');
+ if (p != NULL) *p = '\0';
+ }
+LBL_Continue:
+ tSelectLen += strlen(szFile);
+
+ OPENFILENAMEA ofn = {0};
+ ofn.lStructSize = sizeof(ofn);
+ ofn.hwndOwner = hwndDlg;
+ ofn.nMaxFile = sizeof(szFile);
+ ofn.lpstrFile = szFile;
+ ofn.Flags = OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;
+ if (GetOpenFileNameA(&ofn) != TRUE)
+ break;
+
+ if (strchr(szFile, ' ') != NULL) {
+ char tmpBuf[MAX_PATH + 2];
+ mir_snprintf(tmpBuf, sizeof(tmpBuf), "\"%s\"", szFile);
+ strcpy(szFile, tmpBuf);
+ }
+
+ SendMessage(tEditField, EM_SETSEL, 0, tSelectLen);
+ SendMessageA(tEditField, EM_REPLACESEL, TRUE, LPARAM(szFile));
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->code == (UINT)PSN_APPLY) {
+ bool reconnectRequired = false;
+ TCHAR screenStr[MAX_PATH];
+ char password[100], szEmail[MSN_MAX_EMAIL_LEN];
+ DBVARIANT dbv;
+
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ GetDlgItemTextA(hwndDlg, IDC_HANDLE, szEmail, sizeof(szEmail));
+ if (strcmp(_strlwr(szEmail), proto->MyOptions.szEmail)) {
+ reconnectRequired = true;
+ strcpy(proto->MyOptions.szEmail, szEmail);
+ proto->setString("e-mail", szEmail);
+ }
+
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, password, sizeof(password));
+ if (!proto->getString("Password", &dbv)) {
+ if (strcmp(password, dbv.pszVal)) {
+ reconnectRequired = true;
+ proto->setString("Password", password);
+ }
+ db_free(&dbv);
+ }
+ else {
+ reconnectRequired = true;
+ proto->setString("Password", password);
+ }
+
+ GetDlgItemText(hwndDlg, IDC_HANDLE2, screenStr, SIZEOF(screenStr));
+ if (!proto->getTString("Nick", &dbv)) {
+ if (_tcscmp(dbv.ptszVal, screenStr))
+ proto->MSN_SendNickname(screenStr);
+ db_free(&dbv);
+ }
+ else proto->MSN_SendNickname(screenStr);
+
+ BYTE mblsnd = IsDlgButtonChecked(hwndDlg, IDC_MOBILESEND) == BST_CHECKED;
+ if (mblsnd != proto->getByte("MobileAllowed", 0)) {
+ proto->msnNsThread->sendPacket("PRP", "MOB %c", mblsnd ? 'Y' : 'N');
+ proto->MSN_SetServerStatus(proto->m_iStatus);
+ }
+
+ unsigned tValue = IsDlgButtonChecked(hwndDlg, IDC_DISABLE_ANOTHER_CONTACTS);
+ if (tValue != proto->msnOtherContactsBlocked && proto->msnLoggedIn) {
+ proto->msnOtherContactsBlocked = tValue;
+ proto->msnNsThread->sendPacket("BLP", tValue ? "BL" : "AL");
+ proto->MSN_ABUpdateAttr(NULL, "MSN.IM.BLP", tValue ? "0" : "1");
+ break;
+ }
+
+ proto->setByte("SendFontInfo", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SENDFONTINFO));
+ proto->setByte("RunMailerOnHotmail", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_RUN_APP_ON_HOTMAIL));
+ proto->setByte("ManageServer", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_MANAGEGROUPS));
+
+ GetDlgItemText(hwndDlg, IDC_MAILER_APP, screenStr, SIZEOF(screenStr));
+ proto->setTString("MailerPath", screenStr);
+
+ if (reconnectRequired && proto->msnLoggedIn)
+ MessageBox(hwndDlg,
+ TranslateT("The changes you have made require you to reconnect to the MSN Messenger network before they take effect"),
+ TranslateT("MSN Options"), MB_OK);
+
+ proto->LoadOptions();
+ return TRUE;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN Connection Options dialog procedure
+
+static INT_PTR CALLBACK DlgProcMsnConnOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ DBVARIANT dbv;
+
+ switch (msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ {
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ CMsnProto* proto = (CMsnProto*)lParam;
+
+ if (!proto->getString("DirectServer", &dbv)) {
+ SetDlgItemTextA(hwndDlg, IDC_DIRECTSERVER, dbv.pszVal);
+ db_free(&dbv);
+ }
+ else SetDlgItemTextA(hwndDlg, IDC_DIRECTSERVER, MSN_DEFAULT_LOGIN_SERVER);
+
+ if (!proto->getString("GatewayServer", &dbv)) {
+ SetDlgItemTextA(hwndDlg, IDC_GATEWAYSERVER, dbv.pszVal);
+ db_free(&dbv);
+ }
+ else SetDlgItemTextA(hwndDlg, IDC_GATEWAYSERVER, MSN_DEFAULT_GATEWAY);
+
+ CheckDlgButton(hwndDlg, IDC_SLOWSEND, proto->getByte("SlowSend", 0));
+
+ SendDlgItemMessage(hwndDlg, IDC_HOSTOPT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Automatically obtain host/port"));
+ SendDlgItemMessage(hwndDlg, IDC_HOSTOPT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Manually specify host/port"));
+ SendDlgItemMessage(hwndDlg, IDC_HOSTOPT, CB_ADDSTRING, 0, (LPARAM)TranslateT("Disable"));
+
+ unsigned gethst = proto->getByte("AutoGetHost", 1);
+ if (gethst < 2) gethst = !gethst;
+
+ char ipaddr[256] = "";
+ if (gethst == 1)
+ if (db_get_static(NULL, proto->m_szModuleName, "YourHost", ipaddr, sizeof(ipaddr)))
+ gethst = 0;
+
+ if (gethst == 0)
+ mir_snprintf(ipaddr, sizeof(ipaddr), "%s", proto->msnLoggedIn ? proto->MyConnection.GetMyExtIPStr() : "");
+
+ SendDlgItemMessage(hwndDlg, IDC_HOSTOPT, CB_SETCURSEL, gethst, 0);
+ if (ipaddr[0])
+ SetDlgItemTextA(hwndDlg, IDC_YOURHOST, ipaddr);
+ else
+ SetDlgItemText(hwndDlg, IDC_YOURHOST, TranslateT("IP info available only after login"));
+ EnableWindow(GetDlgItem(hwndDlg, IDC_YOURHOST), gethst == 1);
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_RESETSERVER:
+ SetDlgItemTextA(hwndDlg, IDC_DIRECTSERVER, MSN_DEFAULT_LOGIN_SERVER);
+ SetDlgItemTextA(hwndDlg, IDC_GATEWAYSERVER, MSN_DEFAULT_GATEWAY);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+
+ if (HIWORD(wParam) == EN_CHANGE && (HWND)lParam == GetFocus())
+ switch(LOWORD(wParam)) {
+ case IDC_DIRECTSERVER:
+ case IDC_GATEWAYSERVER:
+ case IDC_YOURHOST:
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+
+ if (HIWORD(wParam) == CBN_SELCHANGE && LOWORD(wParam) == IDC_HOSTOPT) {
+ unsigned gethst = SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_YOURHOST), gethst == 1);
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+
+ if (HIWORD(wParam) == BN_CLICKED) {
+ switch(LOWORD(wParam)) {
+ case IDC_SLOWSEND:
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->code == (UINT)PSN_APPLY) {
+ bool reconnectRequired = false;
+ char str[MAX_PATH];
+
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ GetDlgItemTextA(hwndDlg, IDC_DIRECTSERVER, str, sizeof(str));
+ if (strcmp(str, MSN_DEFAULT_LOGIN_SERVER))
+ proto->setString("DirectServer", str);
+ else
+ proto->delSetting("DirectServer");
+
+ GetDlgItemTextA(hwndDlg, IDC_GATEWAYSERVER, str, sizeof(str));
+ if (strcmp(str, MSN_DEFAULT_GATEWAY))
+ proto->setString("GatewayServer", str);
+ else
+ proto->delSetting("GatewayServer");
+
+ proto->setByte("SlowSend", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_SLOWSEND ));
+ if (proto->getByte("SlowSend", FALSE)) {
+ if (db_get_dw(NULL, "SRMsg", "MessageTimeout", 60000) < 60000 ||
+ db_get_dw(NULL, "SRMM", "MessageTimeout", 60000) < 60000)
+ {
+ MessageBox(NULL, TranslateT("MSN Protocol requires message timeout to be not less then 60 sec. Correct the timeout value."),
+ TranslateT("MSN Protocol"), MB_OK|MB_ICONINFORMATION);
+ }
+ }
+
+ unsigned gethst2 = proto->getByte("AutoGetHost", 1);
+ unsigned gethst = SendDlgItemMessage(hwndDlg, IDC_HOSTOPT, CB_GETCURSEL, 0, 0);
+ if (gethst < 2) gethst = !gethst;
+ proto->setByte("AutoGetHost", (BYTE)gethst);
+
+ if (gethst == 0) {
+ GetDlgItemTextA(hwndDlg, IDC_YOURHOST, str, sizeof(str));
+ proto->setString("YourHost", str);
+ }
+ else proto->delSetting("YourHost");
+
+ if (gethst != gethst2)
+ proto->ForkThread(&CMsnProto::MSNConnDetectThread, NULL);
+
+ if (reconnectRequired && proto->msnLoggedIn)
+ MessageBox(hwndDlg, TranslateT("The changes you have made require you to reconnect to the MSN Messenger network before they take effect"),
+ TranslateT("MSN Options"), MB_OK);
+
+ proto->LoadOptions();
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Popup Options Dialog: style, position, color, font...
+
+static INT_PTR CALLBACK DlgProcHotmailPopupOpts(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ static bool bEnabled;
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ switch(msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ bEnabled = false;
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ proto = (CMsnProto*)lParam;
+ CheckDlgButton(hwndDlg, IDC_DISABLEHOTMAILPOPUP, proto->getByte("DisableHotmail", 0));
+ CheckDlgButton(hwndDlg, IDC_DISABLEHOTMAILTRAY, proto->getByte("DisableHotmailTray", 1));
+ CheckDlgButton(hwndDlg, IDC_DISABLEHOTMAILCL, proto->getByte("DisableHotmailCL", 0));
+ CheckDlgButton(hwndDlg, IDC_DISABLEHOTJUNK, proto->getByte("DisableHotmailJunk", 0));
+ CheckDlgButton(hwndDlg, IDC_NOTIFY_ENDSESSION, proto->getByte("EnableSessionPopup", 0));
+ CheckDlgButton(hwndDlg, IDC_NOTIFY_FIRSTMSG, proto->getByte("EnableDeliveryPopup", 0));
+ CheckDlgButton(hwndDlg, IDC_ERRORS_USING_POPUPS, proto->getByte("ShowErrorsAsPopups", 0));
+
+ bEnabled = true;
+ return TRUE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDC_DISABLEHOTMAILPOPUP:
+ case IDC_DISABLEHOTMAILTRAY:
+ case IDC_DISABLEHOTMAILCL:
+ case IDC_DISABLEHOTJUNK:
+ case IDC_NOTIFY_ENDSESSION:
+ case IDC_NOTIFY_FIRSTMSG:
+ case IDC_ERRORS_USING_POPUPS:
+ if (bEnabled)
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ break;
+ }
+ break;
+
+ case WM_NOTIFY: //Here we have pressed either the OK or the APPLY button.
+ switch(((LPNMHDR)lParam)->idFrom) {
+ case 0:
+ switch (((LPNMHDR)lParam)->code) {
+ case PSN_RESET:
+ proto->LoadOptions();
+ return TRUE;
+
+ case PSN_APPLY:
+ proto->MyOptions.ShowErrorsAsPopups = IsDlgButtonChecked(hwndDlg, IDC_ERRORS_USING_POPUPS) != 0;
+ proto->setByte("ShowErrorsAsPopups", proto->MyOptions.ShowErrorsAsPopups);
+
+ proto->setByte("DisableHotmail", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_DISABLEHOTMAILPOPUP));
+ proto->setByte("DisableHotmailCL", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_DISABLEHOTMAILCL));
+ proto->setByte("DisableHotmailTray", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_DISABLEHOTMAILTRAY));
+ proto->setByte("DisableHotmailJunk",(BYTE)IsDlgButtonChecked(hwndDlg, IDC_DISABLEHOTJUNK));
+ proto->setByte("EnableDeliveryPopup", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_NOTIFY_FIRSTMSG));
+ proto->setByte("EnableSessionPopup", (BYTE)IsDlgButtonChecked(hwndDlg, IDC_NOTIFY_ENDSESSION));
+
+ MCONTACT hContact = proto->MSN_HContactFromEmail(proto->MyOptions.szEmail);
+ if (hContact)
+ proto->displayEmailCount(hContact);
+ return TRUE;
+ }
+ break;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+static INT_PTR CALLBACK DlgProcAccMgrUI(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg) {
+ case WM_INITDIALOG:
+ {
+ TranslateDialogDefault(hwndDlg);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ CMsnProto* proto = (CMsnProto*)lParam;
+
+ SetDlgItemTextA(hwndDlg, IDC_HANDLE, proto->MyOptions.szEmail);
+
+ char tBuffer[MAX_PATH];
+ if (!db_get_static(NULL, proto->m_szModuleName, "Password", tBuffer, sizeof(tBuffer))) {
+ tBuffer[16] = 0;
+ SetDlgItemTextA(hwndDlg, IDC_PASSWORD, tBuffer);
+ }
+ SendDlgItemMessage(hwndDlg, IDC_PASSWORD, EM_SETLIMITTEXT, 16, 0);
+
+ DBVARIANT dbv;
+ if (!proto->getTString("Place", &dbv)) {
+ SetDlgItemText(hwndDlg, IDC_PLACE, dbv.ptszVal);
+ db_free(&dbv);
+ }
+ }
+ return TRUE;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDC_NEWMSNACCOUNTLINK) {
+ CallService(MS_UTILS_OPENURL, 1, (LPARAM)"https://signup.live.com");
+ return TRUE;
+ }
+
+ if (HIWORD(wParam) == EN_CHANGE && (HWND)lParam == GetFocus()) {
+ switch(LOWORD(wParam)) {
+ case IDC_HANDLE:
+ case IDC_PASSWORD:
+ SendMessage(GetParent(hwndDlg), PSM_CHANGED, 0, 0);
+ }
+ }
+ break;
+
+ case WM_NOTIFY:
+ if (((LPNMHDR)lParam)->code == (UINT)PSN_APPLY) {
+ char password[100], szEmail[MSN_MAX_EMAIL_LEN];
+ DBVARIANT dbv;
+
+ CMsnProto* proto = (CMsnProto*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ GetDlgItemTextA(hwndDlg, IDC_HANDLE, szEmail, sizeof(szEmail));
+ if (strcmp(szEmail, proto->MyOptions.szEmail)) {
+ strcpy(proto->MyOptions.szEmail, szEmail);
+ proto->setString("e-mail", szEmail);
+ }
+
+ GetDlgItemTextA(hwndDlg, IDC_PASSWORD, password, sizeof(password));
+ if (!proto->getString("Password", &dbv)) {
+ if (strcmp(password, dbv.pszVal))
+ proto->setString("Password", password);
+ db_free(&dbv);
+ }
+ else proto->setString("Password", password);
+
+ TCHAR szPlace[64];
+ GetDlgItemText(hwndDlg, IDC_PLACE, szPlace, SIZEOF(szPlace));
+ if (szPlace[0])
+ proto->setTString("Place", szPlace);
+ else
+ proto->delSetting("Place");
+
+ return TRUE;
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+INT_PTR CALLBACK DlgDeleteContactUI(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+ return TRUE;
+
+ case WM_CLOSE:
+ EndDialog(hwndDlg, 0);
+ break;
+
+ case WM_COMMAND:
+ if (LOWORD(wParam) == IDOK) {
+ int isBlock = IsDlgButtonChecked(hwndDlg, IDC_REMOVEBLOCK);
+ int isHot = IsDlgButtonChecked(hwndDlg, IDC_REMOVEHOT);
+
+ DeleteParam *param = (DeleteParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+
+ char szEmail[MSN_MAX_EMAIL_LEN];
+ if (!db_get_static(param->hContact, param->proto->m_szModuleName, "e-mail", szEmail, sizeof(szEmail))) {
+ param->proto->MSN_AddUser(param->hContact, szEmail, 0, LIST_FL | (isHot ? LIST_REMOVE : LIST_REMOVENH));
+ if (isBlock) {
+ param->proto->MSN_AddUser(param->hContact, szEmail, 0, LIST_AL | LIST_REMOVE);
+ param->proto->MSN_AddUser(param->hContact, szEmail, 0, LIST_BL);
+ }
+ }
+ EndDialog(hwndDlg, 1);
+ }
+ break;
+ }
+
+ return FALSE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Initialize options pages
+
+int CMsnProto::OnOptionsInit(WPARAM wParam,LPARAM lParam)
+{
+ OPTIONSDIALOGPAGE odp = { sizeof(odp) };
+ odp.position = -790000000;
+ odp.hInstance = hInst;
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_MSN);
+ odp.ptszTitle = m_tszUserName;
+ odp.ptszGroup = LPGENT("Network");
+ odp.ptszTab = LPGENT("Account");
+ odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR | ODPF_DONTTRANSLATE;
+ odp.pfnDlgProc = DlgProcMsnOpts;
+ odp.dwInitParam = (LPARAM)this;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Connection");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_MSN_CONN);
+ odp.pfnDlgProc = DlgProcMsnConnOpts;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Server list");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_LISTSMGR);
+ odp.pfnDlgProc = DlgProcMsnServLists;
+ Options_AddPage(wParam, &odp);
+
+ odp.ptszTab = LPGENT("Notifications");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPT_NOTIFY);
+ odp.pfnDlgProc = DlgProcHotmailPopupOpts;
+ Options_AddPage(wParam, &odp);
+
+ return 0;
+}
+
+INT_PTR CMsnProto::SvcCreateAccMgrUI(WPARAM wParam, LPARAM lParam)
+{
+ return (INT_PTR)CreateDialogParam (hInst, MAKEINTRESOURCE(IDD_ACCMGRUI),
+ (HWND)lParam, DlgProcAccMgrUI, (LPARAM)this);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Load resident option values into memory
+
+void CMsnProto::LoadOptions(void)
+{
+ memset(&MyOptions, 0, sizeof(MyOptions));
+
+ //Popup Options
+ MyOptions.ManageServer = getByte("ManageServer", TRUE) != 0;
+ MyOptions.ShowErrorsAsPopups = getByte("ShowErrorsAsPopups", TRUE) != 0;
+ MyOptions.SlowSend = getByte("SlowSend", FALSE) != 0;
+ if (db_get_static(NULL, m_szModuleName, "e-mail", MyOptions.szEmail, sizeof(MyOptions.szEmail)))
+ MyOptions.szEmail[0] = 0;
+ _strlwr(MyOptions.szEmail);
+
+ if (db_get_static(NULL, m_szModuleName, "MachineGuid", MyOptions.szMachineGuid, sizeof(MyOptions.szMachineGuid))) {
+ char* uuid = getNewUuid();
+ strcpy(MyOptions.szMachineGuid, uuid);
+ setString("MachineGuid", MyOptions.szMachineGuid);
+ mir_free(uuid);
+ }
+ strcpy(MyOptions.szMachineGuidP2P, MyOptions.szMachineGuid);
+ _strlwr(MyOptions.szMachineGuidP2P);
+}
diff --git a/protocols/MSN/src/msn_p2p.cpp b/protocols/MSN/src/msn_p2p.cpp
new file mode 100644
index 0000000000..28001e66cc
--- /dev/null
+++ b/protocols/MSN/src/msn_p2p.cpp
@@ -0,0 +1,2525 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+#include "m_smileyadd.h"
+
+static const char sttP2Pheader[] =
+ "Content-Type: application/x-msnmsgrp2p\r\n"
+ "P2P-Dest: %s\r\n\r\n";
+
+static const char sttP2PheaderV2[] =
+ "Content-Type: application/x-msnmsgrp2p\r\n"
+ "P2P-Dest: %s\r\n"
+ "P2P-Src: %s;%s\r\n\r\n";
+
+const char sttVoidUid[] = "{00000000-0000-0000-0000-000000000000}";
+static const char szUbnCall[] = "{F13B5C79-0126-458F-A29D-747C79C56530}";
+
+static const char p2pV2Caps[] = { 0x01, 0x0C, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x0F, 0x01, 0x00, 0x00 };
+
+void P2P_Header::logHeader(CMsnProto *ppro)
+{
+ ppro->debugLogA("--- Printing message header");
+ ppro->debugLogA(" SessionID = %08X", mSessionID);
+ ppro->debugLogA(" MessageID = %08X", mID);
+#ifndef __GNUC__
+ ppro->debugLogA(" Offset of data = %I64u", mOffset);
+ ppro->debugLogA(" Total amount of data = %I64u", mTotalSize);
+#else
+ ppro->debugLogA(" Offset of data = %llu", mOffset);
+ ppro->debugLogA(" Total amount of data = %llu", hdrdata->mTotalSize);
+#endif
+ ppro->debugLogA(" Data in packet = %lu bytes", mPacketLen);
+ ppro->debugLogA(" Flags = %08X", mFlags);
+ ppro->debugLogA(" Acknowledged session ID: %08X", mAckSessionID);
+ ppro->debugLogA(" Acknowledged message ID: %08X", mAckUniqueID);
+#ifndef __GNUC__
+ ppro->debugLogA(" Acknowledged data size: %I64u", mAckDataSize);
+#else
+ ppro->debugLogA(" Acknowledged data size: %llu", mAckDataSize);
+#endif
+ ppro->debugLogA("------------------------");
+}
+
+void P2PV2_Header::logHeader(CMsnProto *ppro)
+{
+ ppro->debugLogA("--- Printing message header");
+ ppro->debugLogA(" SessionID = %08X", mSessionID);
+ ppro->debugLogA(" MessageID = %08X", mID);
+#ifndef __GNUC__
+ ppro->debugLogA(" Remaining amount of data = %I64u", mRemSize);
+#else
+ ppro->debugLogA(" Remaining amount of data = %llu", mTotalSize);
+#endif
+ ppro->debugLogA(" Data in packet = %lu bytes", mPacketLen);
+ ppro->debugLogA(" Packet Number = %lu", mPacketNum);
+ ppro->debugLogA(" Operation Code = %08X", mOpCode);
+ ppro->debugLogA(" TF Code = %08X", mTFCode);
+ ppro->debugLogA(" Acknowledged message ID: %08X", mAckUniqueID);
+ ppro->debugLogA("------------------------");
+}
+
+bool CMsnProto::p2p_createListener(filetransfer* ft, directconnection *dc, MimeHeaders& chdrs)
+{
+ if (MyConnection.extIP == 0) return false;
+
+ NETLIBBIND nlb = {0};
+ nlb.cbSize = sizeof(nlb);
+ nlb.pfnNewConnectionV2 = MSN_ConnectionProc;
+ nlb.pExtra = this;
+ HANDLE sb = (HANDLE) CallService(MS_NETLIB_BINDPORT, (WPARAM) m_hNetlibUser, (LPARAM)&nlb);
+ if (sb == NULL)
+ {
+ debugLogA("Unable to bind the port for incoming transfers");
+ return false;
+ }
+
+ ThreadData* newThread = new ThreadData;
+ newThread->mType = SERVER_P2P_DIRECT;
+ newThread->mCaller = 3;
+ newThread->mIncomingBoundPort = sb;
+ newThread->mIncomingPort = nlb.wPort;
+ strncpy(newThread->mCookie, dc->callId , sizeof(newThread->mCookie));
+ newThread->mInitialContactWLID = mir_strdup(ft->p2p_dest);
+
+ newThread->startThread(&CMsnProto::p2p_filePassiveThread, this);
+
+ char szIpv4[256] = "";
+ char szIpv6[256] = "";
+ const char *szExtIp = MyConnection.GetMyExtIPStr();
+
+ bool ipInt = false;
+ int i4 = 0, i6 = 0;
+
+ NETLIBIPLIST* ihaddr = (NETLIBIPLIST*)CallService(MS_NETLIB_GETMYIP, 1, 0);
+ for (unsigned i = 0; i < ihaddr->cbNum; ++i)
+ {
+ if (strchr(ihaddr->szIp[i], ':'))
+ {
+ if (i6++ != 0) strcat(szIpv6, " ");
+ strcat(szIpv6, ihaddr->szIp[i]);
+ }
+ else
+ {
+ if (i4++ != 0) strcat(szIpv4, " ");
+ ipInt |= (strcmp(ihaddr->szIp[i], szExtIp) == 0);
+ strcat(szIpv4, ihaddr->szIp[i]);
+ }
+ }
+ mir_free(ihaddr);
+
+ chdrs.addString("Bridge", "TCPv1");
+ chdrs.addBool("Listening", true);
+
+ if (dc->useHashedNonce)
+ chdrs.addString("Hashed-Nonce", dc->mNonceToHash(), 2);
+ else
+ chdrs.addString("Nonce", dc->mNonceToText(), 2);
+
+ bool bUbnCall = !ft->p2p_sessionid;
+
+ if (!ipInt)
+ {
+ chdrs.addString("IPv4External-Addrs", mir_strdup(MyConnection.GetMyExtIPStr()), bUbnCall ? 6 : 2);
+ chdrs.addLong("IPv4External-Port", nlb.wExPort, bUbnCall ? 4 : 0);
+ }
+ chdrs.addString("IPv4Internal-Addrs", mir_strdup(szIpv4), bUbnCall ? 6 : 2);
+ chdrs.addLong("IPv4Internal-Port", nlb.wPort, bUbnCall ? 4 : 0);
+ if (szIpv6[0])
+ {
+ chdrs.addString("IPv6-Addrs", mir_strdup(szIpv6), 2);
+ chdrs.addLong("IPv6-Port", nlb.wPort);
+ }
+ chdrs.addULong("SessionID", ft->p2p_sessionid);
+ chdrs.addString("SChannelState", "0");
+ chdrs.addString("Capabilities-Flags", "1");
+
+ return true;
+}
+
+bool p2p_IsDlFileOk(filetransfer* ft)
+{
+ mir_sha1_ctx sha1ctx;
+ BYTE sha[MIR_SHA1_HASH_SIZE];
+ mir_sha1_init(&sha1ctx);
+
+ bool res = false;
+
+ int fileId = _topen(ft->std.tszCurrentFile, O_RDONLY | _O_BINARY, _S_IREAD);
+ if (fileId != -1)
+ {
+ BYTE buf[4096];
+ int bytes;
+
+ while((bytes = _read(fileId, buf, sizeof(buf))) > 0)
+ mir_sha1_append(&sha1ctx, buf, bytes);
+
+ _close(fileId);
+ mir_sha1_finish(&sha1ctx, sha);
+
+ char *szSha = arrayToHex(sha, MIR_SHA1_HASH_SIZE);
+ char *szAvatarHash = MSN_GetAvatarHash(ft->p2p_object);
+
+ res = szAvatarHash != NULL && _stricmp(szAvatarHash, szSha) == 0;
+
+ mir_free(szSha);
+ mir_free(szAvatarHash);
+ }
+ return res;
+}
+
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// sttSavePicture2disk - final handler for avatars downloading
+
+void CMsnProto::p2p_pictureTransferFailed(filetransfer* ft)
+{
+ switch(ft->p2p_type)
+ {
+ case MSN_APPID_AVATAR:
+ case MSN_APPID_AVATAR2:
+ {
+ PROTO_AVATAR_INFORMATIONT AI = {0};
+ AI.cbSize = sizeof(AI);
+ AI.hContact = ft->std.hContact;
+ delSetting(ft->std.hContact, "AvatarHash");
+ ProtoBroadcastAck(AI.hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, &AI, 0);
+ }
+ break;
+ }
+ _tremove(ft->std.tszCurrentFile);
+}
+
+void CMsnProto::p2p_savePicture2disk(filetransfer* ft)
+{
+ ft->close();
+
+ if (p2p_IsDlFileOk(ft))
+ {
+ int fileId = _topen(ft->std.tszCurrentFile, O_RDONLY | _O_BINARY, _S_IREAD);
+ if (fileId == -1) {
+ p2p_pictureTransferFailed(ft);
+ return;
+ }
+
+ const TCHAR* ext;
+ int format;
+ BYTE buf[6];
+
+ int bytes = _read(fileId, buf, sizeof(buf));
+ _close(fileId);
+ if (bytes > 4)
+ format = ProtoGetBufferFormat(buf, &ext);
+ else {
+ p2p_pictureTransferFailed(ft);
+ return;
+ }
+
+ switch(ft->p2p_type)
+ {
+ case MSN_APPID_AVATAR:
+ case MSN_APPID_AVATAR2:
+ {
+ PROTO_AVATAR_INFORMATIONT AI = {0};
+ AI.cbSize = sizeof(AI);
+ AI.format = format;
+ AI.hContact = ft->std.hContact;
+ MSN_GetAvatarFileName(AI.hContact, AI.filename, SIZEOF(AI.filename), ext);
+
+ _trename(ft->std.tszCurrentFile, AI.filename);
+
+ // Store also avatar hash
+ char *szAvatarHash = MSN_GetAvatarHash(ft->p2p_object);
+ setString(ft->std.hContact, "AvatarSavedHash", szAvatarHash);
+ mir_free(szAvatarHash);
+
+ setString(ft->std.hContact, "PictSavedContext", ft->p2p_object);
+ ProtoBroadcastAck(AI.hContact, ACKTYPE_AVATAR, ACKRESULT_SUCCESS, &AI, 0);
+
+ char *filename = mir_utf8encodeT(AI.filename);
+ debugLogA("Avatar for contact %08x saved to file '%s'", AI.hContact, filename);
+ mir_free(filename);
+ }
+ break;
+
+ case MSN_APPID_CUSTOMSMILEY:
+ case MSN_APPID_CUSTOMANIMATEDSMILEY:
+ {
+ SMADD_CONT cont;
+ cont.cbSize = sizeof(SMADD_CONT);
+ cont.hContact = ft->std.hContact;
+ cont.type = 1;
+
+ TCHAR* pathcpy = mir_tstrdup(ft->std.tszCurrentFile);
+ _tcscpy(_tcsrchr(pathcpy, '.') + 1, ext);
+ _trename(ft->std.tszCurrentFile, pathcpy);
+
+ cont.path = pathcpy;
+
+ CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, (LPARAM)&cont);
+ mir_free(pathcpy);
+ }
+ break;
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_sendAck - sends MSN P2P acknowledgement to the received message
+
+static const char sttVoidSession[] = "ACHTUNG!!! an attempt made to send a message via the empty session";
+
+void CMsnProto::p2p_sendMsg(const char *wlid, unsigned appId, P2PB_Header& hdrdata, char* msgbody, size_t msgsz)
+{
+ ThreadData* info = MSN_GetP2PThreadByContact(wlid);
+ if (info == NULL)
+ {
+ bool isOffline;
+ info = MSN_StartSB(wlid, isOffline);
+ }
+ p2p_sendMsg(info, wlid, appId, hdrdata, msgbody, msgsz);
+}
+
+void CMsnProto::p2p_sendMsg(ThreadData* info, const char *wlid, unsigned appId, P2PB_Header& hdrdata, char* msgbody, size_t msgsz)
+{
+ unsigned msgType;
+
+ if (info == NULL) msgType = 0;
+ else if (info->mType == SERVER_P2P_DIRECT) msgType = 1;
+ else msgType = 2;
+
+ unsigned fportion = msgType == 1 ? 1352 : 1202;
+ if (hdrdata.isV2Hdr()) fportion += 4;
+
+ char* buf = (char*) alloca(sizeof(sttP2PheaderV2)+ MSN_MAX_EMAIL_LEN +
+ 120 + fportion);
+
+ size_t offset = 0;
+ do
+ {
+ size_t portion = msgsz - offset;
+ if (portion > fportion) portion = fportion;
+
+ char* p = buf;
+
+ // add message header
+ p += msgType == 1 ? sizeof(unsigned) :
+ sprintf(p, hdrdata.isV2Hdr() ? sttP2PheaderV2 : sttP2Pheader, wlid, MyOptions.szEmail, MyOptions.szMachineGuidP2P); //!!!!!!!!!!!
+
+ if (hdrdata.isV2Hdr())
+ {
+ P2PV2_Header *ph = (P2PV2_Header*)&hdrdata;
+ if (offset == 0)
+ {
+ if (!info || !info->mBridgeInit)
+ {
+ if (info && ph->mSessionID)
+ {
+ P2PV2_Header tHdr;
+ tHdr.mID = ph->mID;
+ p2p_sendMsg(info, wlid, 0, tHdr, NULL, 0);
+ }
+ else
+ {
+ ph->mOpCode |= ph->mAckUniqueID && msgType != 1 ? 1 : 3;
+ ph->mCap = p2pV2Caps;
+ if (info) info->mBridgeInit = true;
+ }
+ }
+ }
+ else
+ {
+ ph->mOpCode = 0;
+ ph->mCap = NULL;
+ }
+ }
+
+ if (msgsz)
+ {
+ if (hdrdata.isV2Hdr())
+ {
+ P2PV2_Header *ph = (P2PV2_Header*)&hdrdata;
+ ph->mPacketLen = (unsigned)portion;
+ ph->mRemSize = msgsz - offset - portion;
+ ph->mTFCode = offset ? ph->mTFCode & 0xfe : ph->mTFCode | 0x01;
+
+ if (offset == 0)
+ ph->mPacketNum = p2p_getPktNum(wlid);
+ }
+ else
+ {
+ P2P_Header *ph = (P2P_Header*)&hdrdata;
+ ph->mPacketLen = (unsigned)portion;
+ ph->mOffset = offset;
+ ph->mTotalSize = msgsz;
+ }
+ }
+
+ // add message body
+ p = hdrdata.createMsg(p, wlid, this);
+ hdrdata.logHeader(this);
+
+ if (msgsz)
+ memcpy(p, msgbody + offset, portion); p += portion;
+
+ // add message footer
+ if (msgType != 1)
+ {
+ *(unsigned*)p = _htonl(appId);
+ p += 4;
+ }
+
+ char* szEmail;
+ switch (msgType)
+ {
+ case 0:
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+ MsgQueue_Add(szEmail, 'D', buf, p - buf);
+ break;
+
+ case 1:
+ *(unsigned*)buf = (unsigned)(p - buf - sizeof(unsigned));
+ info->send(buf, p - buf);
+ break;
+
+ case 2:
+ info->sendRawMessage('D', buf, p - buf);
+ break;
+ }
+ offset += portion;
+ }
+ while (offset < msgsz);
+}
+
+
+void CMsnProto::p2p_sendAck(const char *wlid, P2PB_Header* hdr)
+{
+ if (hdr == NULL) return;
+
+ if (!hdr->isV2Hdr())
+ {
+ P2P_Header *hdrdata = (P2P_Header*)hdr;
+ P2P_Header tHdr;
+
+ tHdr.mSessionID = hdrdata->mSessionID;
+ tHdr.mAckDataSize = hdrdata->mTotalSize;
+ tHdr.mFlags = 2;
+ tHdr.mAckSessionID = hdrdata->mID;
+ tHdr.mAckUniqueID = hdrdata->mAckSessionID;
+
+ p2p_sendMsg(wlid, 0, tHdr, NULL, 0);
+ }
+ else
+ {
+ P2PV2_Header *hdrdata = (P2PV2_Header*)hdr;
+ P2PV2_Header tHdr;
+
+ tHdr.mAckUniqueID = hdrdata->mID;
+
+ p2p_sendMsg(wlid, 0, tHdr, NULL, 0);
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_sendEndSession - sends MSN P2P file transfer end packet
+
+void CMsnProto::p2p_sendAbortSession(filetransfer* ft)
+{
+ if (ft == NULL)
+ {
+ debugLogA(sttVoidSession);
+ return;
+ }
+
+ if (ft->p2p_isV2) return;
+
+ P2P_Header tHdr;
+
+ tHdr.mSessionID = ft->p2p_sessionid;
+ tHdr.mAckSessionID = ft->p2p_sendmsgid;
+ tHdr.mID = p2p_getMsgId(ft->p2p_dest, 1);
+
+ if (ft->std.flags & PFTS_SENDING)
+ {
+ tHdr.mFlags = 0x40;
+ tHdr.mAckSessionID = tHdr.mID - 2;
+ }
+ else
+ {
+ tHdr.mAckUniqueID = 0x8200000f;
+ tHdr.mFlags = 0x80;
+ tHdr.mAckDataSize = ft->std.currentFileSize;
+ }
+
+ p2p_sendMsg(ft->p2p_dest, 0, tHdr, NULL, 0);
+ ft->ts = time(NULL);
+}
+
+void CMsnProto::p2p_sendRedirect(filetransfer* ft)
+{
+ if (ft == NULL)
+ {
+ debugLogA(sttVoidSession);
+ return;
+ }
+
+ if (ft->p2p_isV2) return;
+
+ P2P_Header tHdr;
+
+ tHdr.mSessionID = ft->p2p_sessionid;
+ tHdr.mFlags = 0x01;
+ tHdr.mAckSessionID = ft->p2p_sendmsgid;
+ tHdr.mAckDataSize = ft->std.currentFileProgress;
+
+ p2p_sendMsg(ft->p2p_dest, 0, tHdr, NULL, 0);
+
+ ft->tTypeReq = MSN_GetP2PThreadByContact(ft->p2p_dest) ? SERVER_P2P_DIRECT : SERVER_SWITCHBOARD;
+ ft->ts = time(NULL);
+ ft->p2p_waitack = true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_sendSlp - send MSN P2P SLP packet
+
+void CMsnProto::p2p_sendSlp(int iKind, filetransfer *ft, MimeHeaders &pHeaders,
+ MimeHeaders &pContent, const char *wlid)
+{
+ if (ft == NULL)
+ {
+ debugLogA(sttVoidSession);
+ return;
+ }
+
+ if (wlid == NULL) wlid = ft->p2p_dest;
+
+ size_t cbContLen = pContent.getLength();
+ pHeaders.addULong("Content-Length", (unsigned)cbContLen + 1);
+
+ char* buf = (char*)alloca(pHeaders.getLength() + cbContLen + 512);
+ char* p = buf;
+
+ switch (iKind)
+ {
+ case -3: p += sprintf(p, "ACK MSNMSGR:%s MSNSLP/1.0", wlid); break; //!!!!!!!!!!!!!!!!!!
+ case -2: p += sprintf(p, "INVITE MSNMSGR:%s MSNSLP/1.0", wlid); break; //!!!!!!!!!!!!!!!!!!
+ case -1: p += sprintf(p, "BYE MSNMSGR:%s MSNSLP/1.0", wlid); break; //!!!!!!!!!!!!!!!!!!
+ case 200: p += sprintf(p, "MSNSLP/1.0 200 OK"); break; //!!!!!!!!!!!!!!!!!!
+ case 481: p += sprintf(p, "MSNSLP/1.0 481 No Such Call"); break; //!!!!!!!!!!!!!!!!!!
+ case 500: p += sprintf(p, "MSNSLP/1.0 500 Internal Error"); break; //!!!!!!!!!!!!!!!!!!
+ case 603: p += sprintf(p, "MSNSLP/1.0 603 DECLINE"); break; //!!!!!!!!!!!!!!!!!!
+ case 1603: p += sprintf(p, "MSNSLP/1.0 603 Decline"); break; //!!!!!!!!!!!!!!!!!!
+ default: return;
+ }
+
+ if (iKind < 0)
+ {
+ mir_free(ft->p2p_branch);
+ ft->p2p_branch = getNewUuid();
+ }
+
+ if (ft->p2p_isV2)
+ {
+ p += sprintf(p,
+ "\r\nTo: <msnmsgr:%s>\r\n"
+ "From: <msnmsgr:%s;%s>\r\n"
+ "Via: MSNSLP/1.0/TLP ;branch=%s\r\n",
+ wlid, MyOptions.szEmail, MyOptions.szMachineGuidP2P, ft->p2p_branch); //!!!!!!!!!!!!!!!!!!
+ }
+ else
+ {
+ p += sprintf(p,
+ "\r\nTo: <msnmsgr:%s>\r\n"
+ "From: <msnmsgr:%s>\r\n"
+ "Via: MSNSLP/1.0/TLP ;branch=%s\r\n",
+ wlid, MyOptions.szEmail, ft->p2p_branch); //!!!!!!!!!!!!!!!!!!
+ }
+
+ p = pHeaders.writeToBuffer(p);
+ p = pContent.writeToBuffer(p);
+
+ unsigned short status = getWord(ft->std.hContact, "Status", ID_STATUS_OFFLINE);
+ if (!(myFlags & cap_SupportsP2PBootstrap) || ft->p2p_sessionid ||
+ MSN_GetThreadByContact(wlid, SERVER_P2P_DIRECT) ||
+ status == ID_STATUS_OFFLINE || status == ID_STATUS_INVISIBLE ||
+ m_iStatus == ID_STATUS_INVISIBLE)
+ {
+ if (!ft->p2p_isV2)
+ {
+ P2P_Header tHdr;
+ tHdr.mAckSessionID = ft->p2p_acksessid;
+
+ p2p_sendMsg(wlid, 0, tHdr, buf, p - buf + 1);
+ ft->p2p_waitack = true;
+
+ switch (iKind)
+ {
+ case -1: case 500: case 603:
+ ft->p2p_byemsgid = tHdr.mID;
+ break;
+ }
+
+ }
+ else
+ {
+ P2PV2_Header tHdr;
+ tHdr.mTFCode = 0x01;
+
+ p2p_sendMsg(wlid, 0, tHdr, buf, p - buf + 1);
+ }
+ }
+ else
+ msnNsThread->sendPacket("UUN", "%s 3 %d\r\n%s", wlid, p - buf, buf);
+
+ ft->ts = time(NULL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_sendBye - closes P2P session
+
+void CMsnProto::p2p_sendBye(filetransfer* ft)
+{
+ if (ft == NULL)
+ {
+ debugLogA(sttVoidSession);
+ return;
+ }
+
+ MimeHeaders tHeaders(8);
+ tHeaders.addString("CSeq", "0 ");
+ tHeaders.addString("Call-ID", ft->p2p_callID);
+ tHeaders.addLong("Max-Forwards", 0);
+ tHeaders.addString("Content-Type", "application/x-msnmsgr-sessionclosebody");
+
+ MimeHeaders chdrs(2);
+ chdrs.addULong("SessionID", ft->p2p_sessionid);
+ chdrs.addString("SChannelState", "0");
+
+ p2p_sendSlp(-1, ft, tHeaders, chdrs);
+}
+
+void CMsnProto::p2p_sendCancel(filetransfer* ft)
+{
+ p2p_sendBye(ft);
+ p2p_sendAbortSession(ft);
+}
+
+void CMsnProto::p2p_sendNoCall(filetransfer* ft)
+{
+ if (ft == NULL)
+ {
+ debugLogA(sttVoidSession);
+ return;
+ }
+
+ MimeHeaders tHeaders(8);
+ tHeaders.addString("CSeq", "0 ");
+ tHeaders.addString("Call-ID", ft->p2p_callID);
+ tHeaders.addLong("Max-Forwards", 0);
+ tHeaders.addString("Content-Type", "application/x-msnmsgr-session-failure-respbody");
+
+ MimeHeaders chdrs(2);
+ chdrs.addULong("SessionID", ft->p2p_sessionid);
+ chdrs.addString("SChannelState", "0");
+
+ p2p_sendSlp(481, ft, tHeaders, chdrs);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_sendStatus - send MSN P2P status and its description
+
+void CMsnProto::p2p_sendStatus(filetransfer* ft, long lStatus)
+{
+ if (ft == NULL)
+ {
+ debugLogA(sttVoidSession);
+ return;
+ }
+
+ MimeHeaders tHeaders(8);
+ tHeaders.addString("CSeq", "1 ");
+ tHeaders.addString("Call-ID", ft->p2p_callID);
+ tHeaders.addLong("Max-Forwards", 0);
+
+ MimeHeaders chdrs(2);
+ chdrs.addULong("SessionID", ft->p2p_sessionid);
+
+ if (lStatus != 1603)
+ {
+ tHeaders.addString("Content-Type", "application/x-msnmsgr-sessionreqbody");
+
+ chdrs.addString("SChannelState", "0");
+ }
+ else
+ tHeaders.addString("Content-Type", "application/x-msnmsgr-transrespbody");
+
+ p2p_sendSlp(lStatus, ft, tHeaders, chdrs);
+}
+
+void CMsnProto::p2p_sendAvatarInit(filetransfer* ft)
+{
+ unsigned body = 0;
+
+ if (ft->p2p_isV2)
+ {
+ P2PV2_Header tHdr;
+ tHdr.mSessionID = ft->p2p_sessionid;
+ tHdr.mTFCode = 0x01;
+ p2p_sendMsg(ft->p2p_dest, ft->p2p_appID, tHdr, (char*)&body, sizeof(body));
+ }
+ else
+ {
+ P2P_Header tHdr;
+ tHdr.mSessionID = ft->p2p_sessionid;
+ tHdr.mAckSessionID = ft->p2p_acksessid;
+ p2p_sendMsg(ft->p2p_dest, ft->p2p_appID, tHdr, (char*)&body, sizeof(body));
+
+ ft->ts = time(NULL);
+ ft->p2p_waitack = true;
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_connectTo - connects to a remote P2P server
+
+static const char p2p_greeting[8] = { 4, 0, 0, 0, 'f', 'o', 'o', 0 };
+
+static void sttSendPacket(ThreadData* T, void* hdr, unsigned len)
+{
+ T->send((char*)&len, sizeof(unsigned));
+ T->send((char*)hdr, len);
+}
+
+bool CMsnProto::p2p_connectTo(ThreadData* info, directconnection *dc)
+{
+ NETLIBOPENCONNECTION tConn = {0};
+ tConn.cbSize = sizeof(tConn);
+ tConn.szHost = info->mServer;
+ tConn.flags = NLOCF_V2;
+ tConn.timeout = 5;
+
+ char* tPortDelim = strrchr(info->mServer, ':');
+ if (tPortDelim != NULL)
+ {
+ *tPortDelim = '\0';
+ tConn.wPort = (WORD)atol(tPortDelim + 1);
+ }
+
+ debugLogA("Connecting to %s:%d", tConn.szHost, tConn.wPort);
+
+ info->s = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)m_hNetlibUser, (LPARAM)&tConn);
+ if (info->s == NULL)
+ {
+ TWinErrorCode err;
+ debugLogA("Connection Failed (%d): %s", err.mErrorCode, err.getText());
+ return false;
+ }
+ info->send(p2p_greeting, sizeof(p2p_greeting));
+
+ bool isV2 = strchr(info->mInitialContactWLID, ';') != NULL;
+
+ P2P_Header reply;
+ if (!isV2)
+ {
+ reply.mFlags = 0x100;
+
+ if (dc->useHashedNonce)
+ memcpy(&reply.mAckSessionID, dc->mNonce, sizeof(UUID));
+ else
+ dc->xNonceToBin((UUID*)&reply.mAckSessionID);
+
+ char buf[48];
+ reply.createMsg(buf, info->mInitialContactWLID, this);
+ sttSendPacket(info, buf, sizeof(buf));
+ }
+ else
+ sttSendPacket(info, dc->mNonce, sizeof(UUID));
+
+ long cbPacketLen;
+ HReadBuffer buf(info, 0);
+ BYTE* p;
+ if ((p = buf.surelyRead(4)) == NULL)
+ {
+ debugLogA("Error reading data, closing filetransfer");
+ return false;
+ }
+
+ cbPacketLen = *(long*)p;
+ if ((p = buf.surelyRead(cbPacketLen)) == NULL)
+ return false;
+
+ bool cookieMatch;
+
+ if (!isV2)
+ {
+ P2P_Header cookie((char*)p);
+
+ if (dc->useHashedNonce)
+ {
+ char* hnonce = dc->calcHashedNonce((UUID*)&cookie.mAckSessionID);
+ cookieMatch = strcmp(hnonce, dc->xNonce) == 0;
+ mir_free(hnonce);
+ }
+ else
+ cookieMatch = memcmp(&cookie.mAckSessionID, &reply.mAckSessionID, sizeof(UUID)) == 0;
+ }
+ else
+ {
+ char* hnonce = dc->calcHashedNonce((UUID*)p);
+ cookieMatch = strcmp(hnonce, dc->xNonce) == 0;
+ mir_free(hnonce);
+ }
+
+ if (!cookieMatch)
+ {
+ debugLogA("Invalid cookie received, exiting");
+ return false;
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_listen - acts like a local P2P server
+
+bool CMsnProto::p2p_listen(ThreadData* info, directconnection *dc)
+{
+ switch(WaitForSingleObject(info->hWaitEvent, 10000))
+ {
+ case WAIT_TIMEOUT:
+ case WAIT_FAILED:
+ debugLogA("Incoming connection timed out, closing file transfer");
+ MSN_StartP2PTransferByContact(info->mInitialContactWLID);
+LBL_Error:
+ debugLogA("File listen failed");
+ return false;
+ }
+
+ HReadBuffer buf(info, 0);
+ BYTE* p;
+
+ if ((p = buf.surelyRead(8)) == NULL)
+ goto LBL_Error;
+
+ if (memcmp(p, p2p_greeting, 8) != 0)
+ {
+ debugLogA("Invalid input data, exiting");
+ return false;
+ }
+
+ if ((p = buf.surelyRead(4)) == NULL)
+ {
+ debugLogA("Error reading data, closing filetransfer");
+ return false;
+ }
+
+ long cbPacketLen = *(long*)p;
+ if ((p = buf.surelyRead(cbPacketLen)) == NULL)
+ goto LBL_Error;
+
+ bool cookieMatch;
+ bool isV2 = strchr(info->mInitialContactWLID, ';') != NULL;
+
+ if (!isV2)
+ {
+ P2P_Header cookie((char*)p);
+
+ if (dc->useHashedNonce)
+ {
+ char* hnonce = dc->calcHashedNonce((UUID*)&cookie.mAckSessionID);
+ cookieMatch = strcmp(hnonce, dc->xNonce) == 0;
+ mir_free(hnonce);
+ memcpy(&cookie.mAckSessionID, dc->mNonce, sizeof(UUID));
+ }
+ else
+ cookieMatch = memcmp(&cookie.mAckSessionID, dc->mNonce, sizeof(UUID)) == 0;
+
+ if (!cookieMatch)
+ {
+ debugLogA("Invalid cookie received, exiting");
+ return false;
+ }
+
+ char buf[48];
+ cookie.createMsg(buf, info->mInitialContactWLID, this);
+ sttSendPacket(info, buf, sizeof(buf));
+ }
+ else
+ {
+ char* hnonce = dc->calcHashedNonce((UUID*)p);
+ cookieMatch = strcmp(hnonce, dc->xNonce) == 0;
+ mir_free(hnonce);
+
+ if (!cookieMatch)
+ {
+ debugLogA("Invalid cookie received, exiting");
+ goto LBL_Error;
+ }
+
+ sttSendPacket(info, dc->mNonce, sizeof(UUID));
+ }
+
+ return true;
+}
+
+LONG CMsnProto::p2p_sendPortion(filetransfer* ft, ThreadData* T, bool isV2)
+{
+ LONG trid;
+ char databuf[1500], *p = databuf;
+
+ // Compute the amount of data to send
+ unsigned fportion = T->mType == SERVER_P2P_DIRECT ? 1352 : 1202;
+ if (isV2) fportion += 4;
+
+ const unsigned __int64 dt = ft->std.currentFileSize - ft->std.currentFileProgress;
+ const unsigned portion = dt > fportion ? fportion : dt;
+
+ // Fill data size for direct transfer
+
+ if (T->mType != SERVER_P2P_DIRECT)
+ p += sprintf(p, isV2 ? sttP2PheaderV2 : sttP2Pheader, ft->p2p_dest, MyOptions.szEmail, MyOptions.szMachineGuidP2P); //!!!!!!!!!!!!!!!!!!
+ else
+ p += sizeof(unsigned);
+
+ if (!isV2)
+ {
+ // Fill P2P header
+ P2P_Header H;
+
+ H.mSessionID = ft->p2p_sessionid;
+ H.mID = ft->p2p_sendmsgid;
+ H.mFlags = ft->p2p_appID == MSN_APPID_FILE ? 0x01000030 : 0x20;
+ H.mTotalSize = ft->std.currentFileSize;
+ H.mOffset = ft->std.currentFileProgress;
+ H.mPacketLen = portion;
+ H.mAckSessionID = ft->p2p_acksessid;
+
+ p = H.createMsg(p, ft->p2p_dest, this);
+ }
+ else
+ {
+ P2PV2_Header H;
+
+ H.mSessionID = ft->p2p_sessionid;
+ H.mTFCode = (ft->p2p_appID == MSN_APPID_FILE ? 6 : 4) | (ft->std.currentFileProgress ? 0 : 1);
+ H.mRemSize = ft->std.currentFileSize - ft->std.currentFileProgress - portion;
+ H.mPacketLen = portion;
+ H.mPacketNum = ft->p2p_sendmsgid;
+
+ p = H.createMsg(p, ft->p2p_dest, this);
+ H.logHeader(this);
+ }
+
+ if (T->mType == SERVER_P2P_DIRECT)
+ *(unsigned*)databuf = portion + (p - databuf) - (unsigned)sizeof(unsigned);
+
+ // Fill data (payload) for transfer
+ if (ft->fileId == -1) return 0;
+ _read(ft->fileId, p, portion);
+ p += portion;
+
+ if (T->mType == SERVER_P2P_DIRECT)
+ trid = T->send(databuf, p - databuf);
+ else
+ {
+ // Define packet footer for server transfer
+ *(unsigned*)p = _htonl(ft->p2p_appID);
+ p += sizeof(unsigned);
+
+ trid = T->sendRawMessage('D', (char *)databuf, p - databuf);
+ }
+
+ if (trid != 0)
+ {
+ ft->std.totalProgress += portion;
+ ft->std.currentFileProgress += portion;
+ if (ft->p2p_appID == MSN_APPID_FILE && clock() >= ft->nNotify)
+ {
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+ ft->nNotify = clock() + 500;
+ }
+ }
+ else
+ debugLogA(" Error sending");
+ ft->ts = time(NULL);
+ ft->p2p_waitack = true;
+
+ return trid;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_sendFeedThread - sends a file via server
+
+void __cdecl CMsnProto::p2p_sendFeedThread(void* arg)
+{
+ ThreadData* info = (ThreadData*)arg;
+
+ bool isV2 = strchr(info->mInitialContactWLID, ';') != NULL;
+
+ info->contactJoined(info->mInitialContactWLID);
+ mir_free(info->mInitialContactWLID); info->mInitialContactWLID = NULL;
+
+ debugLogA("File send thread started");
+
+ switch(WaitForSingleObject(info->hWaitEvent, 6000))
+ {
+ case WAIT_FAILED:
+ debugLogA("File send wait failed");
+ return;
+ }
+
+ HANDLE hLockHandle = NULL;
+ ThreadData* T = NULL;
+ TInfoType lastType = SERVER_NOTIFICATION;
+
+ filetransfer *ft = p2p_getSessionByCallID(info->mCookie,
+ info->mJoinedIdentContactsWLID.getCount() ? info->mJoinedIdentContactsWLID[0] : info->mJoinedContactsWLID[0]);
+
+ if (ft != NULL && WaitForSingleObject(ft->hLockHandle, 2000) == WAIT_OBJECT_0)
+ {
+ hLockHandle = ft->hLockHandle;
+
+ if (isV2)
+ ft->p2p_sendmsgid = p2p_getPktNum(ft->p2p_dest);
+ else
+ {
+ if (ft->p2p_sendmsgid == 0)
+ ft->p2p_sendmsgid = p2p_getMsgId(ft->p2p_dest, 1);
+ }
+
+ T = MSN_GetP2PThreadByContact(ft->p2p_dest);
+ if (T != NULL)
+ ft->tType = lastType = T->mType;
+
+ ReleaseMutex(hLockHandle);
+ }
+ else
+ return;
+
+ bool fault = false;
+ while (WaitForSingleObject(hLockHandle, 2000) == WAIT_OBJECT_0 &&
+ ft->std.currentFileProgress < ft->std.currentFileSize && !ft->bCanceled)
+ {
+ if (ft->tType != lastType)
+ T = MSN_GetThreadByContact(ft->p2p_dest, ft->tType);
+
+ if (ft->bCanceled) break;
+ bool cfault = (T == NULL || p2p_sendPortion(ft, T, isV2) == 0);
+
+ if (cfault)
+ {
+ if (fault)
+ {
+ debugLogA("File send failed");
+ break;
+ }
+ else
+ SleepEx(3000, TRUE); // Allow 3 sec for redirect request
+ }
+ fault = cfault;
+
+ ReleaseMutex(hLockHandle);
+
+ if (T != NULL && T->mType != SERVER_P2P_DIRECT)
+ WaitForSingleObject(T->hWaitEvent, 5000);
+ }
+ ReleaseMutex(hLockHandle);
+
+ if (ft->p2p_appID == MSN_APPID_FILE)
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+
+ if (isV2)
+ {
+ if (!ft->bCanceled)
+ {
+ ft->bCompleted = true;
+ p2p_sendBye(ft);
+ }
+ p2p_sessionComplete(ft);
+ }
+
+ debugLogA("File send thread completed");
+}
+
+
+void CMsnProto::p2p_sendFeedStart(filetransfer* ft)
+{
+ if (ft->std.flags & PFTS_SENDING)
+ {
+ ThreadData* newThread = new ThreadData;
+ newThread->mType = SERVER_FILETRANS;
+ strcpy(newThread->mCookie, ft->p2p_callID);
+ newThread->mInitialContactWLID = mir_strdup(ft->p2p_dest);
+ newThread->startThread(&CMsnProto::p2p_sendFeedThread, this);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_sendFileDirectly - sends a file via MSN P2P protocol
+
+void CMsnProto::p2p_sendRecvFileDirectly(ThreadData* info)
+{
+ long cbPacketLen = 0;
+ long state = 0;
+
+ HReadBuffer buf(info, 0);
+ char *wlid = info->mInitialContactWLID;
+
+ info->contactJoined(wlid);
+ info->mInitialContactWLID = NULL;
+
+ MSN_StartP2PTransferByContact(wlid);
+ p2p_redirectSessions(wlid);
+ p2p_startSessions(wlid);
+
+ bool isV2 = strchr(wlid, ';') != NULL;
+
+ for (;;)
+ {
+ long len = state ? cbPacketLen : 4;
+
+ BYTE* p = buf.surelyRead(len);
+
+ if (p == NULL)
+ break;
+
+ if (state == 0)
+ cbPacketLen = *(long*)p;
+ else if (!isV2)
+ p2p_processMsg(info, (char*)p, wlid);
+ else
+ p2p_processMsgV2(info, (char*)p, wlid);
+
+ state = (state + 1) % 2;
+ }
+
+ info->contactLeft(wlid);
+ p2p_redirectSessions(wlid);
+ mir_free(wlid);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// bunch of thread functions to cover all variants of P2P file transfers
+
+void __cdecl CMsnProto::p2p_fileActiveThread(void* arg)
+{
+ ThreadData* info = (ThreadData*)arg;
+
+ debugLogA("p2p_fileActiveThread() started: connecting to '%s'", info->mServer);
+
+ directconnection *dc = p2p_getDCByCallID(info->mCookie, info->mInitialContactWLID);
+ if (dc)
+ {
+ if (p2p_connectTo(info, dc))
+ p2p_sendRecvFileDirectly(info);
+ else
+ {
+ mir_free(info->mInitialContactWLID);
+ info->mInitialContactWLID = NULL;
+ }
+
+ if (!MSN_GetThreadByContact(dc->wlid, SERVER_P2P_DIRECT) && !MSN_GetUnconnectedThread(dc->wlid, SERVER_P2P_DIRECT))
+ p2p_unregisterDC(dc);
+ }
+
+ debugLogA("p2p_fileActiveThread() completed: connecting to '%s'", info->mServer);
+}
+
+void __cdecl CMsnProto::p2p_filePassiveThread(void* arg)
+{
+ ThreadData* info = (ThreadData*)arg;
+
+ debugLogA("p2p_filePassiveThread() started: listening");
+
+ directconnection *dc = p2p_getDCByCallID(info->mCookie, info->mInitialContactWLID);
+ if (dc)
+ {
+ if (p2p_listen(info, dc))
+ p2p_sendRecvFileDirectly(info);
+ else
+ {
+ mir_free(info->mInitialContactWLID); info->mInitialContactWLID = NULL;
+ }
+
+ if (!MSN_GetThreadByContact(dc->wlid, SERVER_P2P_DIRECT) && !MSN_GetUnconnectedThread(dc->wlid, SERVER_P2P_DIRECT))
+ p2p_unregisterDC(dc);
+ }
+
+ debugLogA("p2p_filePassiveThread() completed");
+}
+
+
+void CMsnProto::p2p_InitFileTransfer(
+ ThreadData* info,
+ MimeHeaders& tFileInfo,
+ MimeHeaders& tFileInfo2,
+ const char* wlid)
+{
+ if (info->mJoinedContactsWLID.getCount() == 0 && info->mJoinedIdentContactsWLID.getCount() == 0)
+ return;
+
+ const char *szCallID = tFileInfo["Call-ID"],
+ *szBranch = tFileInfo["Via"];
+
+ if (szBranch != NULL) {
+ szBranch = strstr(szBranch, "branch=");
+ if (szBranch != NULL)
+ szBranch += 7;
+ }
+ if (szCallID == NULL || szBranch == NULL) {
+ debugLogA("Ignoring invalid invitation: CallID='%s', szBranch='%s'", szCallID, szBranch);
+ return;
+ }
+
+ const char *szSessionID = tFileInfo2["SessionID"],
+ *szEufGuid = tFileInfo2["EUF-GUID"],
+ *szContext = tFileInfo2["Context"],
+ *szAppId = tFileInfo2["AppID"];
+
+ if (szSessionID == NULL || szAppId == NULL || szEufGuid == NULL)
+ {
+ debugLogA("Ignoring invalid invitation: SessionID='%s', AppID=%s, Branch='%s',Context='%s'",
+ szSessionID, szAppId, szEufGuid, szContext);
+ return;
+ }
+
+ unsigned dwAppID = strtoul(szAppId, NULL, 10);
+ unsigned dwSessionId = strtoul(szSessionID, NULL, 10);
+
+ if (p2p_getSessionByID(dwSessionId))
+ return;
+
+ szContext = (char*)mir_base64_decode(szContext, 0);
+
+ filetransfer* ft = new filetransfer(this);
+ ft->p2p_acksessid = MSN_GenRandom();
+ ft->p2p_sessionid = dwSessionId;
+ ft->p2p_appID = dwAppID == MSN_APPID_AVATAR ? MSN_APPID_AVATAR2 : dwAppID;
+ ft->p2p_type = dwAppID;
+ ft->p2p_ackID = dwAppID == MSN_APPID_FILE ? 2000 : 1000;
+ replaceStr(ft->p2p_callID, szCallID);
+ replaceStr(ft->p2p_branch, szBranch);
+ ft->p2p_dest = mir_strdup(wlid);
+ ft->p2p_isV2 = strchr(wlid, ';') != NULL;
+ ft->std.hContact = MSN_HContactFromEmail(wlid);
+
+ p2p_registerSession(ft);
+
+ switch (dwAppID)
+ {
+ case MSN_APPID_AVATAR:
+ case MSN_APPID_AVATAR2:
+ if (!_stricmp(szEufGuid, "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}")) {
+ DBVARIANT dbv;
+ bool pictmatch = !getString("PictObject", &dbv);
+ if (pictmatch)
+ {
+ UrlDecode(dbv.pszVal);
+
+ ezxml_t xmlcon = ezxml_parse_str((char*)szContext, strlen(szContext));
+ ezxml_t xmldb = ezxml_parse_str(dbv.pszVal, strlen(dbv.pszVal));
+
+ const char *szCtBuf = ezxml_attr(xmlcon, "SHA1C");
+ if (szCtBuf)
+ {
+ const char *szPtBuf = ezxml_attr(xmldb, "SHA1C");
+ pictmatch = szPtBuf && strcmp(szCtBuf, szPtBuf) == 0;
+ }
+ else
+ {
+ const char *szCtBuf = ezxml_attr(xmlcon, "SHA1D");
+ const char *szPtBuf = ezxml_attr(xmldb, "SHA1D");
+ pictmatch = szCtBuf && szPtBuf && strcmp(szCtBuf, szPtBuf) == 0;
+ }
+
+ ezxml_free(xmlcon);
+ ezxml_free(xmldb);
+ db_free(&dbv);
+ }
+ if (pictmatch)
+ {
+ TCHAR szFileName[MAX_PATH];
+ MSN_GetAvatarFileName(NULL, szFileName, SIZEOF(szFileName), NULL);
+ ft->fileId = _topen(szFileName, O_RDONLY | _O_BINARY, _S_IREAD);
+ if (ft->fileId == -1)
+ {
+ p2p_sendStatus(ft, 603);
+ MSN_ShowError("Your avatar not set correctly. Avatar should be set in View/Change My Details | Avatar");
+ debugLogA("Unable to open avatar file '%s', error %d", szFileName, errno);
+ p2p_unregisterSession(ft);
+ }
+ else
+ {
+ mir_free(ft->std.tszCurrentFile);
+ ft->std.tszCurrentFile = mir_tstrdup(szFileName);
+// debugLogA("My avatar file opened for %s as %08p::%d", szEmail, ft, ft->fileId);
+ ft->std.totalBytes = ft->std.currentFileSize = _filelengthi64(ft->fileId);
+ ft->std.flags |= PFTS_SENDING;
+
+ //---- send 200 OK Message
+ p2p_sendStatus(ft, 200);
+ p2p_sendFeedStart(ft);
+
+ if (ft->p2p_isV2)
+ {
+ p2p_sendAvatarInit(ft);
+ MSN_StartP2PTransferByContact(ft->p2p_dest);
+ }
+ }
+ }
+ else
+ {
+ p2p_sendStatus(ft, 603);
+ debugLogA("Requested avatar does not match current avatar");
+ p2p_unregisterSession(ft);
+ }
+ }
+ break;
+
+ case MSN_APPID_FILE:
+ if (!_stricmp(szEufGuid, "{5D3E02AB-6190-11D3-BBBB-00C04F795683}"))
+ {
+ wchar_t* wszFileName = ((HFileContext*)szContext)->wszFileName;
+ for (wchar_t* p = wszFileName; *p != 0; p++)
+ {
+ switch(*p)
+ {
+ case ':': case '?': case '/': case '\\': case '*':
+ *p = '_';
+ }
+ }
+
+ mir_free(ft->std.tszCurrentFile);
+ ft->std.tszCurrentFile = mir_u2t(wszFileName);
+
+ ft->std.totalBytes = ft->std.currentFileSize = ((HFileContext*)szContext)->dwSize;
+ ft->std.totalFiles = 1;
+
+ TCHAR tComment[40];
+ mir_sntprintf(tComment, SIZEOF(tComment), TranslateT("%I64u bytes"), ft->std.currentFileSize);
+
+ PROTORECVFILET pre = {0};
+ pre.flags = PREF_TCHAR;
+ pre.fileCount = 1;
+ pre.timestamp = time(NULL);
+ pre.tszDescription = tComment;
+ pre.ptszFiles = &ft->std.tszCurrentFile;
+ pre.lParam = (LPARAM)ft;
+ ProtoChainRecvFile(ft->std.hContact, &pre);
+ }
+ break;
+
+ case MSN_APPID_WEBCAM:
+ if (!_stricmp(szEufGuid, "{4BD96FC0-AB17-4425-A14A-439185962DC8}")) {
+ MSN_ShowPopup(ft->std.hContact,
+ TranslateT("Contact tried to send its webcam data (not currently supported)"),
+ MSN_ALLOW_MSGBOX | MSN_SHOW_ERROR);
+ }
+ if (!_stricmp(szEufGuid, "{1C9AA97E-9C05-4583-A3BD-908A196F1E92}")) {
+ MSN_ShowPopup(ft->std.hContact,
+ TranslateT("Contact tried to view your webcam data (not currently supported)"),
+ MSN_ALLOW_MSGBOX | MSN_SHOW_ERROR);
+ }
+ p2p_sendStatus(ft, 603);
+ p2p_unregisterSession(ft);
+ break;
+
+ case MSN_APPID_MEDIA_SHARING:
+// MSN_ShowPopup(ft->std.hContact,
+// TranslateT("Contact tried to share media with us (not currently supported)"),
+// MSN_ALLOW_MSGBOX | MSN_SHOW_ERROR);
+ p2p_sendStatus(ft, 603);
+ p2p_unregisterSession(ft);
+ break;
+
+ default:
+ p2p_sendStatus(ft, 603);
+ p2p_unregisterSession(ft);
+ debugLogA("Invalid or unknown data transfer request (AppID/EUF-GUID: %ld/%s)", dwAppID, szEufGuid);
+ break;
+ }
+
+ mir_free((void*)szContext);
+}
+
+void CMsnProto::p2p_InitDirectTransfer(MimeHeaders& tFileInfo, MimeHeaders& tFileInfo2, const char* wlid)
+{
+ const char *szCallID = tFileInfo["Call-ID"],
+ *szBranch = tFileInfo["Via"],
+ *szConnType = tFileInfo2["Conn-Type"],
+ *szUPnPNat = tFileInfo2["UPnPNat"],
+ *szNetID = tFileInfo2["NetID"],
+ *szICF = tFileInfo2["ICF"],
+ *szHashedNonce = tFileInfo2["Hashed-Nonce"];
+
+ if (szBranch != NULL)
+ {
+ szBranch = strstr(szBranch, "branch=");
+ if (szBranch != NULL)
+ szBranch += 7;
+ }
+ if (szCallID == NULL || szBranch == NULL)
+ {
+ debugLogA("Ignoring invalid invitation: CallID='%s', Branch='%s'", szCallID, szBranch);
+ return;
+ }
+
+ if (szConnType == NULL || szUPnPNat == NULL || szICF == NULL || szNetID == NULL)
+ {
+ debugLogA("Ignoring invalid invitation: ConnType='%s', UPnPNat='%s', ICF='%s', NetID='%s'",
+ szConnType, szUPnPNat, szICF, szNetID);
+ return;
+ }
+
+ filetransfer ftl(this), *ft = p2p_getSessionByCallID(szCallID, wlid);
+ if (!ft || !ft->p2p_sessionid)
+ {
+ ft = &ftl;
+ replaceStr(ft->p2p_dest, wlid);
+ replaceStr(ft->p2p_callID, szCallID);
+ replaceStr(ft->p2p_branch, szBranch);
+ ft->p2p_isV2 = strchr(wlid, ';') != NULL;
+ ft->std.hContact = MSN_HContactFromEmail(wlid);
+ }
+ else
+ {
+ replaceStr(ft->p2p_callID, szCallID);
+ replaceStr(ft->p2p_branch, szBranch);
+ ft->p2p_acksessid = MSN_GenRandom();
+/*
+ if (p2p_isAvatarOnly(ft->std.hContact))
+ {
+ p2p_sendStatus(ft, 1603);
+ return;
+ }
+ else
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, ft, 0);
+*/
+ }
+
+ directconnection *dc = p2p_getDCByCallID(szCallID, wlid);
+ if (dc)
+ {
+ if (MSN_GetThreadByContact(wlid, SERVER_P2P_DIRECT))
+ {
+ p2p_sendStatus(ft, 1603);
+ p2p_unregisterDC(dc);
+ return;
+ }
+ p2p_unregisterDC(dc);
+ }
+
+ dc = new directconnection(szCallID, wlid);
+ dc->useHashedNonce = szHashedNonce != NULL;
+ if (dc->useHashedNonce)
+ dc->xNonce = mir_strdup(szHashedNonce);
+ p2p_registerDC(dc);
+
+ MimeHeaders tResult(8);
+ tResult.addString("CSeq", "1 ");
+ tResult.addString("Call-ID", szCallID);
+ tResult.addLong("Max-Forwards", 0);
+
+ MyConnectionType conType = {0};
+
+ conType.extIP = atol(szNetID);
+ conType.SetUdpCon(szConnType);
+ conType.upnpNAT = strcmp(szUPnPNat, "true") == 0;
+ conType.icf = strcmp(szICF, "true") == 0;
+ conType.CalculateWeight();
+
+ MimeHeaders chdrs(12);
+ bool listen = false;
+
+ debugLogA("Connection weight, his: %d mine: %d", conType.weight, MyConnection.weight);
+ if (conType.weight <= MyConnection.weight)
+ listen = p2p_createListener(ft, dc, chdrs);
+
+ if (!listen)
+ {
+ chdrs.addString("Bridge", "TCPv1");
+ chdrs.addBool("Listening", false);
+
+ if (dc->useHashedNonce)
+ chdrs.addString("Hashed-Nonce", dc->mNonceToHash(), 2);
+ else
+ chdrs.addString("Nonce", sttVoidUid);
+
+ chdrs.addULong("SessionID", ft->p2p_sessionid);
+ chdrs.addString("SChannelState", "0");
+ chdrs.addString("Capabilities-Flags", "1");
+ }
+
+ tResult.addString("Content-Type", "application/x-msnmsgr-transrespbody");
+
+ if (!ft->p2p_isV2) p2p_getMsgId(ft->p2p_dest, -1);
+ p2p_sendSlp(200, ft, tResult, chdrs);
+}
+
+
+void CMsnProto::p2p_startConnect(const char* wlid, const char* szCallID, const char* addr, const char* port, bool ipv6)
+{
+ if (port == NULL) return;
+
+ char *pPortTokBeg = (char*)port;
+ for (;;)
+ {
+ char *pPortTokEnd = strchr(pPortTokBeg, ' ');
+ if (pPortTokEnd != NULL) *pPortTokEnd = 0;
+
+ char *pAddrTokBeg = (char*)addr;
+ for (;;)
+ {
+ char *pAddrTokEnd = strchr(pAddrTokBeg, ' ');
+ if (pAddrTokEnd != NULL) *pAddrTokEnd = 0;
+
+ ThreadData* newThread = new ThreadData;
+
+ newThread->mType = SERVER_P2P_DIRECT;
+ newThread->mInitialContactWLID = mir_strdup(wlid);
+ mir_snprintf(newThread->mCookie, sizeof(newThread->mCookie), "%s", szCallID);
+ mir_snprintf(newThread->mServer, sizeof(newThread->mServer),
+ ipv6 ? "[%s]:%s" : "%s:%s", pAddrTokBeg, pPortTokBeg);
+
+ newThread->startThread(&CMsnProto::p2p_fileActiveThread, this);
+
+ if (pAddrTokEnd == NULL) break;
+
+ *pAddrTokEnd = ' ';
+ pAddrTokBeg = pAddrTokEnd + 1;
+ }
+
+ if (pPortTokEnd == NULL) break;
+
+ *pPortTokEnd = ' ';
+ pPortTokBeg = pPortTokEnd + 1;
+ }
+}
+
+void CMsnProto::p2p_InitDirectTransfer2(MimeHeaders& tFileInfo, MimeHeaders& tFileInfo2, const char* wlid)
+{
+ const char *szCallID = tFileInfo["Call-ID"],
+ *szInternalAddress = tFileInfo2["IPv4Internal-Addrs"],
+ *szInternalPort = tFileInfo2["IPv4Internal-Port"],
+ *szExternalAddress = tFileInfo2["IPv4External-Addrs"],
+ *szExternalPort = tFileInfo2["IPv4External-Port"],
+ *szNonce = tFileInfo2["Nonce"],
+ *szHashedNonce = tFileInfo2["Hashed-Nonce"],
+ *szListening = tFileInfo2["Listening"],
+ *szV6Address = tFileInfo2["IPv6-Addrs"],
+ *szV6Port = tFileInfo2["IPv6-Port" ];
+
+ if ((szNonce == NULL && szHashedNonce == NULL) || szListening == NULL)
+ {
+ debugLogA("Ignoring invalid invitation: Listening='%s', Nonce=%s", szListening, szNonce);
+ return;
+ }
+
+ directconnection* dc = p2p_getDCByCallID(szCallID, wlid);
+ if (dc == NULL)
+ {
+ dc = new directconnection(szCallID, wlid);
+ p2p_registerDC(dc);
+ }
+
+ dc->useHashedNonce = szHashedNonce != NULL;
+ replaceStr(dc->xNonce, szHashedNonce ? szHashedNonce : szNonce);
+
+ if (!strcmp(szListening, "true") && strcmp(dc->xNonce, sttVoidUid))
+ {
+ p2p_startConnect(wlid, szCallID, szV6Address, szV6Port, true);
+ p2p_startConnect(wlid, szCallID, szInternalAddress, szInternalPort, false);
+ p2p_startConnect(wlid, szCallID, szExternalAddress, szExternalPort, false);
+ }
+}
+
+void CMsnProto::p2p_AcceptTransfer(MimeHeaders& tFileInfo, MimeHeaders& tFileInfo2, const char* wlid)
+{
+ const char *szCallID = tFileInfo["Call-ID"];
+ const char* szOldContentType = tFileInfo["Content-Type"];
+ const char *szBranch = tFileInfo["Via"];
+
+ if (szBranch != NULL) {
+ szBranch = strstr(szBranch, "branch=");
+ if (szBranch != NULL)
+ szBranch += 7;
+ }
+
+ filetransfer ftl(this), *ft = p2p_getSessionByCallID(szCallID, wlid);
+
+ if (!ft || !ft->p2p_sessionid)
+ {
+ ft = &ftl;
+ replaceStr(ft->p2p_branch, szBranch);
+ replaceStr(ft->p2p_callID, szCallID);
+ replaceStr(ft->p2p_dest, wlid);
+ ft->p2p_isV2 = strchr(wlid, ';') != NULL;
+ ft->std.hContact = MSN_HContactFromEmail(wlid);
+ }
+ else
+ {
+ if (!(ft->std.flags & PFTS_SENDING))
+ {
+ replaceStr(ft->p2p_branch, szBranch);
+ replaceStr(ft->p2p_callID, szCallID);
+ }
+ }
+
+ if (szCallID == NULL || szBranch == NULL || szOldContentType == NULL)
+ {
+ debugLogA("Ignoring invalid invitation: CallID='%s', szBranch='%s'", szCallID, szBranch);
+LBL_Close:
+ p2p_sendStatus(ft, 500);
+ return;
+ }
+
+ MimeHeaders tResult(8);
+ tResult.addString("CSeq", "0 ");
+ tResult.addString("Call-ID", ft->p2p_callID);
+ tResult.addLong("Max-Forwards", 0);
+
+ MimeHeaders chdrs(12);
+
+ if (!strcmp(szOldContentType, "application/x-msnmsgr-sessionreqbody"))
+ {
+ if (ft == &ftl)
+ {
+ p2p_sendCancel(ft);
+ return;
+ }
+
+ if (!ft->bAccepted)
+ {
+ replaceStr(ft->p2p_dest, wlid);
+ ft->bAccepted = true;
+ }
+ else
+ return;
+
+ if (ft->p2p_type != MSN_APPID_FILE)
+ {
+ if (ft->fileId == -1) ft->create();
+ return;
+ }
+
+ p2p_sendFeedStart(ft);
+
+ ThreadData* T = MSN_GetP2PThreadByContact(ft->p2p_dest);
+ if (T != NULL && T->mType == SERVER_P2P_DIRECT)
+ {
+ MSN_StartP2PTransferByContact(ft->p2p_dest);
+ return;
+ }
+
+ if (usingGateway)
+ MSN_StartP2PTransferByContact(ft->p2p_dest);
+
+ directconnection* dc = new directconnection(szCallID, wlid);
+ p2p_registerDC(dc);
+
+ tResult.addString("Content-Type", "application/x-msnmsgr-transreqbody");
+
+ chdrs.addString("Bridges", "TCPv1");
+ chdrs.addLong("NetID", MyConnection.extIP);
+ chdrs.addString("Conn-Type", MyConnection.GetMyUdpConStr());
+ chdrs.addBool("UPnPNat", MyConnection.upnpNAT);
+ chdrs.addBool("ICF", MyConnection.icf);
+ chdrs.addString("IPv6-global", GetGlobalIp(), 2);
+ chdrs.addString("Hashed-Nonce", dc->mNonceToHash(), 2);
+ }
+ else if (!strcmp(szOldContentType, "application/x-msnmsgr-transrespbody"))
+ {
+ const char *szListening = tFileInfo2["Listening"],
+ *szNonce = tFileInfo2["Nonce"],
+ *szHashedNonce = tFileInfo2["Hashed-Nonce"],
+ *szExternalAddress = tFileInfo2["IPv4External-Addrs"],
+ *szExternalPort = tFileInfo2["IPv4External-Port" ],
+ *szInternalAddress = tFileInfo2["IPv4Internal-Addrs"],
+ *szInternalPort = tFileInfo2["IPv4Internal-Port" ],
+ *szV6Address = tFileInfo2["IPv6-Addrs"],
+ *szV6Port = tFileInfo2["IPv6-Port" ];
+
+ if ((szNonce == NULL && szHashedNonce == NULL) || szListening == NULL)
+ {
+ debugLogA("Invalid data packet, exiting...");
+ goto LBL_Close;
+ }
+
+ directconnection* dc = p2p_getDCByCallID(szCallID, wlid);
+ if (dc == NULL) return;
+
+ if (!dc->bAccepted)
+ dc->bAccepted = true;
+ else
+ return;
+
+ dc->useHashedNonce = szHashedNonce != NULL;
+ replaceStr(dc->xNonce, szHashedNonce ? szHashedNonce : szNonce);
+
+ // another side reported that it will be a server.
+ if (!strcmp(szListening, "true") && (szNonce == NULL || strcmp(szNonce, sttVoidUid)))
+ {
+ p2p_startConnect(ft->p2p_dest, szCallID, szV6Address, szV6Port, true);
+ p2p_startConnect(ft->p2p_dest, szCallID, szInternalAddress, szInternalPort, false);
+ p2p_startConnect(ft->p2p_dest, szCallID, szExternalAddress, szExternalPort, false);
+ return;
+ }
+
+ // no, send a file via server
+ if (!p2p_createListener(ft, dc, chdrs))
+ {
+ p2p_unregisterDC(dc);
+ if (ft != &ftl)
+ MSN_StartP2PTransferByContact(ft->p2p_dest);
+ else
+ p2p_startSessions(ft->p2p_dest);
+ return;
+ }
+
+ tResult.addString("Content-Type", "application/x-msnmsgr-transrespbody");
+ }
+ else if (!strcmp(szOldContentType, "application/x-msnmsgr-transreqbody"))
+ {
+ const char *szHashedNonce = tFileInfo2["Hashed-Nonce"];
+ const char *szNonce = tFileInfo2["Nonce"];
+
+ directconnection* dc = p2p_getDCByCallID(szCallID, wlid);
+ if (dc == NULL)
+ {
+ dc = new directconnection(szCallID, wlid);
+ p2p_registerDC(dc);
+ }
+
+ dc->useHashedNonce = szHashedNonce != NULL;
+ replaceStr(dc->xNonce, szHashedNonce ? szHashedNonce : szNonce);
+
+ // no, send a file via server
+ if (!p2p_createListener(ft, dc, chdrs))
+ {
+ p2p_unregisterDC(dc);
+ MSN_StartP2PTransferByContact(ft->p2p_dest);
+ return;
+ }
+
+ tResult.addString("Content-Type", "application/x-msnmsgr-transrespbody");
+ }
+ else
+ return;
+
+ if (!ft->p2p_isV2) p2p_getMsgId(ft->p2p_dest, -1);
+ p2p_sendSlp(-2, ft, tResult, chdrs);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_processSIP - processes all MSN SIP commands
+
+void CMsnProto::p2p_processSIP(ThreadData* info, char* msgbody, P2PB_Header* hdrdata, const char* wlid)
+{
+ int iMsgType = 0;
+ if (!memcmp(msgbody, "INVITE MSNMSGR:", 15))
+ iMsgType = 1;
+ else if (!memcmp(msgbody, "MSNSLP/1.0 200 ", 15))
+ iMsgType = 2;
+ else if (!memcmp(msgbody, "BYE MSNMSGR:", 12))
+ iMsgType = 3;
+ else if (!memcmp(msgbody, "MSNSLP/1.0 603 ", 15))
+ iMsgType = 4;
+ else if (!memcmp(msgbody, "MSNSLP/1.0 481 ", 15))
+ iMsgType = 4;
+ else if (!memcmp(msgbody, "MSNSLP/1.0 500 ", 15))
+ iMsgType = 4;
+ else if (!memcmp(msgbody, "ACK MSNMSGR:", 12))
+ iMsgType = 5;
+
+ char* peol = strstr(msgbody, "\r\n");
+ if (peol != NULL)
+ msgbody = peol+2;
+
+ MimeHeaders tFileInfo, tFileInfo2;
+ msgbody = tFileInfo.readFromBuffer(msgbody);
+ msgbody = tFileInfo2.readFromBuffer(msgbody);
+
+ const char* szContentType = tFileInfo["Content-Type"];
+ if (szContentType == NULL)
+ {
+ debugLogA("Invalid or missing Content-Type field, exiting");
+ return;
+ }
+
+ if (hdrdata && !hdrdata->isV2Hdr())
+ {
+ if (iMsgType == 2 || (iMsgType == 1 && !strcmp(szContentType, "application/x-msnmsgr-transreqbody")))
+ p2p_getMsgId(wlid, 1);
+ }
+
+ switch(iMsgType)
+ {
+ case 1:
+ if (!strcmp(szContentType, "application/x-msnmsgr-sessionreqbody"))
+ p2p_InitFileTransfer(info, tFileInfo, tFileInfo2, wlid);
+ else if (!strcmp(szContentType, "application/x-msnmsgr-transreqbody"))
+ p2p_InitDirectTransfer(tFileInfo, tFileInfo2, wlid);
+ else if (!strcmp(szContentType, "application/x-msnmsgr-transrespbody"))
+ p2p_InitDirectTransfer2(tFileInfo, tFileInfo2, wlid);
+ break;
+
+ case 2:
+ p2p_AcceptTransfer(tFileInfo, tFileInfo2, wlid);
+ break;
+
+ case 3:
+ if (!strcmp(szContentType, "application/x-msnmsgr-sessionclosebody"))
+ {
+ filetransfer* ft = p2p_getSessionByCallID(tFileInfo["Call-ID"], wlid);
+ if (ft != NULL)
+ {
+ if (ft->std.currentFileProgress < ft->std.currentFileSize)
+ {
+ ft->bCanceled = true;
+ p2p_sendAbortSession(ft);
+ }
+ else
+ {
+ if (!(ft->std.flags & PFTS_SENDING))
+ ft->bCompleted = true;
+ }
+
+ p2p_sessionComplete(ft);
+ }
+ }
+ break;
+
+ case 4:
+ {
+ const char* szCallID = tFileInfo["Call-ID"];
+
+// application/x-msnmsgr-session-failure-respbody
+
+ directconnection *dc = p2p_getDCByCallID(szCallID, wlid);
+ if (dc != NULL)
+ {
+ p2p_unregisterDC(dc);
+ break;
+ }
+
+ filetransfer* ft = p2p_getSessionByCallID(szCallID, wlid);
+ if (ft == NULL)
+ break;
+
+ ft->close();
+ if (!(ft->std.flags & PFTS_SENDING)) _tremove(ft->std.tszCurrentFile);
+
+ p2p_unregisterSession(ft);
+ }
+ break;
+
+ case 5:
+ if (!strcmp(szContentType, "application/x-msnmsgr-turnsetup"))
+ {
+// tFileInfo2["ServerAddress"];
+// tFileInfo2["SessionUsername"];
+// tFileInfo2["SessionPassword"];
+ }
+ else if (!strcmp(szContentType, "application/x-msnmsgr-transudpswitch"))
+ {
+// tFileInfo2["IPv6AddrsAndPorts"];
+// tFileInfo2["IPv4ExternalAddrsAndPorts"];
+// tFileInfo2["IPv4InternalAddrsAndPorts"];
+ }
+ break;
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_processMsg - processes all MSN P2P incoming messages
+void CMsnProto::p2p_processMsgV2(ThreadData* info, char* msgbody, const char* wlid)
+{
+ P2PV2_Header hdrdata;
+
+ char *msg = hdrdata.parseMsg(msgbody);
+ hdrdata.logHeader(this);
+
+ if (hdrdata.mSessionID == 0)
+ {
+ if (hdrdata.mPacketLen == 0)
+ {
+ if (hdrdata.mOpCode & 0x02)
+ p2p_sendAck(wlid, &hdrdata);
+ return;
+ }
+
+ if (hdrdata.mRemSize || hdrdata.mTFCode == 0)
+ {
+ char msgid[128];
+ mir_snprintf(msgid, sizeof(msgid), "%s_%08x", wlid, hdrdata.mPacketNum);
+
+ int idx;
+ if (hdrdata.mTFCode == 0x01)
+ {
+ const size_t portion = hdrdata.mPacketLen + (msg - msgbody);
+ const size_t len = portion + hdrdata.mRemSize;
+ idx = addCachedMsg(msgid, msgbody, 0, portion, len, false);
+ }
+ else
+ {
+ size_t len = hdrdata.mPacketLen + hdrdata.mRemSize;
+ size_t offset = getCachedMsgSize(msgid); if (offset >= len) offset -= len;
+ idx = addCachedMsg(msgid, msg, offset, hdrdata.mPacketLen, len, false);
+ }
+
+ if (hdrdata.mRemSize == 0)
+ {
+ size_t newsize;
+ if (getCachedMsg(idx, msgbody, newsize))
+ {
+ unsigned id = hdrdata.mID;
+ msg = hdrdata.parseMsg(msgbody);
+ hdrdata.mID = id;
+
+ if (hdrdata.mOpCode & 0x02)
+ p2p_sendAck(wlid, &hdrdata);
+
+ if (hdrdata.mTFCode)
+ p2p_processSIP(info, msg, &hdrdata, wlid);
+ mir_free(msgbody);
+ }
+ else
+ clearCachedMsg(idx);
+ }
+ }
+ else
+ {
+ if (hdrdata.mOpCode & 0x02)
+ p2p_sendAck(wlid, &hdrdata);
+
+ p2p_processSIP(info, msg, &hdrdata, wlid);
+ }
+
+ return;
+ }
+
+ if (hdrdata.mOpCode & 0x02)
+ p2p_sendAck(wlid, &hdrdata);
+
+ filetransfer* ft = p2p_getSessionByID(hdrdata.mSessionID);
+ if (ft == NULL) return;
+
+ ft->ts = time(NULL);
+
+ if (hdrdata.mTFCode >= 4 && hdrdata.mTFCode <= 7)
+ {
+ _write(ft->fileId, msg, hdrdata.mPacketLen);
+
+ ft->std.totalProgress += hdrdata.mPacketLen;
+ ft->std.currentFileProgress += hdrdata.mPacketLen;
+
+ if (ft->p2p_appID == MSN_APPID_FILE && clock() >= ft->nNotify)
+ {
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+ ft->nNotify = clock() + 500;
+
+ //---- send an ack: body was transferred correctly
+ debugLogA("Transferred %I64u bytes remaining %I64u", ft->std.currentFileProgress, hdrdata.mRemSize);
+ }
+
+ if (hdrdata.mRemSize == 0)
+ {
+ if (ft->p2p_appID == MSN_APPID_FILE)
+ {
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+ ft->complete();
+ }
+ else
+ {
+ p2p_savePicture2disk(ft);
+ if (!ft->p2p_isV2) p2p_sendBye(ft);
+ }
+ }
+ }
+}
+
+void CMsnProto::p2p_processMsg(ThreadData* info, char* msgbody, const char* wlid)
+{
+ P2P_Header hdrdata;
+ msgbody = hdrdata.parseMsg(msgbody);
+ hdrdata.logHeader(this);
+
+ //---- if we got a message
+ if (LOWORD(hdrdata.mFlags) == 0 && hdrdata.mSessionID == 0)
+ {
+ // MsnContact *cont = Lists_Get(wlid);
+ // if (cont && cont->places.getCount())
+ // return;
+
+ if (hdrdata.mPacketLen < hdrdata.mTotalSize)
+ {
+ char msgid[128];
+ mir_snprintf(msgid, sizeof(msgid), "%s_%08x", wlid, hdrdata.mID);
+ int idx = addCachedMsg(msgid, msgbody, (size_t)hdrdata.mOffset, hdrdata.mPacketLen,
+ (size_t)hdrdata.mTotalSize, false);
+
+ char* newbody;
+ size_t newsize;
+ if (getCachedMsg(idx, newbody, newsize))
+ {
+ p2p_sendAck(wlid, &hdrdata);
+ p2p_processSIP(info, newbody, &hdrdata, wlid);
+ mir_free(newbody);
+ }
+ else
+ {
+ if (hdrdata.mOffset + hdrdata.mPacketLen >= hdrdata.mTotalSize)
+ clearCachedMsg(idx);
+ }
+ }
+ else
+ {
+ p2p_sendAck(wlid, &hdrdata);
+ p2p_processSIP(info, msgbody, &hdrdata, wlid);
+ }
+
+ return;
+ }
+
+ filetransfer* ft = p2p_getSessionByID(hdrdata.mSessionID);
+ if (ft == NULL)
+ ft = p2p_getSessionByUniqueID(hdrdata.mAckUniqueID);
+
+ if (ft == NULL) return;
+
+ ft->ts = time(NULL);
+
+ //---- receiving redirect -----------
+ if (hdrdata.mFlags == 0x01)
+ {
+ if (WaitForSingleObject(ft->hLockHandle, INFINITE) == WAIT_OBJECT_0)
+ {
+ __int64 dp = (__int64)(ft->std.currentFileProgress - hdrdata.mAckDataSize);
+ ft->std.totalProgress -= dp ;
+ ft->std.currentFileProgress -= dp;
+ _lseeki64(ft->fileId, ft->std.currentFileProgress, SEEK_SET);
+ ft->tType = info->mType;
+ ReleaseMutex(ft->hLockHandle);
+ }
+ }
+
+ //---- receiving ack -----------
+ if (hdrdata.mFlags == 0x02)
+ {
+ ft->p2p_waitack = false;
+
+ if (hdrdata.mAckSessionID == ft->p2p_sendmsgid)
+ {
+ if (ft->p2p_appID == MSN_APPID_FILE)
+ {
+ ft->bCompleted = true;
+ p2p_sendBye(ft);
+ }
+ return;
+ }
+
+ if (hdrdata.mAckSessionID == ft->p2p_byemsgid)
+ {
+ p2p_sessionComplete(ft);
+ return;
+ }
+
+ switch(ft->p2p_ackID)
+ {
+ case 1000:
+ //---- send Data Preparation Message
+ p2p_sendAvatarInit(ft);
+ break;
+
+ case 1001:
+ //---- send Data Messages
+ MSN_StartP2PTransferByContact(ft->p2p_dest);
+ break;
+ }
+
+ ft->p2p_ackID++;
+ return;
+ }
+
+ if (LOWORD(hdrdata.mFlags) == 0)
+ {
+ //---- accept the data preparation message ------
+ // const unsigned* pLongs = (unsigned*)msgbody;
+ if (hdrdata.mPacketLen == 4 && hdrdata.mTotalSize == 4)
+ {
+ p2p_sendAck(ft->p2p_dest, &hdrdata);
+ return;
+ }
+ else
+ hdrdata.mFlags = 0x20;
+ }
+
+ //---- receiving data -----------
+ if (LOWORD(hdrdata.mFlags) == 0x20 || LOWORD(hdrdata.mFlags) == 0x30)
+ {
+ if (hdrdata.mOffset + hdrdata.mPacketLen > hdrdata.mTotalSize)
+ hdrdata.mPacketLen = DWORD(hdrdata.mTotalSize - hdrdata.mOffset);
+
+ if (ft->tTypeReq == 0 || ft->tTypeReq == info->mType)
+ {
+ ft->tType = info->mType;
+ ft->p2p_sendmsgid = hdrdata.mID;
+ }
+
+ __int64 dsz = ft->std.currentFileSize - hdrdata.mOffset;
+ if (dsz > hdrdata.mPacketLen) dsz = hdrdata.mPacketLen;
+
+ if (ft->tType == info->mType)
+ {
+ if (dsz > 0 && ft->fileId >= 0)
+ {
+ if (ft->lstFilePtr != hdrdata.mOffset)
+ _lseeki64(ft->fileId, hdrdata.mOffset, SEEK_SET);
+ _write(ft->fileId, msgbody, (unsigned int)dsz);
+
+ ft->lstFilePtr = hdrdata.mOffset + dsz;
+
+ __int64 dp = ft->lstFilePtr - ft->std.currentFileProgress;
+ if (dp > 0)
+ {
+ ft->std.totalProgress += dp;
+ ft->std.currentFileProgress += dp;
+
+ if (ft->p2p_appID == MSN_APPID_FILE && clock() >= ft->nNotify)
+ {
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+ ft->nNotify = clock() + 500;
+ }
+ }
+
+ //---- send an ack: body was transferred correctly
+ debugLogA("Transferred %I64u bytes out of %I64u", ft->std.currentFileProgress, hdrdata.mTotalSize);
+ }
+
+ if (ft->std.currentFileProgress >= hdrdata.mTotalSize)
+ {
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->std);
+ p2p_sendAck(ft->p2p_dest, &hdrdata);
+ if (ft->p2p_appID == MSN_APPID_FILE)
+ {
+ ft->ts = time(NULL);
+ ft->p2p_waitack = true;
+ ft->complete();
+ }
+ else
+ {
+ p2p_savePicture2disk(ft);
+ p2p_sendBye(ft);
+ }
+ }
+ }
+ }
+
+ if (hdrdata.mFlags == 0x40 || hdrdata.mFlags == 0x80)
+ {
+ p2p_sendAbortSession(ft);
+ p2p_unregisterSession(ft);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// p2p_invite - invite another side to transfer an avatar
+
+void CMsnProto::p2p_invite(unsigned iAppID, filetransfer* ft, const char *wlid)
+{
+ const char* szAppID;
+ switch(iAppID)
+ {
+ case MSN_APPID_FILE: szAppID = "{5D3E02AB-6190-11D3-BBBB-00C04F795683}"; break;
+ case MSN_APPID_AVATAR: szAppID = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}"; break;
+ case MSN_APPID_CUSTOMSMILEY: szAppID = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}"; break;
+ case MSN_APPID_CUSTOMANIMATEDSMILEY: szAppID = "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}"; break;
+ default: return;
+ }
+
+ ft->p2p_type = iAppID;
+ ft->p2p_acksessid = MSN_GenRandom();
+ mir_free(ft->p2p_callID);
+ ft->p2p_callID = getNewUuid();
+
+ MsnContact* cont = Lists_Get(ft->std.hContact);
+ if (cont == NULL) return;
+
+ if (ft->p2p_dest == NULL)
+ {
+ ft->p2p_isV2 = (cont->cap2 & capex_SupportsPeerToPeerV2) != 0 || (cont->cap1 >> 28) >= 10;
+ ft->p2p_dest = mir_strdup(wlid ? wlid : cont->email);
+ }
+
+ char* pContext = NULL;
+ size_t cbContext = 0;
+
+ switch (iAppID)
+ {
+ case MSN_APPID_FILE:
+ {
+ cbContext = sizeof(HFileContext);
+ pContext = (char*)malloc(cbContext);
+ HFileContext* ctx = (HFileContext*)pContext;
+ memset(pContext, 0, cbContext);
+ if (ft->p2p_isV2)
+ {
+ cbContext -= 64;
+ ctx->ver = 2;
+ }
+ else
+ {
+ ctx->ver = 3;
+ ctx->id = 0xffffffff;
+ }
+ ctx->len = (unsigned)cbContext;
+ ctx->type = MSN_TYPEID_FTNOPREVIEW;
+ ctx->dwSize = ft->std.currentFileSize;
+
+ TCHAR* pszFiles = _tcsrchr(ft->std.tszCurrentFile, '\\');
+ if (pszFiles)
+ pszFiles++;
+ else
+ pszFiles = ft->std.tszCurrentFile;
+
+ wchar_t *fname = mir_t2u(pszFiles);
+ wcsncpy(ctx->wszFileName, fname, MAX_PATH);
+ mir_free(fname);
+
+ ft->p2p_appID = MSN_APPID_FILE;
+ }
+ break;
+
+ default:
+ ft->p2p_appID = MSN_APPID_AVATAR2;
+
+ if (ft->p2p_object == NULL)
+ {
+ delete ft;
+ return;
+ }
+
+ ezxml_t xmlo = ezxml_parse_str(NEWSTR_ALLOCA(ft->p2p_object), strlen(ft->p2p_object));
+ ezxml_t xmlr = ezxml_new("msnobj");
+
+ const char* p;
+ p = ezxml_attr(xmlo, "Creator");
+ if (p != NULL)
+ ezxml_set_attr(xmlr, "Creator", p);
+ p = ezxml_attr(xmlo, "Size");
+ if (p != NULL) {
+ ezxml_set_attr(xmlr, "Size", p);
+ ft->std.totalBytes = ft->std.currentFileSize = _atoi64(p);
+ }
+ p = ezxml_attr(xmlo, "Type");
+ if (p != NULL)
+ ezxml_set_attr(xmlr, "Type", p);
+ p = ezxml_attr(xmlo, "Location");
+ if (p != NULL)
+ ezxml_set_attr(xmlr, "Location", p);
+ p = ezxml_attr(xmlo, "Friendly");
+ if (p != NULL)
+ ezxml_set_attr(xmlr, "Friendly", p);
+ p = ezxml_attr(xmlo, "SHA1D");
+ if (p != NULL)
+ ezxml_set_attr(xmlr, "SHA1D", p);
+ p = ezxml_attr(xmlo, "SHA1C");
+ if (p != NULL)
+ ezxml_set_attr(xmlr, "SHA1C", p);
+
+ pContext = ezxml_toxml(xmlr, false);
+ cbContext = strlen(pContext)+1;
+
+ ezxml_free(xmlr);
+ ezxml_free(xmlo);
+
+ break;
+ }
+
+ bool sessionExist = p2p_sessionRegistered(ft);
+ if (!sessionExist)
+ {
+ p2p_registerSession(ft);
+
+ unsigned short status = getWord(ft->std.hContact, "Status", ID_STATUS_OFFLINE);
+ if ((myFlags & 0x4000000) && cont->places.getCount() <= 1 &&
+ status != ID_STATUS_OFFLINE && status != ID_STATUS_INVISIBLE && m_iStatus != ID_STATUS_INVISIBLE)
+ {
+ if (ft->p2p_isV2)
+ {
+ if (cont->places.getCount() && cont->places[0].cap1 & cap_SupportsP2PBootstrap)
+ {
+ char wlid[128];
+ mir_snprintf(wlid, SIZEOF(wlid),
+ strcmp(cont->places[0].id, sttVoidUid) ? "%s;%s" : "%s",
+ cont->email, cont->places[0].id);
+
+ if (!MSN_GetThreadByContact(wlid, SERVER_P2P_DIRECT))
+ p2p_inviteDc(ft, wlid);
+ else
+ p2p_invite(ft->p2p_type, ft, wlid);
+
+ free(pContext);
+ return;
+ }
+ }
+ else
+ {
+ const char *wlid = cont->email;
+ if (cont->cap1 & cap_SupportsP2PBootstrap)
+ {
+ if (!MSN_GetThreadByContact(wlid, SERVER_P2P_DIRECT))
+ p2p_inviteDc(ft, wlid);
+ else
+ p2p_invite(ft->p2p_type, ft, wlid);
+
+ free(pContext);
+ return;
+ }
+ }
+ }
+ }
+
+ if (!ft->bAccepted)
+ ft->p2p_sessionid = MSN_GenRandom();
+
+ ptrA szContextEnc( mir_base64_encode((PBYTE)pContext, (unsigned)cbContext));
+ int cbContextEnc = lstrlenA(szContextEnc);
+
+ MimeHeaders chdrs(10);
+ chdrs.addString("EUF-GUID", szAppID);
+ chdrs.addULong("SessionID", ft->p2p_sessionid);
+ chdrs.addULong("AppID", ft->p2p_appID);
+ chdrs.addString("Context", szContextEnc);
+
+ MimeHeaders tResult(8);
+ tResult.addString("CSeq", "0 ");
+ tResult.addString("Call-ID", ft->p2p_callID);
+ tResult.addLong("Max-Forwards", 0);
+ tResult.addString("Content-Type", "application/x-msnmsgr-sessionreqbody");
+
+ if (iAppID != MSN_APPID_FILE)
+ ft->p2p_waitack = true;
+
+ if (ft->p2p_isV2 && ft->std.currentFileNumber == 0)
+ {
+ for (int i = 0; i < cont->places.getCount(); ++i)
+ {
+ char wlid[128];
+ mir_snprintf(wlid, SIZEOF(wlid),
+ strcmp(cont->places[i].id, sttVoidUid) ? "%s;%s" : "%s",
+ cont->email, cont->places[i].id);
+
+ p2p_sendSlp(-2, ft, tResult, chdrs, wlid);
+ }
+ }
+ else
+ p2p_sendSlp(-2, ft, tResult, chdrs, wlid);
+
+ free(pContext);
+}
+
+
+void CMsnProto::p2p_inviteDc(filetransfer* ft, const char *wlid)
+{
+ directconnection* dc = new directconnection(szUbnCall, wlid);
+ p2p_registerDC(dc);
+
+ MimeHeaders tResult(8);
+ tResult.addString("CSeq", "0 ");
+ tResult.addString("Call-ID", dc->callId);
+ tResult.addLong("Max-Forwards", 0);
+ tResult.addString("Content-Type", "application/x-msnmsgr-transreqbody");
+
+ MimeHeaders chdrs(12);
+
+ chdrs.addString("Bridges", "TCPv1 SBBridge");
+ chdrs.addLong("NetID", MyConnection.extIP);
+ chdrs.addString("Conn-Type", MyConnection.GetMyUdpConStr());
+ chdrs.addBool("UPnPNat", MyConnection.upnpNAT);
+ chdrs.addBool("ICF", MyConnection.icf);
+ chdrs.addString("IPv6-global", GetGlobalIp(), 2);
+ chdrs.addString("Hashed-Nonce", dc->mNonceToHash(), 2);
+ chdrs.addString("SessionID", "0");
+ chdrs.addString("SChannelState", "0");
+ chdrs.addString("Capabilities-Flags", "1");
+
+ p2p_sendSlp(-2, ft, tResult, chdrs, wlid);
+}
+/*
+void CMsnProto::p2p_sendSessionAck(filetransfer* ft)
+{
+ MimeHeaders tResult(8);
+ tResult.addString("CSeq", "0 ");
+ tResult.addString("Call-ID", sttVoidUid);
+ tResult.addLong("Max-Forwards", 0);
+ tResult.addString("Content-Type", "application/x-msnmsgr-transdestaddrupdate");
+
+ MimeHeaders chdrs(8);
+
+ chdrs.addString("IPv4ExternalAddrsAndPorts", mir_strdup(MyConnection.GetMyExtIPStr()), 6);
+ chdrs.addString("IPv4InternalAddrsAndPorts", mir_strdup(MyConnection.GetMyExtIPStr()), 6);
+ chdrs.addString("SessionID", "0");
+ chdrs.addString("SChannelState", "0");
+ chdrs.addString("Capabilities-Flags", "1");
+
+ p2p_sendSlp(-3, ft, tResult, chdrs);
+}
+*/
+void CMsnProto::p2p_sessionComplete(filetransfer* ft)
+{
+ if (ft->p2p_appID != MSN_APPID_FILE)
+ p2p_unregisterSession(ft);
+ else if (ft->std.flags & PFTS_SENDING)
+ {
+ if (ft->openNext() == -1)
+ {
+ bool success = ft->std.currentFileNumber >= ft->std.totalFiles && ft->bCompleted;
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, success ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, ft, 0);
+ }
+ else
+ {
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, ft, 0);
+ p2p_invite(ft->p2p_appID, ft, NULL);
+ }
+ }
+ else
+ {
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ft->bCompleted ? ACKRESULT_SUCCESS : ACKRESULT_FAILED, ft, 0);
+ p2p_unregisterSession(ft);
+ }
+}
+
+char* P2PV2_Header::parseMsg(char *buf)
+{
+ unsigned char hdrLen1 = *(unsigned char*)buf;
+ mOpCode = *(unsigned char*)(buf + 1);
+ mPacketLen = _htons(*(unsigned short*)(buf + 2));
+ mID = _htonl(*(unsigned*)(buf + 4));
+
+ char* buf1 = buf + hdrLen1;
+
+ for (char *tlvp = buf + 8; tlvp < buf1 && *tlvp; tlvp += 2 + tlvp[1])
+ {
+ switch (*tlvp)
+ {
+ case 1:
+ mCap = tlvp;
+ break;
+ case 2:
+ mAckUniqueID = _htonl(*(unsigned*)(tlvp + 4));
+ break;
+ case 3:
+ break;
+ }
+ }
+
+ if (mPacketLen == 0) return buf + hdrLen1;
+
+ unsigned char hdrLen2 = *(unsigned char*)buf1;
+ mTFCode = *(unsigned char*)(buf1 + 1);
+ mPacketNum = _htons(*(unsigned short*)(buf1 + 2));
+ mSessionID = _htonl(*(unsigned*)(buf1 + 4));
+
+ char* buf2 = buf1 + hdrLen2;
+
+ for (char *tlvp1 = buf1 + 8; tlvp1 < buf2 && *tlvp1; tlvp1 += 2 + tlvp1[1])
+ {
+ switch (*tlvp1)
+ {
+ case 1:
+ mRemSize = _htonl64(*(unsigned __int64*)(tlvp1 + 2));
+ break;
+ }
+ }
+
+ mPacketLen -= hdrLen2;
+ return buf1 + hdrLen2;
+}
+
+char* P2PV2_Header::createMsg(char *buf, const char* wlid, CMsnProto *ppro)
+{
+ unsigned char hdrLen1 = 8 + (mAckUniqueID ? 6 : 0) + (mCap ? 2 + mCap[1] : 0);
+ unsigned char comp = hdrLen1 & 3;
+ hdrLen1 += comp ? 4 - comp : 0;
+
+ unsigned char hdrLen2 = mPacketLen ? (8 + (mRemSize ? 10 : 0)) : 0;
+ comp = hdrLen2 & 3;
+ hdrLen2 += comp ? 4 - comp : 0;
+
+ mID = ppro->p2p_getMsgId(wlid, mPacketLen + hdrLen2);
+
+ memset(buf, 0, hdrLen1 + hdrLen2);
+
+ *(unsigned char*)(buf + 0) = hdrLen1;
+ *(unsigned char*)(buf + 1) = mOpCode;
+ *(unsigned short*)(buf + 2) = _htons(mPacketLen + hdrLen2);
+ *(unsigned*)(buf + 4) = _htonl(mID);
+
+ char *buf1 = buf + 8;
+
+ if (mAckUniqueID)
+ {
+ *(unsigned char*)buf1 = 2;
+ *(unsigned char*)(buf1 + 1) = 4;
+ *(unsigned*)(buf1 + 2) = _htonl(mAckUniqueID);
+ buf1 += 6;
+ }
+ if (mCap)
+ {
+ unsigned len = 2 + mCap[1];
+ memcpy(buf1, mCap, len);
+ buf1 += len;
+ }
+
+ buf1 = buf + hdrLen1;
+
+ if (hdrLen2 == 0) return buf1;
+
+ *(unsigned char*)(buf1 + 0) = hdrLen2;
+ *(unsigned char*)(buf1 + 1) = mTFCode;
+ *(unsigned short*)(buf1 + 2) = _htons(mPacketNum);
+ *(unsigned*)(buf1 + 4) = _htonl(mSessionID);
+
+ if (mRemSize)
+ {
+ *(unsigned char*)(buf1 + 8) = 1;
+ *(unsigned char*)(buf1 + 9) = 8;
+ *(unsigned __int64*)(buf1 + 10) = _htonl64(mRemSize);
+ }
+
+ return buf1 + hdrLen2;
+}
+
+char* P2P_Header::createMsg(char *buf, const char* wlid, CMsnProto *ppro)
+{
+ if (!mID) mID = ppro->p2p_getMsgId(wlid, 1);
+ memcpy(buf, &mSessionID, 48);
+ return buf + 48;
+}
diff --git a/protocols/MSN/src/msn_p2ps.cpp b/protocols/MSN/src/msn_p2ps.cpp
new file mode 100644
index 0000000000..07d8f80d5a
--- /dev/null
+++ b/protocols/MSN/src/msn_p2ps.cpp
@@ -0,0 +1,292 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// add file session to a list
+
+void CMsnProto::p2p_registerSession(filetransfer* ft)
+{
+ mir_cslock lck(sessionLock);
+ sessionList.insert(ft);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// remove file session from a list
+
+void CMsnProto::p2p_unregisterSession(filetransfer* ft)
+{
+ mir_cslock lck(sessionLock);
+ sessionList.remove(ft);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// get session by some parameter
+
+filetransfer* CMsnProto::p2p_getSessionByID(unsigned id)
+{
+ if (id == 0)
+ return NULL;
+
+ mir_cslock lck(sessionLock);
+
+ for (int i=0; i < sessionList.getCount(); i++) {
+ filetransfer* FT = &sessionList[i];
+ if (FT->p2p_sessionid == id)
+ return FT;
+ }
+
+ return NULL;
+}
+
+filetransfer* CMsnProto::p2p_getSessionByUniqueID(unsigned id)
+{
+ if (id == 0)
+ return NULL;
+
+ mir_cslock lck(sessionLock);
+
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ if (FT->p2p_acksessid == id)
+ return FT;
+ }
+
+ return NULL;
+}
+
+
+bool CMsnProto::p2p_sessionRegistered(filetransfer* ft)
+{
+ if (ft != NULL && ft->p2p_appID == 0)
+ return true;
+
+ mir_cslock lck(sessionLock);
+ return sessionList.getIndex(ft) > -1;
+}
+
+filetransfer* CMsnProto::p2p_getThreadSession(MCONTACT hContact, TInfoType mType)
+{
+ mir_cslock lck(sessionLock);
+
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ if (FT->std.hContact == hContact && FT->tType == mType)
+ return FT;
+ }
+
+ return NULL;
+}
+
+void CMsnProto::p2p_clearThreadSessions(MCONTACT hContact, TInfoType mType)
+{
+ mir_cslock lck(sessionLock);
+
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* ft = &sessionList[i];
+ if (ft->std.hContact == hContact && ft->tType == mType)
+ {
+ ft->bCanceled = true;
+ ft->tType = SERVER_NOTIFICATION;
+ p2p_sendCancel(ft);
+ }
+ }
+}
+
+filetransfer* CMsnProto::p2p_getAvatarSession(MCONTACT hContact)
+{
+ mir_cslock lck(sessionLock);
+
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ if (FT->std.hContact == hContact && !(FT->std.flags & PFTS_SENDING) && FT->p2p_type == MSN_APPID_AVATAR)
+ return FT;
+ }
+
+ return NULL;
+}
+
+bool CMsnProto::p2p_isAvatarOnly(MCONTACT hContact)
+{
+ mir_cslock lck(sessionLock);
+
+ bool result = true;
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ result &= FT->std.hContact != hContact || FT->p2p_type != MSN_APPID_FILE;
+ }
+
+ return result;
+}
+
+void CMsnProto::p2p_clearDormantSessions(void)
+{
+ mir_cslockfull lck(sessionLock);
+
+ time_t ts = time(NULL);
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ if (!FT->p2p_sessionid && !MSN_GetUnconnectedThread(FT->p2p_dest, SERVER_P2P_DIRECT))
+ p2p_invite(FT->p2p_type, FT, NULL);
+ else if (FT->p2p_waitack && (ts - FT->ts) > 120)
+ {
+ FT->bCanceled = true;
+ p2p_sendCancel(FT);
+ lck.unlock();
+ p2p_unregisterSession(FT);
+ lck.lock();
+ i = 0;
+ }
+ }
+}
+
+void CMsnProto::p2p_redirectSessions(const char *wlid)
+{
+ mir_cslock lck(sessionLock);
+
+ ThreadData* T = MSN_GetP2PThreadByContact(wlid);
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ if (_stricmp(FT->p2p_dest, wlid) == 0 &&
+ FT->std.currentFileProgress < FT->std.currentFileSize &&
+ (T == NULL || (FT->tType != T->mType && FT->tType != 0)))
+ {
+ if (FT->p2p_isV2)
+ {
+ if ((FT->std.flags & PFTS_SENDING) && T)
+ FT->tType = T->mType;
+ }
+ else
+ {
+ if (!(FT->std.flags & PFTS_SENDING))
+ p2p_sendRedirect(FT);
+ }
+ }
+ }
+}
+
+void CMsnProto::p2p_startSessions(const char* wlid)
+{
+ mir_cslock lck(sessionLock);
+
+ char* szEmail;
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ if (!FT->bAccepted && !_stricmp(FT->p2p_dest, szEmail))
+ {
+ if (FT->p2p_appID == MSN_APPID_FILE && (FT->std.flags & PFTS_SENDING))
+ p2p_invite(FT->p2p_type, FT, wlid);
+ else if (FT->p2p_appID != MSN_APPID_FILE && !(FT->std.flags & PFTS_SENDING))
+ p2p_invite(FT->p2p_type, FT, wlid);
+ }
+ }
+}
+
+void CMsnProto::p2p_cancelAllSessions(void)
+{
+ mir_cslock lck(sessionLock);
+
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ sessionList[i].bCanceled = true;
+ p2p_sendCancel(&sessionList[i]);
+ }
+}
+
+filetransfer* CMsnProto::p2p_getSessionByCallID(const char* CallID, const char* wlid)
+{
+ if (CallID == NULL)
+ return NULL;
+
+ mir_cslock lck(sessionLock);
+
+ char* szEmail = NULL;
+ for (int i=0; i < sessionList.getCount(); i++)
+ {
+ filetransfer* FT = &sessionList[i];
+ if (FT->p2p_callID && !_stricmp(FT->p2p_callID, CallID))
+ {
+ if (_stricmp(FT->p2p_dest, wlid))
+ {
+ if (!szEmail)
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+ if (_stricmp(FT->p2p_dest, szEmail))
+ continue;
+ }
+ return FT;
+ }
+ }
+
+ return NULL;
+}
+
+
+void CMsnProto::p2p_registerDC(directconnection* dc)
+{
+ mir_cslock lck(sessionLock);
+ dcList.insert(dc);
+}
+
+void CMsnProto::p2p_unregisterDC(directconnection* dc)
+{
+ mir_cslock lck(sessionLock);
+ dcList.remove(dc);
+}
+
+directconnection* CMsnProto::p2p_getDCByCallID(const char* CallID, const char* wlid)
+{
+ if (CallID == NULL)
+ return NULL;
+
+ mir_cslock lck(sessionLock);
+
+ for (int i=0; i < dcList.getCount(); i++)
+ {
+ directconnection* DC = &dcList[i];
+ if (DC->callId != NULL && !strcmp(DC->callId, CallID) && !strcmp(DC->wlid, wlid))
+ return DC;
+ }
+
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// external functions
+
+void CMsnProto::P2pSessions_Uninit(void)
+{
+ mir_cslock lck(sessionLock);
+ sessionList.destroy();
+ dcList.destroy();
+}
diff --git a/protocols/MSN/src/msn_proto.cpp b/protocols/MSN/src/msn_proto.cpp
new file mode 100644
index 0000000000..f289313ee6
--- /dev/null
+++ b/protocols/MSN/src/msn_proto.cpp
@@ -0,0 +1,1107 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2008-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+static const COLORREF crCols[16] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
+
+int msn_httpGatewayInit(HANDLE hConn,NETLIBOPENCONNECTION *nloc,NETLIBHTTPREQUEST *nlhr);
+int msn_httpGatewayBegin(HANDLE hConn,NETLIBOPENCONNECTION *nloc);
+int msn_httpGatewayWrapSend(HANDLE hConn,PBYTE buf,int len,int flags,MIRANDASERVICE pfnNetlibSend);
+PBYTE msn_httpGatewayUnwrapRecv(NETLIBHTTPREQUEST *nlhr,PBYTE buf,int len,int *outBufLen,void *(*NetlibRealloc)(void*,size_t));
+
+static int CompareLists(const MsnContact* p1, const MsnContact* p2)
+{
+ return _stricmp(p1->email, p2->email);
+}
+
+CMsnProto::CMsnProto(const char* aProtoName, const TCHAR* aUserName) :
+ PROTO<CMsnProto>(aProtoName, aUserName),
+ contList(10, CompareLists),
+ grpList(10, CompareId),
+ sttThreads(10, PtrKeySortT),
+ sessionList(10, PtrKeySortT),
+ dcList(10, PtrKeySortT),
+ lsMessageQueue(1),
+ lsAvatarQueue(1),
+ msgCache(5, CompareId)
+{
+ db_set_resident(m_szModuleName, "Status");
+ db_set_resident(m_szModuleName, "IdleTS");
+ db_set_resident(m_szModuleName, "p2pMsgId");
+ db_set_resident(m_szModuleName, "MobileEnabled");
+ db_set_resident(m_szModuleName, "MobileAllowed");
+
+ // Protocol services and events...
+ hMSNNudge = CreateProtoEvent("/Nudge");
+
+ CreateProtoService(PS_CREATEACCMGRUI, &CMsnProto::SvcCreateAccMgrUI);
+
+ CreateProtoService(PS_GETAVATARINFOT, &CMsnProto::GetAvatarInfo);
+ CreateProtoService(PS_GETMYAWAYMSG, &CMsnProto::GetMyAwayMsg);
+
+ CreateProtoService(PS_LEAVECHAT, &CMsnProto::OnLeaveChat);
+
+ CreateProtoService(PS_GETMYAVATART, &CMsnProto::GetAvatar);
+ CreateProtoService(PS_SETMYAVATART, &CMsnProto::SetAvatar);
+ CreateProtoService(PS_GETAVATARCAPS, &CMsnProto::GetAvatarCaps);
+
+ CreateProtoService(PS_GET_LISTENINGTO, &CMsnProto::GetCurrentMedia);
+ CreateProtoService(PS_SET_LISTENINGTO, &CMsnProto::SetCurrentMedia);
+
+ CreateProtoService(PS_SETMYNICKNAME, &CMsnProto::SetNickName);
+ CreateProtoService(PS_SEND_NUDGE, &CMsnProto::SendNudge);
+
+ CreateProtoService(PS_GETUNREADEMAILCOUNT, &CMsnProto::GetUnreadEmailCount);
+
+ // event hooks
+ HookProtoEvent(ME_MSG_WINDOWPOPUP, &CMsnProto::OnWindowPopup);
+ HookProtoEvent(ME_CLIST_GROUPCHANGE, &CMsnProto::OnGroupChange);
+ HookProtoEvent(ME_OPT_INITIALISE, &CMsnProto::OnOptionsInit);
+ HookProtoEvent(ME_CLIST_DOUBLECLICKED, &CMsnProto::OnContactDoubleClicked);
+
+ LoadOptions();
+
+ for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
+ delSetting(hContact, "Status");
+ delSetting(hContact, "IdleTS");
+ delSetting(hContact, "p2pMsgId");
+ delSetting(hContact, "AccList");
+ }
+ delSetting("MobileEnabled");
+ delSetting("MobileAllowed");
+
+ char path[MAX_PATH];
+ if (db_get_static(NULL, m_szModuleName, "LoginServer", path, sizeof(path)) == 0 &&
+ (strcmp(path, MSN_DEFAULT_LOGIN_SERVER) == 0 ||
+ strcmp(path, MSN_DEFAULT_GATEWAY) == 0))
+ delSetting("LoginServer");
+
+ if (MyOptions.SlowSend) {
+ if (db_get_dw(NULL, "SRMsg", "MessageTimeout", 10000) < 60000)
+ db_set_dw(NULL, "SRMsg", "MessageTimeout", 60000);
+ if (db_get_dw(NULL, "SRMM", "MessageTimeout", 10000) < 60000)
+ db_set_dw(NULL, "SRMM", "MessageTimeout", 60000);
+ }
+
+ mailsoundname = (char*)mir_alloc(64);
+ mir_snprintf(mailsoundname, 64, "%s:Hotmail", m_szModuleName);
+ SkinAddNewSoundExT(mailsoundname, m_tszUserName, LPGENT("Live Mail"));
+
+ alertsoundname = (char*)mir_alloc(64);
+ mir_snprintf(alertsoundname, 64, "%s:Alerts", m_szModuleName);
+ SkinAddNewSoundExT(alertsoundname, m_tszUserName, LPGENT("Live Alert"));
+
+ MsgQueue_Init();
+ AvatarQueue_Init();
+ InitCustomFolders();
+
+ TCHAR szBuffer[MAX_PATH];
+ char szDbsettings[64];
+
+ NETLIBUSER nlu1 = { 0 };
+ nlu1.cbSize = sizeof(nlu1);
+ nlu1.flags = NUF_OUTGOING | NUF_HTTPCONNS | NUF_TCHAR;
+ nlu1.szSettingsModule = szDbsettings;
+ nlu1.ptszDescriptiveName = szBuffer;
+
+ mir_snprintf(szDbsettings, sizeof(szDbsettings), "%s_HTTPS", m_szModuleName);
+ mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s plugin HTTPS connections"), m_tszUserName);
+ hNetlibUserHttps = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu1);
+
+ NETLIBUSER nlu = { 0 };
+ nlu.cbSize = sizeof(nlu);
+ nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_TCHAR;
+ nlu.szSettingsModule = m_szModuleName;
+ nlu.ptszDescriptiveName = szBuffer;
+
+ nlu.szHttpGatewayUserAgent = (char*)MSN_USER_AGENT;
+ nlu.pfnHttpGatewayInit = msn_httpGatewayInit;
+ nlu.pfnHttpGatewayWrapSend = msn_httpGatewayWrapSend;
+ nlu.pfnHttpGatewayUnwrapRecv = msn_httpGatewayUnwrapRecv;
+
+ mir_sntprintf(szBuffer, SIZEOF(szBuffer), TranslateT("%s plugin connections"), m_tszUserName);
+ m_hNetlibUser = (HANDLE)CallService(MS_NETLIB_REGISTERUSER, 0, (LPARAM)&nlu);
+}
+
+CMsnProto::~CMsnProto()
+{
+ MsnRemoveMainMenus();
+
+ DestroyHookableEvent(hMSNNudge);
+
+ MSN_FreeGroups();
+ Threads_Uninit();
+ MsgQueue_Uninit();
+ AvatarQueue_Uninit();
+ Lists_Uninit();
+ P2pSessions_Uninit();
+ CachedMsg_Uninit();
+
+ Netlib_CloseHandle(m_hNetlibUser);
+ Netlib_CloseHandle(hNetlibUserHttps);
+
+ mir_free(mailsoundname);
+ mir_free(alertsoundname);
+
+ for (int i = 0; i < MSN_NUM_MODES; i++)
+ mir_free(msnModeMsgs[i]);
+
+ mir_free(msnLastStatusMsg);
+ mir_free(msnPreviousUUX);
+ mir_free(msnExternalIP);
+
+ mir_free(abCacheKey);
+ mir_free(sharingCacheKey);
+ mir_free(storageCacheKey);
+
+ FreeAuthTokens();
+}
+
+int CMsnProto::OnModulesLoaded(WPARAM, LPARAM)
+{
+ GCREGISTER gcr = { 0 };
+ gcr.cbSize = sizeof(GCREGISTER);
+ gcr.dwFlags = GC_TYPNOTIF | GC_CHANMGR;
+ gcr.iMaxText = 0;
+ gcr.nColors = 16;
+ gcr.pColors = (COLORREF*)crCols;
+ gcr.ptszDispName = m_tszUserName;
+ gcr.pszModule = m_szModuleName;
+ CallServiceSync(MS_GC_REGISTER, 0, (LPARAM)&gcr);
+
+ HookProtoEvent(ME_GC_EVENT, &CMsnProto::MSN_GCEventHook);
+ HookProtoEvent(ME_GC_BUILDMENU, &CMsnProto::MSN_GCMenuHook);
+
+ HookProtoEvent(ME_IDLE_CHANGED, &CMsnProto::OnIdleChanged);
+ InitPopups();
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnPreShutdown - prepare a global Miranda shutdown
+
+int CMsnProto::OnPreShutdown(WPARAM, LPARAM)
+{
+ SetEvent(hevAvatarQueue);
+
+ Popup_UnregisterClass(hPopupError);
+ Popup_UnregisterClass(hPopupHotmail);
+ Popup_UnregisterClass(hPopupNotify);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnAddToList - adds contact to the server list
+
+MCONTACT CMsnProto::AddToListByEmail(const char *email, const char *nick, DWORD flags)
+{
+ MCONTACT hContact = MSN_HContactFromEmail(email, nick, true, flags & PALF_TEMPORARY);
+
+ if (flags & PALF_TEMPORARY) {
+ if (db_get_b(hContact, "CList", "NotOnList", 0) == 1)
+ db_set_b(hContact, "CList", "Hidden", 1);
+ }
+ else {
+ db_unset(hContact, "CList", "Hidden");
+ if (msnLoggedIn) {
+ int netId = strncmp(email, "tel:", 4) ? NETID_MSN : NETID_MOB;
+ if (MSN_AddUser(hContact, email, netId, LIST_FL)) {
+ MSN_AddUser(hContact, email, netId, LIST_PL + LIST_REMOVE);
+ MSN_AddUser(hContact, email, netId, LIST_BL + LIST_REMOVE);
+ MSN_AddUser(hContact, email, netId, LIST_AL);
+ db_unset(hContact, "CList", "Hidden");
+ }
+ MSN_SetContactDb(hContact, email);
+
+ if (MSN_IsMeByContact(hContact)) displayEmailCount(hContact);
+ }
+ else hContact = NULL;
+ }
+ return hContact;
+}
+
+MCONTACT __cdecl CMsnProto::AddToList(int flags, PROTOSEARCHRESULT* psr)
+{
+ TCHAR *id = psr->id ? psr->id : psr->email;
+ return AddToListByEmail(
+ psr->flags & PSR_UNICODE ? UTF8((wchar_t*)id) : UTF8((char*)id),
+ psr->flags & PSR_UNICODE ? UTF8((wchar_t*)psr->nick) : UTF8((char*)psr->nick),
+ flags);
+}
+
+MCONTACT __cdecl CMsnProto::AddToListByEvent(int flags, int iContact, HANDLE hDbEvent)
+{
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ if ((dbei.cbBlob = db_event_getBlobSize(hDbEvent)) == (DWORD)(-1))
+ return NULL;
+
+ dbei.pBlob = (PBYTE)alloca(dbei.cbBlob);
+ if (db_event_get(hDbEvent, &dbei)) return NULL;
+ if (strcmp(dbei.szModule, m_szModuleName)) return NULL;
+ if (dbei.eventType != EVENTTYPE_AUTHREQUEST) return NULL;
+
+ char* nick = (char *)(dbei.pBlob + sizeof(DWORD) * 2);
+ char* firstName = nick + strlen(nick) + 1;
+ char* lastName = firstName + strlen(firstName) + 1;
+ char* email = lastName + strlen(lastName) + 1;
+
+ return AddToListByEmail(email, nick, flags);
+}
+
+int CMsnProto::AuthRecv(MCONTACT hContact, PROTORECVEVENT* pre)
+{
+ Proto_AuthRecv(m_szModuleName, pre);
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSS_AUTHREQUEST
+
+int __cdecl CMsnProto::AuthRequest(MCONTACT hContact, const TCHAR* szMessage)
+{
+ if (msnLoggedIn) {
+ char email[MSN_MAX_EMAIL_LEN];
+ if (db_get_static(hContact, m_szModuleName, "e-mail", email, sizeof(email)))
+ return 1;
+
+ char* szMsg = mir_utf8encodeT(szMessage);
+
+ int netId = strncmp(email, "tel:", 4) == 0 ? NETID_MOB : NETID_MSN;
+ if (MSN_AddUser(hContact, email, netId, LIST_FL, szMsg)) {
+ MSN_AddUser(hContact, email, netId, LIST_PL + LIST_REMOVE);
+ MSN_AddUser(hContact, email, netId, LIST_BL + LIST_REMOVE);
+ MSN_AddUser(hContact, email, netId, LIST_AL);
+ }
+ MSN_SetContactDb(hContact, email);
+ mir_free(szMsg);
+
+ if (MSN_IsMeByContact(hContact)) displayEmailCount(hContact);
+ return 0;
+ }
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnAuthAllow - called after successful authorization
+
+int CMsnProto::Authorize(HANDLE hDbEvent)
+{
+ if (!msnLoggedIn)
+ return 1;
+
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ if ((dbei.cbBlob = db_event_getBlobSize(hDbEvent)) == -1)
+ return 1;
+
+ dbei.pBlob = (PBYTE)alloca(dbei.cbBlob);
+ if (db_event_get(hDbEvent, &dbei))
+ return 1;
+
+ if (dbei.eventType != EVENTTYPE_AUTHREQUEST)
+ return 1;
+
+ if (strcmp(dbei.szModule, m_szModuleName))
+ return 1;
+
+ char *nick = (char*)(dbei.pBlob + sizeof(DWORD) * 2);
+ char *firstName = nick + strlen(nick) + 1;
+ char *lastName = firstName + strlen(firstName) + 1;
+ char *email = lastName + strlen(lastName) + 1;
+
+ MCONTACT hContact = MSN_HContactFromEmail(email, nick, true, 0);
+ int netId = Lists_GetNetId(email);
+
+ MSN_AddUser(hContact, email, netId, LIST_AL);
+ MSN_AddUser(hContact, email, netId, LIST_BL + LIST_REMOVE);
+ MSN_AddUser(hContact, email, netId, LIST_PL + LIST_REMOVE);
+
+ MSN_SetContactDb(hContact, email);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnAuthDeny - called after unsuccessful authorization
+
+int CMsnProto::AuthDeny(HANDLE hDbEvent, const TCHAR* szReason)
+{
+ if (!msnLoggedIn)
+ return 1;
+
+ DBEVENTINFO dbei = { sizeof(dbei) };
+ if ((dbei.cbBlob = db_event_getBlobSize(hDbEvent)) == -1)
+ return 1;
+
+ dbei.pBlob = (PBYTE)alloca(dbei.cbBlob);
+ if (db_event_get(hDbEvent, &dbei))
+ return 1;
+
+ if (dbei.eventType != EVENTTYPE_AUTHREQUEST)
+ return 1;
+
+ if (strcmp(dbei.szModule, m_szModuleName))
+ return 1;
+
+ char* nick = (char*)(dbei.pBlob + sizeof(DWORD) * 2);
+ char* firstName = nick + strlen(nick) + 1;
+ char* lastName = firstName + strlen(firstName) + 1;
+ char* email = lastName + strlen(lastName) + 1;
+
+ MsnContact* msc = Lists_Get(email);
+ if (msc == NULL) return 0;
+
+ MSN_AddUser(NULL, email, msc->netId, LIST_PL + LIST_REMOVE);
+ MSN_AddUser(NULL, email, msc->netId, LIST_BL);
+ MSN_AddUser(NULL, email, msc->netId, LIST_RL);
+
+ if (!(msc->list & (LIST_FL | LIST_LL))) {
+ if (msc->hContact) CallService(MS_DB_CONTACT_DELETE, (WPARAM)msc->hContact, 0);
+ msc->hContact = NULL;
+ MCONTACT hContact = MSN_HContactFromEmail(email);
+ if (hContact) CallService(MS_DB_CONTACT_DELETE, hContact, 0);
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnBasicSearch - search contacts by e-mail
+
+void __cdecl CMsnProto::MsnSearchAckThread(void* arg)
+{
+ const TCHAR* emailT = (TCHAR*)arg;
+ char *email = mir_utf8encodeT(emailT);
+
+ if (Lists_IsInList(LIST_FL, email)) {
+ MSN_ShowPopup(emailT, TranslateT("Contact already in your contact list"), MSN_ALLOW_MSGBOX, NULL);
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, arg, 0);
+ mir_free(arg);
+ return;
+ }
+
+ unsigned res = MSN_ABContactAdd(email, NULL, NETID_MSN, NULL, 1, true);
+ switch (res) {
+ case 0:
+ case 2:
+ case 3:
+ {
+ PROTOSEARCHRESULT isr = { 0 };
+ isr.cbSize = sizeof(isr);
+ isr.flags = PSR_TCHAR;
+ isr.id = (TCHAR*)emailT;
+ isr.nick = (TCHAR*)emailT;
+ isr.email = (TCHAR*)emailT;
+
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_DATA, arg, (LPARAM)&isr);
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, arg, 0);
+ }
+ break;
+
+ case 1:
+ if (strstr(email, "@yahoo.com") == NULL)
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, arg, 0);
+ else {
+ msnSearchId = arg;
+ MSN_FindYahooUser(email);
+ }
+ break;
+
+ default:
+ ProtoBroadcastAck(NULL, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, arg, 0);
+ break;
+ }
+ mir_free(email);
+ mir_free(arg);
+}
+
+
+HANDLE __cdecl CMsnProto::SearchBasic(const PROTOCHAR* id)
+{
+ if (!msnLoggedIn) return 0;
+
+ TCHAR* email = mir_tstrdup(id);
+ ForkThread(&CMsnProto::MsnSearchAckThread, email);
+
+ return email;
+}
+
+HANDLE __cdecl CMsnProto::SearchByEmail(const PROTOCHAR* email)
+{
+ return SearchBasic(email);
+}
+
+
+HANDLE __cdecl CMsnProto::SearchByName(const PROTOCHAR* nick, const PROTOCHAR* firstName, const PROTOCHAR* lastName)
+{
+ return NULL;
+}
+
+HWND __cdecl CMsnProto::SearchAdvanced(HWND hwndDlg)
+{
+ return NULL;
+}
+
+HWND __cdecl CMsnProto::CreateExtendedSearchUI(HWND parent)
+{
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnFileAllow - starts the file transfer
+
+void __cdecl CMsnProto::MsnFileAckThread(void* arg)
+{
+ filetransfer* ft = (filetransfer*)arg;
+
+ TCHAR filefull[MAX_PATH];
+ mir_sntprintf(filefull, SIZEOF(filefull), _T("%s\\%s"), ft->std.tszWorkingDir, ft->std.tszCurrentFile);
+ replaceStrT(ft->std.tszCurrentFile, filefull);
+
+ if (ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&ft->std))
+ return;
+
+ bool fcrt = ft->create() != -1;
+
+ if (ft->p2p_appID != 0) {
+ if (fcrt)
+ p2p_sendFeedStart(ft);
+ p2p_sendStatus(ft, fcrt ? 200 : 603);
+ }
+ else msnftp_sendAcceptReject(ft, fcrt);
+
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, ft, 0);
+}
+
+HANDLE __cdecl CMsnProto::FileAllow(MCONTACT hContact, HANDLE hTransfer, const PROTOCHAR* szPath)
+{
+ filetransfer* ft = (filetransfer*)hTransfer;
+
+ if (!msnLoggedIn || !p2p_sessionRegistered(ft))
+ return 0;
+
+ if ((ft->std.tszWorkingDir = mir_tstrdup(szPath)) == NULL) {
+ TCHAR szCurrDir[MAX_PATH];
+ GetCurrentDirectory(SIZEOF(szCurrDir), szCurrDir);
+ ft->std.tszWorkingDir = mir_tstrdup(szCurrDir);
+ }
+ else {
+ size_t len = _tcslen(ft->std.tszWorkingDir) - 1;
+ if (ft->std.tszWorkingDir[len] == '\\')
+ ft->std.tszWorkingDir[len] = 0;
+ }
+
+ ForkThread(&CMsnProto::MsnFileAckThread, ft);
+
+ return ft;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnFileCancel - cancels the active file transfer
+
+int __cdecl CMsnProto::FileCancel(MCONTACT hContact, HANDLE hTransfer)
+{
+ filetransfer* ft = (filetransfer*)hTransfer;
+
+ if (!msnLoggedIn || !p2p_sessionRegistered(ft))
+ return 0;
+
+ if (!(ft->std.flags & PFTS_SENDING) && ft->fileId == -1) {
+ if (ft->p2p_appID != 0)
+ p2p_sendStatus(ft, 603);
+ else
+ msnftp_sendAcceptReject(ft, false);
+ }
+ else {
+ ft->bCanceled = true;
+ if (ft->p2p_appID != 0) {
+ p2p_sendCancel(ft);
+ if (!(ft->std.flags & PFTS_SENDING) && ft->p2p_isV2)
+ p2p_sessionComplete(ft);
+ }
+ }
+
+ ft->std.ptszFiles = NULL;
+ ft->std.totalFiles = 0;
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnFileDeny - rejects the file transfer request
+
+int __cdecl CMsnProto::FileDeny(MCONTACT hContact, HANDLE hTransfer, const PROTOCHAR* /*szReason*/)
+{
+ filetransfer* ft = (filetransfer*)hTransfer;
+
+ if (!msnLoggedIn || !p2p_sessionRegistered(ft))
+ return 1;
+
+ if (!(ft->std.flags & PFTS_SENDING) && ft->fileId == -1) {
+ if (ft->p2p_appID != 0)
+ p2p_sendStatus(ft, 603);
+ else
+ msnftp_sendAcceptReject(ft, false);
+ }
+ else {
+ ft->bCanceled = true;
+ if (ft->p2p_appID != 0)
+ p2p_sendCancel(ft);
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnFileResume - renames a file
+
+int __cdecl CMsnProto::FileResume(HANDLE hTransfer, int* action, const PROTOCHAR** szFilename)
+{
+ filetransfer* ft = (filetransfer*)hTransfer;
+
+ if (!msnLoggedIn || !p2p_sessionRegistered(ft))
+ return 1;
+
+ switch (*action) {
+ case FILERESUME_SKIP:
+ if (ft->p2p_appID != 0)
+ p2p_sendStatus(ft, 603);
+ else
+ msnftp_sendAcceptReject(ft, false);
+ break;
+
+ case FILERESUME_RENAME:
+ replaceStrT(ft->std.tszCurrentFile, *szFilename);
+
+ default:
+ bool fcrt = ft->create() != -1;
+ if (ft->p2p_appID != 0) {
+ if (fcrt)
+ p2p_sendFeedStart(ft);
+
+ p2p_sendStatus(ft, fcrt ? 200 : 603);
+ }
+ else
+ msnftp_sendAcceptReject(ft, fcrt);
+
+ ProtoBroadcastAck(ft->std.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, ft, 0);
+ break;
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGetAwayMsg - reads the current status message for a user
+
+typedef struct AwayMsgInfo_tag
+{
+ INT_PTR id;
+ MCONTACT hContact;
+} AwayMsgInfo;
+
+void __cdecl CMsnProto::MsnGetAwayMsgThread(void* arg)
+{
+ Sleep(150);
+
+ AwayMsgInfo *inf = (AwayMsgInfo*)arg;
+ DBVARIANT dbv;
+ if (!db_get_ts(inf->hContact, "CList", "StatusMsg", &dbv)) {
+ ProtoBroadcastAck(inf->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)inf->id, (LPARAM)dbv.ptszVal);
+ db_free(&dbv);
+ }
+ else ProtoBroadcastAck(inf->hContact, ACKTYPE_AWAYMSG, ACKRESULT_SUCCESS, (HANDLE)inf->id, 0);
+
+ mir_free(inf);
+}
+
+HANDLE __cdecl CMsnProto::GetAwayMsg(MCONTACT hContact)
+{
+ AwayMsgInfo* inf = (AwayMsgInfo*)mir_alloc(sizeof(AwayMsgInfo));
+ inf->hContact = hContact;
+ inf->id = MSN_GenRandom();
+
+ ForkThread(&CMsnProto::MsnGetAwayMsgThread, inf);
+ return (HANDLE)inf->id;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGetCaps - obtain the protocol capabilities
+
+DWORD_PTR __cdecl CMsnProto::GetCaps(int type, MCONTACT hContact)
+{
+ switch (type) {
+ case PFLAGNUM_1:
+ return PF1_IM | PF1_SERVERCLIST | PF1_AUTHREQ | PF1_BASICSEARCH |
+ PF1_ADDSEARCHRES | PF1_CHAT |
+ PF1_FILESEND | PF1_FILERECV | PF1_URLRECV | PF1_VISLIST | PF1_MODEMSG;
+
+ case PFLAGNUM_2:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_LIGHTDND | PF2_INVISIBLE | PF2_ONTHEPHONE | PF2_IDLE;
+
+ case PFLAGNUM_3:
+ return PF2_ONLINE | PF2_SHORTAWAY | PF2_LIGHTDND;
+
+ case PFLAGNUM_4:
+ return PF4_FORCEAUTH | PF4_FORCEADDED | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SUPPORTIDLE | PF4_IMSENDUTF |
+ PF4_IMSENDOFFLINE | PF4_NOAUTHDENYREASON;
+
+ case PFLAGNUM_5:
+ return PF2_ONTHEPHONE;
+
+ case PFLAG_UNIQUEIDTEXT:
+ return (UINT_PTR)Translate("Live ID");
+
+ case PFLAG_UNIQUEIDSETTING:
+ return (UINT_PTR)"e-mail";
+
+ case PFLAG_MAXLENOFMESSAGE:
+ return 1202;
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGetInfo - nothing to do, cause we cannot obtain information from the server
+
+int __cdecl CMsnProto::GetInfo(MCONTACT hContact, int infoType)
+{
+ return 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// RecvContacts
+
+int __cdecl CMsnProto::RecvContacts(MCONTACT hContact, PROTORECVEVENT*)
+{
+ return 1;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnRecvFile - creates a database event from the file request been received
+
+int __cdecl CMsnProto::RecvFile(MCONTACT hContact, PROTOFILEEVENT* evt)
+{
+ return Proto_RecvFile(hContact, evt);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnRecvMessage - creates a database event from the message been received
+
+int __cdecl CMsnProto::RecvMsg(MCONTACT hContact, PROTORECVEVENT* pre)
+{
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ db_get_static(hContact, m_szModuleName, "e-mail", tEmail, sizeof(tEmail));
+
+ if (Lists_IsInList(LIST_FL, tEmail))
+ db_unset(hContact, "CList", "Hidden");
+
+ return Proto_RecvMessage(hContact, pre);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// RecvUrl
+
+int __cdecl CMsnProto::RecvUrl(MCONTACT hContact, PROTORECVEVENT*)
+{
+ return 1;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendContacts
+
+int __cdecl CMsnProto::SendContacts(MCONTACT hContact, int flags, int nContacts, MCONTACT *hContactsList)
+{
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSendFile - initiates a file transfer
+
+HANDLE __cdecl CMsnProto::SendFile(MCONTACT hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles)
+{
+ if (!msnLoggedIn)
+ return 0;
+
+ if (getWord(hContact, "Status", ID_STATUS_OFFLINE) == ID_STATUS_OFFLINE)
+ return 0;
+
+ MsnContact *cont = Lists_Get(hContact);
+
+ if (!cont || _stricmp(cont->email, MyOptions.szEmail) == 0) return 0;
+
+ if ((cont->cap1 & 0xf0000000) == 0 && cont->netId != NETID_MSN) return 0;
+
+ filetransfer* sft = new filetransfer(this);
+ sft->std.ptszFiles = ppszFiles;
+ sft->std.hContact = hContact;
+ sft->std.flags |= PFTS_SENDING;
+
+ int count = 0;
+ while (ppszFiles[count] != NULL) {
+ struct _stati64 statbuf;
+ if (_tstati64(ppszFiles[count++], &statbuf) == 0 && (statbuf.st_mode & _S_IFDIR) == 0) {
+ sft->std.totalBytes += statbuf.st_size;
+ ++sft->std.totalFiles;
+ }
+ }
+
+ if (sft->openNext() == -1) {
+ delete sft;
+ return 0;
+ }
+
+ if (cont->cap1 & 0xf0000000)
+ p2p_invite(MSN_APPID_FILE, sft, NULL);
+ else {
+ sft->p2p_dest = mir_strdup(cont->email);
+ msnftp_invite(sft);
+ }
+
+ ProtoBroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_SENTREQUEST, sft, 0);
+ return sft;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSendMessage - sends the message to a server
+
+struct TFakeAckParams
+{
+ inline TFakeAckParams(MCONTACT p2, long p3, const char* p4, CMsnProto *p5) :
+ hContact(p2),
+ id(p3),
+ msg(p4),
+ proto(p5)
+ {}
+
+ MCONTACT hContact;
+ long id;
+ const char* msg;
+ CMsnProto *proto;
+};
+
+void CMsnProto::MsnFakeAck(void* arg)
+{
+ TFakeAckParams* tParam = (TFakeAckParams*)arg;
+
+ Sleep(150);
+ tParam->proto->ProtoBroadcastAck(tParam->hContact, ACKTYPE_MESSAGE,
+ tParam->msg ? ACKRESULT_FAILED : ACKRESULT_SUCCESS,
+ (HANDLE)tParam->id, LPARAM(tParam->msg));
+
+ delete tParam;
+}
+
+int __cdecl CMsnProto::SendMsg(MCONTACT hContact, int flags, const char* pszSrc)
+{
+ const char *errMsg = NULL;
+
+ if (!msnLoggedIn) {
+ errMsg = Translate("Protocol is offline");
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, 999999, errMsg, this));
+ return 999999;
+ }
+
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ if (MSN_IsMeByContact(hContact, tEmail)) {
+ errMsg = Translate("You cannot send message to yourself");
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, 999999, errMsg, this));
+ return 999999;
+ }
+
+ char *msg = (char*)pszSrc;
+ if (msg == NULL) return 0;
+
+ if (flags & PREF_UNICODE) {
+ char* p = strchr(msg, '\0');
+ if (p != msg) {
+ while (*(++p) == '\0') {}
+ msg = mir_utf8encodeW((wchar_t*)p);
+ }
+ else
+ msg = mir_strdup(msg);
+ }
+ else
+ msg = (flags & PREF_UTF) ? mir_strdup(msg) : mir_utf8encode(msg);
+
+ int rtlFlag = (flags & PREF_RTL) ? MSG_RTL : 0;
+
+ int seq = 0;
+ int netId = Lists_GetNetId(tEmail);
+
+ switch (netId) {
+ case NETID_MOB:
+ if (strlen(msg) > 133) {
+ errMsg = Translate("Message is too long: SMS page limited to 133 UTF8 chars");
+ seq = 999997;
+ }
+ else {
+ errMsg = NULL;
+ seq = msnNsThread->sendMessage('1', tEmail, netId, msg, rtlFlag);
+ }
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, seq, errMsg, this));
+ break;
+
+ case NETID_YAHOO:
+ if (strlen(msg) > 1202) {
+ seq = 999996;
+ errMsg = Translate("Message is too long: MSN messages are limited by 1202 UTF8 chars");
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, seq, errMsg, this));
+ }
+ else {
+ seq = msnNsThread->sendMessage('1', tEmail, netId, msg, rtlFlag);
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, seq, NULL, this));
+ }
+ break;
+
+ default:
+ if (strlen(msg) > 1202) {
+ seq = 999996;
+ errMsg = Translate("Message is too long: MSN messages are limited by 1202 UTF8 chars");
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, seq, errMsg, this));
+ }
+ else {
+ const char msgType = MyOptions.SlowSend ? 'A' : 'N';
+ bool isOffline;
+ ThreadData* thread = MSN_StartSB(tEmail, isOffline);
+ if (thread == NULL) {
+ if (isOffline) {
+ if (netId != NETID_LCS) {
+ seq = msnNsThread->sendMessage('1', tEmail, netId, msg, rtlFlag | MSG_OFFLINE);
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, seq, NULL, this));
+ }
+ else {
+ seq = 999993;
+ errMsg = Translate("Offline messaging is not allowed for LCS contacts");
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, seq, errMsg, this));
+ }
+ }
+ else
+ seq = MsgQueue_Add(tEmail, msgType, msg, 0, 0, rtlFlag);
+ }
+ else {
+ seq = thread->sendMessage(msgType, tEmail, netId, msg, rtlFlag);
+ if (!MyOptions.SlowSend)
+ ForkThread(&CMsnProto::MsnFakeAck, new TFakeAckParams(hContact, seq, NULL, this));
+ }
+ }
+ break;
+ }
+
+ mir_free(msg);
+ return seq;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSetAwayMsg - sets the current status message for a user
+
+int __cdecl CMsnProto::SetAwayMsg(int status, const TCHAR* msg)
+{
+ char** msgptr = GetStatusMsgLoc(status);
+
+ if (msgptr == NULL)
+ return 1;
+
+ mir_free(*msgptr);
+ char* buf = *msgptr = mir_utf8encodeT(msg);
+ if (buf && strlen(buf) > 1859) {
+ buf[1859] = 0;
+ const int i = 1858;
+ if (buf[i] & 128) {
+ if (buf[i] & 64)
+ buf[i] = '\0';
+ else if ((buf[i - 1] & 224) == 224)
+ buf[i - 1] = '\0';
+ else if ((buf[i - 2] & 240) == 240)
+ buf[i - 2] = '\0';
+ }
+ }
+
+ if (status == m_iDesiredStatus)
+ MSN_SendStatusMessage(*msgptr);
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// PSR_AWAYMSG
+
+int __cdecl CMsnProto::RecvAwayMsg(MCONTACT hContact, int statusMode, PROTORECVEVENT* evt)
+{
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSetStatus - set the plugin's connection status
+
+int __cdecl CMsnProto::SetStatus(int iNewStatus)
+{
+ if (m_iDesiredStatus == iNewStatus) return 0;
+
+ m_iDesiredStatus = iNewStatus;
+ debugLogA("PS_SETSTATUS(%d,0)", iNewStatus);
+
+ if (m_iDesiredStatus == ID_STATUS_OFFLINE) {
+ if (msnNsThread)
+ msnNsThread->sendTerminate();
+ }
+ else if (!msnLoggedIn && m_iStatus == ID_STATUS_OFFLINE) {
+ char szPassword[100];
+ int ps = db_get_static(NULL, m_szModuleName, "Password", szPassword, sizeof(szPassword));
+ if (ps != 0 || *szPassword == 0) {
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_WRONGPASSWORD);
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+ return 0;
+ }
+
+ if (*MyOptions.szEmail == 0) {
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_BADUSERID);
+ m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE;
+ return 0;
+ }
+
+ sessionList.destroy();
+ dcList.destroy();
+
+ usingGateway = false;
+
+ int oldMode = m_iStatus;
+ m_iStatus = ID_STATUS_CONNECTING;
+ ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldMode, m_iStatus);
+
+ ThreadData* newThread = new ThreadData;
+
+ newThread->mType = SERVER_NOTIFICATION;
+ newThread->mIsMainThread = true;
+
+ newThread->startThread(&CMsnProto::MSNServerThread, this);
+ }
+ else
+ if (m_iStatus > ID_STATUS_OFFLINE) MSN_SetServerStatus(m_iDesiredStatus);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnUserIsTyping - notify another contact that we're typing a message
+
+int __cdecl CMsnProto::UserIsTyping(MCONTACT hContact, int type)
+{
+ if (!msnLoggedIn) return 0;
+
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ if (MSN_IsMeByContact(hContact, tEmail)) return 0;
+
+ bool typing = type == PROTOTYPE_SELFTYPING_ON;
+
+ int netId = Lists_GetNetId(tEmail);
+ switch (netId) {
+ case NETID_UNKNOWN:
+ case NETID_MSN:
+ case NETID_LCS:
+ {
+ bool isOffline;
+ ThreadData* thread = MSN_StartSB(tEmail, isOffline);
+
+ if (thread == NULL) {
+ if (isOffline) return 0;
+ MsgQueue_Add(tEmail, 2571, NULL, 0, NULL, typing);
+ }
+ else
+ MSN_StartStopTyping(thread, typing);
+ }
+ break;
+
+ case NETID_YAHOO:
+ if (typing) MSN_SendTyping(msnNsThread, tEmail, netId);
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// SendUrl
+
+int __cdecl CMsnProto::SendUrl(MCONTACT hContact, int flags, const char* url)
+{
+ return 1;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSetApparentMode - controls contact visibility
+
+int __cdecl CMsnProto::SetApparentMode(MCONTACT hContact, int mode)
+{
+ if (mode && mode != ID_STATUS_OFFLINE)
+ return 1;
+
+ WORD oldMode = getWord(hContact, "ApparentMode", 0);
+ if (mode != oldMode)
+ setWord(hContact, "ApparentMode", (WORD)mode);
+
+ return 1;
+}
+
+int __cdecl CMsnProto::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_ONMENU:
+ MsnInitMainMenu();
+ break;
+
+ case EV_PROTO_ONERASE:
+ {
+ char szDbsettings[64];
+ mir_snprintf(szDbsettings, sizeof(szDbsettings), "%s_HTTPS", m_szModuleName);
+ CallService(MS_DB_MODULE_DELETE, 0, (LPARAM)szDbsettings);
+ }
+ break;
+
+ case EV_PROTO_ONRENAME:
+ if (mainMenuRoot) {
+ CLISTMENUITEM clmi = { sizeof(clmi) };
+ clmi.flags = CMIM_NAME | CMIF_TCHAR;
+ clmi.ptszName = m_tszUserName;
+ Menu_ModifyItem(mainMenuRoot, &clmi);
+ }
+ break;
+
+ case EV_PROTO_ONCONTACTDELETED:
+ return OnContactDeleted(wParam, lParam);
+
+ case EV_PROTO_DBSETTINGSCHANGED:
+ return OnDbSettingChanged(wParam, lParam);
+ }
+ return 1;
+}
diff --git a/protocols/MSN/src/msn_proto.h b/protocols/MSN/src/msn_proto.h
new file mode 100644
index 0000000000..9c0b27214b
--- /dev/null
+++ b/protocols/MSN/src/msn_proto.h
@@ -0,0 +1,567 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2009-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef _MSN_PROTO_H_
+#define _MSN_PROTO_H_
+
+#include <m_protoint.h>
+
+struct CMsnProto : public PROTO<CMsnProto>
+{
+ CMsnProto(const char*, const TCHAR*);
+ ~CMsnProto();
+
+ //====================================================================================
+ // PROTO_INTERFACE
+ //====================================================================================
+
+ virtual MCONTACT __cdecl AddToList(int flags, PROTOSEARCHRESULT* psr);
+ virtual MCONTACT __cdecl AddToListByEvent(int flags, int iContact, HANDLE hDbEvent);
+
+ virtual int __cdecl Authorize(HANDLE hDbEvent);
+ virtual int __cdecl AuthDeny(HANDLE hDbEvent, const TCHAR* szReason);
+ virtual int __cdecl AuthRecv(MCONTACT hContact, PROTORECVEVENT*);
+ virtual int __cdecl AuthRequest(MCONTACT hContact, const TCHAR* szMessage);
+
+ virtual HANDLE __cdecl FileAllow(MCONTACT hContact, HANDLE hTransfer, const PROTOCHAR* szPath);
+ virtual int __cdecl FileCancel(MCONTACT hContact, HANDLE hTransfer);
+ virtual int __cdecl FileDeny(MCONTACT hContact, HANDLE hTransfer, const PROTOCHAR* szReason);
+ virtual int __cdecl FileResume(HANDLE hTransfer, int* action, const PROTOCHAR** szFilename);
+
+ virtual DWORD_PTR __cdecl GetCaps(int type, MCONTACT hContact = NULL);
+ virtual int __cdecl GetInfo(MCONTACT 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(MCONTACT hContact, PROTORECVEVENT*);
+ virtual int __cdecl RecvFile(MCONTACT hContact, PROTOFILEEVENT*);
+ virtual int __cdecl RecvMsg(MCONTACT hContact, PROTORECVEVENT*);
+ virtual int __cdecl RecvUrl(MCONTACT hContact, PROTORECVEVENT*);
+
+ virtual int __cdecl SendContacts(MCONTACT hContact, int flags, int nContacts, MCONTACT *hContactsList);
+ virtual HANDLE __cdecl SendFile(MCONTACT hContact, const PROTOCHAR* szDescription, PROTOCHAR** ppszFiles);
+ virtual int __cdecl SendMsg(MCONTACT hContact, int flags, const char* msg);
+ virtual int __cdecl SendUrl(MCONTACT hContact, int flags, const char* url);
+
+ virtual int __cdecl SetApparentMode(MCONTACT hContact, int mode);
+ virtual int __cdecl SetStatus(int iNewStatus);
+
+ virtual HANDLE __cdecl GetAwayMsg(MCONTACT hContact);
+ virtual int __cdecl RecvAwayMsg(MCONTACT hContact, int mode, PROTORECVEVENT* evt);
+ virtual int __cdecl SetAwayMsg(int m_iStatus, const TCHAR* msg);
+
+ virtual int __cdecl UserIsTyping(MCONTACT hContact, int type);
+
+ virtual int __cdecl OnEvent(PROTOEVENTTYPE eventType, WPARAM wParam, LPARAM lParam);
+
+ //====| Services |====================================================================
+ INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl GetAvatarInfo(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetMyAwayMsg(WPARAM wParam,LPARAM lParam);
+
+ INT_PTR __cdecl GetAvatar(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetAvatar(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl GetAvatarCaps(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl GetCurrentMedia(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetCurrentMedia(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl SetNickName(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SendNudge(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl GetUnreadEmailCount(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl ManageAccount(WPARAM wParam, LPARAM lParam);
+
+ INT_PTR __cdecl OnLeaveChat(WPARAM wParam, LPARAM lParam);
+
+ //====| Events |======================================================================
+ int __cdecl OnContactDeleted(WPARAM wParam,LPARAM lParam);
+ int __cdecl OnIdleChanged(WPARAM wParam, LPARAM lParam);
+ int __cdecl OnGroupChange(WPARAM wParam, LPARAM lParam);
+ int __cdecl OnModulesLoaded(WPARAM wParam, LPARAM lParam);
+ int __cdecl OnOptionsInit(WPARAM wParam,LPARAM lParam);
+ int __cdecl OnPrebuildContactMenu(WPARAM wParam,LPARAM lParam);
+ int __cdecl OnPreShutdown(WPARAM wParam,LPARAM lParam);
+ int __cdecl OnContactDoubleClicked(WPARAM wParam,LPARAM lParam);
+ int __cdecl OnDbSettingChanged(WPARAM wParam,LPARAM lParam);
+ int __cdecl OnUserInfoInit(WPARAM wParam,LPARAM lParam);
+ int __cdecl OnWindowEvent(WPARAM wParam, LPARAM lParam);
+ int __cdecl OnWindowPopup(WPARAM wParam, LPARAM lParam);
+
+ //====| Data |========================================================================
+
+ // Security Tokens
+ char *pAuthToken, *tAuthToken;
+ char *oimSendToken;
+ char *authStrToken, *authSecretToken;
+ char *authContactToken;
+ char *authStorageToken;
+ char *hotSecretToken, *hotAuthToken;
+
+ char *abCacheKey, *sharingCacheKey, *storageCacheKey;
+
+ mir_cs csLists;
+ OBJLIST<MsnContact> contList;
+
+ LIST<ServerGroupItem> grpList;
+
+ mir_cs csThreads;
+ OBJLIST<ThreadData> sttThreads;
+
+ mir_cs sessionLock;
+ OBJLIST<filetransfer> sessionList;
+ OBJLIST<directconnection> dcList;
+
+ mir_cs csMsgQueue;
+ int msgQueueSeq;
+ OBJLIST<MsgQueueEntry> lsMessageQueue;
+
+ mir_cs csAvatarQueue;
+ LIST<AvatarQueueEntry> lsAvatarQueue;
+ HANDLE hevAvatarQueue;
+
+ LONG sttChatID;
+
+ int msnPingTimeout;
+ HANDLE hKeepAliveThreadEvt;
+
+ char* msnModeMsgs[MSN_NUM_MODES];
+
+ LISTENINGTOINFO msnCurrentMedia;
+ MYOPTIONS MyOptions;
+ MyConnectionType MyConnection;
+
+ ThreadData* msnNsThread;
+ bool msnLoggedIn;
+ bool usingGateway;
+
+ char* msnExternalIP;
+ char* msnPreviousUUX;
+ char* msnLastStatusMsg;
+
+ char* mailsoundname;
+ char* alertsoundname;
+
+ unsigned langpref;
+ unsigned emailEnabled;
+ unsigned abchMigrated;
+ unsigned myFlags;
+
+ unsigned msnOtherContactsBlocked;
+ int mUnreadMessages;
+ int mUnreadJunkEmails;
+ clock_t mHttpsTS;
+ clock_t mStatusMsgTS;
+
+ HANDLE msnSearchId;
+ HANDLE hNetlibUserHttps;
+ HANDLE hHttpsConnection;
+ HANDLE hMSNNudge;
+ HANDLE hPopupError, hPopupHotmail, hPopupNotify;
+
+ HANDLE hCustomSmileyFolder;
+ bool InitCstFldRan;
+ bool isConnectSuccess;
+ bool isIdle;
+
+ void InitCustomFolders(void);
+
+ char* getSslResult(char** parUrl, const char* parAuthInfo, const char* hdrs, unsigned& status);
+ bool getMyAvatarFile(char *url, TCHAR *fname);
+
+ void MSN_GoOffline(void);
+ void MSN_GetCustomSmileyFileName(MCONTACT hContact, TCHAR* pszDest, size_t cbLen, const char* SmileyName, int Type);
+
+ const char* MirandaStatusToMSN(int status);
+ WORD MSNStatusToMiranda(const char *status);
+ char** GetStatusMsgLoc(int status);
+
+ void MSN_SendStatusMessage(const char* msg);
+ void MSN_SetServerStatus(int newStatus);
+ void MSN_StartStopTyping(ThreadData* info, bool start);
+ void MSN_SendTyping(ThreadData* info, const char* email, int netId );
+
+ void MSN_InitSB(ThreadData* info, const char* szEmail);
+ void MSN_ReceiveMessage(ThreadData* info, char* cmdString, char* params);
+ int MSN_HandleCommands(ThreadData* info, char* cmdString);
+ int MSN_HandleErrors(ThreadData* info, char* cmdString);
+ void MSN_ProcessNotificationMessage(char* buf, unsigned len);
+ void MSN_ProcessStatusMessage(char* buf, unsigned len, const char* wlid);
+ void MSN_ProcessPage(char* buf, unsigned len);
+ void MSN_ProcessRemove(char* buf, size_t len);
+ void MSN_ProcessAdd(char* buf, size_t len);
+ void MSN_ProcessYFind(char* buf, size_t len);
+ void MSN_CustomSmiley(const char* msgBody, char* email, char* nick, int iSmileyType);
+ void MSN_InviteMessage(ThreadData* info, char* msgBody, char* email, char* nick);
+ void MSN_SetMirVer(MCONTACT hContact, DWORD dwValue, bool always);
+
+ void LoadOptions(void);
+
+ void InitPopups(void);
+ void MSN_ShowPopup(const TCHAR* nickname, const TCHAR* msg, int flags, const char* url, MCONTACT hContact = NULL);
+ void MSN_ShowPopup(const MCONTACT hContact, const TCHAR* msg, int flags);
+ void MSN_ShowError(const char* msgtext, ...);
+
+ void MSN_SetNicknameUtf(const char* nickname);
+ void MSN_SendNicknameUtf(const char* nickname);
+
+ typedef struct { TCHAR *szName; const char *szMimeType; unsigned char *data; size_t dataSize; } StoreAvatarData;
+ void __cdecl msn_storeAvatarThread(void* arg);
+
+ void __cdecl msn_storeProfileThread(void*);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN Connection properties detection
+
+ void DecryptEchoPacket(UDPProbePkt& pkt);
+ void MSNatDetect(void);
+
+ void __cdecl MSNConnDetectThread(void*);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN menus
+
+ HGENMENU mainMenuRoot;
+ HGENMENU menuItemsMain[4];
+
+ void MsnInitMainMenu(void);
+ void MsnRemoveMainMenus(void);
+ void MSN_EnableMenuItems(bool parEnable);
+ void MsnInvokeMyURL(bool ismail, const char* url);
+
+ INT_PTR __cdecl MsnBlockCommand(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl MsnGotoInbox(WPARAM, LPARAM);
+ INT_PTR __cdecl MsnSendHotmail(WPARAM wParam, LPARAM);
+ INT_PTR __cdecl MsnEditProfile(WPARAM, LPARAM);
+ INT_PTR __cdecl MsnInviteCommand(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl MsnSendNetMeeting(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl SetNicknameUI(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl MsnViewProfile(WPARAM wParam, LPARAM lParam);
+ INT_PTR __cdecl MsnSetupAlerts(WPARAM wParam, LPARAM lParam);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN thread functions
+
+ void __cdecl msn_keepAliveThread(void* arg);
+ void __cdecl MSNServerThread(void* arg);
+
+ void __cdecl MsnFileAckThread(void* arg);
+ void __cdecl MsnSearchAckThread(void* arg);
+ void __cdecl sttFakeAvatarAck(void* arg);
+ void __cdecl MsnFakeAck(void* arg);
+
+ void __cdecl MsnGetAwayMsgThread(void* arg);
+
+ void __cdecl p2p_sendFeedThread(void* arg );
+ void __cdecl p2p_fileActiveThread(void* arg );
+ void __cdecl p2p_filePassiveThread(void* arg);
+
+ void __cdecl MsgQueue_AllClearThread(void* arg);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN thread support
+
+ void Threads_Uninit(void);
+ void MSN_CloseConnections(void);
+ int MSN_GetChatThreads(ThreadData** parResult);
+ int MSN_GetActiveThreads(ThreadData**);
+ ThreadData* MSN_GetThreadByConnection(HANDLE hConn);
+ ThreadData* MSN_GetThreadByContact(const char* wlid, TInfoType type = SERVER_SWITCHBOARD);
+ ThreadData* MSN_GetThreadByChatId(const TCHAR* chatId);
+ ThreadData* MSN_GetP2PThreadByContact(const char *wlid);
+ void MSN_StartP2PTransferByContact(const char* wlid);
+ ThreadData* MSN_GetThreadByPort(WORD wPort);
+ ThreadData* MSN_GetUnconnectedThread(const char* wlid, TInfoType type = SERVER_SWITCHBOARD);
+ ThreadData* MSN_GetOtherContactThread(ThreadData* thread);
+ ThreadData* MSN_GetThreadByTimer(UINT timerId);
+
+ ThreadData* MSN_StartSB(const char* uid, bool& isOffline);
+ void __cdecl ThreadStub(void* arg);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN message queue support
+
+ int MsgQueue_Add(const char* wlid, int msgType, const char* msg, int msglen, filetransfer* ft = NULL, int flags = 0, STRLIST *cnt = NULL);
+ const char* MsgQueue_CheckContact(const char* wlid, time_t tsc = 0);
+ const char* MsgQueue_GetNextRecipient(void);
+ bool MsgQueue_GetNext(const char* wlid, MsgQueueEntry& retVal);
+ int MsgQueue_NumMsg(const char* wlid);
+ void MsgQueue_Clear(const char* wlid = NULL, bool msg = false);
+
+ void MsgQueue_Init(void);
+ void MsgQueue_Uninit(void);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN message reassembly support
+
+ OBJLIST<chunkedmsg> msgCache;
+
+ int addCachedMsg(const char* id, const char* msg, const size_t offset,
+ const size_t portion, const size_t totsz, const bool bychunk);
+ size_t getCachedMsgSize(const char* id);
+ bool getCachedMsg(const int idx, char*& msg, size_t& size);
+ bool getCachedMsg(const char* id, char*& msg, size_t& size);
+ void clearCachedMsg(int idx = -1);
+ void CachedMsg_Uninit(void);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN P2P session support
+
+ void p2p_clearDormantSessions(void);
+ void p2p_cancelAllSessions(void);
+ void p2p_redirectSessions(const char* wlid);
+ void p2p_startSessions(const char* wlid);
+ void p2p_clearThreadSessions(MCONTACT hContact, TInfoType mType);
+
+ void p2p_invite(unsigned iAppID, filetransfer* ft, const char *wlid);
+ void p2p_inviteDc(filetransfer* ft, const char *wlid);
+ void p2p_processMsg(ThreadData* info, char* msgbody, const char* wlid);
+ void p2p_processMsgV2(ThreadData* info, char* msgbody, const char* wlid);
+ void p2p_processSIP(ThreadData* info, char* msgbody, P2PB_Header* hdr, const char* wlid);
+
+ void p2p_AcceptTransfer(MimeHeaders& tFileInfo, MimeHeaders& tFileInfo2, const char* wlid);
+ void p2p_InitDirectTransfer(MimeHeaders& tFileInfo, MimeHeaders& tFileInfo2, const char* wlid);
+ void p2p_InitDirectTransfer2(MimeHeaders& tFileInfo, MimeHeaders& tFileInfo2, const char* wlid);
+ void p2p_InitFileTransfer(ThreadData* info, MimeHeaders& tFileInfo, MimeHeaders& tFileInfo2, const char* wlid);
+ void p2p_pictureTransferFailed(filetransfer* ft);
+ void p2p_savePicture2disk(filetransfer* ft);
+
+ bool p2p_createListener(filetransfer* ft, directconnection *dc, MimeHeaders& chdrs);
+ void p2p_startConnect(const char* wlid, const char* szCallID, const char* addr, const char* port, bool ipv6);
+
+ void p2p_sendAbortSession(filetransfer* ft);
+ void p2p_sendAck(const char *wlid, P2PB_Header* hdrdata);
+ void p2p_sendAvatarInit(filetransfer* ft);
+ void p2p_sendBye(filetransfer* ft);
+ void p2p_sendCancel(filetransfer* ft);
+ void p2p_sendMsg(const char *wlid, unsigned appId, P2PB_Header& hdrdata, char* msgbody, size_t msgsz);
+ void p2p_sendMsg(ThreadData* info, const char *wlid, unsigned appId, P2PB_Header& hdrdata, char* msgbody, size_t msgsz);
+ void p2p_sendNoCall(filetransfer* ft);
+ void p2p_sendSlp(int iKind, filetransfer *ft, MimeHeaders &pHeaders, MimeHeaders &pContent, const char *wlid = NULL);
+ void p2p_sendRedirect(filetransfer* ft);
+ void p2p_sendStatus(filetransfer* ft, long lStatus);
+
+ void p2p_sendFeedStart(filetransfer* ft);
+ LONG p2p_sendPortion(filetransfer* ft, ThreadData* T, bool isV2);
+ void p2p_sendRecvFileDirectly(ThreadData* info);
+ bool p2p_connectTo(ThreadData* info, directconnection *dc);
+ bool p2p_listen(ThreadData* info, directconnection *dc);
+
+ void p2p_registerSession(filetransfer* ft);
+ void p2p_unregisterSession(filetransfer* ft);
+ void p2p_sessionComplete(filetransfer* ft);
+
+ void P2pSessions_Uninit(void);
+
+ filetransfer* p2p_getAvatarSession(MCONTACT hContact);
+ filetransfer* p2p_getThreadSession(MCONTACT hContact, TInfoType mType);
+ filetransfer* p2p_getSessionByID(unsigned id);
+ filetransfer* p2p_getSessionByUniqueID(unsigned id);
+ filetransfer* p2p_getSessionByCallID(const char* CallID, const char* wlid);
+
+ bool p2p_sessionRegistered(filetransfer* ft);
+ bool p2p_isAvatarOnly(MCONTACT hContact);
+ unsigned p2p_getMsgId(const char* wlid, int inc);
+ unsigned p2p_getPktNum(const char* wlid);
+
+ void p2p_registerDC(directconnection* ft);
+ void p2p_unregisterDC(directconnection* dc);
+ directconnection* p2p_getDCByCallID(const char* CallID, const char* wlid);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN MSNFTP file transfer
+
+ void msnftp_invite(filetransfer *ft);
+ void msnftp_sendAcceptReject(filetransfer *ft, bool acc);
+ void msnftp_startFileSend(ThreadData* info, const char* Invcommand, const char* Invcookie);
+
+ int MSN_HandleMSNFTP(ThreadData *info, char *cmdString);
+
+ void __cdecl msnftp_sendFileThread(void* arg);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN Chat support
+
+ int MSN_ChatInit(ThreadData* info);
+ void MSN_ChatStart(ThreadData* info);
+ void MSN_KillChatSession(const TCHAR* id);
+
+ MCONTACT MSN_GetChatInernalHandle(MCONTACT hContact);
+
+ int __cdecl MSN_GCEventHook(WPARAM wParam, LPARAM lParam);
+ int __cdecl MSN_GCMenuHook(WPARAM wParam, LPARAM lParam);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN contact list
+
+ int Lists_Add(int list, int netId, const char* email, MCONTACT hContact = NULL, const char* nick = NULL, const char* invite = NULL);
+ bool Lists_IsInList(int list, const char* email);
+ int Lists_GetMask(const char* email);
+ int Lists_GetNetId(const char* email);
+ void Lists_Remove(int list, const char* email);
+ void Lists_Populate(void);
+ void Lists_Wipe(void);
+
+ MsnContact* Lists_Get(const char* email);
+ MsnContact* Lists_Get(MCONTACT hContact);
+ MsnContact* Lists_GetNext(int& i);
+
+ MsnPlace* Lists_GetPlace(const char* wlid);
+ MsnPlace* Lists_AddPlace(const char* email, const char* id, unsigned cap1, unsigned cap2);
+
+ void Lists_Uninit(void);
+
+ void AddDelUserContList(const char* email, const int list, const int netId, const bool del);
+
+ void MSN_CreateContList(void);
+ void MSN_CleanupLists(void);
+ void MSN_FindYahooUser(const char* email);
+ bool MSN_RefreshContactList(void);
+
+ bool MSN_IsMyContact(MCONTACT hContact);
+ bool MSN_IsMeByContact(MCONTACT hContact, char* szEmail = NULL);
+ bool MSN_AddUser(MCONTACT hContact, const char* email, int netId, int flags, const char *msg = NULL);
+ void MSN_AddAuthRequest(const char *email, const char *nick, const char *reason);
+ void MSN_SetContactDb(MCONTACT hContact, const char *szEmail);
+ MCONTACT MSN_HContactFromEmail(const char* msnEmail, const char* msnNick = NULL, bool addIfNeeded = false, bool temporary = false);
+ MCONTACT AddToListByEmail(const char *email, const char *nick, DWORD flags);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN server groups
+
+ void MSN_AddGroup(const char* pName, const char* pId, bool init);
+ void MSN_DeleteGroup(const char* pId);
+ void MSN_FreeGroups(void);
+ LPCSTR MSN_GetGroupById(const char* pId);
+ LPCSTR MSN_GetGroupByName(const char* pName);
+ void MSN_SetGroupName(const char* pId, const char* pName);
+
+ void MSN_MoveContactToGroup(MCONTACT hContact, const char* grpName);
+ void MSN_RenameServerGroup(LPCSTR szId, const char* newName);
+ void MSN_DeleteServerGroup(LPCSTR szId);
+ void MSN_RemoveEmptyGroups(void);
+ void MSN_SyncContactToServerGroup(MCONTACT hContact, const char* szContId, ezxml_t cgrp);
+ void MSN_UploadServerGroups(char* group);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN Authentication
+
+ int MSN_GetPassportAuth(void);
+ char* GenerateLoginBlob(char* challenge);
+ CMStringA HotmailLogin(const char* url);
+ void FreeAuthTokens(void);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN avatars support
+
+ void AvatarQueue_Init(void);
+ void AvatarQueue_Uninit(void);
+
+ void MSN_GetAvatarFileName(MCONTACT hContact, TCHAR* pszDest, size_t cbLen, const TCHAR *ext);
+ int MSN_SetMyAvatar(const TCHAR* szFname, void* pData, size_t cbLen);
+
+ void __cdecl MSN_AvatarsThread(void*);
+
+ void pushAvatarRequest(MCONTACT hContact, LPCSTR pszUrl);
+ bool loadHttpAvatar(AvatarQueueEntry *p);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN Mail & Offline messaging support
+
+ bool nickChg;
+
+ void getMetaData(void);
+ void getOIMs(ezxml_t xmli);
+ ezxml_t oimRecvHdr(const char* service, ezxml_t& tbdy, char*& httphdr);
+
+ void processMailData(char* mailData);
+ void sttNotificationMessage(char* msgBody, bool isInitial);
+ void displayEmailCount(MCONTACT hContact);
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN SOAP Address Book
+
+ bool MSN_SharingFindMembership(bool deltas = false, bool allowRecurse = true);
+ bool MSN_SharingAddDelMember(const char* szEmail, const int listId, const int netId, const char* szMethod, bool allowRecurse = true);
+ bool MSN_SharingMyProfile(bool allowRecurse = true);
+ bool MSN_ABAdd(bool allowRecurse = true);
+ bool MSN_ABFind(const char* szMethod, const char* szGuid, bool deltas = false, bool allowRecurse = true);
+ bool MSN_ABAddDelContactGroup(const char* szCntId, const char* szGrpId, const char* szMethod, bool allowRecurse = true);
+ void MSN_ABAddGroup(const char* szGrpName, bool allowRecurse = true);
+ void MSN_ABRenameGroup(const char* szGrpName, const char* szGrpId, bool allowRecurse = true);
+ void MSN_ABUpdateNick(const char* szNick, const char* szCntId);
+ void MSN_ABUpdateAttr(const char* szCntId, const char* szAttr, const char* szValue, bool allowRecurse = true);
+ bool MSN_ABUpdateProperty(const char* szCntId, const char* propName, const char* propValue, bool allowRecurse = true);
+ bool MSN_ABAddRemoveContact(const char* szCntId, int netId, bool add, bool allowRecurse = true);
+ unsigned MSN_ABContactAdd(const char* szEmail, const char* szNick, int netId, const char* szInvite, bool search, bool retry = false, bool allowRecurse = true);
+ void MSN_ABUpdateDynamicItem(bool allowRecurse = true);
+
+ ezxml_t abSoapHdr(const char* service, const char* scenario, ezxml_t& tbdy, char*& httphdr);
+ char* GetABHost(const char* service, bool isSharing);
+ void SetAbParam(MCONTACT hContact, const char *name, const char *par);
+ void UpdateABHost(const char* service, const char* url);
+ void UpdateABCacheKey(ezxml_t bdy, bool isSharing);
+
+ ezxml_t getSoapResponse(ezxml_t bdy, const char* service);
+ ezxml_t getSoapFault(ezxml_t bdy, bool err);
+
+ char mycid[32];
+ char mypuid[32];
+
+ /////////////////////////////////////////////////////////////////////////////////////////
+ // MSN SOAP Roaming Storage
+
+ bool MSN_StoreGetProfile(bool allowRecurse = true);
+ bool MSN_StoreUpdateProfile(const char* szNick, const char* szStatus, bool lock, bool allowRecurse = true);
+ bool MSN_StoreCreateProfile(bool allowRecurse = true);
+ bool MSN_StoreShareItem(const char* id, bool allowRecurse = true);
+ bool MSN_StoreCreateRelationships(bool allowRecurse = true);
+ bool MSN_StoreDeleteRelationships(bool tile, bool allowRecurse = true);
+ bool MSN_StoreCreateDocument(const TCHAR *sztName, const char *szMimeType, const char *szPicData, bool allowRecurse = true);
+ bool MSN_StoreUpdateDocument(const TCHAR *sztName, const char *szMimeType, const char *szPicData, bool allowRecurse = true);
+ bool MSN_StoreFindDocuments(bool allowRecurse = true);
+
+ ezxml_t storeSoapHdr(const char* service, const char* scenario, ezxml_t& tbdy, char*& httphdr);
+ char* GetStoreHost(const char* service);
+ void UpdateStoreHost(const char* service, const char* url);
+ void UpdateStoreCacheKey(ezxml_t bdy);
+
+ char proresid[64];
+ char expresid[64];
+ char photoid[64];
+
+ //////////////////////////////////////////////////////////////////////////////////////
+
+ TCHAR* GetContactNameT(MCONTACT hContact);
+
+ int getStringUtf(MCONTACT hContact, const char* name, DBVARIANT* result);
+ int getStringUtf(const char* name, DBVARIANT* result);
+ void setStringUtf(MCONTACT hContact, const char* name, const char* value);
+};
+
+extern OBJLIST<CMsnProto> g_Instances;
+
+#endif
diff --git a/protocols/MSN/src/msn_soapab.cpp b/protocols/MSN/src/msn_soapab.cpp
new file mode 100644
index 0000000000..7545a1d7d7
--- /dev/null
+++ b/protocols/MSN/src/msn_soapab.cpp
@@ -0,0 +1,1713 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2007-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+static const char abReqHdr[] =
+ "SOAPAction: http://www.msn.com/webservices/AddressBook/%s\r\n";
+
+
+ezxml_t CMsnProto::abSoapHdr(const char* service, const char* scenario, ezxml_t& tbdy, char*& httphdr)
+{
+ ezxml_t xmlp = ezxml_new("soap:Envelope");
+ ezxml_set_attr(xmlp, "xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/");
+ ezxml_set_attr(xmlp, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ ezxml_set_attr(xmlp, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
+ ezxml_set_attr(xmlp, "xmlns:soapenc", "http://schemas.xmlsoap.org/soap/encoding/");
+
+ ezxml_t hdr = ezxml_add_child(xmlp, "soap:Header", 0);
+ ezxml_t apphdr = ezxml_add_child(hdr, "ABApplicationHeader", 0);
+ ezxml_set_attr(apphdr, "xmlns", "http://www.msn.com/webservices/AddressBook");
+ ezxml_t node = ezxml_add_child(apphdr, "ApplicationId", 0);
+ ezxml_set_txt(node, msnAppID);
+ node = ezxml_add_child(apphdr, "IsMigration", 0);
+ ezxml_set_txt(node, abchMigrated ? "false" : "true");
+ node = ezxml_add_child(apphdr, "PartnerScenario", 0);
+ ezxml_set_txt(node, scenario);
+
+ char *cacheKey = strstr(service, "Member") ? sharingCacheKey : abCacheKey;
+ if (cacheKey)
+ {
+ node = ezxml_add_child(apphdr, "CacheKey", 0);
+ ezxml_set_txt(node, cacheKey);
+ }
+
+ ezxml_t authhdr = ezxml_add_child(hdr, "ABAuthHeader", 0);
+ ezxml_set_attr(authhdr, "xmlns", "http://www.msn.com/webservices/AddressBook");
+ node = ezxml_add_child(authhdr, "ManagedGroupRequest", 0);
+ ezxml_set_txt(node, "false");
+ node = ezxml_add_child(authhdr, "TicketToken", 0);
+ if (authContactToken) ezxml_set_txt(node, authContactToken);
+
+ ezxml_t bdy = ezxml_add_child(xmlp, "soap:Body", 0);
+
+ tbdy = ezxml_add_child(bdy, service, 0);
+ ezxml_set_attr(tbdy, "xmlns", "http://www.msn.com/webservices/AddressBook");
+
+ if (strstr(service, "Member") == NULL && strcmp(service, "ABAdd") != 0 && strcmp(service, "ABFindContactsPaged"))
+ {
+ ezxml_t node = ezxml_add_child(tbdy, "abId", 0);
+ ezxml_set_txt(node, "00000000-0000-0000-0000-000000000000");
+ }
+
+ size_t hdrsz = strlen(service) + sizeof(abReqHdr) + 20;
+ httphdr = (char*)mir_alloc(hdrsz);
+
+ mir_snprintf(httphdr, hdrsz, abReqHdr, service);
+
+ return xmlp;
+}
+
+
+ezxml_t CMsnProto::getSoapResponse(ezxml_t bdy, const char* service)
+{
+ char resp1[40], resp2[40];
+ mir_snprintf(resp1, sizeof(resp1), "%sResponse", service);
+ mir_snprintf(resp2, sizeof(resp2), "%sResult", service);
+
+ ezxml_t res = ezxml_get(bdy, "soap:Body", 0, resp1, 0, resp2, -1);
+ if (res == NULL)
+ res = ezxml_get(bdy, "s:Body", 0, resp1, 0, resp2, -1);
+
+ return res;
+}
+
+ezxml_t CMsnProto::getSoapFault(ezxml_t bdy, bool err)
+{
+ ezxml_t flt = ezxml_get(bdy, "soap:Body", 0, "soap:Fault", -1);
+ return err ? ezxml_get(flt, "detail", 0, "errorcode", -1) : flt;
+}
+
+void CMsnProto::UpdateABHost(const char* service, const char* url)
+{
+ char hostname[128];
+ mir_snprintf(hostname, sizeof(hostname), "ABHost-%s", service);
+
+ if (url)
+ setString(hostname, url);
+ else
+ delSetting(hostname);
+}
+
+void CMsnProto::UpdateABCacheKey(ezxml_t bdy, bool isSharing)
+{
+ ezxml_t hdr = ezxml_get(bdy, "soap:Header", 0, "ServiceHeader", -1);
+ bool changed = strcmp(ezxml_txt(ezxml_child(hdr, "CacheKeyChanged")), "true") == 0;
+ if (changed)
+ {
+ replaceStr(isSharing ? sharingCacheKey : abCacheKey, ezxml_txt(ezxml_child(hdr, "CacheKey")));
+ }
+}
+
+char* CMsnProto::GetABHost(const char* service, bool isSharing)
+{
+ char hostname[128];
+ mir_snprintf(hostname, sizeof(hostname), "ABHost-%s", service);
+
+ char* host = (char*)mir_alloc(256);
+ if (db_get_static(NULL, m_szModuleName, hostname, host, 256))
+ {
+ mir_snprintf(host, 256, "https://byrdr.omega.contacts.msn.com/abservice/%s.asmx",
+ isSharing ? "SharingService" : "abservice");
+ }
+
+ return host;
+}
+
+/*
+ezxml_t CMsnProto::PerformSoapReq(const char *service, bool isSharing, char *szData, const char* hdrs, unsigned& status)
+{
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost(service, true);
+ tResult = getSslResult(&abUrl, szData, hdrs, status);
+ if (tResult == NULL) UpdateABHost(service, NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost(service, abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ if (!xmlm || !ezxml_child(xmlm, "soap:Body"))
+ {
+ mir_free(tResult);
+ ezxml_free(xmlm);
+ UpdateABHost("service", NULL);
+ PerformSoapReq(service, isSharing, szData, hdrs, status);
+ }
+ else if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (!szErr[0])
+ {
+ mir_free(tResult);
+ ezxml_free(xmlm);
+ UpdateABHost("service", NULL);
+ PerformSoapReq(service, isSharing, szData, hdrs, status);
+ }
+ }
+ }
+ mir_free(abUrl);
+}
+*/
+
+
+bool CMsnProto::MSN_ABAdd(bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy, node;
+ ezxml_t xmlp = abSoapHdr("ABAdd", "Timer", tbdy, reqHdr);
+
+ ezxml_t abinf = ezxml_add_child(tbdy, "abInfo", 0);
+ ezxml_add_child(abinf, "name", 0);
+ node = ezxml_add_child(abinf, "ownerPuid", 0);
+ ezxml_set_txt(node, "0");
+ node = ezxml_add_child(abinf, "ownerEmail", 0);
+ ezxml_set_txt(node, MyOptions.szEmail);
+ node = ezxml_add_child(abinf, "fDefault", 0);
+ ezxml_set_txt(node, "true");
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("ABAdd", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("ABAdd", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost("ABAdd", abUrl);
+
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_ABAdd(false) ? 200 : 500;
+ }
+ }
+ ezxml_free(xmlm);
+ }
+
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+
+bool CMsnProto::MSN_SharingFindMembership(bool deltas, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("FindMembership", "Initial", tbdy, reqHdr);
+
+ ezxml_t svcflt = ezxml_add_child(tbdy, "serviceFilter", 0);
+ ezxml_t tps = ezxml_add_child(svcflt, "Types", 0);
+ ezxml_t node = ezxml_add_child(tps, "ServiceType", 0);
+ ezxml_set_txt(node, "Messenger");
+/*
+ node = ezxml_add_child(tps, "ServiceType", 0);
+ ezxml_set_txt(node, "Invitation");
+ node = ezxml_add_child(tps, "ServiceType", 0);
+ ezxml_set_txt(node, "SocialNetwork");
+ node = ezxml_add_child(tps, "ServiceType", 0);
+ ezxml_set_txt(node, "Space");
+ node = ezxml_add_child(tps, "ServiceType", 0);
+ ezxml_set_txt(node, "Profile");
+ node = ezxml_add_child(tps, "ServiceType", 0);
+ ezxml_set_txt(node, "Folder");
+ node = ezxml_add_child(tps, "ServiceType", 0);
+ ezxml_set_txt(node, "OfficeLiveWebNotification");
+*/
+ const char *szLastChange = NULL;
+ if (deltas)
+ {
+ DBVARIANT dbv;
+ if (!getString("SharingLastChange", &dbv) && dbv.pszVal[0])
+ {
+ szLastChange = NEWSTR_ALLOCA(dbv.pszVal);
+ db_free(&dbv);
+ }
+ deltas &= (szLastChange != NULL);
+ }
+
+ node = ezxml_add_child(tbdy, "View", 0);
+ ezxml_set_txt(node, "Full");
+ node = ezxml_add_child(tbdy, "deltasOnly", 0);
+ ezxml_set_txt(node, deltas ? "true" : "false");
+ node = ezxml_add_child(tbdy, "lastChange", 0);
+ ezxml_set_txt(node, deltas ? szLastChange : "0001-01-01T00:00:00.0000000-08:00");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("FindMembership", true);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("FindMembership", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ if (status == 200)
+ {
+ UpdateABCacheKey(xmlm, true);
+ ezxml_t body = getSoapResponse(xmlm, "FindMembership");
+ ezxml_t svcs = ezxml_get(body, "Services", 0, "Service", -1);
+
+ UpdateABHost("FindMembership", body ? abUrl : NULL);
+
+ while (svcs != NULL)
+ {
+ const char* szType = ezxml_txt(ezxml_get(svcs, "Info", 0, "Handle", 0, "Type", -1));
+ if (_stricmp(szType, "Messenger") == 0) break;
+ svcs = ezxml_next(svcs);
+ }
+
+ const char* szLastChange = ezxml_txt(ezxml_child(svcs, "LastChange"));
+ if (szLastChange[0]) setString("SharingLastChange", szLastChange);
+
+ ezxml_t mems = ezxml_get(svcs, "Memberships", 0, "Membership", -1);
+
+ while (mems != NULL)
+ {
+ const char* szRole = ezxml_txt(ezxml_child(mems, "MemberRole"));
+
+ int lstId = 0;
+ if (strcmp(szRole, "Allow") == 0) lstId = LIST_AL;
+ else if (strcmp(szRole, "Block") == 0) lstId = LIST_BL;
+ else if (strcmp(szRole, "Reverse") == 0) lstId = LIST_RL;
+ else if (strcmp(szRole, "Pending") == 0) lstId = LIST_PL;
+
+ ezxml_t memb = ezxml_get(mems, "Members", 0, "Member", -1);
+ while (memb != NULL)
+ {
+ bool deleted = strcmp(ezxml_txt(ezxml_child(memb, "Deleted")), "true") == 0;
+ const char* szType = ezxml_txt(ezxml_child(memb, "Type"));
+ if (strcmp(szType, "Passport") == 0)
+ {
+ const char* szInvite = NULL;
+ const char* szEmail = ezxml_txt(ezxml_child(memb, "PassportName"));
+ const char* szNick = ezxml_txt(ezxml_child(memb, "DisplayName")); if (!szNick[0]) szNick = NULL;
+ ezxml_t anot = ezxml_get(memb, "Annotations", 0, "Annotation", -1);
+ while (anot != NULL)
+ {
+ if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.InviteMessage") == 0)
+ {
+ szInvite = ezxml_txt(ezxml_child(anot, "Value"));
+ }
+ anot = ezxml_next(anot);
+ }
+ if (!deleted) Lists_Add(lstId, NETID_MSN, szEmail, NULL, szNick, szInvite); else Lists_Remove(lstId, szEmail);
+ }
+ else if (strcmp(szType, "Phone") == 0)
+ {
+ const char* szEmail = ezxml_txt(ezxml_child(memb, "PhoneNumber"));
+ char email[128];
+ mir_snprintf(email, sizeof(email), "tel:%s", szEmail);
+ if (!deleted) Lists_Add(lstId, NETID_MOB, email); else Lists_Remove(lstId, szEmail);
+ }
+ else if (strcmp(szType, "Email") == 0)
+ {
+ const char* szInvite = NULL;
+ const char* szEmail = ezxml_txt(ezxml_child(memb, "Email"));
+ const char* szNick = ezxml_txt(ezxml_child(memb, "DisplayName")); if (!szNick[0]) szNick = NULL;
+ int netId = strstr(szEmail, "@yahoo.com") ? NETID_YAHOO : NETID_LCS;
+ ezxml_t anot = ezxml_get(memb, "Annotations", 0, "Annotation", -1);
+ while (anot != NULL)
+ {
+ if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.BuddyType") == 0)
+ {
+ netId = atol(ezxml_txt(ezxml_child(anot, "Value")));
+ }
+ else if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.InviteMessage") == 0)
+ {
+ szInvite = ezxml_txt(ezxml_child(anot, "Value"));
+ }
+ anot = ezxml_next(anot);
+ }
+
+ if (!deleted) Lists_Add(lstId, netId, szEmail, NULL, szNick, szInvite); else Lists_Remove(lstId, szEmail);
+ }
+ memb = ezxml_next(memb);
+ }
+ mems = ezxml_next(mems);
+ }
+ }
+ else if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "ABDoesNotExist") == 0)
+ {
+ MSN_ABAdd();
+ status = 200;
+ }
+ else if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_SharingFindMembership(deltas, false) ? 200 : 500;
+ }
+ }
+ else
+ UpdateABHost("FindMembership", NULL);
+
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+// AddMember, DeleteMember
+bool CMsnProto::MSN_SharingAddDelMember(const char* szEmail, const int listId, const int netId, const char* szMethod, bool allowRecurse)
+{
+ const char* szRole;
+ if (listId & LIST_AL) szRole = "Allow";
+ else if (listId & LIST_BL) szRole = "Block";
+ else if (listId & LIST_PL) szRole = "Pending";
+ else if (listId & LIST_RL) szRole = "Reverse";
+ else return false;
+
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr(szMethod, "BlockUnblock", tbdy, reqHdr);
+
+ ezxml_t svchnd = ezxml_add_child(tbdy, "serviceHandle", 0);
+ ezxml_t node = ezxml_add_child(svchnd, "Id", 0);
+ ezxml_set_txt(node, "0");
+ node = ezxml_add_child(svchnd, "Type", 0);
+ ezxml_set_txt(node, "Messenger");
+ node = ezxml_add_child(svchnd, "ForeignId", 0);
+// ezxml_set_txt(node, "");
+
+ const char* szMemberName = "";
+ const char* szTypeName = "";
+ const char* szAccIdName = "";
+
+ switch (netId)
+ {
+ case 1:
+ szMemberName = "PassportMember";
+ szTypeName = "Passport";
+ szAccIdName = "PassportName";
+ break;
+
+ case 4:
+ szMemberName = "PhoneMember";
+ szTypeName = "Phone";
+ szAccIdName = "PhoneNumber";
+ szEmail = strchr(szEmail, ':') + 1;
+ break;
+
+ case 2:
+ case 32:
+ szMemberName = "EmailMember";
+ szTypeName = "Email";
+ szAccIdName = "Email";
+ break;
+ }
+
+ ezxml_t memb = ezxml_add_child(tbdy, "memberships", 0);
+ memb = ezxml_add_child(memb, "Membership", 0);
+ node = ezxml_add_child(memb, "MemberRole", 0);
+ ezxml_set_txt(node, szRole);
+ memb = ezxml_add_child(memb, "Members", 0);
+ memb = ezxml_add_child(memb, "Member", 0);
+ ezxml_set_attr(memb, "xsi:type", szMemberName);
+ ezxml_set_attr(memb, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ node = ezxml_add_child(memb, "Type", 0);
+ ezxml_set_txt(node, szTypeName);
+ node = ezxml_add_child(memb, "State", 0);
+ ezxml_set_txt(node, "Accepted");
+ node = ezxml_add_child(memb, szAccIdName, 0);
+ ezxml_set_txt(node, szEmail);
+
+ char buf[64];
+ if ((netId == NETID_LCS || netId == NETID_YAHOO) && strcmp(szMethod, "DeleteMember") != 0)
+ {
+ node = ezxml_add_child(memb, "Annotations", 0);
+ ezxml_t anot = ezxml_add_child(node, "Annotation", 0);
+ node = ezxml_add_child(anot, "Name", 0);
+ ezxml_set_txt(node, "MSN.IM.BuddyType");
+ node = ezxml_add_child(anot, "Value", 0);
+
+ mir_snprintf(buf, sizeof(buf), "%02d:", netId);
+ ezxml_set_txt(node, buf);
+ }
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status;
+ char *abUrl = NULL, *tResult;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost(szMethod, true);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost(szMethod, NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost(szMethod, abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, true);
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_SharingAddDelMember(szEmail, listId, netId, szMethod, false) ? 200 : 500;
+ }
+ }
+ ezxml_free(xmlm);
+ }
+
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+bool CMsnProto::MSN_SharingMyProfile(bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("AddMember", "RoamingSeed", tbdy, reqHdr);
+
+ ezxml_t svchnd = ezxml_add_child(tbdy, "serviceHandle", 0);
+ ezxml_t node = ezxml_add_child(svchnd, "Id", 0);
+ ezxml_set_txt(node, "0");
+ node = ezxml_add_child(svchnd, "Type", 0);
+ ezxml_set_txt(node, "Profile");
+ node = ezxml_add_child(svchnd, "ForeignId", 0);
+ ezxml_set_txt(node, "MyProfile");
+
+ ezxml_t memb = ezxml_add_child(tbdy, "memberships", 0);
+ memb = ezxml_add_child(memb, "Membership", 0);
+ node = ezxml_add_child(memb, "MemberRole", 0);
+ ezxml_set_txt(node, "ProfileExpression");
+ memb = ezxml_add_child(memb, "Members", 0);
+ memb = ezxml_add_child(memb, "Member", 0);
+ ezxml_set_attr(memb, "xsi:type", "RoleMember");
+ ezxml_set_attr(memb, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ node = ezxml_add_child(memb, "Type", 0);
+ ezxml_set_txt(node, "Role");
+ node = ezxml_add_child(memb, "State", 0);
+ ezxml_set_txt(node, "Accepted");
+ node = ezxml_add_child(memb, "Id", 0);
+ ezxml_set_txt(node, "Allow");
+
+ ezxml_t svcdef = ezxml_add_child(memb, "DefiningService", 0);
+ node = ezxml_add_child(svcdef, "Id", 0);
+ ezxml_set_txt(node, "0");
+ node = ezxml_add_child(svcdef, "Type", 0);
+ ezxml_set_txt(node, "Messenger");
+ node = ezxml_add_child(svcdef, "ForeignId", 0);
+
+ node = ezxml_add_child(memb, "MaxRoleRecursionDepth", 0);
+ ezxml_set_txt(node, "0");
+
+ node = ezxml_add_child(memb, "MaxDegreesSeparationDepth", 0);
+ ezxml_set_txt(node, "0");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("AddMember", true);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("AddMember", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ MSN_SharingMyProfile(false);
+ }
+ }
+ ezxml_free(xmlm);
+
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+
+void CMsnProto::SetAbParam(MCONTACT hContact, const char *name, const char *par)
+{
+ if (*par) setStringUtf(hContact, name, (char*)par);
+// else delSetting(hContact, "FirstName");
+}
+
+// "ABFindAll", "ABFindByContacts", "ABFindContactsPaged"
+bool CMsnProto::MSN_ABFind(const char* szMethod, const char* szGuid, bool deltas, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr(szMethod, "Initial", tbdy, reqHdr);
+
+
+ const char *szLastChange = NULL;
+ if (deltas)
+ {
+ DBVARIANT dbv;
+ if (!getString("ABFullLastChange", &dbv) && dbv.pszVal[0])
+ {
+ szLastChange = NEWSTR_ALLOCA(dbv.pszVal);
+ db_free(&dbv);
+ }
+ deltas &= (szLastChange != NULL);
+ }
+ const char *szDynLastChange = NULL;
+ if (deltas)
+ {
+ DBVARIANT dbv;
+ if (!getString("ABFullDynLastChange", &dbv) && dbv.pszVal[0])
+ {
+ szDynLastChange = NEWSTR_ALLOCA(dbv.pszVal);
+ db_free(&dbv);
+ }
+ deltas &= (szDynLastChange != NULL);
+ }
+
+ const char *szGroups, *szContacts, *szLastChangeStr;
+ if (strcmp(szMethod, "ABFindContactsPaged"))
+ {
+ ezxml_t node = ezxml_add_child(tbdy, "abView", 0);
+ ezxml_set_txt(node, "Full");
+ node = ezxml_add_child(tbdy, "deltasOnly", 0);
+ ezxml_set_txt(node, deltas ? "true" : "false");
+ node = ezxml_add_child(tbdy, "dynamicItemView", 0);
+ ezxml_set_txt(node, "Gleam");
+ if (deltas)
+ {
+ node = ezxml_add_child(tbdy, "lastChange", 0);
+ ezxml_set_txt(node, szLastChange);
+ node = ezxml_add_child(tbdy, "dynamicItemLastChange", 0);
+ ezxml_set_txt(node, szDynLastChange);
+ }
+
+ if (szGuid)
+ {
+ node = ezxml_add_child(tbdy, "contactIds", 0);
+ node = ezxml_add_child(node, "guid", 0);
+ ezxml_set_txt(node, szGuid);
+ }
+ szGroups = "groups";
+ szContacts = "contacts";
+ szLastChangeStr = "LastChange";
+ }
+ else
+ {
+ ezxml_t node = ezxml_add_child(tbdy, "abView", 0);
+ ezxml_set_txt(node, "MessengerClient8");
+ node = ezxml_add_child(tbdy, "extendedContent", 0);
+ ezxml_set_txt(node, "AB AllGroups CircleResult");
+ ezxml_t filt = ezxml_add_child(tbdy, "filterOptions", 0);
+
+ node = ezxml_add_child(filt, "DeltasOnly", 0);
+ ezxml_set_txt(node, deltas ? "true" : "false");
+ if (deltas)
+ {
+ node = ezxml_add_child(filt, "LastChanged", 0);
+ ezxml_set_txt(node, szLastChange);
+ }
+ node = ezxml_add_child(filt, "ContactFilter", 0);
+ node = ezxml_add_child(node, "IncludeHiddenContacts", 0);
+ ezxml_set_txt(node, "true");
+
+ szGroups = "Groups";
+ szContacts = "Contacts";
+ szLastChangeStr = "lastChange";
+ }
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost(szMethod, false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost(szMethod, NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+
+ if (status == 200)
+ {
+ ezxml_t body = getSoapResponse(xmlm, szMethod);
+ UpdateABHost(szMethod, body ? abUrl : NULL);
+
+ ezxml_t ab = ezxml_child(body, "Ab");
+ if (strcmp(szMethod, "ABFindByContacts"))
+ {
+ const char* szLastChange = ezxml_txt(ezxml_child(ab, szLastChangeStr));
+ if (szLastChange[0]) setString("ABFullLastChange", szLastChange);
+ szLastChange = ezxml_txt(ezxml_child(ab, "DynamicItemLastChanged"));
+ if (szLastChange[0]) setString("ABFullDynLastChange", szLastChange);
+ }
+
+ ezxml_t abinf = ezxml_child(ab, "abInfo");
+ mir_snprintf(mycid, sizeof(mycid), "%s", ezxml_txt(ezxml_child(abinf, "OwnerCID")));
+ mir_snprintf(mypuid, sizeof(mycid), "%s", ezxml_txt(ezxml_child(abinf, "ownerPuid")));
+
+ if (MyOptions.ManageServer)
+ {
+ ezxml_t grp = ezxml_get(body, szGroups, 0, "Group", -1);
+ while (grp != NULL)
+ {
+ const char* szGrpId = ezxml_txt(ezxml_child(grp, "groupId"));
+ const char* szGrpName = ezxml_txt(ezxml_get(grp, "groupInfo", 0, "name", -1));
+ MSN_AddGroup(szGrpName, szGrpId, true);
+
+ grp = ezxml_next(grp);
+ }
+ }
+
+ for (ezxml_t cont = ezxml_get(body, szContacts, 0, "Contact", -1); cont != NULL; cont = ezxml_next(cont))
+ {
+ const char* szContId = ezxml_txt(ezxml_child(cont, "contactId"));
+
+ ezxml_t contInf = ezxml_child(cont, "contactInfo");
+ const char* szType = ezxml_txt(ezxml_child(contInf, "contactType"));
+
+ if (strcmp(szType, "Me") != 0)
+ {
+ char email[128];
+
+ const char* szEmail = ezxml_txt(ezxml_child(contInf, "passportName"));
+ const char* szMsgUsr = ezxml_txt(ezxml_child(contInf, "isMessengerUser"));
+
+ int netId = NETID_UNKNOWN;
+ if (strcmp(szMsgUsr, "true") == 0) netId = NETID_MSN;
+
+ if (szEmail[0] == '\0')
+ {
+ ezxml_t eml = ezxml_get(contInf, "emails", 0, "ContactEmail", -1);
+ while (eml != NULL)
+ {
+ szMsgUsr = ezxml_txt(ezxml_child(eml, "isMessengerEnabled"));
+ if (strcmp(szMsgUsr, "true") == 0)
+ {
+ szEmail = ezxml_txt(ezxml_child(eml, "email"));
+ const char* szCntType = ezxml_txt(ezxml_child(eml, "contactEmailType"));
+ if (strcmp(szCntType, "Messenger2") == 0)
+ netId = NETID_YAHOO;
+ else if (strcmp(szCntType, "Messenger3") == 0)
+ netId = NETID_LCS;
+ break;
+ }
+ eml = ezxml_next(eml);
+ }
+
+ if (netId == NETID_UNKNOWN)
+ {
+ ezxml_t phn = ezxml_get(contInf, "phones", 0, "ContactPhone", -1);
+ while (phn != NULL)
+ {
+ szMsgUsr = ezxml_txt(ezxml_child(phn, "isMessengerEnabled"));
+ if (strcmp(szMsgUsr, "true") == 0)
+ {
+ szEmail = ezxml_txt(ezxml_child(phn, "number"));
+ mir_snprintf(email, sizeof(email), "tel:%s", szEmail);
+ szEmail = email;
+ netId = NETID_MOB;
+ break;
+ }
+ phn = ezxml_next(phn);
+ }
+ }
+ }
+
+ if (netId == NETID_UNKNOWN || szEmail[0] == 0) continue;
+
+ Lists_Add(LIST_FL, netId, szEmail);
+ const char *szTmp;
+
+ // Depricated in WLM 8.1
+ // const char* szNick = ezxml_txt(ezxml_child(contInf, "displayName"));
+ // if (*szNick == '\0') szNick = szEmail;
+ MCONTACT hContact = MSN_HContactFromEmail(szEmail, szEmail, true, false);
+ // setStringUtf(hContact, "Nick", (char*)szNick);
+
+ if (MyOptions.ManageServer)
+ {
+ ezxml_t grpid = ezxml_child(contInf, "groupIds");
+ if (!deltas || grpid)
+ {
+ ezxml_t grps = ezxml_child(grpid, "guid");
+ MSN_SyncContactToServerGroup(hContact, szContId, grps);
+ }
+ }
+
+ const char* szNick = NULL;
+ ezxml_t anot = ezxml_get(contInf, "annotations", 0, "Annotation", -1);
+ while (anot != NULL)
+ {
+ if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "AB.NickName") == 0)
+ {
+ szNick = ezxml_txt(ezxml_child(anot, "Value"));
+ db_set_utf(hContact, "CList", "MyHandle", szNick);
+ }
+ if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "AB.JobTitle") == 0)
+ {
+ szTmp = ezxml_txt(ezxml_child(anot, "Value"));
+ SetAbParam(hContact, "CompanyPosition", szTmp);
+ }
+ anot = ezxml_next(anot);
+ }
+ if (szNick == NULL)
+ db_unset(hContact, "CList", "MyHandle");
+
+ setString(hContact, "ID", szContId);
+
+ switch (netId)
+ {
+ case NETID_YAHOO:
+ setString(hContact, "Transport", "YAHOO");
+ break;
+
+ case NETID_LCS:
+ setString(hContact, "Transport", "LCS");
+ break;
+
+ default:
+ delSetting(hContact, "Transport");
+ }
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "CID"));
+ SetAbParam(hContact, "CID", szTmp);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "IsNotMobileVisible"));
+ setByte(hContact, "MobileAllowed", strcmp(szTmp, "true") != 0);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "isMobileIMEnabled"));
+ setByte(hContact, "MobileEnabled", strcmp(szTmp, "true") == 0);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "firstName"));
+ SetAbParam(hContact, "FirstName", szTmp);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "lastName"));
+ SetAbParam(hContact, "LastName", szTmp);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "birthdate"));
+ char *szPtr;
+ if (strtol(szTmp, &szPtr, 10) > 1)
+ {
+ setWord(hContact, "BirthYear", (WORD)strtol(szTmp, &szPtr, 10));
+ setByte(hContact, "BirthMonth", (BYTE)strtol(szPtr+1, &szPtr, 10));
+ setByte(hContact, "BirthDay", (BYTE)strtol(szPtr+1, &szPtr, 10));
+ }
+ else
+ {
+ // delSetting(hContact, "BirthYear");
+ // delSetting(hContact, "BirthMonth");
+ // delSetting(hContact, "BirthDay");
+ }
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "comment"));
+ if (*szTmp) db_set_s(hContact, "UserInfo", "MyNotes", szTmp);
+ // else db_unset(hContact, "UserInfo", "MyNotes");
+
+ ezxml_t loc = ezxml_get(contInf, "locations", 0, "ContactLocation", -1);
+ while (loc != NULL)
+ {
+ const char* szCntType = ezxml_txt(ezxml_child(loc, "contactLocationType"));
+
+ int locid = -1;
+ if (strcmp(szCntType, "ContactLocationPersonal") == 0)
+ locid = 0;
+ else if (strcmp(szCntType, "ContactLocationBusiness") == 0)
+ locid = 1;
+
+ if (locid >= 0)
+ {
+ szTmp = ezxml_txt(ezxml_child(loc, "name"));
+ SetAbParam(hContact, "Company", szTmp);
+ szTmp = ezxml_txt(ezxml_child(loc, "street"));
+ SetAbParam(hContact, locid ? "CompanyStreet" : "Street", szTmp);
+ szTmp = ezxml_txt(ezxml_child(loc, "city"));
+ SetAbParam(hContact, locid ? "CompanyCity" : "City", szTmp);
+ szTmp = ezxml_txt(ezxml_child(loc, "state"));
+ SetAbParam(hContact, locid ? "CompanyState" : "State", szTmp);
+ szTmp = ezxml_txt(ezxml_child(loc, "country"));
+ SetAbParam(hContact, locid ? "CompanyCountry" : "Country", szTmp);
+ szTmp = ezxml_txt(ezxml_child(loc, "postalCode"));
+ SetAbParam(hContact, locid ? "CompanyZIP" : "ZIP", szTmp);
+ }
+ loc = ezxml_next(loc);
+ }
+
+ ezxml_t web = ezxml_get(contInf, "webSites", 0, "ContactWebSite", -1);
+ while (web != NULL)
+ {
+ const char* szCntType = ezxml_txt(ezxml_child(web, "contactWebSiteType"));
+ if (strcmp(szCntType, "ContactWebSiteBusiness") == 0)
+ {
+ szTmp = ezxml_txt(ezxml_child(web, "webURL"));
+ SetAbParam(hContact, "CompanyHomepage", szTmp);
+ }
+ web = ezxml_next(web);
+ }
+ }
+ else
+ {
+ // This depricated in WLM 8.1
+ // if (!getByte("NeverUpdateNickname", 0))
+ // {
+ // const char* szNick = ezxml_txt(ezxml_child(contInf, "displayName"));
+ // setStringUtf(NULL, "Nick", (char*)szNick);
+ // }
+ const char *szTmp;
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "isMobileIMEnabled"));
+ setByte("MobileEnabled", strcmp(szTmp, "true") == 0);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "IsNotMobileVisible"));
+ setByte("MobileAllowed", strcmp(szTmp, "true") != 0);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "firstName"));
+ setStringUtf(NULL, "FirstName", szTmp);
+
+ szTmp = ezxml_txt(ezxml_child(contInf, "lastName"));
+ setStringUtf(NULL, "LastName", szTmp);
+
+ ezxml_t anot = ezxml_get(contInf, "annotations", 0, "Annotation", -1);
+ while (anot != NULL)
+ {
+ if (strcmp(ezxml_txt(ezxml_child(anot, "Name")), "MSN.IM.BLP") == 0)
+ msnOtherContactsBlocked = !atol(ezxml_txt(ezxml_child(anot, "Value")));
+
+ anot = ezxml_next(anot);
+ }
+ }
+ }
+ if (!msnLoggedIn && msnNsThread)
+ {
+ char *szCircleTicket = ezxml_txt(ezxml_get(body, "CircleResult", 0, "CircleTicket", -1));
+ ptrA szCircleTicketEnc( mir_base64_encode((PBYTE)szCircleTicket, (unsigned)strlen(szCircleTicket)));
+ if (szCircleTicketEnc)
+ msnNsThread->sendPacket("USR", "SHA A %s", szCircleTicketEnc);
+ }
+
+ }
+ else if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_ABFind(szMethod, szGuid, deltas, false) ? 200 : 500;
+ }
+ else if (strcmp(szErr, "FullSyncRequired") == 0 && deltas)
+ {
+ status = MSN_ABFind(szMethod, szGuid, false, false) ? 200 : 500;
+ }
+ }
+ else
+ UpdateABHost(szMethod, NULL);
+
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+
+// "ABGroupContactAdd" : "ABGroupContactDelete", "ABGroupDelete", "ABContactDelete"
+bool CMsnProto::MSN_ABAddDelContactGroup(const char* szCntId, const char* szGrpId, const char* szMethod, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy, node;
+ ezxml_t xmlp = abSoapHdr(szMethod, "Timer", tbdy, reqHdr);
+
+ if (szGrpId != NULL)
+ {
+ node = ezxml_add_child(tbdy, "groupFilter", 0);
+ node = ezxml_add_child(node, "groupIds", 0);
+ node = ezxml_add_child(node, "guid", 0);
+ ezxml_set_txt(node, szGrpId);
+ }
+
+ if (szCntId != NULL)
+ {
+ node = ezxml_add_child(tbdy, "contacts", 0);
+ node = ezxml_add_child(node, "Contact", 0);
+ node = ezxml_add_child(node, "contactId", 0);
+ ezxml_set_txt(node, szCntId);
+ }
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost(szMethod, false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost(szMethod, NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost(szMethod, abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_ABAddDelContactGroup(szCntId, szGrpId, szMethod, false) ? 200 : 500;
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+void CMsnProto::MSN_ABAddGroup(const char* szGrpName, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("ABGroupAdd", "GroupSave", tbdy, reqHdr);
+
+ ezxml_t node = ezxml_add_child(tbdy, "groupAddOptions", 0);
+ node = ezxml_add_child(node, "fRenameOnMsgrConflict", 0);
+ ezxml_set_txt(node, "false");
+
+ node = ezxml_add_child(tbdy, "groupInfo", 0);
+ ezxml_t grpi = ezxml_add_child(node, "GroupInfo", 0);
+ node = ezxml_add_child(grpi, "name", 0);
+ ezxml_set_txt(node, szGrpName);
+ node = ezxml_add_child(grpi, "groupType", 0);
+ ezxml_set_txt(node, "C8529CE2-6EAD-434d-881F-341E17DB3FF8");
+ node = ezxml_add_child(grpi, "fMessenger", 0);
+ ezxml_set_txt(node, "false");
+ node = ezxml_add_child(grpi, "annotations", 0);
+ ezxml_t annt = ezxml_add_child(node, "Annotation", 0);
+ node = ezxml_add_child(annt, "Name", 0);
+ ezxml_set_txt(node, "MSN.IM.Display");
+ node = ezxml_add_child(annt, "Value", 0);
+ ezxml_set_txt(node, "1");
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("ABGroupAdd", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("ABGroupAdd", NULL);
+ else break;
+ }
+
+ free(szData);
+ mir_free(reqHdr);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost("ABGroupAdd", abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 200)
+ {
+ ezxml_t body = getSoapResponse(xmlm, "ABGroupAdd");
+ const char* szGrpId = ezxml_txt(ezxml_child(body, "guid"));
+ MSN_AddGroup(szGrpName, szGrpId, false);
+ }
+ else if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ MSN_ABAddGroup(szGrpName, false);
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+}
+
+
+void CMsnProto::MSN_ABRenameGroup(const char* szGrpName, const char* szGrpId, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("ABGroupUpdate", "Timer", tbdy, reqHdr);
+
+ ezxml_t node = ezxml_add_child(tbdy, "groups", 0);
+ ezxml_t grp = ezxml_add_child(node, "Group", 0);
+
+ node = ezxml_add_child(grp, "groupId", 0);
+ ezxml_set_txt(node, szGrpId);
+ ezxml_t grpi = ezxml_add_child(grp, "groupInfo", 0);
+ node = ezxml_add_child(grpi, "name", 0);
+ ezxml_set_txt(node, szGrpName);
+ node = ezxml_add_child(grp, "propertiesChanged", 0);
+ ezxml_set_txt(node, "GroupName");
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("ABGroupUpdate", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("ABGroupUpdate", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost("ABGroupUpdate", abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ MSN_ABRenameGroup(szGrpName, szGrpId, false);
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+}
+
+
+bool CMsnProto::MSN_ABAddRemoveContact(const char* szCntId, int netId, bool add, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("ABContactUpdate", "Timer", tbdy, reqHdr);
+
+ ezxml_t node = ezxml_add_child(tbdy, "contacts", 0);
+ ezxml_t cont = ezxml_add_child(node, "Contact", 0);
+ ezxml_set_attr(cont, "xmlns", "http://www.msn.com/webservices/AddressBook");
+
+ node = ezxml_add_child(cont, "contactId", 0);
+ ezxml_set_txt(node, szCntId);
+ ezxml_t conti = ezxml_add_child(cont, "contactInfo", 0);
+
+ switch (netId)
+ {
+ case NETID_MSN:
+ node = ezxml_add_child(conti, "isMessengerUser", 0);
+ ezxml_set_txt(node, add ? "true" : "false");
+ node = ezxml_add_child(cont, "propertiesChanged", 0);
+ ezxml_set_txt(node, "IsMessengerUser");
+ break;
+
+ case NETID_LCS:
+ case NETID_YAHOO:
+ {
+ ezxml_t contp = ezxml_add_child(conti, "emails", 0);
+ contp = ezxml_add_child(contp, "ContactEmail", 0);
+ node = ezxml_add_child(contp, "contactEmailType", 0);
+ ezxml_set_txt(node, netId == NETID_YAHOO ? "Messenger2" : "Messenger3");
+ node = ezxml_add_child(contp, "isMessengerEnabled", 0);
+ ezxml_set_txt(node, add ? "true" : "false");
+ node = ezxml_add_child(contp, "propertiesChanged", 0);
+ ezxml_set_txt(node, "IsMessengerEnabled");
+ node = ezxml_add_child(cont, "propertiesChanged", 0);
+ ezxml_set_txt(node, "ContactEmail");
+ }
+ break;
+
+ case NETID_MOB:
+ {
+ ezxml_t contp = ezxml_add_child(conti, "phones", 0);
+ contp = ezxml_add_child(contp, "ContactPhone", 0);
+ node = ezxml_add_child(contp, "contactPhoneType", 0);
+ ezxml_set_txt(node, "ContactPhoneMobile");
+ node = ezxml_add_child(contp, "isMessengerEnabled", 0);
+ ezxml_set_txt(node, add ? "true" : "false");
+ node = ezxml_add_child(contp, "propertiesChanged", 0);
+ ezxml_set_txt(node, "IsMessengerEnabled");
+ node = ezxml_add_child(cont, "propertiesChanged", 0);
+ ezxml_set_txt(node, "ContactPhone");
+ }
+ break;
+ }
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("ABContactUpdate", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("ABContactUpdate", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost("ABContactUpdate", abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ if (MSN_ABAddRemoveContact(szCntId, netId, add, false))
+ status = 200;
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+
+bool CMsnProto::MSN_ABUpdateProperty(const char* szCntId, const char* propName, const char* propValue, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("ABContactUpdate", "Timer", tbdy, reqHdr);
+
+ ezxml_t node = ezxml_add_child(tbdy, "contacts", 0);
+ ezxml_t cont = ezxml_add_child(node, "Contact", 0);
+ ezxml_set_attr(cont, "xmlns", "http://www.msn.com/webservices/AddressBook");
+
+ ezxml_t conti = ezxml_add_child(cont, "contactInfo", 0);
+ if (szCntId == NULL)
+ {
+ node = ezxml_add_child(conti, "contactType", 0);
+ ezxml_set_txt(node, "Me");
+ }
+ else
+ {
+ node = ezxml_add_child(cont, "contactId", 0);
+ ezxml_set_txt(node, szCntId);
+ }
+ node = ezxml_add_child(conti, propName, 0);
+ ezxml_set_txt(node, propValue);
+
+ node = ezxml_add_child(cont, "propertiesChanged", 0);
+ char* szPrpChg = mir_strdup(propName);
+ *szPrpChg = _toupper(*szPrpChg);
+ ezxml_set_txt(node, szPrpChg);
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+ mir_free(szPrpChg);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("ABContactUpdate", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("ABContactUpdate", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost("ABContactUpdate", abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ if (MSN_ABUpdateProperty(szCntId, propName, propValue, false))
+ status = 200;
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status == 200;
+}
+
+
+void CMsnProto::MSN_ABUpdateAttr(const char* szCntId, const char* szAttr, const char* szValue, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("ABContactUpdate", "Timer", tbdy, reqHdr);
+
+ ezxml_t node = ezxml_add_child(tbdy, "contacts", 0);
+ ezxml_t cont = ezxml_add_child(node, "Contact", 0);
+ ezxml_set_attr(cont, "xmlns", "http://www.msn.com/webservices/AddressBook");
+ ezxml_t conti = ezxml_add_child(cont, "contactInfo", 0);
+ if (szCntId == NULL)
+ {
+ node = ezxml_add_child(conti, "contactType", 0);
+ ezxml_set_txt(node, "Me");
+ }
+ else
+ {
+ node = ezxml_add_child(cont, "contactId", 0);
+ ezxml_set_txt(node, szCntId);
+ }
+ node = ezxml_add_child(conti, "annotations", 0);
+ ezxml_t anot = ezxml_add_child(node, "Annotation", 0);
+ node = ezxml_add_child(anot, "Name", 0);
+ ezxml_set_txt(node, szAttr);
+ node = ezxml_add_child(anot, "Value", 0);
+ if (szValue != NULL) ezxml_set_txt(node, szValue);
+
+ node = ezxml_add_child(cont, "propertiesChanged", 0);
+ ezxml_set_txt(node, "Annotation");
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("ABContactUpdate", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("ABContactUpdate", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost("ABContactUpdate", abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ MSN_ABUpdateAttr(szCntId, szAttr, szValue, false);
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+}
+
+
+void CMsnProto::MSN_ABUpdateNick(const char* szNick, const char* szCntId)
+{
+ if (szCntId != NULL)
+ MSN_ABUpdateAttr(szCntId, "AB.NickName", szNick);
+ else
+ MSN_ABUpdateProperty(szCntId, "displayName", szNick);
+}
+
+
+unsigned CMsnProto::MSN_ABContactAdd(const char* szEmail, const char* szNick, int netId, const char* szInvite, bool search, bool retry, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("ABContactAdd", "ContactSave", tbdy, reqHdr);
+
+ ezxml_t conts = ezxml_add_child(tbdy, "contacts", 0);
+ ezxml_t node = ezxml_add_child(conts, "Contact", 0);
+ ezxml_set_attr(node, "xmlns", "http://www.msn.com/webservices/AddressBook");
+ ezxml_t conti = ezxml_add_child(node, "contactInfo", 0);
+ ezxml_t contp;
+
+ const char* szEmailNP = strchr(szEmail, ':');
+ if (szEmailNP != NULL) netId = NETID_MOB;
+
+ switch (netId)
+ {
+ case NETID_MSN:
+ node = ezxml_add_child(conti, "contactType", 0);
+ ezxml_set_txt(node, "LivePending");
+ node = ezxml_add_child(conti, "passportName", 0);
+ ezxml_set_txt(node, szEmail);
+ node = ezxml_add_child(conti, "isMessengerUser", 0);
+ ezxml_set_txt(node, "true");
+
+ if (szInvite)
+ {
+ node = ezxml_add_child(conti, "MessengerMemberInfo", 0);
+ node = ezxml_add_child(node, "PendingAnnotations", 0);
+ ezxml_t anot = ezxml_add_child(node, "Annotation", 0);
+ node = ezxml_add_child(anot, "Name", 0);
+ ezxml_set_txt(node, "MSN.IM.InviteMessage");
+ node = ezxml_add_child(anot, "Value", 0);
+ ezxml_set_txt(node, szInvite);
+ }
+ break;
+
+ case NETID_MOB:
+ ++szEmailNP;
+ if (szNick == NULL) szNick = szEmailNP;
+ node = ezxml_add_child(conti, "phones", 0);
+ contp = ezxml_add_child(node, "ContactPhone", 0);
+ node = ezxml_add_child(contp, "contactPhoneType", 0);
+ ezxml_set_txt(node, "ContactPhoneMobile");
+ node = ezxml_add_child(contp, "number", 0);
+ ezxml_set_txt(node, szEmailNP);
+ node = ezxml_add_child(contp, "isMessengerEnabled", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(contp, "propertiesChanged", 0);
+ ezxml_set_txt(node, "Number IsMessengerEnabled");
+ break;
+
+ case NETID_LCS:
+ case NETID_YAHOO:
+ node = ezxml_add_child(conti, "emails", 0);
+ contp = ezxml_add_child(node, "ContactEmail", 0);
+ node = ezxml_add_child(contp, "contactEmailType", 0);
+ ezxml_set_txt(node, netId == NETID_YAHOO ? "Messenger2" : "Messenger3");
+ node = ezxml_add_child(contp, "email", 0);
+ ezxml_set_txt(node, szEmail);
+ node = ezxml_add_child(contp, "isMessengerEnabled", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(contp, "Capability", 0);
+ ezxml_set_txt(node, netId == NETID_YAHOO ? "32" : "2");
+ node = ezxml_add_child(contp, "propertiesChanged", 0);
+ ezxml_set_txt(node, "Email IsMessengerEnabled Capability");
+ break;
+ }
+
+ if (szNick != NULL)
+ {
+ node = ezxml_add_child(conti, "annotations", 0);
+ ezxml_t annt = ezxml_add_child(node, "Annotation", 0);
+ node = ezxml_add_child(annt, "Name", 0);
+ ezxml_set_txt(node, "MSN.IM.Display");
+ node = ezxml_add_child(annt, "Value", 0);
+ ezxml_set_txt(node, szNick);
+ }
+
+ node = ezxml_add_child(conts, "options", 0);
+ node = ezxml_add_child(node, "EnableAllowListManagement", 0);
+ ezxml_set_txt(node, "true");
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *abUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("ABContactAdd", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("ABContactAdd", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 200)
+ {
+ ezxml_t body = getSoapResponse(xmlm, "ABContactAdd");
+
+ const char* szContId = ezxml_txt(ezxml_child(body, "guid"));
+
+ if (search)
+ MSN_ABAddDelContactGroup(szContId , NULL, "ABContactDelete");
+ else
+ {
+ MSN_ABAddRemoveContact(szContId, NETID_MSN, true);
+ MCONTACT hContact = MSN_HContactFromEmail(szEmail, szNick ? szNick : szEmail, true, false);
+ setString(hContact, "ID", szContId);
+ }
+ status = 0;
+ }
+ else if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+
+ if (strcmp(szErr, "InvalidPassportUser") == 0)
+ status = 1;
+ else if (strcmp(szErr, "FederatedQueryFailure") == 0)
+ status = 4;
+ else if (strcmp(szErr, "EmailDomainIsFederated") == 0)
+ status = 2;
+ else if (strcmp(szErr, "BadEmailArgument") == 0)
+ status = 4;
+ else if (strcmp(szErr, "ContactAlreadyExists") == 0)
+ {
+ status = 3;
+
+ ezxml_t node = getSoapFault(xmlm, false);
+ node = ezxml_get(node, "detail", 0, "additionalDetails", 0, "conflictObjectId", -1);
+ const char* szContId = ezxml_txt(node);
+
+ if (search)
+ {
+ if (retry)
+ {
+ MSN_ABAddDelContactGroup(szContId , NULL, "ABContactDelete");
+ status = 0;
+ }
+ }
+ else
+ {
+ MCONTACT hContact = MSN_HContactFromEmail(szEmail, szNick ? szNick : szEmail, true, false);
+ setString(hContact, "ID", szContId);
+ }
+ }
+ else if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_ABContactAdd(szEmail, szNick, netId, NULL, search, retry, false);
+ }
+ else
+ {
+ status = MSN_ABContactAdd(szEmail, szNick, netId, NULL, search, false);
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+
+ return status;
+}
+
+
+void CMsnProto::MSN_ABUpdateDynamicItem(bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = abSoapHdr("UpdateDynamicItem", "RoamingIdentityChanged", tbdy, reqHdr);
+
+ ezxml_t dynitms = ezxml_add_child(tbdy, "dynamicItems", 0);
+ ezxml_t dynitm = ezxml_add_child(dynitms, "DynamicItem", 0);
+
+ ezxml_set_attr(dynitm, "xsi:type", "PassportDynamicItem");
+ ezxml_t node = ezxml_add_child(dynitm, "Type", 0);
+ ezxml_set_txt(node, "Passport");
+ node = ezxml_add_child(dynitm, "PassportName", 0);
+ ezxml_set_txt(node, MyOptions.szEmail);
+
+ ezxml_t nots = ezxml_add_child(dynitm, "Notifications", 0);
+ ezxml_t notd = ezxml_add_child(nots, "NotificationData", 0);
+ ezxml_t strsvc = ezxml_add_child(notd, "StoreService", 0);
+ ezxml_t info = ezxml_add_child(strsvc, "Info", 0);
+
+ ezxml_t hnd = ezxml_add_child(info, "Handle", 0);
+ node = ezxml_add_child(hnd, "Id", 0);
+ ezxml_set_txt(node, "0");
+ node = ezxml_add_child(hnd, "Type", 0);
+ ezxml_set_txt(node, "Profile");
+ node = ezxml_add_child(hnd, "ForeignId", 0);
+ ezxml_set_txt(node, "MyProfile");
+
+ node = ezxml_add_child(info, "InverseRequired", 0);
+ ezxml_set_txt(node, "false");
+ node = ezxml_add_child(info, "IsBot", 0);
+ ezxml_set_txt(node, "false");
+
+ node = ezxml_add_child(strsvc, "Changes", 0);
+ node = ezxml_add_child(strsvc, "LastChange", 0);
+ ezxml_set_txt(node, "0001-01-01T00:00:00");
+ node = ezxml_add_child(strsvc, "Deleted", 0);
+ ezxml_set_txt(node, "false");
+
+ node = ezxml_add_child(notd, "Status", 0);
+ ezxml_set_txt(node, "Exist Access");
+ node = ezxml_add_child(notd, "LastChanged", 0);
+
+ time_t timer;
+ time(&timer);
+ tm *tmst = gmtime(&timer);
+
+ char tmstr[32];
+ mir_snprintf(tmstr, sizeof(tmstr), "%04u-%02u-%02uT%02u:%02u:%02uZ",
+ tmst->tm_year + 1900, tmst->tm_mon+1, tmst->tm_mday,
+ tmst->tm_hour, tmst->tm_min, tmst->tm_sec);
+
+ ezxml_set_txt(node, tmstr);
+ node = ezxml_add_child(notd, "Gleam", 0);
+ ezxml_set_txt(node, "false");
+ node = ezxml_add_child(notd, "InstanceId", 0);
+ ezxml_set_txt(node, "0");
+
+ node = ezxml_add_child(dynitm, "Changes", 0);
+ ezxml_set_txt(node, "Notifications");
+
+ char* szData = ezxml_toxml(xmlp, true);
+ ezxml_free(xmlp);
+
+ unsigned status;
+ char *abUrl = NULL, *tResult;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(abUrl);
+ abUrl = GetABHost("UpdateDynamicItem", false);
+ tResult = getSslResult(&abUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateABHost("UpdateDynamicItem", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateABHost("UpdateDynamicItem", abUrl);
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateABCacheKey(xmlm, false);
+ if (status == 500)
+ {
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ MSN_ABUpdateDynamicItem(false);
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ mir_free(tResult);
+ mir_free(abUrl);
+}
diff --git a/protocols/MSN/src/msn_soapstore.cpp b/protocols/MSN/src/msn_soapstore.cpp
new file mode 100644
index 0000000000..0edbe6b9b0
--- /dev/null
+++ b/protocols/MSN/src/msn_soapstore.cpp
@@ -0,0 +1,781 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2007-2012 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+static const char storeReqHdr[] =
+ "SOAPAction: http://www.msn.com/webservices/storage/2008/%s\r\n";
+
+ezxml_t CMsnProto::storeSoapHdr(const char* service, const char* scenario, ezxml_t& tbdy, char*& httphdr)
+{
+ ezxml_t xmlp = ezxml_new("soap:Envelope");
+ ezxml_set_attr(xmlp, "xmlns:soap", "http://schemas.xmlsoap.org/soap/envelope/");
+ ezxml_set_attr(xmlp, "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
+ ezxml_set_attr(xmlp, "xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
+ ezxml_set_attr(xmlp, "xmlns:soapenc", "http://schemas.xmlsoap.org/soap/encoding/");
+
+ ezxml_t hdr = ezxml_add_child(xmlp, "soap:Header", 0);
+
+ if (storageCacheKey)
+ {
+ ezxml_t cachehdr = ezxml_add_child(hdr, "AffinityCacheHeader", 0);
+ ezxml_set_attr(cachehdr, "xmlns", "http://www.msn.com/webservices/storage/2008");
+ ezxml_t node = ezxml_add_child(cachehdr, "CacheKey", 0);
+ ezxml_set_txt(node, storageCacheKey);
+ }
+
+ ezxml_t apphdr = ezxml_add_child(hdr, "StorageApplicationHeader", 0);
+ ezxml_set_attr(apphdr, "xmlns", "http://www.msn.com/webservices/storage/2008");
+ ezxml_t node = ezxml_add_child(apphdr, "ApplicationID", 0);
+ ezxml_set_txt(node, "Messenger Client 9.0");
+ node = ezxml_add_child(apphdr, "Scenario", 0);
+ ezxml_set_txt(node, scenario);
+
+ ezxml_t authhdr = ezxml_add_child(hdr, "StorageUserHeader", 0);
+ ezxml_set_attr(authhdr, "xmlns", "http://www.msn.com/webservices/storage/2008");
+ node = ezxml_add_child(authhdr, "Puid", 0);
+ ezxml_set_txt(node, mypuid);
+ node = ezxml_add_child(authhdr, "TicketToken", 0);
+ if (authStorageToken) ezxml_set_txt(node, authStorageToken);
+
+ ezxml_t bdy = ezxml_add_child(xmlp, "soap:Body", 0);
+
+ tbdy = ezxml_add_child(bdy, service, 0);
+ ezxml_set_attr(tbdy, "xmlns", "http://www.msn.com/webservices/storage/2008");
+
+ size_t hdrsz = strlen(service) + sizeof(storeReqHdr) + 20;
+ httphdr = (char*)mir_alloc(hdrsz);
+
+ mir_snprintf(httphdr, hdrsz, storeReqHdr, service);
+
+ return xmlp;
+}
+
+char* CMsnProto::GetStoreHost(const char* service)
+{
+ char hostname[128];
+ mir_snprintf(hostname, sizeof(hostname), "StoreHost-%s", service);
+
+ char* host = (char*)mir_alloc(256);
+ if (db_get_static(NULL, m_szModuleName, hostname, host, 256))
+ strcpy(host, "https://tkrdr.storage.msn.com/storageservice/SchematizedStore.asmx");
+
+ return host;
+}
+
+void CMsnProto::UpdateStoreHost(const char* service, const char* url)
+{
+ char hostname[128];
+ mir_snprintf(hostname, sizeof(hostname), "StoreHost-%s", service);
+
+ setString(hostname, url);
+}
+
+void CMsnProto::UpdateStoreCacheKey(ezxml_t bdy)
+{
+ ezxml_t key = ezxml_get(bdy, "soap:Header", 0, "AffinityCacheHeader", 0, "CacheKey", -1);
+ if (key) replaceStr(storageCacheKey, ezxml_txt(key));
+}
+
+bool CMsnProto::MSN_StoreCreateProfile(bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("CreateProfile", "RoamingSeed", tbdy, reqHdr);
+
+ ezxml_t pro = ezxml_add_child(tbdy, "profile", 0);
+ ezxml_t node;
+
+ pro = ezxml_add_child(pro, "ExpressionProfile", 0);
+ ezxml_add_child(pro, "PersonalStatus", 0);
+ node = ezxml_add_child(pro, "RoleDefinitionName", 0);
+ ezxml_set_txt(node, "ExpressionProfileDefault");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *storeUrl, *tResult = NULL;
+
+ storeUrl = mir_strdup("https://storage.msn.com/storageservice/SchematizedStore.asmx");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ if (status == 200)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ UpdateStoreCacheKey(xmlm);
+ ezxml_t body = getSoapResponse(xmlm, "CreateProfile");
+
+ MSN_StoreShareItem(ezxml_txt(body));
+ MSN_SharingMyProfile();
+
+ ezxml_free(xmlm);
+ }
+ else if (status == 500)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreCreateProfile(false) ? 200 : 500;
+ }
+ ezxml_free(xmlm);
+ }
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+bool CMsnProto::MSN_StoreShareItem(const char* id, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("ShareItem", "RoamingSeed", tbdy, reqHdr);
+
+ ezxml_t node = ezxml_add_child(tbdy, "resourceID", 0);
+ ezxml_set_txt(node, id);
+ node = ezxml_add_child(tbdy, "displayName", 0);
+ ezxml_set_txt(node, "Messenger Roaming Identity");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *storeUrl, *tResult = NULL;
+
+ storeUrl = mir_strdup("https://storage.msn.com/storageservice/SchematizedStore.asmx");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL && status == 500)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreCreateProfile(false) ? 200 : 500;
+ }
+ ezxml_free(xmlm);
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+bool CMsnProto::MSN_StoreGetProfile(bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("GetProfile", "Initial", tbdy, reqHdr);
+
+ ezxml_t prohndl = ezxml_add_child(tbdy, "profileHandle", 0);
+
+ ezxml_t alias = ezxml_add_child(prohndl, "Alias", 0);
+ ezxml_t node = ezxml_add_child(alias, "Name", 0);
+ ezxml_set_txt(node, mycid);
+ node = ezxml_add_child(alias, "NameSpace", 0);
+ ezxml_set_txt(node, "MyCidStuff");
+
+ node = ezxml_add_child(prohndl, "RelationshipName", 0);
+ ezxml_set_txt(node, "MyProfile");
+
+ ezxml_t proattr = ezxml_add_child(tbdy, "profileAttributes", 0);
+ node = ezxml_add_child(proattr, "ResourceID", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(proattr, "DateModified", 0);
+ ezxml_set_txt(node, "true");
+
+ ezxml_t exproattr = ezxml_add_child(proattr, "ExpressionProfileAttributes", 0);
+ node = ezxml_add_child(exproattr, "ResourceID", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "DateModified", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "DisplayName", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "DisplayNameLastModified", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "PersonalStatus", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "PersonalStatusLastModified", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "StaticUserTilePublicURL", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "Photo", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(exproattr, "Flags", 0);
+ ezxml_set_txt(node, "true");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *storeUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(storeUrl);
+ storeUrl = GetStoreHost("GetProfile");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateStoreHost("GetProfile", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ if (status == 200)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ ezxml_t body = getSoapResponse(xmlm, "GetProfile");
+
+ UpdateStoreHost("GetProfile", body ? storeUrl : NULL);
+
+ mir_snprintf(proresid, sizeof(proresid), "%s", ezxml_txt(ezxml_child(body, "ResourceID")));
+
+ ezxml_t expr = ezxml_child(body, "ExpressionProfile");
+ if (expr == NULL)
+ {
+ MSN_StoreShareItem(proresid);
+ MSN_SharingMyProfile();
+ if (allowRecurse) MSN_StoreGetProfile(false);
+ }
+ else
+ {
+ const char* szNick = ezxml_txt(ezxml_child(expr, "DisplayName"));
+ setStringUtf(NULL, "Nick", (char*)szNick);
+
+ const char* szStatus = ezxml_txt(ezxml_child(expr, "PersonalStatus"));
+ replaceStr(msnLastStatusMsg, szStatus);
+
+ mir_snprintf(expresid, sizeof(expresid), "%s", ezxml_txt(ezxml_child(expr, "ResourceID")));
+
+ ezxml_t photo = ezxml_child(expr, "Photo");
+ mir_snprintf(photoid, sizeof(photoid), "%s", ezxml_txt(ezxml_child(photo, "ResourceID")));
+
+ ezxml_t docstr = ezxml_get(photo, "DocumentStreams", 0, "DocumentStream", -1);
+ while (docstr)
+ {
+ const char *docname = ezxml_txt(ezxml_child(docstr, "DocumentStreamName"));
+ if (!strcmp(docname, "UserTileStatic"))
+ {
+ getMyAvatarFile(ezxml_txt(ezxml_child(docstr, "PreAuthURL")), _T("miranda_avatar.tmp"));
+ break;
+ }
+ docstr = ezxml_next(docstr);
+ }
+ }
+ ezxml_free(xmlm);
+ }
+ else if (status == 500 && allowRecurse)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0)
+ {
+ MSN_GetPassportAuth();
+ MSN_StoreGetProfile(false);
+ }
+ else
+ {
+ MSN_StoreCreateProfile();
+ if (MSN_StoreGetProfile(false)) status = 200;
+ }
+ ezxml_free(xmlm);
+ }
+ else
+ UpdateStoreHost("GetProfile", NULL);
+
+ }
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+bool CMsnProto::MSN_StoreUpdateProfile(const char* szNick, const char* szStatus, bool lock, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("UpdateProfile", "RoamingIdentityChanged", tbdy, reqHdr);
+
+ ezxml_t pro = ezxml_add_child(tbdy, "profile", 0);
+ ezxml_t node = ezxml_add_child(pro, "ResourceID", 0);
+ ezxml_set_txt(node, proresid);
+
+ ezxml_t expro = ezxml_add_child(pro, "ExpressionProfile", 0);
+ node = ezxml_add_child(expro, "FreeText", 0);
+ ezxml_set_txt(node, "Update");
+ if (szNick)
+ {
+ node = ezxml_add_child(expro, "DisplayName", 0);
+ ezxml_set_txt(node, szNick);
+ }
+ if (szStatus)
+ {
+ node = ezxml_add_child(expro, "PersonalStatus", 0);
+ ezxml_set_txt(node, szStatus);
+ }
+ node = ezxml_add_child(expro, "Flags", 0);
+ ezxml_set_txt(node, lock ? "1" : "0");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *storeUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(storeUrl);
+ storeUrl = GetStoreHost("UpdateProfile");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateStoreHost("UpdateProfile", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateStoreHost("UpdateProfile", storeUrl);
+ if (status == 200)
+ {
+ replaceStr(msnLastStatusMsg, szStatus);
+ MSN_ABUpdateDynamicItem();
+ }
+ else if (status == 500 && allowRecurse)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreUpdateProfile(szNick, szStatus, lock, false) ? 200 : 500;
+ }
+ ezxml_free(xmlm);
+ }
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+
+bool CMsnProto::MSN_StoreCreateRelationships(bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("CreateRelationships", "RoamingIdentityChanged", tbdy, reqHdr);
+
+ ezxml_t rels = ezxml_add_child(tbdy, "relationships", 0);
+ ezxml_t rel = ezxml_add_child(rels, "Relationship", 0);
+ ezxml_t node = ezxml_add_child(rel, "SourceID", 0);
+ ezxml_set_txt(node, expresid);
+ node = ezxml_add_child(rel, "SourceType", 0);
+ ezxml_set_txt(node, "SubProfile");
+ node = ezxml_add_child(rel, "TargetID", 0);
+ ezxml_set_txt(node, photoid);
+ node = ezxml_add_child(rel, "TargetType", 0);
+ ezxml_set_txt(node, "Photo");
+ node = ezxml_add_child(rel, "RelationshipName", 0);
+ ezxml_set_txt(node, "ProfilePhoto");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *storeUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(storeUrl);
+ storeUrl = GetStoreHost("CreateRelationships");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateStoreHost("CreateRelationships", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateStoreHost("CreateRelationships", storeUrl);
+
+ if (status == 500)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreCreateRelationships(false) ? 200 : 500;
+ }
+ ezxml_free(xmlm);
+ }
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+
+bool CMsnProto::MSN_StoreDeleteRelationships(bool tile, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("DeleteRelationships", "RoamingIdentityChanged", tbdy, reqHdr);
+
+ ezxml_t srch = ezxml_add_child(tbdy, "sourceHandle", 0);
+
+ ezxml_t node;
+ if (tile)
+ {
+ node = ezxml_add_child(srch, "RelationshipName", 0);
+ ezxml_set_txt(node, "/UserTiles");
+
+ ezxml_t alias = ezxml_add_child(srch, "Alias", 0);
+ node = ezxml_add_child(alias, "Name", 0);
+ ezxml_set_txt(node, mycid);
+ node = ezxml_add_child(alias, "NameSpace", 0);
+ ezxml_set_txt(node, "MyCidStuff");
+ }
+ else
+ {
+ node = ezxml_add_child(srch, "ResourceID", 0);
+ ezxml_set_txt(node, expresid);
+ }
+
+ node = ezxml_add_child(tbdy, "targetHandles", 0);
+ node = ezxml_add_child(node, "ObjectHandle", 0);
+ node = ezxml_add_child(node, "ResourceID", 0);
+ ezxml_set_txt(node, photoid);
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *storeUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(storeUrl);
+ storeUrl = GetStoreHost("DeleteRelationships");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateStoreHost("DeleteRelationships", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateStoreHost("DeleteRelationships", storeUrl);
+ if (status == 500)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreDeleteRelationships(tile, false) ? 200 : 500;
+ }
+ ezxml_free(xmlm);
+ }
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+
+bool CMsnProto::MSN_StoreCreateDocument(const TCHAR *sztName, const char *szMimeType, const char *szPicData, bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ char* szName = mir_utf8encodeT(sztName);
+ ezxml_t xmlp = storeSoapHdr("CreateDocument", "RoamingIdentityChanged", tbdy, reqHdr);
+
+ ezxml_t hndl = ezxml_add_child(tbdy, "parentHandle", 0);
+ ezxml_t node = ezxml_add_child(hndl, "RelationshipName", 0);
+ ezxml_set_txt(node, "/UserTiles");
+
+ ezxml_t alias = ezxml_add_child(hndl, "Alias", 0);
+ node = ezxml_add_child(alias, "Name", 0);
+ ezxml_set_txt(node, mycid);
+ node = ezxml_add_child(alias, "NameSpace", 0);
+ ezxml_set_txt(node, "MyCidStuff");
+
+ ezxml_t doc = ezxml_add_child(tbdy, "document", 0);
+ ezxml_set_attr(doc, "xsi:type", "Photo");
+ node = ezxml_add_child(doc, "Name", 0);
+ ezxml_set_txt(node, szName);
+
+ doc = ezxml_add_child(doc, "DocumentStreams", 0);
+ doc = ezxml_add_child(doc, "DocumentStream", 0);
+ ezxml_set_attr(doc, "xsi:type", "PhotoStream");
+ node = ezxml_add_child(doc, "DocumentStreamType", 0);
+
+ ezxml_set_txt(node, "UserTileStatic");
+ node = ezxml_add_child(doc, "MimeType", 0);
+ ezxml_set_txt(node, szMimeType);
+ node = ezxml_add_child(doc, "Data", 0);
+ ezxml_set_txt(node, szPicData);
+ node = ezxml_add_child(doc, "DataSize", 0);
+ ezxml_set_txt(node, "0");
+
+ node = ezxml_add_child(tbdy, "relationshipName", 0);
+ ezxml_set_txt(node, "Messenger User Tile");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+ mir_free(szName);
+
+ unsigned status = 0;
+ char *storeUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(storeUrl);
+ storeUrl = GetStoreHost("CreateDocument");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateStoreHost("CreateDocument", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateStoreHost("CreateDocument", storeUrl);
+ if (status == 200)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ ezxml_t bdy = getSoapResponse(xmlm, "CreateDocument");
+ mir_snprintf(photoid, sizeof(photoid), "%s", ezxml_txt(bdy));
+ ezxml_free(xmlm);
+ }
+ else if (status == 500)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreCreateDocument(sztName, szMimeType, szPicData, false) ? 200 : 500;
+ }
+ ezxml_free(xmlm);
+ }
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+
+bool CMsnProto::MSN_StoreUpdateDocument(const TCHAR *sztName, const char *szMimeType, const char *szPicData, bool allowRecurse)
+{
+ char* reqHdr;
+ char* szName = mir_utf8encodeT(sztName);
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("UpdateDocument", "RoamingIdentityChanged", tbdy, reqHdr);
+
+ ezxml_t doc = ezxml_add_child(tbdy, "document", 0);
+ ezxml_set_attr(doc, "xsi:type", "Photo");
+ ezxml_t node = ezxml_add_child(doc, "ResourceID", 0);
+ ezxml_set_txt(node, photoid);
+ node = ezxml_add_child(doc, "Name", 0);
+ ezxml_set_txt(node, szName);
+
+ doc = ezxml_add_child(doc, "DocumentStreams", 0);
+ doc = ezxml_add_child(doc, "DocumentStream", 0);
+ ezxml_set_attr(doc, "xsi:type", "PhotoStream");
+
+ node = ezxml_add_child(doc, "MimeType", 0);
+ ezxml_set_txt(node, szMimeType);
+ node = ezxml_add_child(doc, "Data", 0);
+ ezxml_set_txt(node, szPicData);
+ node = ezxml_add_child(doc, "DataSize", 0);
+ ezxml_set_txt(node, "0");
+ node = ezxml_add_child(doc, "DocumentStreamType", 0);
+ ezxml_set_txt(node, "UserTileStatic");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+ mir_free(szName);
+
+ unsigned status = 0;
+ char *storeUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(storeUrl);
+ storeUrl = GetStoreHost("UpdateDocument");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateStoreHost("UpdateDocument", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateStoreHost("UpdateDocument", storeUrl);
+ if (status == 500 && allowRecurse)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreUpdateDocument(sztName, szMimeType, szPicData, false) ? 200 : 500;
+ }
+ else if (szErr[0])
+ {
+ MSN_StoreDeleteRelationships(true);
+ MSN_StoreDeleteRelationships(false);
+
+ MSN_StoreCreateDocument(sztName, szMimeType, szPicData);
+ MSN_StoreCreateRelationships();
+ }
+ ezxml_free(xmlm);
+ }
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
+
+
+bool CMsnProto::MSN_StoreFindDocuments(bool allowRecurse)
+{
+ char* reqHdr;
+ ezxml_t tbdy;
+ ezxml_t xmlp = storeSoapHdr("FindDocuments", "RoamingIdentityChanged", tbdy, reqHdr);
+
+ ezxml_t srch = ezxml_add_child(tbdy, "objectHandle", 0);
+ ezxml_t node = ezxml_add_child(srch, "RelationshipName", 0);
+ ezxml_set_txt(node, "/UserTiles");
+
+ ezxml_t alias = ezxml_add_child(srch, "Alias", 0);
+ node = ezxml_add_child(alias, "Name", 0);
+ ezxml_set_txt(node, mycid);
+ node = ezxml_add_child(alias, "NameSpace", 0);
+ ezxml_set_txt(node, "MyCidStuff");
+
+ ezxml_t doc = ezxml_add_child(tbdy, "documentAttributes", 0);
+ node = ezxml_add_child(doc, "ResourceID", 0);
+ ezxml_set_txt(node, "true");
+ node = ezxml_add_child(doc, "Name", 0);
+ ezxml_set_txt(node, "true");
+
+ doc = ezxml_add_child(tbdy, "documentFilter", 0);
+ node = ezxml_add_child(doc, "FilterAttributes", 0);
+ ezxml_set_txt(node, "None");
+
+ doc = ezxml_add_child(tbdy, "documentSort", 0);
+ node = ezxml_add_child(doc, "SortBy", 0);
+ ezxml_set_txt(node, "DateModified");
+
+ doc = ezxml_add_child(tbdy, "findContext", 0);
+ node = ezxml_add_child(doc, "FindMethod", 0);
+ ezxml_set_txt(node, "Default");
+ node = ezxml_add_child(doc, "ChunkSize", 0);
+ ezxml_set_txt(node, "25");
+
+ char* szData = ezxml_toxml(xmlp, true);
+
+ ezxml_free(xmlp);
+
+ unsigned status = 0;
+ char *storeUrl = NULL, *tResult = NULL;
+
+ for (int k = 4; --k;)
+ {
+ mir_free(storeUrl);
+ storeUrl = GetStoreHost("FindDocuments");
+ tResult = getSslResult(&storeUrl, szData, reqHdr, status);
+ if (tResult == NULL) UpdateStoreHost("FindDocuments", NULL);
+ else break;
+ }
+
+ mir_free(reqHdr);
+ free(szData);
+
+ if (tResult != NULL)
+ {
+ UpdateStoreHost("FindDocuments", storeUrl);
+ if (status == 500)
+ {
+ ezxml_t xmlm = ezxml_parse_str(tResult, strlen(tResult));
+ const char* szErr = ezxml_txt(getSoapFault(xmlm, true));
+ if (strcmp(szErr, "PassportAuthFail") == 0 && allowRecurse)
+ {
+ MSN_GetPassportAuth();
+ status = MSN_StoreFindDocuments(false) ? 200 : 500;
+ }
+ ezxml_free(xmlm);
+ }
+ }
+
+ mir_free(tResult);
+ mir_free(storeUrl);
+
+ return status == 200;
+}
diff --git a/protocols/MSN/src/msn_srv.cpp b/protocols/MSN/src/msn_srv.cpp
new file mode 100644
index 0000000000..57eee9f381
--- /dev/null
+++ b/protocols/MSN/src/msn_srv.cpp
@@ -0,0 +1,378 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_AddGroup - adds new server group to the list
+
+void CMsnProto::MSN_AddGroup(const char* grpName, const char *grpId, bool init)
+{
+ ServerGroupItem* p = grpList.find((ServerGroupItem*)&grpId);
+ if (p != NULL) return;
+
+ p = (ServerGroupItem*)mir_alloc(sizeof(ServerGroupItem));
+ p->id = mir_strdup(grpId);
+ p->name = mir_strdup(grpName);
+
+ grpList.insert(p);
+
+ if (init)
+ Clist_CreateGroup(0, ptrT( mir_utf8decodeT(grpName)));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_DeleteGroup - deletes a group from the list
+
+void CMsnProto::MSN_DeleteGroup(const char* pId)
+{
+ int i = grpList.getIndex((ServerGroupItem*)&pId);
+ if (i > -1)
+ {
+ ServerGroupItem* p = grpList[i];
+ mir_free(p->id);
+ mir_free(p->name);
+ mir_free(p);
+ grpList.remove(i);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_DeleteServerGroup - deletes group from the server
+
+void CMsnProto::MSN_DeleteServerGroup(LPCSTR szId)
+{
+ if (!MyOptions.ManageServer) return;
+
+ MSN_ABAddDelContactGroup(NULL, szId, "ABGroupDelete");
+
+ int count = -1;
+ for (;;)
+ {
+ MsnContact *msc = Lists_GetNext(count);
+ if (msc == NULL) break;
+
+ char szGroupID[100];
+ if (!db_get_static(msc->hContact, m_szModuleName, "GroupID", szGroupID, sizeof(szGroupID)))
+ {
+ if (strcmp(szGroupID, szId) == 0)
+ delSetting(msc->hContact, "GroupID");
+ }
+ }
+ MSN_DeleteGroup(szId);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_FreeGroups - clears the server groups list
+
+void CMsnProto::MSN_FreeGroups(void)
+{
+ for (int i=0; i < grpList.getCount(); i++) {
+ ServerGroupItem* p = grpList[i];
+ mir_free(p->id);
+ mir_free(p->name);
+ mir_free(p);
+ }
+ grpList.destroy();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_GetGroupById - tries to return a group name associated with given UUID
+
+LPCSTR CMsnProto::MSN_GetGroupById(const char* pId)
+{
+ ServerGroupItem* p = grpList.find((ServerGroupItem*)&pId);
+ return p ? p->name : NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_GetGroupByName - tries to return a group UUID associated with the given name
+
+LPCSTR CMsnProto::MSN_GetGroupByName(const char* pName)
+{
+ for (int i=0; i < grpList.getCount(); i++)
+ {
+ const ServerGroupItem* p = grpList[i];
+ if (strcmp(p->name, pName) == 0)
+ return p->id;
+ }
+
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_SetGroupName - sets a new name to a server group
+
+void CMsnProto::MSN_SetGroupName(const char* pId, const char* pNewName)
+{
+ ServerGroupItem* p = grpList.find((ServerGroupItem*)&pId);
+ if (p != NULL)
+ replaceStr(p->name, pNewName);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_MoveContactToGroup - sends a contact to the specified group
+
+void CMsnProto::MSN_MoveContactToGroup(MCONTACT hContact, const char* grpName)
+{
+ if (!MyOptions.ManageServer) return;
+
+ LPCSTR szId = NULL;
+ char szContactID[100], szGroupID[100];
+ if (db_get_static(hContact, m_szModuleName, "ID", szContactID, sizeof(szContactID)))
+ return;
+
+ if (db_get_static(hContact, m_szModuleName, "GroupID", szGroupID, sizeof(szGroupID)))
+ szGroupID[0] = 0;
+
+ bool bInsert = false, bDelete = szGroupID[0] != 0;
+
+ if (grpName != NULL) {
+ szId = MSN_GetGroupByName(grpName);
+ if (szId == NULL) {
+ MSN_ABAddGroup(grpName);
+ szId = MSN_GetGroupByName(grpName);
+ }
+ if (szId == NULL) return;
+ if (_stricmp(szGroupID, szId) == 0) bDelete = false;
+ else bInsert = true;
+ }
+
+ if (bDelete) {
+ MSN_ABAddDelContactGroup(szContactID, szGroupID, "ABGroupContactDelete");
+ delSetting(hContact, "GroupID");
+ }
+
+ if (bInsert) {
+ MSN_ABAddDelContactGroup(szContactID, szId, "ABGroupContactAdd");
+ setString(hContact, "GroupID", szId);
+ }
+
+ if (bDelete)
+ MSN_RemoveEmptyGroups();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_RemoveEmptyGroups - removes empty groups from the server list
+
+void CMsnProto::MSN_RemoveEmptyGroups(void)
+{
+ if (!MyOptions.ManageServer) return;
+
+ unsigned *cCount = (unsigned*)mir_calloc(grpList.getCount() * sizeof(unsigned));
+
+ int count = -1;
+ for (;;)
+ {
+ MsnContact *msc = Lists_GetNext(count);
+ if (msc == NULL) break;
+
+ char szGroupID[100];
+ if (!db_get_static(msc->hContact, m_szModuleName, "GroupID", szGroupID, sizeof(szGroupID)))
+ {
+ const char *pId = szGroupID;
+ int i = grpList.getIndex((ServerGroupItem*)&pId);
+ if (i > -1) ++cCount[i];
+ }
+ }
+
+ for (int i=grpList.getCount(); i--;)
+ {
+ if (cCount[i] == 0) MSN_DeleteServerGroup(grpList[i]->id);
+ }
+ mir_free(cCount);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_RenameServerGroup - renames group on the server
+
+void CMsnProto::MSN_RenameServerGroup(LPCSTR szId, const char* newName)
+{
+ MSN_SetGroupName(szId, newName);
+ MSN_ABRenameGroup(newName, szId);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_UploadServerGroups - adds a group to the server list and contacts into the group
+
+void CMsnProto::MSN_UploadServerGroups(char* group)
+{
+ if (!MyOptions.ManageServer) return;
+
+ int count = -1;
+ for (;;)
+ {
+ MsnContact *msc = Lists_GetNext(count);
+ if (msc == NULL) break;
+
+ DBVARIANT dbv;
+ if (!db_get_utf(msc->hContact, "CList", "Group", &dbv))
+ {
+ char szGroupID[100];
+ if (group == NULL || (strcmp(group, dbv.pszVal) == 0 &&
+ db_get_static(msc->hContact, m_szModuleName, "GroupID", szGroupID, sizeof(szGroupID)) != 0))
+ {
+ MSN_MoveContactToGroup(msc->hContact, dbv.pszVal);
+ }
+ db_free(&dbv);
+ }
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN_SyncContactToServerGroup - moves contact into appropriate group according to server
+// if contact in multiple server groups it get removed from all of them other them it's
+// in or the last one
+
+void CMsnProto::MSN_SyncContactToServerGroup(MCONTACT hContact, const char* szContId, ezxml_t cgrp)
+{
+ if (!MyOptions.ManageServer) return;
+
+ const char* szGrpName = "";
+
+ DBVARIANT dbv;
+ if (!db_get_utf(hContact, "CList", "Group", &dbv)) {
+ szGrpName = NEWSTR_ALLOCA(dbv.pszVal);
+ db_free(&dbv);
+ }
+
+ const char* szGrpIdF = NULL;
+ while(cgrp != NULL)
+ {
+ const char* szGrpId = ezxml_txt(cgrp);
+ cgrp = ezxml_next(cgrp);
+
+ const char* szGrpNameById = MSN_GetGroupById(szGrpId);
+
+ if (szGrpNameById && (strcmp(szGrpNameById, szGrpName) == 0 ||
+ (cgrp == NULL && szGrpIdF == NULL)))
+ szGrpIdF = szGrpId;
+ else
+ MSN_ABAddDelContactGroup(szContId, szGrpId, "ABGroupContactDelete");
+ }
+
+ if (szGrpIdF != NULL)
+ {
+ setString(hContact, "GroupID", szGrpIdF);
+ const char* szGrpNameById = MSN_GetGroupById(szGrpIdF);
+ if (strcmp(szGrpNameById, szGrpName))
+ db_set_utf(hContact, "CList", "Group", szGrpNameById);
+ }
+ else
+ {
+ if (szGrpName[0])
+ db_unset(hContact, "CList", "Group");
+ delSetting(hContact, "GroupID");
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Msn_SendNickname - update our own nickname on the server
+
+void CMsnProto::MSN_SendNicknameUtf(const char* nickname)
+{
+ if (nickname[0])
+ setStringUtf(NULL, "Nick", nickname);
+ else
+ delSetting("Nick");
+
+ MSN_SetNicknameUtf(nickname[0] ? nickname : MyOptions.szEmail);
+
+ ForkThread(&CMsnProto::msn_storeProfileThread, (void*)1);
+}
+
+void CMsnProto::MSN_SetNicknameUtf(const char* nickname)
+{
+ msnNsThread->sendPacket("PRP", "MFN %s", ptrA(mir_urlEncode(nickname)));
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// msn_storeAvatarThread - update our own avatar on the server
+
+void CMsnProto::msn_storeAvatarThread(void* arg)
+{
+ StoreAvatarData* dat = (StoreAvatarData*)arg;
+ ptrA szEncBuf;
+
+ if (dat)
+ szEncBuf = mir_base64_encode(dat->data, (unsigned)dat->dataSize);
+
+ if (photoid[0] && dat)
+ MSN_StoreUpdateDocument(dat->szName, dat->szMimeType, szEncBuf);
+ else {
+ MSN_StoreUpdateProfile(NULL, NULL, 1);
+
+ if (photoid[0]) {
+ MSN_StoreDeleteRelationships(true);
+ MSN_StoreDeleteRelationships(false);
+ photoid[0] = 0;
+ }
+
+ if (dat) {
+ MSN_StoreCreateDocument(dat->szName, dat->szMimeType, szEncBuf);
+ MSN_StoreCreateRelationships();
+ }
+
+ MSN_StoreUpdateProfile(NULL, NULL, 0);
+ }
+
+ MSN_ABUpdateDynamicItem();
+
+ if (dat)
+ {
+ mir_free(dat->szName);
+ mir_free(dat->data);
+ mir_free(dat);
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// msn_storeProfileThread - update our own avatar on the server
+
+void CMsnProto::msn_storeProfileThread(void* param)
+{
+ DBVARIANT dbv;
+ char *szNick = NULL;
+ bool needFree = false;
+ if (!getStringUtf("Nick", &dbv))
+ {
+ szNick = dbv.pszVal[0] ? dbv.pszVal : NULL;
+ needFree = true;
+ }
+
+ char** msgptr = GetStatusMsgLoc(m_iStatus);
+ char *szStatus = msgptr ? *msgptr : NULL;
+
+ if (param || (msnLastStatusMsg != szStatus &&
+ (msnLastStatusMsg && szStatus && strcmp(msnLastStatusMsg, szStatus))))
+ {
+
+ if (MSN_StoreUpdateProfile(szNick, szStatus, false))
+ MSN_ABUpdateDynamicItem();
+ // MSN_ABUpdateNick(nickname, NULL);
+ }
+
+ if (needFree) db_free(&dbv);
+}
diff --git a/protocols/MSN/src/msn_ssl.cpp b/protocols/MSN/src/msn_ssl.cpp
new file mode 100644
index 0000000000..42b324655c
--- /dev/null
+++ b/protocols/MSN/src/msn_ssl.cpp
@@ -0,0 +1,140 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+char* CMsnProto::getSslResult(char** parUrl, const char* parAuthInfo, const char* hdrs, unsigned& status)
+{
+ mHttpsTS = clock();
+
+ char* result = NULL;
+ NETLIBHTTPREQUEST nlhr = {0};
+
+ // initialize the netlib request
+ nlhr.cbSize = sizeof(nlhr);
+ nlhr.requestType = REQUEST_POST;
+ nlhr.flags = NLHRF_HTTP11 | NLHRF_DUMPASTEXT | NLHRF_PERSISTENT | NLHRF_REDIRECT;
+ nlhr.szUrl = *parUrl;
+ nlhr.dataLength = (int)strlen(parAuthInfo);
+ nlhr.pData = (char*)parAuthInfo;
+ nlhr.nlc = hHttpsConnection;
+
+#ifndef _DEBUG
+ if (strstr(*parUrl, "login")) nlhr.flags |= NLHRF_NODUMPSEND;
+#endif
+
+ nlhr.headersCount = 4;
+ nlhr.headers=(NETLIBHTTPHEADER*)alloca(sizeof(NETLIBHTTPHEADER) * (nlhr.headersCount + 5));
+ nlhr.headers[0].szName = "User-Agent";
+ nlhr.headers[0].szValue = (char*)MSN_USER_AGENT;
+ nlhr.headers[1].szName = "Accept";
+ nlhr.headers[1].szValue = "text/*";
+ nlhr.headers[2].szName = "Content-Type";
+ nlhr.headers[2].szValue = "text/xml; charset=utf-8";
+ nlhr.headers[3].szName = "Cache-Control";
+ nlhr.headers[3].szValue = "no-cache";
+
+ if (hdrs)
+ {
+ unsigned count = 0;
+ char* hdrprs = NEWSTR_ALLOCA(hdrs);
+ for (;;)
+ {
+ char* fnd = strchr(hdrprs, ':');
+ if (fnd == NULL) break;
+ *fnd = 0;
+ fnd += 2;
+
+ nlhr.headers[nlhr.headersCount].szName = hdrprs;
+ nlhr.headers[nlhr.headersCount].szValue = fnd;
+
+ fnd = strchr(fnd, '\r');
+ *fnd = 0;
+ hdrprs = fnd + 2;
+ ++nlhr.headersCount;
+ if (++count >= 5) break;
+ }
+ }
+
+ // download the page
+ NETLIBHTTPREQUEST *nlhrReply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION,
+ (WPARAM)hNetlibUserHttps,(LPARAM)&nlhr);
+
+ if (nlhrReply)
+ {
+ hHttpsConnection = nlhrReply->nlc;
+ status = nlhrReply->resultCode;
+
+ if (nlhrReply->szUrl)
+ {
+ mir_free(*parUrl);
+ *parUrl = nlhrReply->szUrl;
+ nlhrReply->szUrl = NULL;
+ }
+
+ result = nlhrReply->pData;
+
+ nlhrReply->dataLength = 0;
+ nlhrReply->pData = NULL;
+
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply);
+ }
+ else
+ hHttpsConnection = NULL;
+
+ mHttpsTS = clock();
+
+ return result;
+}
+
+bool CMsnProto::getMyAvatarFile(char *url, TCHAR *fname)
+{
+ NETLIBHTTPREQUEST nlhr = {0};
+ bool result = true;
+
+ // initialize the netlib request
+ nlhr.cbSize = sizeof(nlhr);
+ nlhr.requestType = REQUEST_GET;
+ nlhr.flags = NLHRF_HTTP11 | NLHRF_REDIRECT;
+ nlhr.szUrl = url;
+
+ nlhr.headersCount = 1;
+ nlhr.headers=(NETLIBHTTPHEADER*)alloca(sizeof(NETLIBHTTPHEADER) * nlhr.headersCount);
+ nlhr.headers[0].szName = "User-Agent";
+ nlhr.headers[0].szValue = (char*)MSN_USER_AGENT;
+
+ // download the page
+ NETLIBHTTPREQUEST *nlhrReply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION,
+ (WPARAM)hNetlibUserHttps,(LPARAM)&nlhr);
+
+ if (nlhrReply)
+ {
+ if (nlhrReply->resultCode == 200 && nlhrReply->dataLength)
+ MSN_SetMyAvatar(fname, nlhrReply->pData, nlhrReply->dataLength);
+ else
+ result = false;
+
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)nlhrReply);
+ }
+ return result;
+}
diff --git a/protocols/MSN/src/msn_std.cpp b/protocols/MSN/src/msn_std.cpp
new file mode 100644
index 0000000000..b6e2163e4d
--- /dev/null
+++ b/protocols/MSN/src/msn_std.cpp
@@ -0,0 +1,65 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Standard functions
+
+int CMsnProto::getStringUtf(MCONTACT hContact, const char* name, DBVARIANT* result)
+{ return db_get_utf(hContact, m_szModuleName, name, result);
+}
+
+int CMsnProto::getStringUtf(const char* name, DBVARIANT* result)
+{ return db_get_utf(NULL, m_szModuleName, name, result);
+}
+
+void CMsnProto::setStringUtf(MCONTACT hContact, const char* name, const char* value)
+{ db_set_utf(hContact, m_szModuleName, name, value);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+TCHAR* CMsnProto::GetContactNameT(MCONTACT hContact)
+{
+ if (hContact)
+ return (TCHAR*)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, WPARAM(hContact), GCDNF_TCHAR);
+ else
+ {
+ CONTACTINFO ci = {0};
+ ci.cbSize = sizeof(ci);
+ ci.dwFlag = CNF_DISPLAY | CNF_TCHAR;
+ ci.szProto = m_szModuleName;
+ if (!CallService(MS_CONTACT_GETCONTACTINFO, 0, (LPARAM)&ci))
+ return (TCHAR*)ci.pszVal;
+ else
+ return _T("Me");
+ }
+}
+
+unsigned MSN_GenRandom(void)
+{
+ unsigned rndnum;
+ CallService(MS_UTILS_GETRANDOM, sizeof(rndnum), (LPARAM)&rndnum);
+ return rndnum & 0x7FFFFFFF;
+}
diff --git a/protocols/MSN/src/msn_svcs.cpp b/protocols/MSN/src/msn_svcs.cpp
new file mode 100644
index 0000000000..9c6c61b915
--- /dev/null
+++ b/protocols/MSN/src/msn_svcs.cpp
@@ -0,0 +1,624 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+extern int avsPresent;
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// GetMyAwayMsg - obtain the current away message
+
+INT_PTR CMsnProto::GetMyAwayMsg(WPARAM wParam,LPARAM lParam)
+{
+ char** msgptr = GetStatusMsgLoc(wParam ? wParam : m_iStatus);
+ if (msgptr == NULL) return 0;
+
+ return (lParam & SGMA_UNICODE) ? (INT_PTR)mir_utf8decodeW(*msgptr) : (INT_PTR)mir_utf8decodeA(*msgptr);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGetAvatar - retrieves the file name of my own avatar
+
+INT_PTR CMsnProto::GetAvatar(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR* buf = (TCHAR*)wParam;
+ int size = (int)lParam;
+
+ if (buf == NULL || size <= 0)
+ return -1;
+
+ MSN_GetAvatarFileName(NULL, buf, size, NULL);
+ return _taccess(buf, 0);
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGetAvatarInfo - retrieve the avatar info
+
+void CMsnProto::sttFakeAvatarAck(void* arg)
+{
+ Sleep(100);
+ ProtoBroadcastAck(((PROTO_AVATAR_INFORMATIONT*)arg)->hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, arg, 0);
+}
+
+INT_PTR CMsnProto::GetAvatarInfo(WPARAM wParam,LPARAM lParam)
+{
+ PROTO_AVATAR_INFORMATIONT* AI = (PROTO_AVATAR_INFORMATIONT*)lParam;
+ TCHAR filename[MAX_PATH];
+ MsnContact *cont = NULL;
+
+ if (AI->hContact) {
+ cont = Lists_Get(AI->hContact);
+ if (cont == NULL) return GAIR_NOAVATAR;
+
+ if ((cont->cap1 & 0xf0000000) == 0)
+ return GAIR_NOAVATAR;
+ }
+
+ if (AI->hContact == NULL || _stricmp(cont->email, MyOptions.szEmail) == 0) {
+ MSN_GetAvatarFileName(NULL, filename, SIZEOF(filename), NULL);
+ AI->format = ProtoGetAvatarFormat(filename);
+ if (AI->format != PA_FORMAT_UNKNOWN)
+ _tcscpy(AI->filename, filename);
+ return AI->format == PA_FORMAT_UNKNOWN ? GAIR_NOAVATAR : GAIR_SUCCESS;
+ }
+
+ char *szContext;
+ DBVARIANT dbv;
+ if ( getString(AI->hContact, AI->hContact ? "PictContext" : "PictObject", &dbv) == 0) {
+ szContext = (char*)NEWSTR_ALLOCA(dbv.pszVal);
+ db_free(&dbv);
+ }
+ else return GAIR_NOAVATAR;
+
+ MSN_GetAvatarFileName(AI->hContact, filename, SIZEOF(filename), NULL);
+ AI->format = ProtoGetAvatarFormat(filename);
+
+ if (AI->format != PA_FORMAT_UNKNOWN) {
+ bool needupdate = true;
+ if (getString(AI->hContact, "PictSavedContext", &dbv) == 0) {
+ needupdate = strcmp(dbv.pszVal, szContext) != 0;
+ db_free(&dbv);
+ }
+
+ if (needupdate) {
+ setString(AI->hContact, "PictSavedContext", szContext);
+
+ // Store also avatar hash
+ char* szAvatarHash = MSN_GetAvatarHash(szContext);
+ if (szAvatarHash != NULL) {
+ setString(AI->hContact, "AvatarSavedHash", szAvatarHash);
+ mir_free(szAvatarHash);
+ }
+ }
+ _tcscpy(AI->filename, filename);
+ return GAIR_SUCCESS;
+ }
+
+ if ((wParam & GAIF_FORCE) != 0 && AI->hContact != NULL) {
+ if (avsPresent < 0) avsPresent = ServiceExists(MS_AV_SETMYAVATAR) != 0;
+ if (!avsPresent)
+ return GAIR_NOAVATAR;
+
+ WORD wStatus = getWord(AI->hContact, "Status", ID_STATUS_OFFLINE);
+ if (wStatus == ID_STATUS_OFFLINE) {
+ delSetting(AI->hContact, "AvatarHash");
+ PROTO_AVATAR_INFORMATIONT* fakeAI = new PROTO_AVATAR_INFORMATIONT;
+ *fakeAI = *AI;
+ ForkThread(&CMsnProto::sttFakeAvatarAck, fakeAI);
+ }
+ else if ( !getString(AI->hContact, "AvatarUrl", &dbv)) {
+ pushAvatarRequest(AI->hContact, dbv.pszVal);
+ db_free(&dbv);
+ }
+ else if (p2p_getAvatarSession(AI->hContact) == NULL) {
+ filetransfer* ft = new filetransfer(this);
+ ft->std.hContact = AI->hContact;
+ ft->p2p_object = mir_strdup(szContext);
+
+ MSN_GetAvatarFileName(AI->hContact, filename, SIZEOF(filename), _T("unk"));
+ ft->std.tszCurrentFile = mir_tstrdup(filename);
+
+ p2p_invite(MSN_APPID_AVATAR, ft, NULL);
+ }
+
+ return GAIR_WAITFOR;
+ }
+ return GAIR_NOAVATAR;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGetAvatarCaps - retrieves avatar capabilities
+
+INT_PTR CMsnProto::GetAvatarCaps(WPARAM wParam, LPARAM lParam)
+{
+ int res = 0;
+
+ switch (wParam)
+ {
+ case AF_MAXSIZE:
+ ((POINT*)lParam)->x = 96;
+ ((POINT*)lParam)->y = 96;
+ break;
+
+ case AF_PROPORTION:
+ res = PIP_NONE;
+ break;
+
+ case AF_FORMATSUPPORTED:
+ res = lParam == PA_FORMAT_PNG || lParam == PA_FORMAT_GIF || lParam == PA_FORMAT_JPEG;
+ break;
+
+ case AF_ENABLED:
+ res = 1;
+ break;
+ }
+
+ return res;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSetAvatar - sets an avatar without UI
+
+INT_PTR CMsnProto::SetAvatar(WPARAM wParam, LPARAM lParam)
+{
+ TCHAR* szFileName = (TCHAR*)lParam;
+
+ TCHAR tFileName[MAX_PATH];
+ MSN_GetAvatarFileName(NULL, tFileName, SIZEOF(tFileName), NULL);
+ _tremove(tFileName);
+
+ if (szFileName == NULL)
+ {
+ delSetting("PictObject");
+ delSetting("AvatarHash");
+ ForkThread(&CMsnProto::msn_storeAvatarThread, NULL);
+ }
+ else
+ {
+ int fileId = _topen(szFileName, _O_RDONLY | _O_BINARY, _S_IREAD);
+ if (fileId < 0) return 1;
+
+ size_t dwPngSize = _filelengthi64(fileId);
+ unsigned char* pData = (unsigned char*)mir_alloc(dwPngSize);
+ if (pData == NULL) return 2;
+
+ _read(fileId, pData, (unsigned)dwPngSize);
+ _close(fileId);
+
+ TCHAR drive[_MAX_DRIVE];
+ TCHAR dir[_MAX_DIR];
+ TCHAR fname[_MAX_FNAME];
+ TCHAR ext[_MAX_EXT];
+ _tsplitpath(szFileName, drive, dir, fname, ext);
+
+ int fmt = MSN_SetMyAvatar(fname, pData, dwPngSize);
+
+ StoreAvatarData* par = (StoreAvatarData*)mir_alloc(sizeof(StoreAvatarData));
+ par->szName = mir_tstrdup(fname);
+ par->data = pData;
+ par->dataSize = dwPngSize;
+ par->szMimeType = "image/png";
+
+ ForkThread(&CMsnProto::msn_storeAvatarThread, par);
+ }
+
+ MSN_SetServerStatus(m_iStatus);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// SetNickname - sets a nick name without UI
+
+INT_PTR CMsnProto::SetNickName(WPARAM wParam, LPARAM lParam)
+{
+ if (wParam & SMNN_UNICODE)
+ MSN_SendNickname((wchar_t*)lParam);
+ else
+ MSN_SendNickname((char*)lParam);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnSendNudge - Sending a nudge
+
+INT_PTR CMsnProto::SendNudge(WPARAM hContact, LPARAM lParam)
+{
+ if (!msnLoggedIn) return 0;
+
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ if (MSN_IsMeByContact(hContact, tEmail)) return 0;
+
+ static const char nudgemsg[] =
+ "Content-Type: text/x-msnmsgr-datacast\r\n\r\n"
+ "ID: 1\r\n\r\n";
+
+ int netId = Lists_GetNetId(tEmail);
+
+ switch (netId) {
+ case NETID_UNKNOWN:
+ hContact = MSN_GetChatInernalHandle(hContact);
+
+ case NETID_MSN:
+ case NETID_LCS:
+ {
+ bool isOffline;
+ ThreadData* thread = MSN_StartSB(tEmail, isOffline);
+ if (thread == NULL)
+ {
+ if (isOffline) return 0;
+ MsgQueue_Add(tEmail, 'N', nudgemsg, -1);
+ }
+ else
+ {
+ int tNnetId = netId == NETID_UNKNOWN ? NETID_MSN : netId;
+ thread->sendMessage('N', tEmail, tNnetId, nudgemsg, MSG_DISABLE_HDR);
+ }
+ }
+ break;
+
+ case NETID_YAHOO:
+ msnNsThread->sendMessage('3', tEmail, netId, nudgemsg, MSG_DISABLE_HDR);
+ break;
+
+ default:
+ break;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// GetCurrentMedia - get current media
+
+INT_PTR CMsnProto::GetCurrentMedia(WPARAM wParam, LPARAM lParam)
+{
+ LISTENINGTOINFO *cm = (LISTENINGTOINFO *)lParam;
+
+ if (cm == NULL || cm->cbSize != sizeof(LISTENINGTOINFO))
+ return -1;
+
+ cm->ptszArtist = mir_tstrdup(msnCurrentMedia.ptszArtist);
+ cm->ptszAlbum = mir_tstrdup(msnCurrentMedia.ptszAlbum);
+ cm->ptszTitle = mir_tstrdup(msnCurrentMedia.ptszTitle);
+ cm->ptszTrack = mir_tstrdup(msnCurrentMedia.ptszTrack);
+ cm->ptszYear = mir_tstrdup(msnCurrentMedia.ptszYear);
+ cm->ptszGenre = mir_tstrdup(msnCurrentMedia.ptszGenre);
+ cm->ptszLength = mir_tstrdup(msnCurrentMedia.ptszLength);
+ cm->ptszPlayer = mir_tstrdup(msnCurrentMedia.ptszPlayer);
+ cm->ptszType = mir_tstrdup(msnCurrentMedia.ptszType);
+ cm->dwFlags = msnCurrentMedia.dwFlags;
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// SetCurrentMedia - set current media
+
+INT_PTR CMsnProto::SetCurrentMedia(WPARAM wParam, LPARAM lParam)
+{
+ // Clear old info
+ mir_free(msnCurrentMedia.ptszArtist);
+ mir_free(msnCurrentMedia.ptszAlbum);
+ mir_free(msnCurrentMedia.ptszTitle);
+ mir_free(msnCurrentMedia.ptszTrack);
+ mir_free(msnCurrentMedia.ptszYear);
+ mir_free(msnCurrentMedia.ptszGenre);
+ mir_free(msnCurrentMedia.ptszLength);
+ mir_free(msnCurrentMedia.ptszPlayer);
+ mir_free(msnCurrentMedia.ptszType);
+ memset(&msnCurrentMedia, 0, sizeof(msnCurrentMedia));
+
+ // Copy new info
+ LISTENINGTOINFO *cm = (LISTENINGTOINFO *)lParam;
+ if (cm != NULL && cm->cbSize == sizeof(LISTENINGTOINFO) && (cm->ptszArtist != NULL || cm->ptszTitle != NULL))
+ {
+ bool unicode = (cm->dwFlags & LTI_UNICODE) != 0;
+
+ msnCurrentMedia.cbSize = sizeof(msnCurrentMedia); // Marks that there is info set
+ msnCurrentMedia.dwFlags = LTI_TCHAR;
+
+ overrideStr(msnCurrentMedia.ptszType, cm->ptszType, unicode, _T("Music"));
+ overrideStr(msnCurrentMedia.ptszArtist, cm->ptszArtist, unicode);
+ overrideStr(msnCurrentMedia.ptszAlbum, cm->ptszAlbum, unicode);
+ overrideStr(msnCurrentMedia.ptszTitle, cm->ptszTitle, unicode, _T("No Title"));
+ overrideStr(msnCurrentMedia.ptszTrack, cm->ptszTrack, unicode);
+ overrideStr(msnCurrentMedia.ptszYear, cm->ptszYear, unicode);
+ overrideStr(msnCurrentMedia.ptszGenre, cm->ptszGenre, unicode);
+ overrideStr(msnCurrentMedia.ptszLength, cm->ptszLength, unicode);
+ overrideStr(msnCurrentMedia.ptszPlayer, cm->ptszPlayer, unicode);
+ }
+
+ // Set user text
+ if (msnCurrentMedia.cbSize == 0)
+ delSetting("ListeningTo");
+ else
+ {
+ TCHAR *text;
+ if (ServiceExists(MS_LISTENINGTO_GETPARSEDTEXT))
+ text = (TCHAR *) CallService(MS_LISTENINGTO_GETPARSEDTEXT, (WPARAM) _T("%title% - %artist%"), (LPARAM) &msnCurrentMedia);
+ else
+ {
+ text = (TCHAR *) mir_alloc(128 * sizeof(TCHAR));
+ mir_sntprintf(text, 128, _T("%s - %s"), (msnCurrentMedia.ptszTitle ? msnCurrentMedia.ptszTitle : _T("")),
+ (msnCurrentMedia.ptszArtist ? msnCurrentMedia.ptszArtist : _T("")));
+ }
+ setTString("ListeningTo", text);
+ mir_free(text);
+ }
+
+ // Send it
+ char** msgptr = GetStatusMsgLoc(m_iDesiredStatus);
+ MSN_SendStatusMessage(msgptr ? *msgptr : NULL);
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnContactDeleted - called when a contact is deleted from list
+
+int CMsnProto::OnContactDeleted(WPARAM hContact, LPARAM lParam)
+{
+ if (!msnLoggedIn) //should never happen for MSN contacts
+ return 0;
+
+ if ( isChatRoom(hContact)) {
+ DBVARIANT dbv;
+ if (!getTString(hContact, "ChatRoomID", &dbv)) {
+ MSN_KillChatSession(dbv.ptszVal);
+ db_free(&dbv);
+ }
+ }
+ else {
+ char szEmail[MSN_MAX_EMAIL_LEN];
+ if (MSN_IsMeByContact(hContact, szEmail))
+ CallService(MS_CLIST_REMOVEEVENT, hContact, (LPARAM) 1);
+
+ if (szEmail[0])
+ {
+ debugLogA("Deleted Handler Email");
+
+ if (Lists_IsInList(LIST_FL, szEmail))
+ {
+ DeleteParam param = { this, hContact };
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_DELETECONTACT), NULL, DlgDeleteContactUI, (LPARAM)&param);
+
+ MsnContact* msc = Lists_Get(szEmail);
+ if (msc) msc->hContact = NULL;
+ }
+ if (Lists_IsInList(LIST_LL, szEmail))
+ {
+ MSN_AddUser(hContact, szEmail, 0, LIST_LL | LIST_REMOVE);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+int CMsnProto::OnGroupChange(WPARAM hContact, LPARAM lParam)
+{
+ if (!msnLoggedIn || !MyOptions.ManageServer) return 0;
+
+ const CLISTGROUPCHANGE* grpchg = (CLISTGROUPCHANGE*)lParam;
+
+ if (hContact == NULL)
+ {
+ if (grpchg->pszNewName == NULL && grpchg->pszOldName != NULL)
+ {
+ LPCSTR szId = MSN_GetGroupByName(UTF8(grpchg->pszOldName));
+ if (szId != NULL) MSN_DeleteServerGroup(szId);
+ }
+ else if (grpchg->pszNewName != NULL && grpchg->pszOldName != NULL)
+ {
+ LPCSTR szId = MSN_GetGroupByName(UTF8(grpchg->pszOldName));
+ if (szId != NULL) MSN_RenameServerGroup(szId, UTF8(grpchg->pszNewName));
+ }
+ }
+ else
+ {
+ if (MSN_IsMyContact(hContact))
+ {
+ char* szNewName = grpchg->pszNewName ? mir_utf8encodeT(grpchg->pszNewName) : NULL;
+ MSN_MoveContactToGroup(hContact, szNewName);
+ mir_free(szNewName);
+ }
+ }
+ return 0;
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnDbSettingChanged - look for contact's settings changes
+
+int CMsnProto::OnDbSettingChanged(WPARAM hContact, LPARAM lParam)
+{
+ DBCONTACTWRITESETTING* cws = (DBCONTACTWRITESETTING*)lParam;
+
+ if (!msnLoggedIn)
+ return 0;
+
+ if (hContact == NULL)
+ {
+ if (MyOptions.SlowSend && strcmp(cws->szSetting, "MessageTimeout") == 0 &&
+ (strcmp(cws->szModule, "SRMM") == 0 || strcmp(cws->szModule, "SRMsg") == 0))
+ {
+ if (cws->value.dVal < 60000)
+ MessageBox(NULL, TranslateT("MSN requires message send timeout in your Message window plugin to be not less then 60 sec. Please correct the timeout value."),
+ TranslateT("MSN Protocol"), MB_OK|MB_ICONINFORMATION);
+ }
+ return 0;
+ }
+
+ if (!strcmp(cws->szSetting, "ApparentMode"))
+ {
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ if (!db_get_static(hContact, m_szModuleName, "e-mail", tEmail, sizeof(tEmail)))
+ {
+ bool isBlocked = Lists_IsInList(LIST_BL, tEmail);
+
+ if (isBlocked && (cws->value.type == DBVT_DELETED || cws->value.wVal == 0))
+ {
+ MSN_AddUser(hContact, tEmail, 0, LIST_BL + LIST_REMOVE);
+ MSN_AddUser(hContact, tEmail, 0, LIST_AL);
+ }
+ else if (!isBlocked && cws->value.wVal == ID_STATUS_OFFLINE)
+ {
+ MSN_AddUser(hContact, tEmail, 0, LIST_AL + LIST_REMOVE);
+ MSN_AddUser(hContact, tEmail, 0, LIST_BL);
+ }
+ }
+ }
+
+ if (!strcmp(cws->szSetting, "MyHandle") && !strcmp(cws->szModule, "CList"))
+ {
+ bool isMe = MSN_IsMeByContact(hContact);
+ if (!isMe || !nickChg)
+ {
+ char szContactID[100];
+ if (!db_get_static(hContact, m_szModuleName, "ID", szContactID, sizeof(szContactID)))
+ {
+ if (cws->value.type != DBVT_DELETED)
+ {
+ if (cws->value.type == DBVT_UTF8)
+ MSN_ABUpdateNick(cws->value.pszVal, szContactID);
+ else
+ MSN_ABUpdateNick(UTF8(cws->value.pszVal), szContactID);
+ }
+ else
+ MSN_ABUpdateNick(NULL, szContactID);
+ }
+ if (isMe) displayEmailCount(hContact);
+ }
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnIdleChanged - transitions to Idle
+
+int CMsnProto::OnIdleChanged(WPARAM wParam, LPARAM lParam)
+{
+ if (m_iStatus == ID_STATUS_INVISIBLE || m_iStatus <= ID_STATUS_OFFLINE)
+ return 0;
+
+ bool bIdle = (lParam & IDF_ISIDLE) != 0;
+ bool bPrivacy = (lParam & IDF_PRIVACY) != 0;
+
+ if (isIdle && !bIdle)
+ {
+ isIdle = false;
+ MSN_SetServerStatus(m_iDesiredStatus);
+ }
+ else if (!isIdle && bIdle && !bPrivacy && m_iDesiredStatus != ID_STATUS_AWAY)
+ {
+ isIdle = true;
+ MSN_SetServerStatus(ID_STATUS_IDLE);
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnWindowEvent - creates session on window open
+
+int CMsnProto::OnWindowEvent(WPARAM wParam, LPARAM lParam)
+{
+ MessageWindowEventData* msgEvData = (MessageWindowEventData*)lParam;
+
+ if (msgEvData->uType == MSG_WINDOW_EVT_OPENING)
+ {
+ if (m_iStatus == ID_STATUS_OFFLINE || m_iStatus == ID_STATUS_INVISIBLE)
+ return 0;
+
+ if (!MSN_IsMyContact(msgEvData->hContact)) return 0;
+
+ char tEmail[MSN_MAX_EMAIL_LEN];
+ if (MSN_IsMeByContact(msgEvData->hContact, tEmail)) return 0;
+
+ int netId = Lists_GetNetId(tEmail);
+ if (netId != NETID_MSN && netId != NETID_LCS) return 0;
+
+ if (Lists_IsInList(LIST_BL, tEmail)) return 0;
+
+ bool isOffline;
+ ThreadData* thread = MSN_StartSB(tEmail, isOffline);
+
+ if (thread == NULL && !isOffline)
+ MsgQueue_Add(tEmail, 'X', NULL, 0);
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnWindowEvent - creates session on window open
+
+int CMsnProto::OnWindowPopup(WPARAM wParam, LPARAM lParam)
+{
+ MessageWindowPopupData *mwpd = (MessageWindowPopupData *)lParam;
+
+ if (!MSN_IsMyContact(mwpd->hContact) || isChatRoom(mwpd->hContact))
+ return 0;
+
+ switch (mwpd->uType)
+ {
+ case MSG_WINDOWPOPUP_SHOWING:
+ AppendMenu(mwpd->hMenu, MF_STRING, 13465, TranslateT("Convert to Chat"));
+ break;
+
+ case MSG_WINDOWPOPUP_SELECTED:
+ if (mwpd->selection == 13465)
+ {
+ DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_CHATROOM_INVITE), NULL, DlgInviteToChat,
+ LPARAM(new InviteChatParam(NULL, mwpd->hContact, this)));
+ }
+ break;
+ }
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MsnGetUnread - returns the actual number of unread emails in the INBOX
+
+INT_PTR CMsnProto::GetUnreadEmailCount(WPARAM wParam, LPARAM lParam)
+{
+ if (!msnLoggedIn)
+ return 0;
+ return mUnreadMessages;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// OnLeaveChat - closes MSN chat window
+
+INT_PTR CMsnProto::OnLeaveChat(WPARAM hContact, LPARAM lParam)
+{
+ if (isChatRoom(hContact) != 0) {
+ DBVARIANT dbv;
+ if (getTString(hContact, "ChatRoomID", &dbv) == 0) {
+ MSN_KillChatSession(dbv.ptszVal);
+ db_free(&dbv);
+ }
+ }
+ return 0;
+}
diff --git a/protocols/MSN/src/msn_switchboard.cpp b/protocols/MSN/src/msn_switchboard.cpp
new file mode 100644
index 0000000000..4ff32a7938
--- /dev/null
+++ b/protocols/MSN/src/msn_switchboard.cpp
@@ -0,0 +1,53 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+int ThreadData::contactJoined(const char* email)
+{
+ for (int i=0; i < mJoinedContactsWLID.getCount(); i++)
+ if (_stricmp(mJoinedContactsWLID[i], email) == 0)
+ return mJoinedContactsWLID.getCount();
+
+ if (strchr(email, ';'))
+ mJoinedIdentContactsWLID.insertn(email);
+ else
+ mJoinedContactsWLID.insertn(email);
+
+ return mJoinedContactsWLID.getCount();
+}
+
+int ThreadData::contactLeft(const char* email)
+{
+ if (strchr(email, ';'))
+ mJoinedIdentContactsWLID.remove(email);
+ else
+ mJoinedContactsWLID.remove(email);
+
+ return mJoinedContactsWLID.getCount();
+}
+
+MCONTACT ThreadData::getContactHandle(void)
+{
+ return mJoinedContactsWLID.getCount() ? proto->MSN_HContactFromEmail(mJoinedContactsWLID[0]) : NULL;
+}
diff --git a/protocols/MSN/src/msn_threads.cpp b/protocols/MSN/src/msn_threads.cpp
new file mode 100644
index 0000000000..e643bce91b
--- /dev/null
+++ b/protocols/MSN/src/msn_threads.cpp
@@ -0,0 +1,769 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Keep-alive thread for the main connection
+
+void __cdecl CMsnProto::msn_keepAliveThread(void*)
+{
+ bool keepFlag = true;
+
+ hKeepAliveThreadEvt = CreateEvent(NULL, FALSE, FALSE, NULL);
+
+ msnPingTimeout = 45;
+
+ while (keepFlag)
+ {
+ switch (WaitForSingleObject(hKeepAliveThreadEvt, msnPingTimeout * 1000))
+ {
+ case WAIT_TIMEOUT:
+ keepFlag = msnNsThread != NULL;
+ if (usingGateway)
+ msnPingTimeout = 45;
+ else
+ {
+ msnPingTimeout = 20;
+ keepFlag = keepFlag && msnNsThread->send("PNG\r\n", 5);
+ }
+ p2p_clearDormantSessions();
+ if (hHttpsConnection && (clock() - mHttpsTS) > 60 * CLOCKS_PER_SEC)
+ {
+ HANDLE hConn = hHttpsConnection;
+ hHttpsConnection = NULL;
+ Netlib_CloseHandle(hConn);
+ }
+ if (mStatusMsgTS && (clock() - mStatusMsgTS) > 60 * CLOCKS_PER_SEC)
+ {
+ mStatusMsgTS = 0;
+ ForkThread(&CMsnProto::msn_storeProfileThread, NULL);
+ }
+ break;
+
+ case WAIT_OBJECT_0:
+ keepFlag = msnPingTimeout > 0;
+ break;
+
+ default:
+ keepFlag = false;
+ break;
+ }
+ }
+
+ CloseHandle(hKeepAliveThreadEvt); hKeepAliveThreadEvt = NULL;
+ debugLogA("Closing keep-alive thread");
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// MSN server thread - read and process commands from a server
+
+void __cdecl CMsnProto::MSNServerThread(void* arg)
+{
+ ThreadData* info = (ThreadData*)arg;
+
+ if (info->mIsMainThread)
+ isConnectSuccess = false;
+
+ char* tPortDelim = strrchr(info->mServer, ':');
+ if (tPortDelim != NULL)
+ *tPortDelim = '\0';
+
+ if (usingGateway)
+ {
+ if (info->mServer[0] == 0)
+ strcpy(info->mServer, MSN_DEFAULT_LOGIN_SERVER);
+ else if (info->mIsMainThread)
+ strcpy(info->mGatewayIP, info->mServer);
+
+ if (info->gatewayType)
+ strcpy(info->mGatewayIP, info->mServer);
+ else
+ {
+ if (info->mGatewayIP[0] == 0 && db_get_static(NULL, m_szModuleName, "GatewayServer", info->mGatewayIP, sizeof(info->mGatewayIP)))
+ strcpy(info->mGatewayIP, MSN_DEFAULT_GATEWAY);
+ }
+ }
+ else
+ {
+ if (info->mServer[0] == 0 && db_get_static(NULL, m_szModuleName, "DirectServer", info->mServer, sizeof(info->mServer)))
+ strcpy(info->mServer, MSN_DEFAULT_LOGIN_SERVER);
+ }
+
+ NETLIBOPENCONNECTION tConn = { 0 };
+ tConn.cbSize = sizeof(tConn);
+ tConn.flags = NLOCF_V2;
+ tConn.timeout = 5;
+
+ if (usingGateway)
+ {
+ tConn.flags |= NLOCF_HTTPGATEWAY;
+ tConn.szHost = info->mGatewayIP;
+ tConn.wPort = MSN_DEFAULT_GATEWAY_PORT;
+ }
+ else
+ {
+ tConn.szHost = info->mServer;
+ tConn.wPort = MSN_DEFAULT_PORT;
+ if (tPortDelim != NULL)
+ {
+ int tPortNumber = atoi(tPortDelim + 1);
+ if (tPortNumber)
+ tConn.wPort = (WORD)tPortNumber;
+ }
+ }
+
+ debugLogA("Thread started: server='%s:%d', type=%d", tConn.szHost, tConn.wPort, info->mType);
+
+ info->s = (HANDLE)CallService(MS_NETLIB_OPENCONNECTION, (WPARAM)m_hNetlibUser, (LPARAM)&tConn);
+ if (info->s == NULL)
+ {
+ debugLogA("Connection Failed (%d) server='%s:%d'", WSAGetLastError(), tConn.szHost, tConn.wPort);
+
+ switch (info->mType)
+ {
+ case SERVER_NOTIFICATION:
+ goto LBL_Exit;
+ break;
+
+ case SERVER_SWITCHBOARD:
+ if (info->mCaller) msnNsThread->sendPacket("XFR", "SB");
+ break;
+ }
+ return;
+ }
+
+ if (usingGateway)
+ CallService(MS_NETLIB_SETPOLLINGTIMEOUT, WPARAM(info->s), info->mGatewayTimeout);
+
+ debugLogA("Connected with handle=%08X", info->s);
+
+ if (info->mType == SERVER_NOTIFICATION)
+ {
+ info->sendPacket("VER", "MSNP18 MSNP17 CVR0");
+ }
+ else if (info->mType == SERVER_SWITCHBOARD)
+ {
+ info->sendPacket(info->mCaller ? "USR" : "ANS", "%s;%s %s", MyOptions.szEmail, MyOptions.szMachineGuid, info->mCookie);
+ }
+ else if (info->mType == SERVER_FILETRANS && info->mCaller == 0)
+ {
+ info->send("VER MSNFTP\r\n", 12);
+ }
+
+ if (info->mIsMainThread)
+ {
+ msnNsThread = info;
+ }
+
+ debugLogA("Entering main recv loop");
+ info->mBytesInData = 0;
+ for (;;)
+ {
+ int recvResult = info->recv(info->mData + info->mBytesInData, sizeof(info->mData) - info->mBytesInData);
+ if (recvResult == SOCKET_ERROR)
+ {
+ debugLogA("Connection %08p [%08X] was abortively closed", info->s, GetCurrentThreadId());
+ break;
+ }
+
+ if (!recvResult)
+ {
+ debugLogA("Connection %08p [%08X] was gracefully closed", info->s, GetCurrentThreadId());
+ break;
+ }
+
+ info->mBytesInData += recvResult;
+
+ if (info->mCaller == 1 && info->mType == SERVER_FILETRANS)
+ {
+ if (MSN_HandleMSNFTP(info, info->mData))
+ break;
+ }
+ else
+ {
+ for (;;)
+ {
+ char* peol = strchr(info->mData, '\r');
+ if (peol == NULL)
+ break;
+
+ if (info->mBytesInData < peol-info->mData + 2)
+ break; //wait for full line end
+
+ char msg[sizeof(info->mData)];
+ memcpy(msg, info->mData, peol-info->mData); msg[peol-info->mData] = 0;
+
+ if (*++peol != '\n')
+ debugLogA("Dodgy line ending to command: ignoring");
+ else
+ peol++;
+
+ info->mBytesInData -= peol - info->mData;
+ memmove(info->mData, peol, info->mBytesInData);
+ debugLogA("RECV: %s", msg);
+
+ if (!isalnum(msg[0]) || !isalnum(msg[1]) || !isalnum(msg[2]) || (msg[3] && msg[3] != ' '))
+ {
+ debugLogA("Invalid command name");
+ continue;
+ }
+
+ if (info->mType != SERVER_FILETRANS)
+ {
+ int handlerResult;
+ if (isdigit(msg[0]) && isdigit(msg[1]) && isdigit(msg[2])) //all error messages
+ handlerResult = MSN_HandleErrors(info, msg);
+ else
+ handlerResult = MSN_HandleCommands(info, msg);
+
+ if (handlerResult)
+ {
+ if (info->sessionClosed) goto LBL_Exit;
+ info->sendTerminate();
+ }
+ }
+ else
+ if (MSN_HandleMSNFTP(info, msg))
+ goto LBL_Exit;
+ }
+ }
+
+ if (info->mBytesInData == sizeof(info->mData))
+ {
+ debugLogA("sizeof(data) is too small: the longest line won't fit");
+ break;
+ }
+ }
+
+LBL_Exit:
+ if (info->mIsMainThread)
+ {
+ if (!isConnectSuccess && !usingGateway && m_iDesiredStatus != ID_STATUS_OFFLINE)
+ {
+ msnNsThread = NULL;
+ usingGateway = true;
+
+ ThreadData* newThread = new ThreadData;
+ newThread->mType = SERVER_NOTIFICATION;
+ newThread->mIsMainThread = true;
+
+ newThread->startThread(&CMsnProto::MSNServerThread, this);
+ }
+ else
+ {
+ if (hKeepAliveThreadEvt)
+ {
+ msnPingTimeout *= -1;
+ SetEvent(hKeepAliveThreadEvt);
+ }
+
+ if (info->s == NULL)
+ ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, LOGINERR_NONETWORK);
+ else
+ {
+ p2p_cancelAllSessions();
+ MSN_CloseConnections();
+ }
+
+ if (hHttpsConnection)
+ {
+ Netlib_CloseHandle(hHttpsConnection);
+ hHttpsConnection = NULL;
+ }
+
+ MSN_GoOffline();
+ msnNsThread = NULL;
+ }
+ }
+
+ debugLogA("Thread [%08X] ending now", GetCurrentThreadId());
+}
+
+void CMsnProto::MSN_CloseConnections(void)
+{
+ mir_cslockfull lck(csThreads);
+
+ NETLIBSELECTEX nls = {0};
+ nls.cbSize = sizeof(nls);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+
+ switch (T->mType)
+ {
+ case SERVER_NOTIFICATION :
+ case SERVER_SWITCHBOARD :
+ if (T->s != NULL && !T->sessionClosed && !T->termPending)
+ {
+ nls.hReadConns[0] = T->s;
+ int res = CallService(MS_NETLIB_SELECTEX, 0, (LPARAM)&nls);
+ if (res >= 0 || nls.hReadStatus[0] == 0)
+ T->sendTerminate();
+ }
+ break;
+
+ case SERVER_P2P_DIRECT :
+ CallService(MS_NETLIB_SHUTDOWN, (WPARAM)T->s, 0);
+ break;
+ }
+ }
+
+ lck.unlock();
+
+ if (hHttpsConnection)
+ CallService(MS_NETLIB_SHUTDOWN, (WPARAM)hHttpsConnection, 0);
+}
+
+void CMsnProto::Threads_Uninit(void)
+{
+ mir_cslock lck(csThreads);
+ sttThreads.destroy();
+}
+
+ThreadData* CMsnProto::MSN_GetThreadByContact(const char* wlid, TInfoType type)
+{
+ mir_cslock lck(csThreads);
+
+ if (type == SERVER_P2P_DIRECT)
+ {
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mType != SERVER_P2P_DIRECT || !T->mJoinedIdentContactsWLID.getCount() || T->s == NULL)
+ continue;
+
+ if (_stricmp(T->mJoinedIdentContactsWLID[0], wlid) == 0)
+ return T;
+ }
+ }
+
+ char *szEmail = NULL;
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mType != type || !T->mJoinedContactsWLID.getCount() || T->mInitialContactWLID || T->s == NULL)
+ continue;
+
+ if (_stricmp(T->mJoinedContactsWLID[0], szEmail) == 0 && T->mChatID[0] == 0)
+ return T;
+ }
+
+ return NULL;
+}
+
+ThreadData* CMsnProto::MSN_GetThreadByChatId(const TCHAR* chatId)
+{
+ mir_cslock lck(csThreads);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+
+ if (_tcsicmp(T->mChatID, chatId) == 0)
+ return T;
+ }
+
+ return NULL;
+}
+
+ThreadData* CMsnProto::MSN_GetThreadByTimer(UINT timerId)
+{
+ mir_cslock lck(csThreads);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mType == SERVER_SWITCHBOARD && T->mTimerId == timerId)
+ return T;
+ }
+
+ return NULL;
+}
+
+ThreadData* CMsnProto::MSN_GetP2PThreadByContact(const char *wlid)
+{
+ mir_cslock lck(csThreads);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mType != SERVER_P2P_DIRECT || !T->mJoinedIdentContactsWLID.getCount())
+ continue;
+
+ if (_stricmp(T->mJoinedIdentContactsWLID[0], wlid) == 0)
+ return T;
+ }
+
+ char *szEmail = NULL;
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+
+ ThreadData *result = NULL;
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mJoinedContactsWLID.getCount() && !T->mInitialContactWLID &&
+ _stricmp(T->mJoinedContactsWLID[0], szEmail) == 0)
+ {
+ if (T->mType == SERVER_P2P_DIRECT)
+ return T;
+
+ if (T->mType == SERVER_SWITCHBOARD)
+ result = T;
+ }
+ }
+
+ return result;
+}
+
+
+void CMsnProto::MSN_StartP2PTransferByContact(const char* wlid)
+{
+ mir_cslock lck(csThreads);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mType == SERVER_FILETRANS && T->hWaitEvent != INVALID_HANDLE_VALUE)
+ {
+ if ((T->mInitialContactWLID && !_stricmp(T->mInitialContactWLID, wlid)) ||
+ (T->mJoinedContactsWLID.getCount() && !_stricmp(T->mJoinedContactsWLID[0], wlid)) ||
+ (T->mJoinedIdentContactsWLID.getCount() && !_stricmp(T->mJoinedIdentContactsWLID[0], wlid)))
+ ReleaseSemaphore(T->hWaitEvent, 1, NULL);
+ }
+ }
+}
+
+
+ThreadData* CMsnProto::MSN_GetOtherContactThread(ThreadData* thread)
+{
+ mir_cslock lck(csThreads);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mJoinedContactsWLID.getCount() == 0 || T->s == NULL)
+ continue;
+
+ if (T != thread && _stricmp(T->mJoinedContactsWLID[0], thread->mJoinedContactsWLID[0]) == 0)
+ return T;
+ }
+
+ return NULL;
+}
+
+ThreadData* CMsnProto::MSN_GetUnconnectedThread(const char* wlid, TInfoType type)
+{
+ mir_cslock lck(csThreads);
+
+ char* szEmail = (char*)wlid;
+
+ if (type == SERVER_SWITCHBOARD && strchr(wlid, ';'))
+ parseWLID(NEWSTR_ALLOCA(wlid), NULL, &szEmail, NULL);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mType == type && T->mInitialContactWLID && _stricmp(T->mInitialContactWLID, szEmail) == 0)
+ return T;
+ }
+
+ return NULL;
+}
+
+
+ThreadData* CMsnProto::MSN_StartSB(const char* wlid, bool& isOffline)
+{
+ isOffline = false;
+ ThreadData* thread = MSN_GetThreadByContact(wlid);
+ if (thread == NULL)
+ {
+ MCONTACT hContact = MSN_HContactFromEmail(wlid);
+ WORD wStatus = getWord(hContact, "Status", ID_STATUS_OFFLINE);
+ if (wStatus != ID_STATUS_OFFLINE)
+ {
+ if (MSN_GetUnconnectedThread(wlid) == NULL && MsgQueue_CheckContact(wlid, 5) == NULL)
+ msnNsThread->sendPacket("XFR", "SB");
+ }
+ else isOffline = true;
+ }
+ return thread;
+}
+
+
+
+int CMsnProto::MSN_GetActiveThreads(ThreadData** parResult)
+{
+ int tCount = 0;
+ mir_cslock lck(csThreads);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mType == SERVER_SWITCHBOARD && T->mJoinedContactsWLID.getCount() != 0 && T->mJoinedContactsWLID.getCount())
+ parResult[tCount++] = T;
+ }
+
+ return tCount;
+}
+
+ThreadData* CMsnProto::MSN_GetThreadByConnection(HANDLE s)
+{
+ mir_cslock lck(csThreads);
+
+ for (int i = 0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->s == s)
+ return T;
+ }
+
+ return NULL;
+}
+
+ThreadData* CMsnProto::MSN_GetThreadByPort(WORD wPort)
+{
+ mir_cslock lck(csThreads);
+
+ for (int i=0; i < sttThreads.getCount(); i++)
+ {
+ ThreadData* T = &sttThreads[i];
+ if (T->mIncomingPort == wPort)
+ return T;
+ }
+
+ return NULL;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// class ThreadData members
+
+ThreadData::ThreadData()
+{
+ memset(&mInitialContactWLID, 0, sizeof(ThreadData) - 2*sizeof(STRLIST));
+ mGatewayTimeout = 2;
+ resetTimeout();
+ hWaitEvent = CreateSemaphore(NULL, 0, MSN_PACKETS_COMBINE, NULL);
+}
+
+ThreadData::~ThreadData()
+{
+ int i;
+
+ if (s != NULL)
+ {
+ proto->debugLogA("Closing connection handle %08X", s);
+ Netlib_CloseHandle(s);
+ }
+
+ if (mIncomingBoundPort != NULL)
+ {
+ Netlib_CloseHandle(mIncomingBoundPort);
+ }
+
+ if (mMsnFtp != NULL)
+ {
+ delete mMsnFtp;
+ mMsnFtp = NULL;
+ }
+
+ if (hWaitEvent != INVALID_HANDLE_VALUE)
+ CloseHandle(hWaitEvent);
+
+ if (mTimerId != 0)
+ KillTimer(NULL, mTimerId);
+
+ if (mType == SERVER_SWITCHBOARD)
+ {
+ for (i=0; i<mJoinedContactsWLID.getCount(); ++i)
+ {
+ const char* wlid = mJoinedContactsWLID[i];
+ MCONTACT hContact = proto->MSN_HContactFromEmail(wlid);
+ int temp_status = proto->getWord(hContact, "Status", ID_STATUS_OFFLINE);
+ if (temp_status == ID_STATUS_INVISIBLE && proto->MSN_GetThreadByContact(wlid) == NULL)
+ proto->setWord(hContact, "Status", ID_STATUS_OFFLINE);
+ }
+ }
+
+ mJoinedContactsWLID.destroy();
+ mJoinedIdentContactsWLID.destroy();
+
+ const char* wlid = NEWSTR_ALLOCA(mInitialContactWLID);
+ mir_free(mInitialContactWLID); mInitialContactWLID = NULL;
+
+ if (proto && mType == SERVER_P2P_DIRECT)
+ proto->p2p_clearDormantSessions();
+
+ if (wlid != NULL && mType == SERVER_SWITCHBOARD &&
+ proto->MSN_GetThreadByContact(wlid) == NULL &&
+ proto->MSN_GetUnconnectedThread(wlid) == NULL)
+ {
+ proto->MsgQueue_Clear(wlid, true);
+ }
+}
+
+void ThreadData::applyGatewayData(HANDLE hConn, bool isPoll)
+{
+ char szHttpPostUrl[300];
+ getGatewayUrl(szHttpPostUrl, sizeof(szHttpPostUrl), isPoll);
+
+ proto->debugLogA("applying '%s' to %08X [%08X]", szHttpPostUrl, this, GetCurrentThreadId());
+
+ NETLIBHTTPPROXYINFO nlhpi = {0};
+ nlhpi.cbSize = sizeof(nlhpi);
+ nlhpi.flags = NLHPIF_HTTP11;
+ nlhpi.szHttpGetUrl = NULL;
+ nlhpi.szHttpPostUrl = szHttpPostUrl;
+ nlhpi.combinePackets = 5;
+ CallService(MS_NETLIB_SETHTTPPROXYINFO, (WPARAM)hConn, (LPARAM)&nlhpi);
+}
+
+void ThreadData::getGatewayUrl(char* dest, int destlen, bool isPoll)
+{
+ static const char openFmtStr[] = "http://%s/gateway/gateway.dll?Action=open&Server=%s&IP=%s";
+ static const char pollFmtStr[] = "http://%s/gateway/gateway.dll?Action=poll&SessionID=%s";
+ static const char cmdFmtStr[] = "http://%s/gateway/gateway.dll?SessionID=%s";
+
+ if (mSessionID[0] == 0)
+ {
+ const char* svr = mType == SERVER_NOTIFICATION ? "NS" : "SB";
+ mir_snprintf(dest, destlen, openFmtStr, mGatewayIP, svr, mServer);
+ }
+ else
+ mir_snprintf(dest, destlen, isPoll ? pollFmtStr : cmdFmtStr, mGatewayIP, mSessionID);
+}
+
+void ThreadData::processSessionData(const char* xMsgr, const char* xHost)
+{
+ char tSessionID[40], tGateIP[80];
+
+ char* tDelim = (char*)strchr(xMsgr, ';');
+ if (tDelim == NULL)
+ return;
+
+ *tDelim = 0; tDelim += 2;
+
+ if (!sscanf(xMsgr, "SessionID=%s", tSessionID))
+ return;
+
+ char* tDelim2 = strchr(tDelim, ';');
+ if (tDelim2 != NULL)
+ *tDelim2 = '\0';
+ if (xHost)
+ strcpy(tGateIP, xHost);
+ else if (!sscanf(tDelim, "GW-IP=%s", tGateIP))
+ return;
+
+ strcpy(mGatewayIP, tGateIP);
+ if (gatewayType) strcpy(mServer, tGateIP);
+ strcpy(mSessionID, tSessionID);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// thread start code
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void __cdecl CMsnProto::ThreadStub(void* arg)
+{
+ ThreadData* info = (ThreadData*)arg;
+
+ debugLogA("Starting thread %08X (%08X)", GetCurrentThreadId(), info->mFunc);
+
+ (this->*(info->mFunc))(info);
+
+ debugLogA("Leaving thread %08X (%08X)", GetCurrentThreadId(), info->mFunc);
+ {
+ mir_cslock lck(csThreads);
+ sttThreads.LIST<ThreadData>::remove(info);
+ }
+ delete info;
+}
+
+void ThreadData::startThread(MsnThreadFunc parFunc, CMsnProto *prt)
+{
+ mFunc = parFunc;
+ proto = prt;
+ {
+ mir_cslock lck(prt->csThreads);
+ proto->sttThreads.insert(this);
+ }
+ proto->ForkThread(&CMsnProto::ThreadStub, this);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// HReadBuffer members
+
+HReadBuffer::HReadBuffer(ThreadData* T, int iStart)
+{
+ owner = T;
+ buffer = (BYTE*)T->mData;
+ totalDataSize = T->mBytesInData;
+ startOffset = iStart;
+}
+
+HReadBuffer::~HReadBuffer()
+{
+ if (totalDataSize > startOffset)
+ {
+ memmove(buffer, buffer + startOffset, (totalDataSize -= startOffset));
+ owner->mBytesInData = (int)totalDataSize;
+ }
+ else owner->mBytesInData = 0;
+}
+
+BYTE* HReadBuffer::surelyRead(size_t parBytes)
+{
+ const size_t bufferSize = sizeof(owner->mData);
+
+ if ((startOffset + parBytes) > bufferSize)
+ {
+ if (totalDataSize > startOffset)
+ memmove(buffer, buffer + startOffset, (totalDataSize -= startOffset));
+ else
+ totalDataSize = 0;
+
+ startOffset = 0;
+
+ if (parBytes > bufferSize)
+ {
+ owner->proto->debugLogA("HReadBuffer::surelyRead: not enough memory, %d %d %d", parBytes, bufferSize, startOffset);
+ return NULL;
+ }
+ }
+
+ while ((startOffset + parBytes) > totalDataSize)
+ {
+ int recvResult = owner->recv((char*)buffer + totalDataSize, bufferSize - totalDataSize);
+
+ if (recvResult <= 0)
+ return NULL;
+
+ totalDataSize += recvResult;
+ }
+
+ BYTE* result = buffer + startOffset; startOffset += parBytes;
+ return result;
+}
diff --git a/protocols/MSN/src/msn_ws.cpp b/protocols/MSN/src/msn_ws.cpp
new file mode 100644
index 0000000000..22a4cbc0aa
--- /dev/null
+++ b/protocols/MSN/src/msn_ws.cpp
@@ -0,0 +1,180 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2006-2012 Boris Krasnovskiy.
+Copyright (c) 2003-2005 George Hazan.
+Copyright (c) 2002-2003 Richard Hughes (original version).
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h"
+#include "msn_proto.h"
+
+
+//=======================================================================================
+
+int ThreadData::send(const char data[], size_t datalen)
+{
+ NETLIBBUFFER nlb = { (char*)data, (int)datalen, 0 };
+
+ resetTimeout();
+
+ if (proto->usingGateway && !(mType == SERVER_FILETRANS || mType == SERVER_P2P_DIRECT))
+ {
+ mGatewayTimeout = 2;
+ CallService(MS_NETLIB_SETPOLLINGTIMEOUT, WPARAM(s), mGatewayTimeout);
+ }
+
+ int rlen = CallService(MS_NETLIB_SEND, (WPARAM)s, (LPARAM)&nlb);
+ if (rlen == SOCKET_ERROR)
+ {
+ // should really also check if sendlen is the same as datalen
+ proto->debugLogA("Send failed: %d", WSAGetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void ThreadData::resetTimeout(bool term)
+{
+ int timeout = term ? 10 : mIsMainThread ? 65 : 120;
+ mWaitPeriod = clock() + timeout * CLOCKS_PER_SEC;
+}
+
+bool ThreadData::isTimeout(void)
+{
+ bool res = false;
+
+ if (mWaitPeriod >= clock()) return false;
+
+ if (mIsMainThread)
+ {
+ res = !proto->usingGateway;
+ }
+ else if (mJoinedContactsWLID.getCount() <= 1 || mChatID[0] == 0)
+ {
+ MCONTACT hContact = getContactHandle();
+
+ if (mJoinedContactsWLID.getCount() == 0 || termPending)
+ res = true;
+ else if (proto->p2p_getThreadSession(hContact, mType) != NULL)
+ res = false;
+ else if (mType == SERVER_SWITCHBOARD)
+ {
+ res = MSN_MsgWndExist(hContact);
+ if (res)
+ {
+ WORD status = proto->getWord(hContact, "Status", ID_STATUS_OFFLINE);
+ if ((status == ID_STATUS_OFFLINE || status == ID_STATUS_INVISIBLE || proto->m_iStatus == ID_STATUS_INVISIBLE))
+ res = false;
+ }
+ }
+ else
+ res = true;
+ }
+
+ if (res)
+ {
+ bool sbsess = mType == SERVER_SWITCHBOARD;
+
+ proto->debugLogA("Dropping the idle %s due to inactivity", sbsess ? "switchboard" : "p2p");
+ if (!sbsess || termPending) return true;
+
+ if (proto->getByte("EnableSessionPopup", 0))
+ {
+ MCONTACT hContact = NULL;
+ if (mJoinedContactsWLID.getCount())
+ hContact = proto->MSN_HContactFromEmail(mJoinedContactsWLID[0]);
+ else if (mInitialContactWLID)
+ hContact = proto->MSN_HContactFromEmail(mInitialContactWLID);
+
+ if (hContact)
+ proto->MSN_ShowPopup(hContact, TranslateT("Chat session dropped due to inactivity"), 0);
+ }
+
+ sendTerminate();
+ resetTimeout(true);
+ }
+ else
+ resetTimeout();
+
+ return false;
+}
+
+int ThreadData::recv(char* data, size_t datalen)
+{
+ NETLIBBUFFER nlb = { data, (int)datalen, 0 };
+
+ if (!proto->usingGateway)
+ {
+ resetTimeout();
+ NETLIBSELECT nls = { 0 };
+ nls.cbSize = sizeof(nls);
+ nls.dwTimeout = 1000;
+ nls.hReadConns[0] = s;
+
+ for (;;)
+ {
+ int ret = CallService(MS_NETLIB_SELECT, 0, (LPARAM)&nls);
+ if (ret < 0)
+ {
+ proto->debugLogA("Connection abortively closed, error %d", WSAGetLastError());
+ return ret;
+ }
+ else if (ret == 0)
+ {
+ if (isTimeout()) return 0;
+ }
+ else
+ break;
+ }
+ }
+
+LBL_RecvAgain:
+ int ret = CallService(MS_NETLIB_RECV, (WPARAM)s, (LPARAM)&nlb);
+ if (ret == 0)
+ {
+ proto->debugLogA("Connection closed gracefully");
+ return 0;
+ }
+
+ if (ret < 0)
+ {
+ proto->debugLogA("Connection abortively closed, error %d", WSAGetLastError());
+ return ret;
+ }
+
+ if (proto->usingGateway)
+ {
+ if (ret == 1 && *data == 0)
+ {
+ if (sessionClosed || isTimeout()) return 0;
+ if ((mGatewayTimeout += 2) > 20) mGatewayTimeout = 20;
+
+ CallService(MS_NETLIB_SETPOLLINGTIMEOUT, WPARAM(s), mGatewayTimeout);
+ goto LBL_RecvAgain;
+ }
+ else
+ {
+ resetTimeout();
+ mGatewayTimeout = 1;
+ CallService(MS_NETLIB_SETPOLLINGTIMEOUT, WPARAM(s), mGatewayTimeout);
+ }
+ }
+
+ return ret;
+}
diff --git a/protocols/MSN/src/resource.h b/protocols/MSN/src/resource.h
new file mode 100644
index 0000000000..938a0124b7
--- /dev/null
+++ b/protocols/MSN/src/resource.h
@@ -0,0 +1,100 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ..\res\msn.rc
+//
+#define IDD_CHATROOM_INVITE 40
+#define IDI_MSN 101
+#define IDD_USEROPTS 104
+#define IDD_OPT_MSN_CONN 106
+#define IDD_OPT_NOTIFY 107
+#define IDI_MSNBLOCK 108
+#define IDI_SERVICES 109
+#define IDI_INBOX 112
+#define IDI_INVITE 113
+#define IDI_NETMEETING 114
+#define IDI_PROFILE 115
+#define IDD_LISTSMGR 119
+#define IDI_LIST_FL 120
+#define IDI_LIST_AL 121
+#define IDI_LIST_BL 122
+#define IDI_LIST_RL 123
+#define IDD_CARD_GEN 133
+#define IDD_CARD_CONTACT 134
+#define IDD_ACCMGRUI 135
+#define IDD_DELETECONTACT 136
+#define IDI_LIST_LC 138
+#define IDC_MSG 158
+#define IDC_CCLIST 173
+#define IDC_EDITSCR 174
+#define IDC_ADDSCR 175
+#define IDD_OPT_MSN 185
+#define IDD_SETNICKNAME 226
+#define IDC_STMSNGROUP 1002
+#define IDC_LIST 1015
+#define IDC_ICON_FL 1016
+#define IDC_ICON_LC 1017
+#define IDC_ICON_AL 1018
+#define IDC_ICON_BL 1019
+#define IDC_PASSWORD 1020
+#define IDC_ICON_RL 1021
+#define IDC_PLACE 1021
+#define IDC_HANDLE 1022
+#define IDC_HANDLE2 1023
+#define IDC_SLOWSEND 1033
+#define IDC_MANAGEGROUPS 1034
+#define IDC_NOTIFY_ENDSESSION 1035
+#define IDC_MOBILESEND 1035
+#define IDC_HOSTOPT 1038
+#define IDC_CCARD_TAB1 1039
+#define IDC_CARD_GEN_PHONE 1043
+#define IDC_EDIT1 1043
+#define IDC_CARD_GEN_IM2 1044
+#define IDC_EDIT2 1044
+#define IDC_EDIT3 1045
+#define IDC_COMBO1 1045
+#define IDC_SENDFONTINFO 1046
+#define IDC_EDIT4 1046
+#define IDC_REMOVEHOT 1046
+#define IDC_EDIT5 1047
+#define IDC_REMOVEBLOCK 1047
+#define IDC_LISTREFRESH 1047
+#define IDC_NICKNAME 1048
+#define IDC_EDIT6 1048
+#define IDC_DISABLEHOTJUNK 1049
+#define IDC_EDIT7 1049
+#define IDC_EDIT8 1050
+#define IDC_COMBO2 1050
+#define IDC_EDIT9 1051
+#define IDC_CARD_GEN_IM3 1051
+#define IDC_EDIT10 1052
+#define IDC_COMBO3 1052
+#define IDC_NOTIFY_FIRSTMSG 1053
+#define IDC_COMBO4 1053
+#define IDC_DISABLE_ANOTHER_CONTACTS 1054
+#define IDC_CARD_GEN_IM4 1054
+#define IDC_ERRORS_USING_POPUPS 1056
+#define IDC_CARD_GEN_IM5 1056
+#define IDC_MAILER_APP 1057
+#define IDC_RUN_APP_ON_HOTMAIL 1058
+#define IDC_ENTER_MAILER_APP 1059
+#define IDC_DIRECTSERVER 1171
+#define IDC_YOURHOST 1172
+#define IDC_GATEWAYSERVER 1174
+#define IDC_DISABLEHOTMAILPOPUP 1301
+#define IDC_DISABLEHOTMAILTRAY 1302
+#define IDC_DISABLEHOTMAILCL 1303
+#define IDC_NEWMSNACCOUNTLINK 1438
+#define IDC_RESETSERVER 1472
+#define IDC_STATIC -1
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NO_MFC 1
+#define _APS_NEXT_RESOURCE_VALUE 139
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1048
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/protocols/MSN/src/stdafx.cpp b/protocols/MSN/src/stdafx.cpp
new file mode 100644
index 0000000000..01a3153a4d
--- /dev/null
+++ b/protocols/MSN/src/stdafx.cpp
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2012-14 Miranda NG team (http://miranda-ng.org)
+
+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 version 2
+of the License.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "msn_global.h" \ No newline at end of file
diff --git a/protocols/MSN/src/version.h b/protocols/MSN/src/version.h
new file mode 100644
index 0000000000..f3f5952fe3
--- /dev/null
+++ b/protocols/MSN/src/version.h
@@ -0,0 +1,33 @@
+/*
+Plugin of Miranda IM for communicating with users of the MSN Messenger protocol.
+
+Copyright (c) 2012-2014 Miranda NG Team
+Copyright (c) 2008-2009 Boris Krasnovskiy.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#define __MAJOR_VERSION 0
+#define __MINOR_VERSION 11
+#define __RELEASE_NUM 1
+#define __BUILD_NUM 1
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "MSN protocol"
+#define __DESCRIPTION "Microsoft Network (MSN) protocol support for Miranda NG."
+#define __AUTHOR "Boris Krasnovskiy, George Hazan, Richard Hughes"
+#define __AUTHOREMAIL "borkra@miranda-im.org"
+#define __COPYRIGHT "© 2001-2012 Richard Hughes, George Hazan, Boris Krasnovskiy"
+#define __AUTHORWEB "http://miranda-ng.org/p/MSN/"