summaryrefslogtreecommitdiff
path: root/protocols/Twitter/src
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Twitter/src')
-rw-r--r--protocols/Twitter/src/Base64Coder.cpp319
-rw-r--r--protocols/Twitter/src/Base64Coder.h69
-rw-r--r--protocols/Twitter/src/Debug.c127
-rw-r--r--protocols/Twitter/src/Debug.h75
-rw-r--r--protocols/Twitter/src/StringConv.cpp87
-rw-r--r--protocols/Twitter/src/StringConv.h51
-rw-r--r--protocols/Twitter/src/StringUtil.cpp111
-rw-r--r--protocols/Twitter/src/StringUtil.h39
-rw-r--r--protocols/Twitter/src/chat.cpp227
-rw-r--r--protocols/Twitter/src/common.h103
-rw-r--r--protocols/Twitter/src/connection.cpp689
-rw-r--r--protocols/Twitter/src/contacts.cpp306
-rw-r--r--protocols/Twitter/src/http.cpp32
-rw-r--r--protocols/Twitter/src/http.h38
-rw-r--r--protocols/Twitter/src/main.cpp119
-rw-r--r--protocols/Twitter/src/oauth.cpp670
-rw-r--r--protocols/Twitter/src/oauth.dev.h2
-rw-r--r--protocols/Twitter/src/oauth/Makefile.am8
-rw-r--r--protocols/Twitter/src/oauth/hash.c492
-rw-r--r--protocols/Twitter/src/oauth/oauth.c921
-rw-r--r--protocols/Twitter/src/oauth/oauth.h764
-rw-r--r--protocols/Twitter/src/oauth/oauth_http.c728
-rw-r--r--protocols/Twitter/src/oauth/sha1.c317
-rw-r--r--protocols/Twitter/src/oauth/xmalloc.c60
-rw-r--r--protocols/Twitter/src/oauth/xmalloc.h10
-rw-r--r--protocols/Twitter/src/proto.cpp590
-rw-r--r--protocols/Twitter/src/proto.h191
-rw-r--r--protocols/Twitter/src/resource.h50
-rw-r--r--protocols/Twitter/src/stdafx.h33
-rw-r--r--protocols/Twitter/src/stubs.cpp139
-rw-r--r--protocols/Twitter/src/targetver.h13
-rw-r--r--protocols/Twitter/src/tc2.cpp805
-rw-r--r--protocols/Twitter/src/tc2.h83
-rw-r--r--protocols/Twitter/src/theme.cpp179
-rw-r--r--protocols/Twitter/src/theme.h27
-rw-r--r--protocols/Twitter/src/tinyjson.hpp586
-rw-r--r--protocols/Twitter/src/twitter.cpp477
-rw-r--r--protocols/Twitter/src/twitter.h114
-rw-r--r--protocols/Twitter/src/ui.cpp589
-rw-r--r--protocols/Twitter/src/ui.h26
-rw-r--r--protocols/Twitter/src/utility.cpp231
-rw-r--r--protocols/Twitter/src/utility.h158
-rw-r--r--protocols/Twitter/src/version.h20
43 files changed, 10675 insertions, 0 deletions
diff --git a/protocols/Twitter/src/Base64Coder.cpp b/protocols/Twitter/src/Base64Coder.cpp
new file mode 100644
index 0000000000..4944034c3e
--- /dev/null
+++ b/protocols/Twitter/src/Base64Coder.cpp
@@ -0,0 +1,319 @@
+// Base64Coder.cpp: implementation of the Base64Coder class.
+// http://support.microsoft.com/kb/191239
+//////////////////////////////////////////////////////////////////////
+
+#include "Base64Coder.h"
+
+// Digits...
+static char Base64Digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+BOOL Base64Coder::m_Init = FALSE;
+char Base64Coder::m_DecodeTable[256];
+
+#ifndef PAGESIZE
+#define PAGESIZE 4096
+#endif
+
+#ifndef ROUNDTOPAGE
+#define ROUNDTOPAGE(a) (((a/4096)+1)*4096)
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// Construction/Destruction
+//////////////////////////////////////////////////////////////////////
+
+Base64Coder::Base64Coder()
+: m_pDBuffer(NULL),
+ m_pEBuffer(NULL),
+ m_nDBufLen(0),
+ m_nEBufLen(0)
+{
+
+}
+
+Base64Coder::~Base64Coder()
+{
+ if(m_pDBuffer != NULL)
+ delete [] m_pDBuffer;
+
+ if(m_pEBuffer != NULL)
+ delete [] m_pEBuffer;
+}
+
+LPCSTR Base64Coder::DecodedMessage() const
+{
+ return (LPCSTR) m_pDBuffer;
+}
+
+LPCSTR Base64Coder::EncodedMessage() const
+{
+ return (LPCSTR) m_pEBuffer;
+}
+
+void Base64Coder::AllocEncode(DWORD nSize)
+{
+ if(m_nEBufLen < nSize)
+ {
+ if(m_pEBuffer != NULL)
+ delete [] m_pEBuffer;
+
+ m_nEBufLen = ROUNDTOPAGE(nSize);
+ m_pEBuffer = new BYTE[m_nEBufLen];
+ }
+
+ ::ZeroMemory(m_pEBuffer, m_nEBufLen);
+ m_nEDataLen = 0;
+}
+
+void Base64Coder::AllocDecode(DWORD nSize)
+{
+ if(m_nDBufLen < nSize)
+ {
+ if(m_pDBuffer != NULL)
+ delete [] m_pDBuffer;
+
+ m_nDBufLen = ROUNDTOPAGE(nSize);
+ m_pDBuffer = new BYTE[m_nDBufLen];
+ }
+
+ ::ZeroMemory(m_pDBuffer, m_nDBufLen);
+ m_nDDataLen = 0;
+}
+
+void Base64Coder::SetEncodeBuffer(const PBYTE pBuffer, DWORD nBufLen)
+{
+ DWORD i = 0;
+
+ AllocEncode(nBufLen);
+ while(i < nBufLen)
+ {
+ if(!_IsBadMimeChar(pBuffer[i]))
+ {
+ m_pEBuffer[m_nEDataLen] = pBuffer[i];
+ m_nEDataLen++;
+ }
+
+ i++;
+ }
+}
+
+void Base64Coder::SetDecodeBuffer(const PBYTE pBuffer, DWORD nBufLen)
+{
+ AllocDecode(nBufLen);
+ ::CopyMemory(m_pDBuffer, pBuffer, nBufLen);
+ m_nDDataLen = nBufLen;
+}
+
+void Base64Coder::Encode(const PBYTE pBuffer, DWORD nBufLen)
+{
+ SetDecodeBuffer(pBuffer, nBufLen);
+ AllocEncode(nBufLen * 2);
+
+ TempBucket Raw;
+ DWORD nIndex = 0;
+
+ while((nIndex + 3) <= nBufLen)
+ {
+ Raw.Clear();
+ ::CopyMemory(&Raw, m_pDBuffer + nIndex, 3);
+ Raw.nSize = 3;
+ _EncodeToBuffer(Raw, m_pEBuffer + m_nEDataLen);
+ nIndex += 3;
+ m_nEDataLen += 4;
+ }
+
+ if(nBufLen > nIndex)
+ {
+ Raw.Clear();
+ Raw.nSize = (BYTE) (nBufLen - nIndex);
+ ::CopyMemory(&Raw, m_pDBuffer + nIndex, nBufLen - nIndex);
+ _EncodeToBuffer(Raw, m_pEBuffer + m_nEDataLen);
+ m_nEDataLen += 4;
+ }
+}
+
+/*void Base64Coder::Encode(LPCSTR szMessage)
+{
+ if(szMessage != NULL)
+ Base64Coder::Encode((const PBYTE)szMessage, strlen(szMessage));
+}*/
+
+void Base64Coder::Decode(const PBYTE pBuffer, DWORD dwBufLen)
+{
+ if(!Base64Coder::m_Init)
+ _Init();
+
+ SetEncodeBuffer(pBuffer, dwBufLen);
+
+ AllocDecode(dwBufLen);
+
+ TempBucket Raw;
+
+ DWORD nIndex = 0;
+
+ while((nIndex + 4) <= m_nEDataLen)
+ {
+ Raw.Clear();
+ Raw.nData[0] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex]];
+ Raw.nData[1] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 1]];
+ Raw.nData[2] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 2]];
+ Raw.nData[3] = Base64Coder::m_DecodeTable[m_pEBuffer[nIndex + 3]];
+
+ if(Raw.nData[2] == 255)
+ Raw.nData[2] = 0;
+ if(Raw.nData[3] == 255)
+ Raw.nData[3] = 0;
+
+ Raw.nSize = 4;
+ _DecodeToBuffer(Raw, m_pDBuffer + m_nDDataLen);
+ nIndex += 4;
+ m_nDDataLen += 3;
+ }
+
+ // If nIndex < m_nEDataLen, then we got a decode message without padding.
+ // We may want to throw some kind of warning here, but we are still required
+ // to handle the decoding as if it was properly padded.
+ if(nIndex < m_nEDataLen)
+ {
+ Raw.Clear();
+ for(DWORD i = nIndex; i < m_nEDataLen; i++)
+ {
+ Raw.nData[i - nIndex] = Base64Coder::m_DecodeTable[m_pEBuffer[i]];
+ Raw.nSize++;
+ if(Raw.nData[i - nIndex] == 255)
+ Raw.nData[i - nIndex] = 0;
+ }
+
+ _DecodeToBuffer(Raw, m_pDBuffer + m_nDDataLen);
+ m_nDDataLen += (m_nEDataLen - nIndex);
+ }
+}
+
+/*void Base64Coder::Decode(LPCSTR szMessage)
+{
+ if(szMessage != NULL)
+ Base64Coder::Decode((const PBYTE)szMessage, strlen(szMessage));
+}*/
+
+DWORD Base64Coder::_DecodeToBuffer(const TempBucket &Decode, PBYTE pBuffer)
+{
+ TempBucket Data;
+ DWORD nCount = 0;
+
+ _DecodeRaw(Data, Decode);
+
+ for(int i = 0; i < 3; i++)
+ {
+ pBuffer[i] = Data.nData[i];
+ if(pBuffer[i] != 255)
+ nCount++;
+ }
+
+ return nCount;
+}
+
+
+void Base64Coder::_EncodeToBuffer(const TempBucket &Decode, PBYTE pBuffer)
+{
+ TempBucket Data;
+
+ _EncodeRaw(Data, Decode);
+
+ for(int i = 0; i < 4; i++)
+ pBuffer[i] = Base64Digits[Data.nData[i]];
+
+ switch(Decode.nSize)
+ {
+ case 1:
+ pBuffer[2] = '=';
+ case 2:
+ pBuffer[3] = '=';
+ }
+}
+
+void Base64Coder::_DecodeRaw(TempBucket &Data, const TempBucket &Decode)
+{
+ BYTE nTemp;
+
+ Data.nData[0] = Decode.nData[0];
+ Data.nData[0] <<= 2;
+
+ nTemp = Decode.nData[1];
+ nTemp >>= 4;
+ nTemp &= 0x03;
+ Data.nData[0] |= nTemp;
+
+ Data.nData[1] = Decode.nData[1];
+ Data.nData[1] <<= 4;
+
+ nTemp = Decode.nData[2];
+ nTemp >>= 2;
+ nTemp &= 0x0F;
+ Data.nData[1] |= nTemp;
+
+ Data.nData[2] = Decode.nData[2];
+ Data.nData[2] <<= 6;
+ nTemp = Decode.nData[3];
+ nTemp &= 0x3F;
+ Data.nData[2] |= nTemp;
+}
+
+void Base64Coder::_EncodeRaw(TempBucket &Data, const TempBucket &Decode)
+{
+ BYTE nTemp;
+
+ Data.nData[0] = Decode.nData[0];
+ Data.nData[0] >>= 2;
+
+ Data.nData[1] = Decode.nData[0];
+ Data.nData[1] <<= 4;
+ nTemp = Decode.nData[1];
+ nTemp >>= 4;
+ Data.nData[1] |= nTemp;
+ Data.nData[1] &= 0x3F;
+
+ Data.nData[2] = Decode.nData[1];
+ Data.nData[2] <<= 2;
+
+ nTemp = Decode.nData[2];
+ nTemp >>= 6;
+
+ Data.nData[2] |= nTemp;
+ Data.nData[2] &= 0x3F;
+
+ Data.nData[3] = Decode.nData[2];
+ Data.nData[3] &= 0x3F;
+}
+
+BOOL Base64Coder::_IsBadMimeChar(BYTE nData)
+{
+ switch(nData)
+ {
+ case '\r': case '\n': case '\t': case ' ' :
+ case '\b': case '\a': case '\f': case '\v':
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+
+void Base64Coder::_Init()
+{ // Initialize Decoding table.
+
+ int i;
+
+ for(i = 0; i < 256; i++)
+ Base64Coder::m_DecodeTable[i] = -2;
+
+ for(i = 0; i < 64; i++)
+ {
+ Base64Coder::m_DecodeTable[Base64Digits[i]] = i;
+ Base64Coder::m_DecodeTable[Base64Digits[i]|0x80] = i;
+ }
+
+ Base64Coder::m_DecodeTable['='] = -1;
+ Base64Coder::m_DecodeTable['='|0x80] = -1;
+
+ Base64Coder::m_Init = TRUE;
+}
+
diff --git a/protocols/Twitter/src/Base64Coder.h b/protocols/Twitter/src/Base64Coder.h
new file mode 100644
index 0000000000..d65868070e
--- /dev/null
+++ b/protocols/Twitter/src/Base64Coder.h
@@ -0,0 +1,69 @@
+// Base64Coder.h: interface for the Base64Coder class.
+// http://support.microsoft.com/kb/191239
+//////////////////////////////////////////////////////////////////////
+
+#if !defined(AFX_BASE64CODER_H__B2E45717_0625_11D2_A80A_00C04FB6794C__INCLUDED_)
+#define AFX_BASE64CODER_H__B2E45717_0625_11D2_A80A_00C04FB6794C__INCLUDED_
+
+#if _MSC_VER >= 1000
+#pragma once
+#endif // _MSC_VER >= 1000
+
+#ifdef _AFXDLL
+ #include <afx.h>
+ typedef CString String;
+#else
+ #include <windows.h>
+ #include <string>
+ typedef std::string String;
+#endif
+
+class Base64Coder
+{
+ // Internal bucket class.
+ class TempBucket
+ {
+ public:
+ BYTE nData[4];
+ BYTE nSize;
+ void Clear() { ::ZeroMemory(nData, 4); nSize = 0; };
+ };
+
+ PBYTE m_pDBuffer;
+ PBYTE m_pEBuffer;
+ DWORD m_nDBufLen;
+ DWORD m_nEBufLen;
+ DWORD m_nDDataLen;
+ DWORD m_nEDataLen;
+
+public:
+ Base64Coder();
+ virtual ~Base64Coder();
+
+public:
+ virtual void Encode(const PBYTE, DWORD);
+ virtual void Decode(const PBYTE, DWORD);
+ //virtual void Encode(LPCSTR sMessage);
+ //virtual void Decode(LPCSTR sMessage);
+
+ virtual LPCSTR DecodedMessage() const;
+ virtual LPCSTR EncodedMessage() const;
+
+ virtual void AllocEncode(DWORD);
+ virtual void AllocDecode(DWORD);
+ virtual void SetEncodeBuffer(const PBYTE pBuffer, DWORD nBufLen);
+ virtual void SetDecodeBuffer(const PBYTE pBuffer, DWORD nBufLen);
+
+protected:
+ virtual void _EncodeToBuffer(const TempBucket &Decode, PBYTE pBuffer);
+ virtual ULONG _DecodeToBuffer(const TempBucket &Decode, PBYTE pBuffer);
+ virtual void _EncodeRaw(TempBucket &, const TempBucket &);
+ virtual void _DecodeRaw(TempBucket &, const TempBucket &);
+ virtual BOOL _IsBadMimeChar(BYTE);
+
+ static char m_DecodeTable[256];
+ static BOOL m_Init;
+ void _Init();
+};
+
+#endif // !defined(AFX_BASE64CODER_H__B2E45717_0625_11D2_A80A_00C04FB6794C__INCLUDED_)
diff --git a/protocols/Twitter/src/Debug.c b/protocols/Twitter/src/Debug.c
new file mode 100644
index 0000000000..947df7a536
--- /dev/null
+++ b/protocols/Twitter/src/Debug.c
@@ -0,0 +1,127 @@
+/*
+
+Copyright (c) 2010 Brook Miles
+
+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.
+
+*/
+
+#define WIN32_LEAN_AND_MEAN
+#include <tchar.h>
+#include <windows.h>
+#include <strsafe.h>
+#include <stdlib.h>
+
+#include "Debug.h"
+
+#define MESSAGEBOXFMT_BUF_SIZE 256
+
+#define DEBUG_REPORT_BUF_SIZE 128
+#define DEBUG_ABORT_CODE 0xBADD061E
+
+#define DEBUG_TRACE_BUF_SIZE 1024
+#define DEBUG_TRACE_FMT_SIZE 128
+
+#define SIZEOF(x) (sizeof(x)/sizeof(*x))
+
+UINT MessageBoxFmt(HWND hwnd, LPCTSTR pszTextFmt, LPCTSTR pszTitle, UINT uType, ...)
+{
+ TCHAR buf[MESSAGEBOXFMT_BUF_SIZE];
+ va_list args;
+ va_start(args, uType);
+ _vsntprintf_s(buf, SIZEOF(buf), _TRUNCATE, pszTextFmt, args);
+ va_end(args);
+ return MessageBox(hwnd, buf, pszTitle, uType);
+}
+
+#if defined(_DEBUG)
+/////////////////////////////////////////////////////////////////////////////
+// _DebugReport is used by the ASSERTE macro. Since the preprocessor
+// constants __FILE__ and __LINE__ are always ANSI, it takes LPCSTR
+// instead of LPCTSTR, and then converts to wide strings.
+//
+
+int _DebugReport(LPCSTR pszFile, int iLine, LPCSTR pszExpr, UINT gle, BOOL bConsoleOnly)
+{
+ char buf[DEBUG_REPORT_BUF_SIZE];
+ wchar_t wbuf[DEBUG_REPORT_BUF_SIZE];
+ wchar_t wbufTitle[DEBUG_REPORT_BUF_SIZE];
+ UINT ret;
+
+ size_t tmp = 0;
+
+ _snprintf_s(buf, DEBUG_REPORT_BUF_SIZE, _TRUNCATE, "Assertion Failed! (0x%08X)", gle);
+ mbstowcs_s(&tmp, wbufTitle, SIZEOF(wbufTitle), buf, _TRUNCATE);
+
+ _snprintf_s(buf, DEBUG_REPORT_BUF_SIZE, _TRUNCATE, "%s:%d\r\n%s",
+ strrchr(pszFile, '\\') + 1, iLine, pszExpr);
+ mbstowcs_s(&tmp, wbuf, SIZEOF(wbuf), buf, _TRUNCATE);
+
+ OutputDebugString(wbufTitle);
+ OutputDebugString(_T("\r\n"));
+ OutputDebugString(wbuf);
+ OutputDebugString(_T("\r\n"));
+
+ if(!bConsoleOnly)
+ {
+ ret = MessageBox(NULL, wbuf, wbufTitle, MB_ICONERROR | MB_ABORTRETRYIGNORE);
+ if(ret == IDABORT)
+ ExitThread(DEBUG_ABORT_CODE);
+ return (ret == IDRETRY);
+ }
+ else
+ {
+ return TRUE;
+ }
+}
+
+void _TRACE(LPCSTR fmt, ...)
+{
+ int len;
+ TCHAR buf[DEBUG_TRACE_BUF_SIZE + 3] = _T("");
+ va_list va;
+
+
+ wchar_t tfmt[DEBUG_TRACE_FMT_SIZE];
+ size_t tmp = 0;
+ mbstowcs_s(&tmp, tfmt, SIZEOF(tfmt), fmt, _TRUNCATE);
+
+
+ va_start(va, fmt);
+ len = _vsntprintf_s(buf, SIZEOF(buf), _TRUNCATE, tfmt, va);
+ va_end(va);
+
+ if(len >= -1)
+ {
+ if(len == -1)
+ {
+ len = DEBUG_TRACE_BUF_SIZE;
+ }
+ _tcscpy_s(&buf[len], SIZEOF(buf) - len, _T("\r\n"));
+ }
+ else
+ {
+ _tcscpy_s(buf, SIZEOF(buf), _T("_DEBUG FORMATTING ERROR\r\n"));
+ }
+
+ //_tprintf(buf);
+ OutputDebugString(buf);
+}
+
+#endif//defined(_DEBUG) \ No newline at end of file
diff --git a/protocols/Twitter/src/Debug.h b/protocols/Twitter/src/Debug.h
new file mode 100644
index 0000000000..b8814193f5
--- /dev/null
+++ b/protocols/Twitter/src/Debug.h
@@ -0,0 +1,75 @@
+/*
+
+Copyright (c) 2010 Brook Miles
+
+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 _DEBUG_H_INCLUDED_
+#define _DEBUG_H_INCLUDED_
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// MessageBoxFmt
+
+UINT MessageBoxFmt(HWND hwnd, LPCTSTR pszTextFmt, LPCTSTR pszTitle, UINT uType, ...);
+
+/////////////////////////////////////////////////////////////////////////////
+// DebugReport
+
+#ifdef _DEBUG
+ #ifdef ASSERT_NO_BREAK
+ int _DebugReport(LPCSTR pszFile, int iLine, LPCSTR pszExpr, UINT gle, BOOL bConsoleOnly);
+ #ifdef _ASSERTE
+ #undef _ASSERTE
+ #endif
+ #define _ASSERTE(x) if(!(x) && _DebugReport(__FILE__, __LINE__, #x, GetLastError(), true)) while(0)
+ #define _ASSERTC(x) if(!(x) && _DebugReport(__FILE__, __LINE__, #x, GetLastError(), true)) while(0)
+ #else
+ int _DebugReport(LPCSTR pszFile, int iLine, LPCSTR pszExpr, UINT gle, BOOL bConsoleOnly);
+ #ifdef _ASSERTE
+ #undef _ASSERTE
+ #endif
+ #define _ASSERTE(x) if(!(x) && _DebugReport(__FILE__, __LINE__, #x, GetLastError(), false)) DebugBreak()
+ #define _ASSERTC(x) if(!(x) && _DebugReport(__FILE__, __LINE__, #x, GetLastError(), true)) DebugBreak()
+ #endif
+#else
+ #ifndef _ASSERTE
+ #define _ASSERTE(x)
+ #endif
+ #define _ASSERTC(x)
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// DebugTrace
+
+#if defined(_DEBUG)
+ void _TRACE(LPCSTR fmt, ...);
+#else
+ __inline void _TRACE(LPCSTR fmt, ...) {}
+#endif//defined(_DEBUG)
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif//_DEBUG_H_INCLUDED_
diff --git a/protocols/Twitter/src/StringConv.cpp b/protocols/Twitter/src/StringConv.cpp
new file mode 100644
index 0000000000..c0e4f9221c
--- /dev/null
+++ b/protocols/Twitter/src/StringConv.cpp
@@ -0,0 +1,87 @@
+/*
+
+Copyright (c) 2010 Brook Miles
+
+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.
+
+*/
+
+#include <windows.h>
+#include <string>
+#include "StringConv.h"
+
+std::string WideToMB(const std::wstring& str, UINT codePage)
+{
+ std::string ret;
+ if(str.length() > 0)
+ {
+ DWORD mbChars = WideCharToMultiByte(codePage, 0, str.c_str(), -1, NULL, 0, NULL, NULL);
+ _ASSERTE(mbChars > 0);
+ if(mbChars > 0)
+ {
+ char* buf = new char[mbChars];
+ _ASSERTE( buf != NULL );
+ if( buf != NULL )
+ {
+ ZeroMemory(buf, mbChars);
+
+ DWORD charsConverted = WideCharToMultiByte(codePage, 0, str.c_str(), -1, buf, mbChars, NULL, NULL);
+ _ASSERTE( charsConverted > 0 );
+ _ASSERTE( charsConverted <= mbChars );
+
+ buf[mbChars - 1] = 0;
+ ret = buf;
+
+ delete[] buf;
+ }
+ }
+ }
+ return ret;
+}
+
+std::wstring MBToWide(const std::string& str, UINT codePage)
+{
+ std::wstring ret;
+ if(str.length() > 0)
+ {
+ DWORD wChars = MultiByteToWideChar(codePage, 0, str.c_str(), -1, NULL, 0);
+ _ASSERTE(wChars > 0);
+ if(wChars > 0)
+ {
+ wchar_t* buf = new wchar_t[wChars];
+ _ASSERTE( buf != NULL );
+ if( buf != NULL )
+ {
+ size_t bytesNeeded = sizeof(wchar_t)*wChars;
+ ZeroMemory(buf, bytesNeeded);
+
+ DWORD charsConverted = MultiByteToWideChar(codePage, 0, str.c_str(), -1, buf, wChars);
+ _ASSERTE( charsConverted > 0 );
+ _ASSERTE( charsConverted <= wChars );
+
+ buf[wChars - 1] = 0;
+ ret = buf;
+
+ delete[] buf;
+ }
+ }
+ }
+ return ret;
+}
+
diff --git a/protocols/Twitter/src/StringConv.h b/protocols/Twitter/src/StringConv.h
new file mode 100644
index 0000000000..ec25c15ef7
--- /dev/null
+++ b/protocols/Twitter/src/StringConv.h
@@ -0,0 +1,51 @@
+/*
+
+Copyright (c) 2010 Brook Miles
+
+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.
+
+*/
+
+#include <windows.h>
+
+#ifndef StringConv_h__
+#define StringConv_h__
+
+#pragma once
+
+
+std::string WideToMB(const std::wstring& str, UINT codePage = CP_ACP);
+std::wstring MBToWide(const std::string& str, UINT codePage = CP_ACP);
+
+inline std::string WideToUTF8(const std::wstring& str) { return WideToMB(str, CP_UTF8); }
+inline std::wstring UTF8ToWide(const std::string& str) { return MBToWide(str, CP_UTF8); }
+
+inline std::string ANSIToUTF8(const std::string& str, UINT codePage = CP_ACP) { return WideToUTF8(MBToWide(str, codePage)); }
+inline std::string UTF8ToANSI(const std::string& str, UINT codePage = CP_ACP) { return WideToMB(UTF8ToWide(str), codePage); }
+
+
+#define TCHARToUTF8 WideToUTF8
+#define UTF8ToTCHAR UTF8ToWide
+#define TCHARToWide
+#define WideToTCHAR
+#define TCHARToMB WideToMB
+#define MBToTCHAR MBToWide
+
+
+#endif // StringConv_h__
diff --git a/protocols/Twitter/src/StringUtil.cpp b/protocols/Twitter/src/StringUtil.cpp
new file mode 100644
index 0000000000..a395b67fa7
--- /dev/null
+++ b/protocols/Twitter/src/StringUtil.cpp
@@ -0,0 +1,111 @@
+/*
+
+Copyright (c) 2010 Brook Miles
+
+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.
+
+*/
+
+#include "stdafx.h"
+
+void Split(const tstring& str, std::vector<tstring>& out, TCHAR sep, bool includeEmpty)
+{
+ unsigned start = 0;
+ unsigned end = 0;
+
+ while(true)
+ {
+ if(end == str.size() || str[end] == sep)
+ {
+ if(end > start || includeEmpty)
+ {
+ out.push_back(str.substr(start, end - start));
+ }
+
+ if(end == str.size())
+ {
+ break;
+ }
+
+ ++end;
+ start = end;
+ }
+ else
+ {
+ ++end;
+ }
+ }
+}
+
+tstring GetWord(const tstring& str, unsigned index, bool getRest)
+{
+ unsigned start = 0;
+ unsigned end = 0;
+
+ unsigned count = 0;
+
+ while(true)
+ {
+ if(end == str.size() || str[end] == _T(' '))
+ {
+ if(end > start)
+ {
+ if(count == index)
+ {
+ if(getRest)
+ {
+ return str.substr(start);
+ }
+ else
+ {
+ return str.substr(start, end - start);
+ }
+ }
+ ++count;
+ }
+
+ if(end == str.size())
+ {
+ break;
+ }
+
+ ++end;
+ start = end;
+ }
+ else
+ {
+ ++end;
+ }
+ }
+ return _T("");
+}
+
+// takes a pointer to a string, and does an inplace replace of all the characters "from" found
+// within the string with "to". returns the pointer to the string which is kinda silly IMO
+std::string& replaceAll(std::string& context, const std::string& from, const std::string& to)
+{
+ size_t lookHere = 0;
+ size_t foundHere;
+ while((foundHere = context.find(from, lookHere)) != std::string::npos)
+ {
+ context.replace(foundHere, from.size(), to);
+ lookHere = foundHere + to.size();
+ }
+ return context;
+}
diff --git a/protocols/Twitter/src/StringUtil.h b/protocols/Twitter/src/StringUtil.h
new file mode 100644
index 0000000000..902e262c02
--- /dev/null
+++ b/protocols/Twitter/src/StringUtil.h
@@ -0,0 +1,39 @@
+/*
+
+Copyright (c) 2010 Brook Miles
+
+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 _STRINGUTIL_H_INCLUDED_
+#define _STRINGUTIL_H_INCLUDED_
+
+void Split(const tstring& str, std::vector<tstring>& out, TCHAR sep, bool includeEmpty);
+tstring GetWord(const tstring& str, unsigned index, bool getRest = false);
+
+std::string& replaceAll(std::string& context, const std::string& from, const std::string& to);
+
+
+inline bool Compare(const tstring& one, const tstring& two, bool caseSensitive)
+{
+ return caseSensitive ? (one == two) : (_tcsicmp(one.c_str(), two.c_str()) == 0);
+}
+
+#endif//_STRINGUTIL_H_INCLUDED_
diff --git a/protocols/Twitter/src/chat.cpp b/protocols/Twitter/src/chat.cpp
new file mode 100644
index 0000000000..cafb46025c
--- /dev/null
+++ b/protocols/Twitter/src/chat.cpp
@@ -0,0 +1,227 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "proto.h"
+
+#include <set>
+#include <ctime>
+
+void TwitterProto::UpdateChat(const twitter_user &update)
+{
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(m_tszUserName);
+ gcd.iType = GC_EVENT_MESSAGE;
+
+ GCEVENT gce = {sizeof(gce)};
+ gce.pDest = &gcd;
+ gce.dwFlags = GC_TCHAR|GCEF_ADDTOLOG;
+ gce.pDest = &gcd;
+ gce.ptszUID = mir_a2t(update.username.c_str());
+ gce.bIsMe = (update.username == twit_.get_username());
+ //TODO: write code here to replace % with %% in update.status.text (which is a std::string)
+
+ std::string chatText = update.status.text;
+
+ replaceAll(chatText, "%", "%%");
+
+ gce.ptszText = mir_a2t_cp(chatText.c_str(),CP_UTF8);
+ //gce.ptszText = mir_a2t_cp(update.status.text.c_str(),CP_UTF8);
+ gce.time = static_cast<DWORD>(update.status.time);
+
+ DBVARIANT nick;
+ HANDLE hContact = UsernameToHContact(update.username.c_str());
+ if(hContact && !DBGetContactSettingString(hContact,"CList","MyHandle",&nick))
+ {
+ gce.ptszNick = mir_a2t(nick.pszVal);
+ DBFreeVariant(&nick);
+ }
+ else
+ gce.ptszNick = mir_a2t(update.username.c_str());
+
+ CallServiceSync(MS_GC_EVENT,0,reinterpret_cast<LPARAM>(&gce));
+
+ mir_free(const_cast<TCHAR*>(gce.ptszNick));
+ mir_free(const_cast<TCHAR*>(gce.ptszUID));
+ mir_free(const_cast<TCHAR*>(gce.ptszText));
+}
+
+int TwitterProto::OnChatOutgoing(WPARAM wParam,LPARAM lParam)
+{
+ GCHOOK *hook = reinterpret_cast<GCHOOK*>(lParam);
+ if ( strcmp(hook->pDest->pszModule, m_szModuleName))
+ return 0;
+
+ switch(hook->pDest->iType) {
+ case GC_USER_MESSAGE:
+ LOG ( _T("**Chat - Outgoing message: %s"), hook->ptszText);
+ {
+ mir_ptr<char> text( mir_utf8encodeT(hook->ptszText));
+
+ std::string tweet(text);
+ replaceAll(tweet, "%%", "%"); // the chat plugin will turn "%" into "%%", so we have to change it back :/
+
+ char *varTweet = mir_strdup( tweet.c_str());
+ ForkThread(&TwitterProto::SendTweetWorker, this, varTweet);
+ }
+ break;
+
+ case GC_USER_PRIVMESS:
+ {
+ mir_ptr<char> text( mir_t2a(hook->ptszUID));
+ CallService(MS_MSG_SENDMESSAGE, WPARAM(UsernameToHContact(text)), 0);
+ }
+ break;
+ }
+
+ return 0;
+}
+
+// TODO: remove nick?
+void TwitterProto::AddChatContact(const char *name,const char *nick)
+{
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(m_tszUserName);
+ gcd.iType = GC_EVENT_JOIN;
+
+ GCEVENT gce = {sizeof(gce)};
+ gce.pDest = &gcd;
+ gce.dwFlags = GC_TCHAR;
+ gce.ptszNick = mir_a2t(nick ? nick:name);
+ gce.ptszUID = mir_a2t(name);
+ gce.bIsMe = false;
+ gce.ptszStatus = _T("Normal");
+ gce.time = static_cast<DWORD>(time(0));
+ CallServiceSync(MS_GC_EVENT,0,reinterpret_cast<LPARAM>(&gce));
+
+ mir_free(const_cast<TCHAR*>(gce.ptszNick));
+ mir_free(const_cast<TCHAR*>(gce.ptszUID));
+}
+
+void TwitterProto::DeleteChatContact(const char *name)
+{
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(m_tszUserName);
+ gcd.iType = GC_EVENT_PART;
+
+ GCEVENT gce = {sizeof(gce)};
+ gce.pDest = &gcd;
+ gce.dwFlags = GC_TCHAR;
+ gce.ptszNick = mir_a2t(name);
+ gce.ptszUID = gce.ptszNick;
+ gce.time = static_cast<DWORD>(time(0));
+ CallServiceSync(MS_GC_EVENT,0,reinterpret_cast<LPARAM>(&gce));
+
+ mir_free(const_cast<TCHAR*>(gce.ptszNick));
+}
+
+int TwitterProto::OnJoinChat(WPARAM,LPARAM suppress)
+{
+ GCSESSION gcw = {sizeof(gcw)};
+
+ // ***** Create the group chat session
+ gcw.dwFlags = GC_TCHAR;
+ gcw.iType = GCW_CHATROOM;
+ gcw.pszModule = m_szModuleName;
+ gcw.ptszName = m_tszUserName;
+ gcw.ptszID = m_tszUserName;
+ CallServiceSync(MS_GC_NEWSESSION, 0, (LPARAM)&gcw);
+
+ if(m_iStatus != ID_STATUS_ONLINE)
+ return 0;
+
+ // ***** Create a group
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(m_tszUserName);
+
+ GCEVENT gce = {sizeof(gce)};
+ gce.pDest = &gcd;
+ gce.dwFlags = GC_TCHAR;
+
+ gcd.iType = GC_EVENT_ADDGROUP;
+ gce.ptszStatus = _T("Normal");
+ CallServiceSync(MS_GC_EVENT,0,reinterpret_cast<LPARAM>(&gce));
+
+ // ***** Hook events
+ HookProtoEvent(ME_GC_EVENT,&TwitterProto::OnChatOutgoing,this);
+
+ // Note: Initialization will finish up in SetChatStatus, called separately
+ if(!suppress)
+ SetChatStatus(m_iStatus);
+
+ in_chat_ = true;
+ return 0;
+}
+
+int TwitterProto::OnLeaveChat(WPARAM,LPARAM)
+{
+ in_chat_ = false;
+
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(m_tszUserName);
+ gcd.iType = GC_EVENT_CONTROL;
+
+ GCEVENT gce = {sizeof(gce)};
+ gce.dwFlags = GC_TCHAR;
+ gce.pDest = &gcd;
+
+ CallServiceSync(MS_GC_EVENT,SESSION_OFFLINE, reinterpret_cast<LPARAM>(&gce));
+ CallServiceSync(MS_GC_EVENT,SESSION_TERMINATE,reinterpret_cast<LPARAM>(&gce));
+
+ return 0;
+}
+
+void TwitterProto::SetChatStatus(int status)
+{
+ GCDEST gcd = { m_szModuleName };
+ gcd.ptszID = const_cast<TCHAR*>(m_tszUserName);
+ gcd.iType = GC_EVENT_CONTROL;
+
+ GCEVENT gce = {sizeof(gce)};
+ gce.dwFlags = GC_TCHAR;
+ gce.pDest = &gcd;
+
+ if(status == ID_STATUS_ONLINE)
+ {
+ // Add all friends to contact list
+ for(HANDLE hContact = db_find_first();
+ hContact;
+ hContact = db_find_next(hContact))
+ {
+ if(!IsMyContact(hContact))
+ continue;
+
+ DBVARIANT uid,nick;
+ if( DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_UN,&uid))
+ continue;
+
+ if( !DBGetContactSettingString(hContact,"CList","MyHandle",&nick))
+ AddChatContact(uid.pszVal,nick.pszVal);
+ else
+ AddChatContact(uid.pszVal);
+
+ DBFreeVariant(&nick);
+ DBFreeVariant(&uid);
+ }
+
+ // For some reason, I have to send an INITDONE message, even if I'm not actually
+ // initializing the room...
+ CallServiceSync(MS_GC_EVENT,SESSION_INITDONE,reinterpret_cast<LPARAM>(&gce));
+ CallServiceSync(MS_GC_EVENT,SESSION_ONLINE, reinterpret_cast<LPARAM>(&gce));
+ }
+ else
+ CallServiceSync(MS_GC_EVENT,SESSION_OFFLINE,reinterpret_cast<LPARAM>(&gce));
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/common.h b/protocols/Twitter/src/common.h
new file mode 100644
index 0000000000..b9fade2a9f
--- /dev/null
+++ b/protocols/Twitter/src/common.h
@@ -0,0 +1,103 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#define MIRANDA_VER 0x0A00
+
+#include <string>
+using std::string;
+using std::wstring;
+#include <map>
+using std::map;
+#include <vector>
+using std::vector;
+#include <list>
+using std::list;
+#include <algorithm>
+using std::min;
+
+#include <windows.h>
+
+#include "resource.h"
+
+#pragma warning(push)
+# pragma warning(disable:4312)
+#include <newpluginapi.h>
+#include <m_avatars.h>
+#include <m_button.h>
+#include <m_chat.h>
+#include <m_clc.h>
+#include <m_clist.h>
+#include <m_clistint.h>
+#include <m_clui.h>
+//#include "m_cluiframes.h"
+#include <m_database.h>
+#include <m_history.h>
+#include <m_idle.h>
+#include <m_langpack.h>
+#include <m_message.h>
+#include <m_netlib.h>
+#include <m_options.h>
+#include <m_popup.h>
+#include <m_protocols.h>
+#include <m_protomod.h>
+#include <m_protosvc.h>
+#include <m_skin.h>
+#include <statusmodes.h>
+#include <m_system.h>
+#include <m_userinfo.h>
+#include <m_addcontact.h>
+#include <m_icolib.h>
+#include <m_utils.h>
+#include <m_system_cpp.h>
+#include <m_hotkeys.h>
+#include <win2k.h>
+#pragma warning(pop)
+
+extern HINSTANCE g_hInstance;
+
+#define TWITTER_KEY_NICK "Nick" // we need one called Nick for the chat thingo to work
+#define TWITTER_KEY_UN "Username"
+#define TWITTER_KEY_PASS "Password"
+#define TWITTER_KEY_OAUTH_PIN "OAuthPIN"
+#define TWITTER_KEY_OAUTH_TOK "OAuthToken"
+#define TWITTER_KEY_OAUTH_TOK_SECRET "OAuthTokenSecret"
+#define TWITTER_KEY_OAUTH_ACCESS_TOK "OAuthAccessToken"
+#define TWITTER_KEY_OAUTH_ACCESS_TOK_SECRET "OAuthAccessTokenSecret"
+#define TWITTER_KEY_BASEURL "BaseURL"
+#define TWITTER_KEY_CHATFEED "ChatFeed"
+#define TWITTER_KEY_POLLRATE "PollRate"
+#define TWITTER_KEY_GROUP "DefaultGroup"
+
+#define TWITTER_KEY_POPUP_SHOW "Popup/Show"
+#define TWITTER_KEY_POPUP_SIGNON "Popup/Signon"
+#define TWITTER_KEY_POPUP_COLBACK "Popup/ColorBack"
+#define TWITTER_KEY_POPUP_COLTEXT "Popup/ColorText"
+#define TWITTER_KEY_POPUP_TIMEOUT "Popup/Timeout"
+
+#define TWITTER_KEY_TWEET_TO_MSG "TweetToMsg"
+
+#define TWITTER_KEY_SINCEID "SinceID"
+#define TWITTER_KEY_DMSINCEID "DMSinceID"
+#define TWITTER_KEY_NEW "NewAcc"
+
+#define TWITTER_KEY_AV_URL "AvatarURL"
+
+#define TWITTER_DB_EVENT_TYPE_TWEET 2718
+
+#define WM_SETREPLY WM_APP+10
diff --git a/protocols/Twitter/src/connection.cpp b/protocols/Twitter/src/connection.cpp
new file mode 100644
index 0000000000..488bd3e657
--- /dev/null
+++ b/protocols/Twitter/src/connection.cpp
@@ -0,0 +1,689 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "proto.h"
+//#include "tc2.h"
+#include "twitter.h"
+
+void CALLBACK TwitterProto::APC_callback(ULONG_PTR p)
+{
+ reinterpret_cast<TwitterProto*>(p)->LOG( _T("***** Executing APC"));
+}
+
+template<typename T>
+inline static T db_pod_get(HANDLE hContact,const char *module,const char *setting,
+ T errorValue)
+{
+ DBVARIANT dbv;
+ DBCONTACTGETSETTING cgs;
+
+ cgs.szModule = module;
+ cgs.szSetting = setting;
+ cgs.pValue = &dbv;
+ if(CallService(MS_DB_CONTACT_GETSETTING,(WPARAM)hContact,(LPARAM)&cgs))
+ return errorValue;
+
+ // TODO: remove this, it's just a temporary workaround
+ if(dbv.type == DBVT_DWORD)
+ return dbv.dVal;
+
+ if(dbv.cpbVal != sizeof(T))
+ return errorValue;
+ return *reinterpret_cast<T*>(dbv.pbVal);
+}
+
+template<typename T>
+inline static INT_PTR db_pod_set(HANDLE hContact,const char *module,const char *setting,
+ T val)
+{
+ DBCONTACTWRITESETTING cws;
+
+ cws.szModule = module;
+ cws.szSetting = setting;
+ cws.value.type = DBVT_BLOB;
+ cws.value.cpbVal = sizeof(T);
+ cws.value.pbVal = reinterpret_cast<BYTE*>(&val);
+ return CallService(MS_DB_CONTACT_WRITESETTING,(WPARAM)hContact,(LPARAM)&cws);
+}
+
+void TwitterProto::SignOn(void*)
+{
+ LOG( _T("***** Beginning SignOn process"));
+ WaitForSingleObject(&signon_lock_,INFINITE);
+
+ // Kill the old thread if it's still around
+ // this doesn't seem to work.. should we wait infinitely?
+ if(hMsgLoop_)
+ {
+ LOG( _T("***** Requesting MessageLoop to exit"));
+ QueueUserAPC(APC_callback,hMsgLoop_,(ULONG_PTR)this);
+ LOG( _T("***** Waiting for old MessageLoop to exit"));
+ //WaitForSingleObject(hMsgLoop_,INFINITE);
+ WaitForSingleObject(hMsgLoop_,180000);
+ CloseHandle(hMsgLoop_);
+ }
+ if(NegotiateConnection()) // Could this be? The legendary Go Time??
+ {
+ if(!in_chat_ && db_get_b(0,m_szModuleName,TWITTER_KEY_CHATFEED,0))
+ OnJoinChat(0,true);
+
+ SetAllContactStatuses(ID_STATUS_ONLINE);
+ hMsgLoop_ = ForkThreadEx(&TwitterProto::MessageLoop,this);
+ }
+
+ ReleaseMutex(signon_lock_);
+ LOG( _T("***** SignOn complete"));
+}
+
+bool TwitterProto::NegotiateConnection()
+{
+ LOG( _T("***** Negotiating connection with Twitter"));
+ disconnectionCount = 0;
+
+ // saving the current status to a temp var
+ int old_status = m_iStatus;
+ DBVARIANT dbv;
+
+ wstring oauthToken;
+ wstring oauthTokenSecret;
+ wstring oauthAccessToken;
+ wstring oauthAccessTokenSecret;
+ string screenName;
+
+ INT_PTR dbTOK = DBGetContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_TOK,&dbv);
+ if (!dbTOK) {
+ oauthToken = dbv.pwszVal;
+ DBFreeVariant(&dbv);
+ //WLOG("**NegotiateConnection - we have an oauthToken already in the db - %s", oauthToken);
+ }
+
+ INT_PTR dbTOKSec = DBGetContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_TOK_SECRET,&dbv);
+ if (!dbTOKSec) {
+ oauthTokenSecret = dbv.pwszVal;
+ DBFreeVariant(&dbv);
+ //WLOG("**NegotiateConnection - we have an oauthTokenSecret already in the db - %s", oauthTokenSecret);
+ }
+
+ INT_PTR dbName = DBGetContactSettingString(0,m_szModuleName,TWITTER_KEY_NICK,&dbv);
+ if (!dbName) {
+ screenName = dbv.pszVal;
+ DBFreeVariant(&dbv);
+ //WLOG("**NegotiateConnection - we have a username already in the db - %s", screenName);
+ }
+ else {
+ dbName = DBGetContactSettingString(0,m_szModuleName,TWITTER_KEY_UN,&dbv);
+ if (!dbName) {
+ screenName = dbv.pszVal;
+ DBWriteContactSettingString(0,m_szModuleName,TWITTER_KEY_NICK,dbv.pszVal);
+ //WLOG("**NegotiateConnection - we have a username already in the db - %s", screenName);
+ }
+ DBFreeVariant(&dbv);
+ }
+
+
+ if((oauthToken.size() <= 1) || (oauthTokenSecret.size() <= 1)) {
+ // first, reset all the keys so we can start fresh
+ resetOAuthKeys();
+ LOG( _T("**NegotiateConnection - Reset OAuth Keys"));
+
+ //twit_.set_credentials(ConsumerKey, ConsumerSecret, oauthAccessToken, oauthAccessTokenSecret, L"", false);
+ // i think i was doin the wrong thing here.. i was setting the credentials as oauthAccessToken instead of oauthToken
+ // have to test..
+ LOG( _T("**NegotiateConnection - Setting Consumer Keys..."));
+ /*WLOG("**NegotiateConnection - sending set_cred: consumerKey is %s", ConsumerKey);
+ WLOG("**NegotiateConnection - sending set_cred: consumerSecret is %s", ConsumerSecret);
+ WLOG("**NegotiateConnection - sending set_cred: oauthToken is %s", oauthToken);
+ WLOG("**NegotiateConnection - sending set_cred: oauthTokenSecret is %s", oauthTokenSecret);
+ LOG("**NegotiateConnection - sending set_cred: no pin");*/
+ twit_.set_credentials("", ConsumerKey, ConsumerSecret, oauthToken, oauthTokenSecret, L"", false);
+ LOG( _T("**NegotiateConnection - Requesting oauthTokens"));
+ http::response resp = twit_.request_token();
+
+ //wstring rdata_WSTR(resp.data.length(),L' ');
+ //std::copy(resp.data.begin(), resp.data.end(), rdata_WSTR.begin());
+ wstring rdata_WSTR = UTF8ToWide(resp.data);
+
+ //WLOG("**NegotiateConnection - REQUEST TOKEN IS %s", rdata_WSTR);
+ OAuthParameters response = twit_.ParseQueryString(rdata_WSTR);
+ oauthToken = response[L"oauth_token"];
+ oauthTokenSecret = response[L"oauth_token_secret"];
+ //WLOG("**NegotiateConnection - oauthToken is %s", oauthToken);
+ //WLOG("**NegotiateConnection - oauthTokenSecret is %s", oauthTokenSecret);
+
+ if (oauthToken.length() < 1) {
+ ShowPopup("OAuth Tokens not received, check your internet connection?", 1);
+ LOG( _T("**NegotiateConnection - OAuth tokens not received, stopping before we open the web browser.."));
+ return false;
+ }
+
+ //write those bitches to the db foe latta
+ DBWriteContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_TOK,oauthToken.c_str());
+ DBWriteContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_TOK_SECRET,oauthTokenSecret.c_str());
+
+ // this looks like bad code.. can someone clean this up please? or confirm that it's ok
+ wchar_t buf[1024] = {};
+ swprintf_s(buf, SIZEOF(buf), AuthorizeUrl.c_str(), oauthToken.c_str());
+
+ WLOG( _T("**NegotiateConnection - Launching %s"), buf);
+ ShellExecute(NULL, L"open", buf, NULL, NULL, SW_SHOWNORMAL);
+
+ ShowPinDialog();
+ }
+
+ if (!DBGetContactSettingTString(NULL,m_szModuleName,TWITTER_KEY_GROUP,&dbv)) {
+ CallService( MS_CLIST_GROUPCREATE, 0, (LPARAM)dbv.ptszVal );
+ DBFreeVariant(&dbv);
+ }
+
+ bool realAccessTok = false;
+ bool realAccessTokSecret = false;
+
+ // remember, dbTOK is 0 (false) if the db setting has returned something
+ dbTOK = DBGetContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_ACCESS_TOK,&dbv);
+ if (!dbTOK) {
+ oauthAccessToken = dbv.pwszVal;
+ DBFreeVariant(&dbv);
+ // this bit is saying "if we have found the db key, but it contains no data, then set dbTOK to 1"
+ if (oauthAccessToken.size() > 1) {
+ realAccessTok = true;
+ //WLOG("**NegotiateConnection - we have an oauthAccessToken already in the db - %s", oauthAccessToken);
+ }
+ else { LOG( _T("**NegotiateConnection - oauthAccesToken too small? this is.. weird.")); }
+ }
+
+ dbTOKSec = DBGetContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_ACCESS_TOK_SECRET,&dbv);
+ if (!dbTOKSec) {
+ oauthAccessTokenSecret = dbv.pwszVal;
+ DBFreeVariant(&dbv);
+ if (oauthAccessTokenSecret.size() > 1) {
+ realAccessTokSecret = true;
+ //WLOG("**NegotiateConnection - we have an oauthAccessTokenSecret already in the db - %s", oauthAccessTokenSecret);
+ }
+ else { LOG( _T("**NegotiateConnection - oauthAccessTokenSecret too small? weird")); }
+ }
+
+ if (!realAccessTok || !realAccessTokSecret) { // if we don't have one of these beasties then lets go get 'em!
+ wstring pin;
+ LOG( _T("**NegotiateConnection - either the accessToken or accessTokenSecret was not there.."));
+ if (!DBGetContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_PIN,&dbv)) {
+ pin = dbv.pwszVal;
+ //WLOG("**NegotiateConnection - we have an pin already in the db - %s", pin);
+ DBFreeVariant(&dbv);
+ }
+ else {
+ ShowPopup(TranslateT("OAuth variables are out of sequence, they have been reset. Please reconnect and reauthorise Miranda to Twitter.com (do the PIN stuff again)"));
+ LOG( _T("**NegotiateConnection - We don't have a PIN? this doesn't make sense. Resetting OAuth keys and setting offline."));
+ resetOAuthKeys();
+
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_FAILED,
+ (HANDLE)old_status,m_iStatus);
+
+ // Set to offline
+ old_status = m_iStatus;
+ m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE;
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_SUCCESS,
+ (HANDLE)old_status,m_iStatus);
+
+ return false;
+ }
+
+ LOG( _T("**NegotiateConnection - Setting Consumer Keys and PIN..."));
+ /*WLOG("**NegotiateConnection - sending set_cred: consumerKey is %s", ConsumerKey);
+ WLOG("**NegotiateConnection - sending set_cred: consumerSecret is %s", ConsumerSecret);
+ WLOG("**NegotiateConnection - sending set_cred: oauthToken is %s", oauthToken);
+ WLOG("**NegotiateConnection - sending set_cred: oauthTokenSecret is %s", oauthTokenSecret);
+ WLOG("**NegotiateConnection - sending set_cred: pin is %s", pin);*/
+
+ twit_.set_credentials("", ConsumerKey, ConsumerSecret, oauthToken, oauthTokenSecret, pin, false);
+
+ LOG( _T("**NegotiateConnection - requesting access tokens..."));
+ http::response accessResp = twit_.request_access_tokens();
+ if (accessResp.code != 200) {
+ LOG( _T("**NegotiateConnection - Failed to get Access Tokens, HTTP response code is: %d"), accessResp.code);
+ ShowPopup(TranslateT("Failed to get Twitter Access Tokens, please go offline and try again. If this keeps happening, check your internet connection."));
+
+ resetOAuthKeys();
+
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_FAILED,
+ (HANDLE)old_status,m_iStatus);
+
+ // Set to offline
+ old_status = m_iStatus;
+ m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE;
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_SUCCESS,
+ (HANDLE)old_status,m_iStatus);
+
+ return false;
+ }
+ else {
+ LOG( _T("**NegotiateConnection - Successfully retrieved Access Tokens"));
+
+ wstring rdata_WSTR2 = UTF8ToWide(accessResp.data);
+ //WLOG("**NegotiateConnection - accessToken STring is %s", rdata_WSTR2);
+
+ OAuthParameters accessTokenParameters = twit_.ParseQueryString(rdata_WSTR2);
+
+ oauthAccessToken = accessTokenParameters[L"oauth_token"];
+ //WLOG("**NegotiateConnection - oauthAccessToken is %s", oauthAccessToken);
+
+ oauthAccessTokenSecret = accessTokenParameters[L"oauth_token_secret"];
+ //WLOG("**NegotiateConnection - oauthAccessTokenSecret is %s", oauthAccessTokenSecret);
+
+ screenName = WideToUTF8(accessTokenParameters[L"screen_name"]);
+ LOG( _T("**NegotiateConnection - screen name is %s"), screenName.c_str());
+
+ //save em
+ DBWriteContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_ACCESS_TOK,oauthAccessToken.c_str());
+ DBWriteContactSettingWString(0,m_szModuleName,TWITTER_KEY_OAUTH_ACCESS_TOK_SECRET,oauthAccessTokenSecret.c_str());
+ DBWriteContactSettingString(0,m_szModuleName,TWITTER_KEY_NICK,screenName.c_str());
+ DBWriteContactSettingString(0,m_szModuleName,TWITTER_KEY_UN,screenName.c_str());
+ }
+ }
+
+/* if( !DBGetContactSettingString(0,m_szModuleName,TWITTER_KEY_PASS,&dbv)) {
+ CallService(MS_DB_CRYPT_DECODESTRING,strlen(dbv.pszVal)+1,
+ reinterpret_cast<LPARAM>(dbv.pszVal));
+ pass = dbv.pszVal;
+ DBFreeVariant(&dbv);
+ }
+ else {
+ ShowPopup(TranslateT("Please enter a password."));
+ return false;
+ }*/
+
+ if( !DBGetContactSettingString(0,m_szModuleName,TWITTER_KEY_BASEURL,&dbv))
+ {
+ ScopedLock s(twitter_lock_);
+ twit_.set_base_url(dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ LOG( _T("**NegotiateConnection - Setting Consumer Keys and verifying creds..."));
+ /*WLOG("**NegotiateConnection - sending set_cred: consumerKey is %s", ConsumerKey);
+ WLOG("**NegotiateConnection - sending set_cred: consumerSecret is %s", ConsumerSecret);
+ WLOG("**NegotiateConnection - sending set_cred: oauthAccessToken is %s", oauthAccessToken);
+ WLOG("**NegotiateConnection - sending set_cred: oauthAccessTokenSecret is %s", oauthAccessTokenSecret);
+ LOG("**NegotiateConnection - sending set_cred: no pin");*/
+
+ if (screenName.empty()) {
+ ShowPopup(TranslateT("You're missing the Nick key in the database. This isn't really a big deal, but you'll notice some minor quirks (self contact in list, no group chat outgoing message highlighting, etc). To fix it either add it manually or reset your twitter account in the miranda account options"));
+ LOG( _T("**NegotiateConnection - Missing the Nick key in the database. Everything will still work, but it's nice to have"));
+ }
+
+ bool success;
+ {
+ ScopedLock s(twitter_lock_);
+
+ success = twit_.set_credentials(screenName, ConsumerKey, ConsumerSecret, oauthAccessToken, oauthAccessTokenSecret, L"", true);
+ }
+
+ if(!success) {
+ //ShowPopup(TranslateT("Something went wrong with authorisation, OAuth keys have been reset. Please try to reconnect. If problems persist, please se your doctor"));
+ LOG( _T("**NegotiateConnection - Verifying credentials failed! No internet maybe?"));
+
+ //resetOAuthKeys();
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_FAILED,
+ (HANDLE)old_status,m_iStatus);
+
+ // Set to offline
+ old_status = m_iStatus;
+ m_iDesiredStatus = m_iStatus = ID_STATUS_OFFLINE;
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_SUCCESS,
+ (HANDLE)old_status,m_iStatus);
+
+ return false;
+ }
+ else {
+ m_iStatus = m_iDesiredStatus;
+
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_SUCCESS,
+ (HANDLE)old_status,m_iStatus);
+ return true;
+ }
+}
+
+
+void TwitterProto::MessageLoop(void*)
+{
+ LOG( _T("***** Entering Twitter::MessageLoop"));
+
+ since_id_ = db_pod_get<twitter_id>(0,m_szModuleName,TWITTER_KEY_SINCEID,0);
+ dm_since_id_ = db_pod_get<twitter_id>(0,m_szModuleName,TWITTER_KEY_DMSINCEID,0);
+
+ bool new_account = db_get_b(0,m_szModuleName,TWITTER_KEY_NEW,1) != 0;
+ bool popups = db_get_b(0,m_szModuleName,TWITTER_KEY_POPUP_SIGNON,1) != 0;
+
+ // if this isn't set, it will automatically not turn a tweet into a msg. probably should make the default that it does turn a tweet into a message
+ bool tweetToMsg = db_get_b(0,m_szModuleName,TWITTER_KEY_TWEET_TO_MSG,0) != 0;
+
+ int poll_rate = db_get_dw(0,m_szModuleName,TWITTER_KEY_POLLRATE,80);
+
+ for(unsigned int i=0;;i++)
+ {
+
+ if(m_iStatus != ID_STATUS_ONLINE)
+ goto exit;
+ if(i%4 == 0)
+ UpdateFriends();
+
+ if(m_iStatus != ID_STATUS_ONLINE)
+ goto exit;
+ UpdateStatuses(new_account,popups, tweetToMsg);
+
+ if(m_iStatus != ID_STATUS_ONLINE)
+ goto exit;
+ UpdateMessages(new_account);
+
+ if(new_account) // Not anymore!
+ {
+ new_account = false;
+ DBWriteContactSettingByte(0,m_szModuleName,TWITTER_KEY_NEW,0);
+ }
+
+ if(m_iStatus != ID_STATUS_ONLINE)
+ goto exit;
+ LOG( _T("***** TwitterProto::MessageLoop going to sleep..."));
+ if(SleepEx(poll_rate*1000,true) == WAIT_IO_COMPLETION)
+ goto exit;
+ LOG( _T("***** TwitterProto::MessageLoop waking up..."));
+
+ popups = true;
+ }
+
+exit:
+ {
+ ScopedLock s(twitter_lock_);
+ twit_.set_credentials("",L"",L"",L"",L"",L"", false);
+ }
+ LOG( _T("***** Exiting TwitterProto::MessageLoop"));
+}
+
+struct update_avatar
+{
+ update_avatar(HANDLE hContact,const std::string &url) : hContact(hContact),url(url) {}
+ HANDLE hContact;
+ std::string url;
+};
+
+void TwitterProto::UpdateAvatarWorker(void *p)
+{
+ if(p == 0)
+ return;
+ std::auto_ptr<update_avatar> data( static_cast<update_avatar*>(p));
+ DBVARIANT dbv;
+
+ if(DBGetContactSettingTString(data->hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ return;
+
+ std::string ext = data->url.substr(data->url.rfind('.'));
+ std::tstring filename = GetAvatarFolder() + _T('\\') + dbv.ptszVal + (TCHAR*)_A2T(ext.c_str());
+ DBFreeVariant(&dbv);
+
+ PROTO_AVATAR_INFORMATIONT ai = {sizeof(ai)};
+ ai.hContact = data->hContact;
+ ai.format = ext_to_format(ext);
+
+ if (ai.format == PA_FORMAT_UNKNOWN) {
+ LOG( _T("***** Update avatar: Terminated for this contact, extension format unknown for %s"), data->url.c_str());
+ return; // lets just ignore unknown formats... if not it crashes miranda. should probably speak to borkra about this.
+ }
+
+ _tcsncpy(ai.filename,filename.c_str(),MAX_PATH);
+
+ LOG( _T("***** Updating avatar: %s"), data->url.c_str());
+ WaitForSingleObjectEx(avatar_lock_,INFINITE,true);
+ if(CallService(MS_SYSTEM_TERMINATED,0,0))
+ {
+ LOG( _T("***** Terminating avatar update early: %s"),data->url.c_str());
+ return;
+ }
+
+ if(save_url(hAvatarNetlib_,data->url,filename))
+ {
+ DBWriteContactSettingString(data->hContact,m_szModuleName,TWITTER_KEY_AV_URL,
+ data->url.c_str());
+ ProtoBroadcastAck(m_szModuleName,data->hContact,ACKTYPE_AVATAR,
+ ACKRESULT_SUCCESS,&ai,0);
+ }
+ else
+ ProtoBroadcastAck(m_szModuleName,data->hContact,ACKTYPE_AVATAR,
+ ACKRESULT_FAILED, &ai,0);
+ ReleaseMutex(avatar_lock_);
+ LOG( _T("***** Done avatar: %s"),data->url.c_str());
+}
+
+void TwitterProto::UpdateAvatar(HANDLE hContact,const std::string &url,bool force)
+{
+ DBVARIANT dbv;
+
+ if( !force &&
+ ( !DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_AV_URL,&dbv) &&
+ url == dbv.pszVal))
+ {
+ LOG( _T("***** Avatar already up-to-date: %s"), url.c_str());
+ }
+ else
+ {
+ // TODO: more defaults (configurable?)
+ if(url == "http://static.twitter.com/images/default_profile_normal.png")
+ {
+ PROTO_AVATAR_INFORMATIONT ai = {sizeof(ai),hContact};
+
+ db_set_s(hContact,m_szModuleName,TWITTER_KEY_AV_URL,url.c_str());
+ ProtoBroadcastAck(m_szModuleName,hContact,ACKTYPE_AVATAR,
+ ACKRESULT_SUCCESS,&ai,0);
+ }
+ else
+ {
+ ForkThread(&TwitterProto::UpdateAvatarWorker, this,
+ new update_avatar(hContact,url));
+ }
+ }
+
+ DBFreeVariant(&dbv);
+}
+
+void TwitterProto::UpdateFriends()
+{
+ try
+ {
+ ScopedLock s(twitter_lock_);
+ std::vector<twitter_user> friends = twit_.get_friends();
+ s.Unlock();
+ for(std::vector<twitter_user>::iterator i=friends.begin(); i!=friends.end(); ++i)
+ {
+ if(i->username == twit_.get_username())
+ continue;
+
+ HANDLE hContact = AddToClientList(i->username.c_str(),i->status.text.c_str());
+ UpdateAvatar(hContact,i->profile_image_url);
+ }
+ disconnectionCount = 0;
+ LOG( _T("***** Friends list updated"));
+ }
+ catch(const bad_response &)
+ {
+ ++disconnectionCount;
+ LOG( _T("***** UpdateFriends - Bad response from server, this has happened %d time(s)"), disconnectionCount);
+ if (disconnectionCount > 2) {
+ LOG( _T("***** UpdateFriends - Too many bad responses from the server, signing off"));
+ SetStatus(ID_STATUS_OFFLINE);
+ }
+ }
+ catch(const std::exception &e)
+ {
+ ShowPopup( (std::string("While updating friends list, an error occurred: ")
+ +e.what()).c_str());
+ LOG( _T("***** Error updating friends list: %s"), e.what());
+ }
+
+}
+
+void TwitterProto::ShowContactPopup(HANDLE hContact,const std::string &text)
+{
+ if(!ServiceExists(MS_POPUP_ADDPOPUPT) || DBGetContactSettingByte(0,
+ m_szModuleName,TWITTER_KEY_POPUP_SHOW,0) == 0)
+ {
+ return;
+ }
+
+ POPUPDATAT popup = {};
+ popup.lchContact = hContact;
+ popup.iSeconds = db_get_dw(0,m_szModuleName,TWITTER_KEY_POPUP_TIMEOUT,0);
+
+ popup.colorBack = db_get_dw(0,m_szModuleName,TWITTER_KEY_POPUP_COLBACK,0);
+ if(popup.colorBack == 0xFFFFFFFF)
+ popup.colorBack = GetSysColor(COLOR_WINDOW);
+ popup.colorText = db_get_dw(0,m_szModuleName,TWITTER_KEY_POPUP_COLTEXT,0);
+ if(popup.colorBack == 0xFFFFFFFF)
+ popup.colorBack = GetSysColor(COLOR_WINDOWTEXT);
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(hContact,"CList","MyHandle",&dbv) ||
+ !DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ {
+ mbcs_to_tcs(CP_UTF8,dbv.pszVal,popup.lptzContactName,MAX_CONTACTNAME);
+ DBFreeVariant(&dbv);
+ }
+
+ mbcs_to_tcs(CP_UTF8,text.c_str(),popup.lptzText,MAX_SECONDLINE);
+ CallService(MS_POPUP_ADDPOPUPT,reinterpret_cast<WPARAM>(&popup),0);
+}
+
+void TwitterProto::UpdateStatuses(bool pre_read, bool popups, bool tweetToMsg)
+{
+ try
+ {
+ ScopedLock s(twitter_lock_);
+ twitter::status_list updates = twit_.get_statuses(200,since_id_);
+ s.Unlock();
+ if(!updates.empty()) {
+ since_id_ = std::max(since_id_, updates[0].status.id);
+ }
+
+ for(twitter::status_list::reverse_iterator i=updates.rbegin(); i!=updates.rend(); ++i)
+ {
+
+ if(!pre_read && in_chat_)
+ UpdateChat(*i);
+
+ if(i->username == twit_.get_username())
+ continue;
+
+ HANDLE hContact = AddToClientList(i->username.c_str(),"");
+
+ // i think we maybe should just do that DBEF_READ line instead of stopping ALL this code. have to test.
+ if (tweetToMsg) {
+ DBEVENTINFO dbei = {sizeof(dbei)};
+
+ dbei.pBlob = (BYTE*)(i->status.text.c_str());
+ dbei.cbBlob = (int)i->status.text.size()+1;
+ dbei.eventType = TWITTER_DB_EVENT_TYPE_TWEET;
+ dbei.flags = DBEF_UTF;
+ dbei.flags = DBEF_READ; // i had commented this line out.. can't remember why :( might need to do it again, uncommented for mrQQ for testing
+ dbei.timestamp = static_cast<DWORD>(i->status.time);
+ dbei.szModule = m_szModuleName;
+ CallService(MS_DB_EVENT_ADD, (WPARAM)hContact, (LPARAM)&dbei);
+ }
+
+ DBWriteContactSettingUTF8String(hContact,"CList","StatusMsg",
+ i->status.text.c_str());
+
+ if(!pre_read && popups)
+ ShowContactPopup(hContact,i->status.text);
+ }
+
+ db_pod_set(0,m_szModuleName,TWITTER_KEY_SINCEID,since_id_);
+ disconnectionCount = 0;
+ LOG( _T("***** Status messages updated"));
+ }
+ catch(const bad_response &)
+ {
+ ++disconnectionCount;
+ LOG( _T("***** UpdateStatuses - Bad response from server, this has happened %d time(s)"), disconnectionCount);
+ if (disconnectionCount > 2) {
+ LOG( _T("***** UpdateStatuses - Too many bad responses from the server, signing off"));
+ SetStatus(ID_STATUS_OFFLINE);
+ }
+ }
+ catch(const std::exception &e)
+ {
+ ShowPopup( (std::string("While updating status messages, an error occurred: ")
+ +e.what()).c_str());
+ LOG( _T("***** Error updating status messages: %s"), e.what());
+ }
+}
+
+void TwitterProto::UpdateMessages(bool pre_read)
+{
+ try
+ {
+ ScopedLock s(twitter_lock_);
+ twitter::status_list messages = twit_.get_direct(dm_since_id_);
+ s.Unlock();
+
+ if(messages.size())
+ dm_since_id_ = std::max(dm_since_id_, messages[0].status.id);
+
+ for(twitter::status_list::reverse_iterator i=messages.rbegin(); i!=messages.rend(); ++i)
+ {
+ HANDLE hContact = AddToClientList(i->username.c_str(),"");
+
+ PROTORECVEVENT recv = {};
+ CCSDATA ccs = {};
+
+ recv.flags = PREF_UTF;
+ if(pre_read)
+ recv.flags |= PREF_CREATEREAD;
+ recv.szMessage = const_cast<char*>(i->status.text.c_str());
+ recv.timestamp = static_cast<DWORD>(i->status.time);
+
+ ccs.hContact = hContact;
+ ccs.szProtoService = PSR_MESSAGE;
+ ccs.wParam = ID_STATUS_ONLINE;
+ ccs.lParam = reinterpret_cast<LPARAM>(&recv);
+ CallService(MS_PROTO_CHAINRECV,0,reinterpret_cast<LPARAM>(&ccs));
+ }
+
+ db_pod_set(0,m_szModuleName,TWITTER_KEY_DMSINCEID,dm_since_id_);
+ disconnectionCount = 0;
+ LOG( _T("***** Direct messages updated"));
+ }
+ catch(const bad_response &)
+ {
+ ++disconnectionCount;
+ LOG( _T("***** UpdateMessages - Bad response from server, this has happened %d time(s)"), disconnectionCount);
+ if (disconnectionCount > 2) {
+ LOG( _T("***** UpdateMessages - Too many bad responses from the server, signing off"));
+ SetStatus(ID_STATUS_OFFLINE);
+ }
+ }
+ catch(const std::exception &e)
+ {
+ ShowPopup( (std::string("While updating direct messages, an error occurred: ")
+ +e.what()).c_str());
+ LOG( _T("***** Error updating direct messages: %s"), e.what());
+ }
+}
+
+void TwitterProto::resetOAuthKeys() {
+ DBDeleteContactSetting(0,m_szModuleName,TWITTER_KEY_OAUTH_ACCESS_TOK);
+ DBDeleteContactSetting(0,m_szModuleName,TWITTER_KEY_OAUTH_ACCESS_TOK_SECRET);
+ DBDeleteContactSetting(0,m_szModuleName,TWITTER_KEY_OAUTH_TOK);
+ DBDeleteContactSetting(0,m_szModuleName,TWITTER_KEY_OAUTH_TOK_SECRET);
+ DBDeleteContactSetting(0,m_szModuleName,TWITTER_KEY_OAUTH_PIN);
+}
diff --git a/protocols/Twitter/src/contacts.cpp b/protocols/Twitter/src/contacts.cpp
new file mode 100644
index 0000000000..78a53f47be
--- /dev/null
+++ b/protocols/Twitter/src/contacts.cpp
@@ -0,0 +1,306 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "proto.h"
+
+
+void TwitterProto::AddToListWorker(void *p)
+{
+ // TODO: what happens if there is an error?
+ if(p == 0)
+ return;
+
+ char *name = static_cast<char*>(p);
+
+ try
+ {
+ ScopedLock s(twitter_lock_);
+ twitter_user user = twit_.add_friend(name);
+ s.Unlock();
+
+ HANDLE hContact = UsernameToHContact(name);
+ UpdateAvatar(hContact,user.profile_image_url);
+ }
+ catch(const std::exception &e)
+ {
+ ShowPopup((std::string("While adding a friend, an error occurred: ")
+ +e.what()).c_str());
+ LOG( _T("***** Error adding friend: %s"),e.what());
+ }
+ mir_free(name);
+}
+
+HANDLE TwitterProto::AddToList(int flags,PROTOSEARCHRESULT *result)
+{
+ if(m_iStatus != ID_STATUS_ONLINE)
+ return 0;
+
+ ForkThread(&TwitterProto::AddToListWorker,this,mir_utf8encodeT(result->nick));
+ return AddToClientList( _T2A(result->nick),"");
+}
+
+// *************************
+
+void TwitterProto::UpdateInfoWorker(void *hContact)
+{
+ twitter_user user;
+ std::string username;
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ {
+ username = dbv.pszVal;
+ DBFreeVariant(&dbv);
+ }
+ else
+ return;
+
+ {
+ ScopedLock s(twitter_lock_);
+ twit_.get_info(username,&user);
+ }
+
+ UpdateAvatar(hContact,user.profile_image_url,true);
+ ProtoBroadcastAck(m_szModuleName,hContact,ACKTYPE_GETINFO,ACKRESULT_SUCCESS,0,0);
+}
+
+int TwitterProto::GetInfo(HANDLE hContact,int info_type)
+{
+ if(m_iStatus != ID_STATUS_ONLINE)
+ return 1;
+
+ if(!IsMyContact(hContact)) // Do nothing for chat rooms
+ return 1;
+
+ if(info_type == 0) // From clicking "Update" in the Userinfo dialog
+ {
+ ForkThread(&TwitterProto::UpdateInfoWorker,this,hContact);
+ return 0;
+ }
+
+ return 1;
+}
+
+// *************************
+
+struct search_query
+{
+ search_query(const std::tstring &_query,bool _by_email) : query(_query),by_email(_by_email)
+ {}
+ std::tstring query;
+ bool by_email;
+};
+
+void TwitterProto::DoSearch(void *p)
+{
+ if(p == 0)
+ return;
+ search_query *query = static_cast<search_query*>(p);
+
+ twitter_user info;
+ PROTOSEARCHRESULT psr = {sizeof(psr)};
+
+ bool found;
+ try
+ {
+ char* p = mir_utf8encodeT( query->query.c_str());
+
+ ScopedLock s(twitter_lock_);
+ if(query->by_email)
+ found = twit_.get_info_by_email(p,&info);
+ else
+ found = twit_.get_info(p,&info);
+ mir_free( p );
+ }
+ catch(const std::exception &e)
+ {
+ ShowPopup( (std::string("While searching for contacts, an error occurred: ")
+ +e.what()).c_str());
+ LOG( _T("***** Error searching for contacts: %s"), e.what());
+ }
+
+ if(found) {
+ psr.nick = mir_a2t( info.username. c_str());
+ psr.firstName = mir_a2t( info.real_name.c_str());
+
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_SEARCH,ACKRESULT_DATA,(HANDLE)1, (LPARAM)&psr);
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_SEARCH,ACKRESULT_SUCCESS,(HANDLE)1,0);
+
+ mir_free(psr.nick);
+ mir_free(psr.firstName);
+ }
+ else
+ {
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_SEARCH,ACKRESULT_SUCCESS,(HANDLE)1,0);
+ }
+
+ delete query;
+}
+
+HANDLE TwitterProto::SearchBasic(const TCHAR *username)
+{
+ ForkThread(&TwitterProto::DoSearch,this,new search_query(username,false));
+ return (HANDLE)1;
+}
+
+HANDLE TwitterProto::SearchByEmail(const TCHAR *email)
+{
+ ForkThread(&TwitterProto::DoSearch,this,new search_query(email,true));
+ return (HANDLE)1;
+}
+
+// *************************
+
+void TwitterProto::GetAwayMsgWorker(void *hContact)
+{
+ if(hContact == 0)
+ return;
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingTString(hContact,"CList","StatusMsg",&dbv)) {
+ ProtoBroadcastAck(m_szModuleName,hContact,ACKTYPE_AWAYMSG,ACKRESULT_SUCCESS, (HANDLE)1,(LPARAM)dbv.ptszVal);
+ DBFreeVariant(&dbv);
+ }
+ else ProtoBroadcastAck(m_szModuleName,hContact,ACKTYPE_AWAYMSG,ACKRESULT_FAILED, (HANDLE)1,(LPARAM)0);
+}
+
+HANDLE TwitterProto::GetAwayMsg(HANDLE hContact)
+{
+ ForkThread(&TwitterProto::GetAwayMsgWorker, this,hContact);
+ return (HANDLE)1;
+}
+
+int TwitterProto::OnContactDeleted(WPARAM wParam,LPARAM lParam)
+{
+ if(m_iStatus != ID_STATUS_ONLINE)
+ return 0;
+
+ const HANDLE hContact = reinterpret_cast<HANDLE>(wParam);
+
+ if(!IsMyContact(hContact))
+ return 0;
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ {
+ if(in_chat_)
+ DeleteChatContact(dbv.pszVal);
+
+ ScopedLock s(twitter_lock_);
+ twit_.remove_friend(dbv.pszVal); // Be careful about this until Miranda is fixed
+ DBFreeVariant(&dbv);
+ }
+ return 0;
+}
+
+// *************************
+
+bool TwitterProto::IsMyContact(HANDLE hContact,bool include_chat)
+{
+ const char *proto = reinterpret_cast<char*>( CallService(MS_PROTO_GETCONTACTBASEPROTO,
+ reinterpret_cast<WPARAM>(hContact),0));
+
+ if(proto && strcmp(m_szModuleName,proto) == 0)
+ {
+ if(include_chat)
+ return true;
+ else
+ return DBGetContactSettingByte(hContact,m_szModuleName,"ChatRoom",0) == 0;
+ }
+ else
+ return false;
+}
+
+HANDLE TwitterProto::UsernameToHContact(const char *name)
+{
+ for(HANDLE hContact = db_find_first();
+ hContact;
+ hContact = db_find_next(hContact))
+ {
+ if(!IsMyContact(hContact))
+ continue;
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ {
+ if(strcmp(name,dbv.pszVal) == 0)
+ {
+ DBFreeVariant(&dbv);
+ return hContact;
+ }
+ else
+ DBFreeVariant(&dbv);
+ }
+ }
+
+ return 0;
+}
+
+HANDLE TwitterProto::AddToClientList(const char *name,const char *status)
+{
+ // First, check if this contact exists
+ HANDLE hContact = UsernameToHContact(name);
+ if(hContact)
+ return hContact;
+
+ if(in_chat_)
+ AddChatContact(name);
+
+ // If not, make a new contact!
+ hContact = (HANDLE)CallService(MS_DB_CONTACT_ADD, 0, 0);
+ if(hContact)
+ {
+ if(CallService(MS_PROTO_ADDTOCONTACT,(WPARAM)hContact,(LPARAM)m_szModuleName) == 0)
+ {
+ DBWriteContactSettingString (hContact,m_szModuleName,TWITTER_KEY_UN,name);
+ DBWriteContactSettingWord (hContact,m_szModuleName,"Status",ID_STATUS_ONLINE);
+ DBWriteContactSettingUTF8String(hContact,"CList","StatusMsg",status);
+
+ std::string url = profile_base_url(twit_.get_base_url())+http::url_encode(name);
+ DBWriteContactSettingString (hContact,m_szModuleName,"Homepage",url.c_str());
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingTString(NULL,m_szModuleName,TWITTER_KEY_GROUP,&dbv))
+ {
+ DBWriteContactSettingTString(hContact,"CList","Group",dbv.ptszVal);
+ DBFreeVariant(&dbv);
+ }
+
+
+ return hContact;
+ }
+ else
+ CallService(MS_DB_CONTACT_DELETE,(WPARAM)hContact,0);
+ }
+
+ return 0;
+}
+
+void TwitterProto::SetAllContactStatuses(int status)
+{
+ for(HANDLE hContact = db_find_first();
+ hContact;
+ hContact = db_find_next(hContact))
+ {
+ if(!IsMyContact(hContact))
+ continue;
+
+ DBWriteContactSettingWord(hContact,m_szModuleName,"Status",status);
+ }
+
+ SetChatStatus(status);
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/http.cpp b/protocols/Twitter/src/http.cpp
new file mode 100644
index 0000000000..877399cc11
--- /dev/null
+++ b/protocols/Twitter/src/http.cpp
@@ -0,0 +1,32 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "http.h"
+
+#include <windows.h>
+#include <newpluginapi.h>
+#include <m_netlib.h>
+
+std::string http::url_encode(const std::string &s)
+{
+ char *encoded = reinterpret_cast<char*>(CallService( MS_NETLIB_URLENCODE,
+ 0,reinterpret_cast<LPARAM>(s.c_str())));
+ std::string ret = encoded;
+ HeapFree(GetProcessHeap(),0,encoded);
+
+ return ret;
+}
diff --git a/protocols/Twitter/src/http.h b/protocols/Twitter/src/http.h
new file mode 100644
index 0000000000..65ef5af407
--- /dev/null
+++ b/protocols/Twitter/src/http.h
@@ -0,0 +1,38 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#include <string>
+
+namespace http
+{
+ enum method
+ {
+ get,
+ post
+ };
+
+ struct response
+ {
+ response() : code(0) {}
+ int code;
+ std::string data;
+ };
+
+ std::string url_encode(const std::string &);
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/main.cpp b/protocols/Twitter/src/main.cpp
new file mode 100644
index 0000000000..d5612be8b4
--- /dev/null
+++ b/protocols/Twitter/src/main.cpp
@@ -0,0 +1,119 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "common.h"
+#include "version.h"
+
+#include "proto.h"
+#include "theme.h"
+
+
+CLIST_INTERFACE* pcli;
+
+HINSTANCE g_hInstance;
+int hLangpack = 0;
+
+PLUGININFOEX pluginInfo={
+ sizeof(PLUGININFOEX),
+ "Twitter Plugin",
+ __VERSION_DWORD,
+ "Provides basic support for Twitter protocol.",
+ "dentist, omniwolf, Thief",
+ "",
+ "© 2009-2010 dentist, 2010-2011 omniwolf and Thief",
+ "http://miranda-ng.org/",
+ UNICODE_AWARE,
+ //{BC09A71B-B86E-4d33-B18D-82D30451DD3C}
+ { 0xbc09a71b, 0xb86e, 0x4d33, { 0xb1, 0x8d, 0x82, 0xd3, 0x4, 0x51, 0xdd, 0x3c } }
+};
+
+/////////////////////////////////////////////////////////////////////////////
+// Protocol instances
+
+static int compare_protos(const TwitterProto *p1, const TwitterProto *p2)
+{
+ return _tcscmp(p1->m_tszUserName, p2->m_tszUserName);
+}
+
+OBJLIST<TwitterProto> g_Instances(1, compare_protos);
+
+DWORD WINAPI DllMain(HINSTANCE hInstance,DWORD,LPVOID)
+{
+ g_hInstance = hInstance;
+ return TRUE;
+}
+
+extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD mirandaVersion)
+{
+ return &pluginInfo;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Interface information
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = {MIID_PROTOCOL, MIID_LAST};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Load
+
+static PROTO_INTERFACE* protoInit(const char *proto_name,const TCHAR *username )
+{
+ TwitterProto *proto = new TwitterProto(proto_name,username);
+ g_Instances.insert(proto);
+ return proto;
+}
+
+static int protoUninit(PROTO_INTERFACE *proto)
+{
+ g_Instances.remove(static_cast<TwitterProto*>(proto));
+ return 0;
+}
+
+static HANDLE g_hEvents[1];
+
+extern "C" int __declspec(dllexport) Load(void)
+{
+
+ mir_getLP(&pluginInfo);
+
+ pcli = reinterpret_cast<CLIST_INTERFACE*>( CallService(
+ MS_CLIST_RETRIEVE_INTERFACE,0,reinterpret_cast<LPARAM>(g_hInstance)));
+
+ PROTOCOLDESCRIPTOR pd = {sizeof(pd)};
+ pd.szName = "Twitter";
+ pd.type = PROTOTYPE_PROTOCOL;
+ pd.fnInit = protoInit;
+ pd.fnUninit = protoUninit;
+ CallService(MS_PROTO_REGISTERMODULE,0,reinterpret_cast<LPARAM>(&pd));
+
+ InitIcons();
+ InitContactMenus();
+
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Unload
+
+extern "C" int __declspec(dllexport) Unload(void)
+{
+ UninitContactMenus();
+ for(size_t i=1; i<SIZEOF(g_hEvents); i++)
+ UnhookEvent(g_hEvents[i]);
+
+ return 0;
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/oauth.cpp b/protocols/Twitter/src/oauth.cpp
new file mode 100644
index 0000000000..2235ca88ce
--- /dev/null
+++ b/protocols/Twitter/src/oauth.cpp
@@ -0,0 +1,670 @@
+/* aww whatup? this is all the oauth functions, at the moment
+ * they're all part of the twitter class.. i think this is the
+ * best way?
+ */
+
+#include "twitter.h"
+//#include "tc2.h"
+#include "utility.h"
+#include "stdafx.h"
+#include "common.h"
+
+OAuthParameters mir_twitter::BuildSignedOAuthParameters( const OAuthParameters& requestParameters,
+ const std::wstring& url,
+ const std::wstring& httpMethod,
+ const OAuthParameters *postData,
+ const std::wstring& consumerKey,
+ const std::wstring& consumerSecret,
+ const std::wstring& requestToken = L"",
+ const std::wstring& requestTokenSecret = L"",
+ const std::wstring& pin = L"" )
+{
+ wstring timestamp = OAuthCreateTimestamp();
+ wstring nonce = OAuthCreateNonce();
+
+ // create oauth requestParameters
+ OAuthParameters oauthParameters;
+
+ oauthParameters[L"oauth_timestamp"] = timestamp;
+ oauthParameters[L"oauth_nonce"] = nonce;
+ oauthParameters[L"oauth_version"] = L"1.0";
+ oauthParameters[L"oauth_signature_method"] = L"HMAC-SHA1";
+ oauthParameters[L"oauth_consumer_key"] = consumerKey;
+
+ // add the request token if found
+ if (!requestToken.empty())
+ {
+ oauthParameters[L"oauth_token"] = requestToken; /*WLOG("requestToken not empty: %s", requestToken);*/
+ }
+
+ // add the authorization pin if found
+ if (!pin.empty())
+ {
+ oauthParameters[L"oauth_verifier"] = pin;
+ }
+
+ // create a parameter list containing both oauth and original parameters
+ // this will be used to create the parameter signature
+ OAuthParameters allParameters = requestParameters;
+
+ if(Compare(httpMethod, L"POST", false) && postData) {
+ //LOG("in post section of buildOAuthParams");
+ allParameters.insert(postData->begin(), postData->end());
+ }
+
+ allParameters.insert(oauthParameters.begin(), oauthParameters.end());
+
+ // prepare a signature base, a carefully formatted string containing
+ // all of the necessary information needed to generate a valid signature
+ wstring normalUrl = OAuthNormalizeUrl(url);
+ //WLOG("normalURL is %s", normalUrl);
+ wstring normalizedParameters = OAuthNormalizeRequestParameters(allParameters);
+ //WLOG("normalisedparams is %s", normalizedParameters);
+ wstring signatureBase = OAuthConcatenateRequestElements(httpMethod, normalUrl, normalizedParameters);
+ //WLOG("sigBase is %s", signatureBase);
+
+ // obtain a signature and add it to header requestParameters
+ wstring signature = OAuthCreateSignature(signatureBase, consumerSecret, requestTokenSecret);
+ //WLOG("**BuildSignedOAuthParameters - sig is %s", signature);
+ oauthParameters[L"oauth_signature"] = signature;
+
+ return oauthParameters;
+}
+
+wstring mir_twitter::UrlGetQuery( const wstring& url )
+{
+ wstring query;
+ /*
+ URL_COMPONENTS components = {sizeof(URL_COMPONENTS)};
+
+ wchar_t buf[1024*4] = {};
+
+ components.lpszExtraInfo = buf;
+ components.dwExtraInfoLength = SIZEOF(buf);
+
+ BOOL crackUrlOk = InternetCrackUrl(url.c_str(), url.size(), 0, &components);
+ _ASSERTE(crackUrlOk);
+ if(crackUrlOk)
+ {*/
+
+ map<wstring, wstring> brokenURL = CrackURL(url);
+
+ query = brokenURL[L"extraInfo"];
+ //WLOG("inside crack, url is %s", url);
+ wstring::size_type q = query.find_first_of(L'?');
+ if(q != wstring::npos)
+ {
+ query = query.substr(q + 1);
+ }
+
+ wstring::size_type h = query.find_first_of(L'#');
+ if(h != wstring::npos)
+ {
+ query = query.substr(0, h);
+ }
+ //}
+ return query;
+}
+
+// OAuthWebRequest used for all OAuth related queries
+//
+// consumerKey and consumerSecret - must be provided for every call, they identify the application
+// oauthToken and oauthTokenSecret - need to be provided for every call, except for the first token request before authorizing
+// pin - only used during authorization, when the user enters the PIN they received from the twitter website
+wstring mir_twitter::OAuthWebRequestSubmit(
+ const wstring& url,
+ const wstring& httpMethod,
+ const OAuthParameters *postData,
+ const wstring& consumerKey,
+ const wstring& consumerSecret,
+ const wstring& oauthToken,
+ const wstring& oauthTokenSecret,
+ const wstring& pin
+ )
+{
+ //WLOG("URL is %s", url);
+ wstring query = UrlGetQuery(url);
+ //WLOG("query is %s", query);
+ OAuthParameters originalParameters = ParseQueryString(query);
+
+ OAuthParameters oauthSignedParameters = BuildSignedOAuthParameters(
+ originalParameters,
+ url,
+ httpMethod, postData,
+ consumerKey, consumerSecret,
+ oauthToken, oauthTokenSecret,
+ pin );
+ return OAuthWebRequestSubmit(oauthSignedParameters, url);
+}
+
+wstring mir_twitter::OAuthWebRequestSubmit(
+ const OAuthParameters& parameters,
+ const wstring& url
+ )
+{
+ //WLOG("OAuthWebRequestSubmit(%s)", url);
+
+ //wstring oauthHeader = L"Authorization: OAuth ";
+ wstring oauthHeader = L"OAuth ";
+
+ for(OAuthParameters::const_iterator it = parameters.begin();
+ it != parameters.end();
+ ++it)
+ {
+ //WLOG("%s = ", it->first);
+ //WLOG("%s", it->second);
+ //LOG("---------");
+
+ if(it != parameters.begin())
+ {
+ oauthHeader += L",";
+ }
+
+ wstring pair;
+ pair += it->first + L"=\"" + it->second + L"\"";
+ oauthHeader += pair;
+ }
+
+ //WLOG("oauthheader is %s", oauthHeader);
+
+ return oauthHeader;
+}
+
+// parameters must already be URL encoded before calling BuildQueryString
+std::wstring mir_twitter::BuildQueryString( const OAuthParameters &parameters )
+{
+ wstring query;
+ //LOG("do we ever get here?");
+ for(OAuthParameters::const_iterator it = parameters.begin();
+ it != parameters.end();
+ ++it)
+ {
+ //LOG("aww como ONNNNNN");
+ //LOG("%s = %s", it->first.c_str(), it->second.c_str());
+ //WLOG("in buildqueryString bit, first is %s", it->first);
+
+ if(it != parameters.begin())
+ {
+ query += L"&";
+ }
+
+ wstring pair;
+ pair += it->first + L"=" + it->second + L"";
+ query += pair;
+ }
+ return query;
+}
+
+wstring mir_twitter::OAuthConcatenateRequestElements( const wstring& httpMethod, wstring url, const wstring& parameters )
+{
+ wstring escapedUrl = UrlEncode(url);
+ //WLOG("before OAUTHConcat, params are %s", parameters);
+ wstring escapedParameters = UrlEncode(parameters);
+ //LOG(")))))))))))))))))))))))))))))))))))))))))))))))");
+ //WLOG("after url encode, its %s", escapedParameters);
+ wstring ret = httpMethod + L"&" + escapedUrl + L"&" + escapedParameters;
+ return ret;
+}
+
+/* CrackURL.. just basically pulls apart a url into a map of wstrings:
+ * scheme, domain, port, path, extraInfo, explicitPort
+ * explicitPort will be 0 or 1, 0 if there was no actual port in the url,
+ * and 1 if it was explicitely specified.
+ * eg "http://twitter.com/blah.htm" will give:
+ * http, twitter.com, 80, blah.htm, "", 0
+ * "https://twitter.com:989/blah.htm?boom" will give:
+ * https, twitter.com, 989, blah.htm?boom, ?boom, 1
+ */
+map<wstring, wstring> mir_twitter::CrackURL(wstring url) {
+
+ wstring scheme1, domain1, port1, path1, extraInfo, explicitPort;
+ vector<wstring> urlToks, urlToks2, extraInfoToks;
+
+ Split(url, urlToks, L':', false);
+ //WLOG("**CRACK - URL to split is %s", url);
+
+ scheme1 = urlToks[0];
+ //WLOG("**CRACK - scheme is %s", scheme1);
+
+ if (urlToks.size() == 2) { // if there is only 1 ":" in the url
+ if (Compare(scheme1, L"http", false)) {
+ port1 = L"80";
+ }
+ else {
+ port1 = L"443";
+ }
+
+ //WLOG("**CRACK::2 - port is %s", port1);
+
+ Split(urlToks[1], urlToks2, L'/', false);
+ domain1 = urlToks2[0];
+ //WLOG("**CRACK::2 - domain is %s", domain1);
+ explicitPort = L"0";
+ }
+ else if (urlToks.size() == 3) { // if there are 2 ":"s in the URL, ie a port is explicitly set
+ domain1 = urlToks[1].substr(2, urlToks[1].size());
+ //WLOG("**CRACK::3 - domain is %s", domain1);
+ Split(urlToks[2], urlToks2, L'/', false);
+ port1 = urlToks2[0];
+ //WLOG("**CRACK::3 - port is %s", port1);
+ explicitPort = L"1";
+ }
+ else {
+ WLOG("**CRACK - not a proper URL? doesn't have a colon. URL is %s", url);
+
+ }
+
+ for (size_t i = 1; i < urlToks2.size(); ++i) {
+ if (i > 1) {
+ path1 += L"/";
+ }
+ path1 += urlToks2[i];
+ }
+ //WLOG("**CRACK - path is %s", path1);
+
+ wstring::size_type foundHash = path1.find(L"#");
+ wstring::size_type foundQ = path1.find(L"?");
+
+ if ((foundHash != wstring::npos) || (foundQ != wstring::npos)) { // if we've found a # or a ?...
+ if (foundHash == wstring::npos) { // if we didn't find a #, we must have found a ?
+ extraInfo = path1.substr(foundQ);
+ }
+ else if (foundQ == wstring::npos) { // if we didn't find a ?, we must have found a #
+ extraInfo = path1.substr(foundHash);
+ }
+ else { // we found both a # and a ?, whichever came first we grab the sub string from there
+ if (foundQ < foundHash) {
+ extraInfo = path1.substr(foundQ);
+ }
+ else {
+ extraInfo = path1.substr(foundHash);
+ }
+ }
+ }
+ else { // we have no # or ? in the path...
+ extraInfo = L"";
+ }
+
+ //WLOG("**CRACK - extraInfo is %s", extraInfo);
+
+ map<wstring, wstring> result;
+ result[L"scheme"] = scheme1;
+ result[L"domain"] = domain1;
+ result[L"port"] = port1;
+ result[L"path"] = path1;
+ result[L"extraInfo"] = extraInfo;
+ result[L"explicitPort"] = explicitPort;
+
+ return result;
+}
+
+wstring mir_twitter::OAuthNormalizeUrl( const wstring& url )
+{
+ /*wchar_t scheme[1024*4] = {};
+ wchar_t host[1024*4] = {};
+ wchar_t path[1024*4] = {};
+
+ URL_COMPONENTS components = { sizeof(URL_COMPONENTS) };
+
+ components.lpszScheme = scheme;
+ components.dwSchemeLength = SIZEOF(scheme);
+
+ components.lpszHostName = host;
+ components.dwHostNameLength = SIZEOF(host);
+
+ components.lpszUrlPath = path;
+ components.dwUrlPathLength = SIZEOF(path);
+
+ BOOL crackUrlOk = InternetCrackUrl(url.c_str(), url.size(), 0, &components);*/
+
+ wstring normalUrl = url;
+ map<wstring, wstring> brokenURL = CrackURL(url);
+
+ /*_ASSERTE(crackUrlOk);
+ if(crackUrlOk)
+ {*/
+ wchar_t port[10] = {};
+
+ // The port number must only be included if it is non-standard
+ if(Compare(brokenURL[L"scheme"], L"http", false) && !(Compare(brokenURL[L"port"], L"80", false)) ||
+ (Compare(brokenURL[L"scheme"], L"https", false) && !(Compare(brokenURL[L"port"], L"443", false))))
+ {
+ swprintf_s(port, SIZEOF(port), L":%s", brokenURL[L"port"]);
+ }
+
+ // InternetCrackUrl includes ? and # elements in the path,
+ // which we need to strip off
+ wstring pathOnly = brokenURL[L"path"];
+ wstring::size_type q = pathOnly.find_first_of(L"#?");
+ if(q != wstring::npos)
+ {
+ pathOnly = pathOnly.substr(0, q);
+ }
+
+ normalUrl = brokenURL[L"scheme"] + L"://" + brokenURL[L"domain"] + port + L"/" + pathOnly;
+ //WLOG("**OAuthNOrmailseURL - normalUrl is %s", normalUrl);
+ //}
+ return normalUrl;
+}
+
+wstring mir_twitter::OAuthNormalizeRequestParameters( const OAuthParameters& requestParameters )
+{
+ list<wstring> sorted;
+ for(OAuthParameters::const_iterator it = requestParameters.begin();
+ it != requestParameters.end();
+ ++it)
+ {
+ wstring param = it->first + L"=" + it->second;
+ sorted.push_back(param);
+ }
+ sorted.sort();
+
+ wstring params;
+ for(list<wstring>::iterator it = sorted.begin(); it != sorted.end(); ++it)
+ {
+ if(params.size() > 0)
+ {
+ params += L"&";
+ }
+ params += *it;
+ }
+
+ return params;
+}
+
+OAuthParameters mir_twitter::ParseQueryString( const wstring& url )
+{
+ OAuthParameters ret;
+
+ vector<wstring> queryParams;
+ Split(url, queryParams, L'&', false);
+
+ for(size_t i = 0; i < queryParams.size(); ++i)
+ {
+ vector<wstring> paramElements;
+ Split(queryParams[i], paramElements, L'=', true);
+ _ASSERTE(paramElements.size() == 2);
+ if(paramElements.size() == 2)
+ {
+ ret[paramElements[0]] = paramElements[1];
+ }
+ }
+ return ret;
+}
+
+wstring mir_twitter::OAuthCreateNonce()
+{
+ wchar_t ALPHANUMERIC[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ wstring nonce;
+
+ for(int i = 0; i <= 16; ++i)
+ {
+ nonce += ALPHANUMERIC[rand() % (SIZEOF(ALPHANUMERIC) - 1)]; // don't count null terminator in array
+ }
+ return nonce;
+}
+
+wstring mir_twitter::OAuthCreateTimestamp()
+{
+ __time64_t utcNow;
+ __time64_t ret = _time64(&utcNow);
+ _ASSERTE(ret != -1);
+
+ wchar_t buf[100] = {};
+ swprintf_s(buf, SIZEOF(buf), L"%I64u", utcNow);
+
+ return buf;
+}
+
+string mir_twitter::HMACSHA1( const string& keyBytes, const string& data )
+{
+ // based on http://msdn.microsoft.com/en-us/library/aa382379%28v=VS.85%29.aspx
+
+ string hash;
+
+ //--------------------------------------------------------------------
+ // Declare variables.
+ //
+ // hProv: Handle to a cryptographic service provider (CSP).
+ // This example retrieves the default provider for
+ // the PROV_RSA_FULL provider type.
+ // hHash: Handle to the hash object needed to create a hash.
+ // hKey: Handle to a symmetric key. This example creates a
+ // key for the RC4 algorithm.
+ // hHmacHash: Handle to an HMAC hash.
+ // pbHash: Pointer to the hash.
+ // dwDataLen: Length, in bytes, of the hash.
+ // Data1: Password string used to create a symmetric key.
+ // Data2: Message string to be hashed.
+ // HmacInfo: Instance of an HMAC_INFO structure that contains
+ // information about the HMAC hash.
+ //
+ HCRYPTPROV hProv = NULL;
+ HCRYPTHASH hHash = NULL;
+ HCRYPTKEY hKey = NULL;
+ HCRYPTHASH hHmacHash = NULL;
+ PBYTE pbHash = NULL;
+ DWORD dwDataLen = 0;
+ //BYTE Data1[] = {0x70,0x61,0x73,0x73,0x77,0x6F,0x72,0x64};
+ //BYTE Data2[] = {0x6D,0x65,0x73,0x73,0x61,0x67,0x65};
+ HMAC_INFO HmacInfo;
+
+ //--------------------------------------------------------------------
+ // Zero the HMAC_INFO structure and use the SHA1 algorithm for
+ // hashing.
+
+ ZeroMemory(&HmacInfo, sizeof(HmacInfo));
+ HmacInfo.HashAlgid = CALG_SHA1;
+
+ //--------------------------------------------------------------------
+ // Acquire a handle to the default RSA cryptographic service provider.
+
+ if (!CryptAcquireContext(
+ &hProv, // handle of the CSP
+ NULL, // key container name
+ NULL, // CSP name
+ PROV_RSA_FULL, // provider type
+ CRYPT_VERIFYCONTEXT)) // no key access is requested
+ {
+ _TRACE(" Error in AcquireContext 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ //--------------------------------------------------------------------
+ // Derive a symmetric key from a hash object by performing the
+ // following steps:
+ // 1. Call CryptCreateHash to retrieve a handle to a hash object.
+ // 2. Call CryptHashData to add a text string (password) to the
+ // hash object.
+ // 3. Call CryptDeriveKey to create the symmetric key from the
+ // hashed password derived in step 2.
+ // You will use the key later to create an HMAC hash object.
+
+ if (!CryptCreateHash(
+ hProv, // handle of the CSP
+ CALG_SHA1, // hash algorithm to use
+ 0, // hash key
+ 0, // reserved
+ &hHash)) // address of hash object handle
+ {
+ _TRACE("Error in CryptCreateHash 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ if (!CryptHashData(
+ hHash, // handle of the hash object
+ (BYTE*)keyBytes.c_str(), // password to hash
+ (DWORD)keyBytes.size(), // number of bytes of data to add
+ 0)) // flags
+ {
+ _TRACE("Error in CryptHashData 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ // key creation based on
+ // http://mirror.leaseweb.com/NetBSD/NetBSD-release-5-0/src/dist/wpa/src/crypto/crypto_cryptoapi.c
+ struct {
+ BLOBHEADER hdr;
+ DWORD len;
+ BYTE key[1024]; // TODO might want to dynamically allocate this, Should Be Fine though
+ } key_blob;
+
+ key_blob.hdr.bType = PLAINTEXTKEYBLOB;
+ key_blob.hdr.bVersion = CUR_BLOB_VERSION;
+ key_blob.hdr.reserved = 0;
+ /*
+ * Note: RC2 is not really used, but that can be used to
+ * import HMAC keys of up to 16 byte long.
+ * CRYPT_IPSEC_HMAC_KEY flag for CryptImportKey() is needed to
+ * be able to import longer keys (HMAC-SHA1 uses 20-byte key).
+ */
+ key_blob.hdr.aiKeyAlg = CALG_RC2;
+ key_blob.len = (DWORD)keyBytes.size();
+ ZeroMemory(key_blob.key, sizeof(key_blob.key));
+
+ _ASSERTE(keyBytes.size() <= SIZEOF(key_blob.key));
+ CopyMemory(key_blob.key, keyBytes.c_str(), min(keyBytes.size(), SIZEOF(key_blob.key)));
+
+ if (!CryptImportKey(
+ hProv,
+ (BYTE *)&key_blob,
+ sizeof(key_blob),
+ 0,
+ CRYPT_IPSEC_HMAC_KEY,
+ &hKey))
+ {
+ _TRACE("Error in CryptImportKey 0x%08x \n", GetLastError());
+ goto ErrorExit;
+ }
+
+ //--------------------------------------------------------------------
+ // Create an HMAC by performing the following steps:
+ // 1. Call CryptCreateHash to create a hash object and retrieve
+ // a handle to it.
+ // 2. Call CryptSetHashParam to set the instance of the HMAC_INFO
+ // structure into the hash object.
+ // 3. Call CryptHashData to compute a hash of the message.
+ // 4. Call CryptGetHashParam to retrieve the size, in bytes, of
+ // the hash.
+ // 5. Call malloc to allocate memory for the hash.
+ // 6. Call CryptGetHashParam again to retrieve the HMAC hash.
+
+ if (!CryptCreateHash(
+ hProv, // handle of the CSP.
+ CALG_HMAC, // HMAC hash algorithm ID
+ hKey, // key for the hash (see above)
+ 0, // reserved
+ &hHmacHash)) // address of the hash handle
+ {
+ _TRACE("Error in CryptCreateHash 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ if (!CryptSetHashParam(
+ hHmacHash, // handle of the HMAC hash object
+ HP_HMAC_INFO, // setting an HMAC_INFO object
+ (BYTE*)&HmacInfo, // the HMAC_INFO object
+ 0)) // reserved
+ {
+ _TRACE("Error in CryptSetHashParam 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ if (!CryptHashData(
+ hHmacHash, // handle of the HMAC hash object
+ (BYTE*)data.c_str(), // message to hash
+ (DWORD)data.size(), // number of bytes of data to add
+ 0)) // flags
+ {
+ _TRACE("Error in CryptHashData 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ //--------------------------------------------------------------------
+ // Call CryptGetHashParam twice. Call it the first time to retrieve
+ // the size, in bytes, of the hash. Allocate memory. Then call
+ // CryptGetHashParam again to retrieve the hash value.
+
+ if (!CryptGetHashParam(
+ hHmacHash, // handle of the HMAC hash object
+ HP_HASHVAL, // query on the hash value
+ NULL, // filled on second call
+ &dwDataLen, // length, in bytes, of the hash
+ 0))
+ {
+ _TRACE("Error in CryptGetHashParam 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ pbHash = (BYTE*)malloc(dwDataLen);
+ if(NULL == pbHash)
+ {
+ _TRACE("unable to allocate memory\n");
+ goto ErrorExit;
+ }
+
+ if (!CryptGetHashParam(
+ hHmacHash, // handle of the HMAC hash object
+ HP_HASHVAL, // query on the hash value
+ pbHash, // pointer to the HMAC hash value
+ &dwDataLen, // length, in bytes, of the hash
+ 0))
+ {
+ _TRACE("Error in CryptGetHashParam 0x%08x \n", GetLastError());
+ goto ErrorExit;
+ }
+
+ for(DWORD i = 0 ; i < dwDataLen ; i++)
+ {
+ hash.push_back((char)pbHash[i]);
+ }
+
+ // Free resources.
+ // lol goto
+ErrorExit:
+ if(hHmacHash)
+ CryptDestroyHash(hHmacHash);
+ if(hKey)
+ CryptDestroyKey(hKey);
+ if(hHash)
+ CryptDestroyHash(hHash);
+ if(hProv)
+ CryptReleaseContext(hProv, 0);
+ if(pbHash)
+ free(pbHash);
+
+ return hash;
+}
+
+wstring mir_twitter::Base64String( const string& hash )
+{
+ Base64Coder coder;
+ coder.Encode((BYTE*)hash.c_str(), (DWORD)hash.size());
+ wstring encoded = UTF8ToWide(coder.EncodedMessage());
+ return encoded;
+}
+
+wstring mir_twitter::OAuthCreateSignature( const wstring& signatureBase, const wstring& consumerSecret, const wstring& requestTokenSecret )
+{
+ // URL encode key elements
+ wstring escapedConsumerSecret = UrlEncode(consumerSecret);
+ wstring escapedTokenSecret = UrlEncode(requestTokenSecret);
+
+ wstring key = escapedConsumerSecret + L"&" + escapedTokenSecret;
+ string keyBytes = WideToUTF8(key);
+
+ string data = WideToUTF8(signatureBase);
+ string hash = HMACSHA1(keyBytes, data);
+ wstring signature = Base64String(hash);
+
+ // URL encode the returned signature
+ signature = UrlEncode(signature);
+ return signature;
+}
diff --git a/protocols/Twitter/src/oauth.dev.h b/protocols/Twitter/src/oauth.dev.h
new file mode 100644
index 0000000000..13c383b3ab
--- /dev/null
+++ b/protocols/Twitter/src/oauth.dev.h
@@ -0,0 +1,2 @@
+#define OAUTH_CONSUMER_KEY L"AwSuQV9A7uXpat81MQB48g"
+#define OAUTH_CONSUMER_SECRET L"x8pPGCCV5wFs26euODb9gv4VQ4kiuxTp3ed2P8Of4" \ No newline at end of file
diff --git a/protocols/Twitter/src/oauth/Makefile.am b/protocols/Twitter/src/oauth/Makefile.am
new file mode 100644
index 0000000000..b59c7c71df
--- /dev/null
+++ b/protocols/Twitter/src/oauth/Makefile.am
@@ -0,0 +1,8 @@
+ACLOCAL_AMFLAGS= -I m4
+lib_LTLIBRARIES = liboauth.la
+include_HEADERS = oauth.h
+
+liboauth_la_SOURCES=oauth.c config.h hash.c xmalloc.c xmalloc.h oauth_http.c
+liboauth_la_LDFLAGS=@LIBOAUTH_LDFLAGS@ -version-info @VERSION_INFO@
+liboauth_la_LIBADD=@HASH_LIBS@ @CURL_LIBS@
+liboauth_la_CFLAGS=@LIBOAUTH_CFLAGS@ @HASH_CFLAGS@ @CURL_CFLAGS@
diff --git a/protocols/Twitter/src/oauth/hash.c b/protocols/Twitter/src/oauth/hash.c
new file mode 100644
index 0000000000..ca00adaa8c
--- /dev/null
+++ b/protocols/Twitter/src/oauth/hash.c
@@ -0,0 +1,492 @@
+/*
+ * hash algorithms used in OAuth
+ *
+ * Copyright 2007-2010 Robin Gareus <robin@gareus.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 HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if USE_BUILTIN_HASH // built-in / AVR -- TODO: check license of sha1.c
+#include <stdio.h>
+#include "oauth.h" // oauth_encode_base64
+#include "xmalloc.h"
+
+#include "sha1.c" // TODO: sha1.h ; Makefile.am: add sha1.c
+
+/* API */
+char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) {
+ sha1nfo s;
+ sha1_initHmac(&s, (const uint8_t*) k, kl);
+ sha1_write(&s, m, ml);
+ unsigned char *digest = sha1_resultHmac(&s);
+ return oauth_encode_base64(HASH_LENGTH, digest);
+}
+
+char *oauth_sign_hmac_sha1 (const char *m, const char *k) {
+ return(oauth_sign_hmac_sha1_raw (m, strlen(m), k, strlen(k)));
+}
+
+char *oauth_body_hash_file(char *filename) {
+ FILE *F= fopen(filename, "r");
+ if (!F) return NULL;
+
+ size_t len=0;
+ char fb[BUFSIZ];
+ sha1nfo s;
+ sha1_init(&s);
+
+ while (!feof(F) && (len=fread(fb,sizeof(char),BUFSIZ, F))>0) {
+ sha1_write(&s, fb, len);
+ }
+ fclose(F);
+
+ unsigned char *dgst = xmalloc(HASH_LENGTH*sizeof(char)); // oauth_body_hash_encode frees the digest..
+ memcpy(dgst, sha1_result(&s), HASH_LENGTH);
+ return oauth_body_hash_encode(HASH_LENGTH, dgst);
+}
+
+char *oauth_body_hash_data(size_t length, const char *data) {
+ sha1nfo s;
+ sha1_init(&s);
+ for (;length--;) sha1_writebyte(&s, *data++);
+
+ unsigned char *dgst = xmalloc(HASH_LENGTH*sizeof(char)); // oauth_body_hash_encode frees the digest..
+ memcpy(dgst, sha1_result(&s), HASH_LENGTH);
+ return oauth_body_hash_encode(HASH_LENGTH, dgst);
+}
+
+char *oauth_sign_rsa_sha1 (const char *m, const char *k) {
+ /* NOT RSA/PK11 support */
+ return xstrdup("---RSA/PK11-is-not-supported-by-this-version-of-liboauth---");
+}
+
+int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *sig) {
+ /* NOT RSA/PK11 support */
+ return -1; // mismatch , error
+}
+
+#elif defined (USE_NSS)
+/* use http://www.mozilla.org/projects/security/pki/nss/ for hash/sign */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xmalloc.h"
+#include "oauth.h" // oauth base64 encode fn's.
+
+// NSS includes
+#include "pk11pub.h"
+#include "nss.h"
+#include "base64.h"
+#include "keyhi.h"
+#include "cryptohi.h"
+#include "cert.h"
+
+#if 1 // work-around compiler-warning
+ // see http://bugzilla.mozilla.org/show_bug.cgi?id=243245#c3
+ extern CERTCertificate *
+ __CERT_DecodeDERCertificate (SECItem *derSignedCert, PRBool copyDER, char *nickname);
+#endif
+
+static const char NS_CERT_HEADER[] = "-----BEGIN CERTIFICATE-----";
+static const char NS_CERT_TRAILER[] = "-----END CERTIFICATE-----";
+static const char NS_PRIV_HEADER[] = "-----BEGIN PRIVATE KEY-----";
+static const char NS_PRIV_TRAILER[] = "-----END PRIVATE KEY-----";
+
+void oauth_init_nss() {
+ static short nss_initialized = 0;
+ if (!nss_initialized) { NSS_NoDB_Init("."); nss_initialized=1;}
+}
+
+/**
+ * Removes heading & trailing strings; used only internally.
+ * similar to NSS-source/nss/lib/pkcs7/certread.c
+ *
+ * the returned string (if not NULL) needs to be freed by the caller
+ */
+char *oauth_strip_pkcs(const char *txt, const char *h, const char *t) {
+ char *start, *end, *rv;
+ size_t len;
+ if ((start=strstr(txt, h))==NULL) return NULL;
+ start+=strlen(h);
+ while (*start=='\r' || *start=='\n') start++;
+ if ((end=strstr(start, t))==NULL) return NULL;
+ end--;
+ while (*end=='\r' || *end=='\n') end--;
+ len = end-start+2;
+ rv = xmalloc(len*sizeof(char));
+ memcpy(rv,start,len);
+ rv[len-1]='\0';
+ return rv;
+}
+
+char *oauth_sign_hmac_sha1 (const char *m, const char *k) {
+ return(oauth_sign_hmac_sha1_raw (m, strlen(m), k, strlen(k)));
+}
+
+char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) {
+ PK11SlotInfo *slot = NULL;
+ PK11SymKey *pkey = NULL;
+ PK11Context *context = NULL;
+ unsigned char digest[20]; // Is there a way to tell how large the output is?
+ unsigned int len;
+ SECStatus s;
+ SECItem keyItem, noParams;
+ char *rv=NULL;
+
+ keyItem.type = siBuffer;
+ keyItem.data = (unsigned char*) k;
+ keyItem.len = kl;
+
+ noParams.type = siBuffer;
+ noParams.data = NULL;
+ noParams.len = 0;
+
+ oauth_init_nss();
+
+ slot = PK11_GetInternalKeySlot();
+ if (!slot) goto looser;
+ pkey = PK11_ImportSymKey(slot, CKM_SHA_1_HMAC, PK11_OriginUnwrap, CKA_SIGN, &keyItem, NULL);
+ if (!pkey) goto looser;
+ context = PK11_CreateContextBySymKey(CKM_SHA_1_HMAC, CKA_SIGN, pkey, &noParams);
+ if (!context) goto looser;
+
+ s = PK11_DigestBegin(context);
+ if (s != SECSuccess) goto looser;
+ s = PK11_DigestOp(context, (unsigned char*) m, ml);
+ if (s != SECSuccess) goto looser;
+ s = PK11_DigestFinal(context, digest, &len, sizeof digest);
+ if (s != SECSuccess) goto looser;
+
+ rv=oauth_encode_base64(len, digest);
+
+looser:
+ if (context) PK11_DestroyContext(context, PR_TRUE);
+ if (pkey) PK11_FreeSymKey(pkey);
+ if (slot) PK11_FreeSlot(slot);
+ return rv;
+}
+
+char *oauth_sign_rsa_sha1 (const char *m, const char *k) {
+ PK11SlotInfo *slot = NULL;
+ SECKEYPrivateKey *pkey = NULL;
+ SECItem signature;
+ SECStatus s;
+ SECItem der;
+ char *rv=NULL;
+
+ char *key = oauth_strip_pkcs(k, NS_PRIV_HEADER, NS_PRIV_TRAILER);
+ if (!key) return NULL;
+
+ oauth_init_nss();
+
+ slot = PK11_GetInternalKeySlot();
+ if (!slot) goto looser;
+ s = ATOB_ConvertAsciiToItem(&der, key);
+ if (s != SECSuccess) goto looser;
+ s = PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, &der, NULL, NULL, PR_FALSE, PR_TRUE, KU_ALL, &pkey, NULL);
+ SECITEM_FreeItem(&der, PR_FALSE);
+ if (s != SECSuccess) goto looser;
+ if (!pkey) goto looser;
+ if (pkey->keyType != rsaKey) goto looser;
+ s = SEC_SignData(&signature, (unsigned char*) m, strlen(m), pkey, SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE);
+ if (s != SECSuccess) goto looser;
+
+ rv=oauth_encode_base64(signature.len, signature.data);
+ SECITEM_FreeItem(&signature, PR_FALSE);
+
+looser:
+ if (pkey) SECKEY_DestroyPrivateKey(pkey);
+ if (slot) PK11_FreeSlot(slot);
+ free(key);
+ return rv;
+}
+
+int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *sig) {
+ PK11SlotInfo *slot = NULL;
+ SECKEYPublicKey *pkey = NULL;
+ CERTCertificate *cert = NULL;
+ SECItem signature;
+ SECStatus s;
+ SECItem der;
+ int rv=0;
+
+ char *key = oauth_strip_pkcs(c, NS_CERT_HEADER, NS_CERT_TRAILER);
+ if (!key) return 0;
+
+ oauth_init_nss();
+
+ s = ATOB_ConvertAsciiToItem(&signature, (char*) sig); // XXX cast (const char*) -> (char*)
+ if (s != SECSuccess) goto looser;
+ slot = PK11_GetInternalKeySlot();
+ if (!slot) goto looser;
+ s = ATOB_ConvertAsciiToItem(&der, key);
+ if (s != SECSuccess) goto looser;
+ cert = __CERT_DecodeDERCertificate(&der, PR_TRUE, NULL);
+ SECITEM_FreeItem(&der, PR_FALSE);
+ if (!cert) goto looser;
+ pkey = CERT_ExtractPublicKey(cert);
+ if (!pkey) goto looser;
+ if (pkey->keyType != rsaKey) goto looser;
+
+ s = VFY_VerifyData((unsigned char*) m, strlen(m), pkey, &signature, SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE, NULL);
+ if (s == SECSuccess) rv=1;
+#if 0
+ else if (PR_GetError()!= SEC_ERROR_BAD_SIGNATURE) rv=-1;
+#endif
+
+looser:
+ if (pkey) SECKEY_DestroyPublicKey(pkey);
+ if (slot) PK11_FreeSlot(slot);
+ free(key);
+ return rv;
+}
+
+char *oauth_body_hash_file(char *filename) {
+ PK11SlotInfo *slot = NULL;
+ PK11Context *context = NULL;
+ unsigned char digest[20]; // Is there a way to tell how large the output is?
+ unsigned int len;
+ SECStatus s;
+ char *rv=NULL;
+ size_t bl;
+ unsigned char fb[BUFSIZ];
+
+ FILE *F= fopen(filename, "r");
+ if (!F) return NULL;
+
+ oauth_init_nss();
+
+ slot = PK11_GetInternalKeySlot();
+ if (!slot) goto looser;
+ context = PK11_CreateDigestContext(SEC_OID_SHA1);
+ if (!context) goto looser;
+
+ s = PK11_DigestBegin(context);
+ if (s != SECSuccess) goto looser;
+ while (!feof(F) && (bl=fread(fb,sizeof(char),BUFSIZ, F))>0) {
+ s = PK11_DigestOp(context, (unsigned char*) fb, bl);
+ if (s != SECSuccess) goto looser;
+ }
+ s = PK11_DigestFinal(context, digest, &len, sizeof digest);
+ if (s != SECSuccess) goto looser;
+
+ unsigned char *dgst = xmalloc(len*sizeof(char)); // oauth_body_hash_encode frees the digest..
+ memcpy(dgst, digest, len);
+ rv=oauth_body_hash_encode(len, dgst);
+
+looser:
+ fclose(F);
+ if (context) PK11_DestroyContext(context, PR_TRUE);
+ if (slot) PK11_FreeSlot(slot);
+ return rv;
+}
+
+char *oauth_body_hash_data(size_t length, const char *data) {
+ PK11SlotInfo *slot = NULL;
+ PK11Context *context = NULL;
+ unsigned char digest[20]; // Is there a way to tell how large the output is?
+ unsigned int len;
+ SECStatus s;
+ char *rv=NULL;
+
+ oauth_init_nss();
+
+ slot = PK11_GetInternalKeySlot();
+ if (!slot) goto looser;
+ context = PK11_CreateDigestContext(SEC_OID_SHA1);
+ if (!context) goto looser;
+
+ s = PK11_DigestBegin(context);
+ if (s != SECSuccess) goto looser;
+ s = PK11_DigestOp(context, (unsigned char*) data, length);
+ if (s != SECSuccess) goto looser;
+ s = PK11_DigestFinal(context, digest, &len, sizeof digest);
+ if (s != SECSuccess) goto looser;
+
+ unsigned char *dgst = xmalloc(len*sizeof(char)); // oauth_body_hash_encode frees the digest..
+ memcpy(dgst, digest, len);
+ rv=oauth_body_hash_encode(len, dgst);
+
+looser:
+ if (context) PK11_DestroyContext(context, PR_TRUE);
+ if (slot) PK11_FreeSlot(slot);
+ return rv;
+}
+
+#else
+/* use http://www.openssl.org/ for hash/sign */
+
+#ifdef _GNU_SOURCE
+/*
+ * In addition, as a special exception, the copyright holders give
+ * permission to link the code of portions of this program with the
+ * OpenSSL library under certain conditions as described in each
+ * individual source file, and distribute linked combinations
+ * including the two.
+ * You must obey the GNU General Public License in all respects
+ * for all of the code used other than OpenSSL. If you modify
+ * file(s) with this exception, you may extend this exception to your
+ * version of the file(s), but you are not obligated to do so. If you
+ * do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source
+ * files in the program, then also delete it here.
+ */
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "xmalloc.h"
+#include "oauth.h" // base64 encode fn's.
+#include <openssl/hmac.h>
+
+char *oauth_sign_hmac_sha1 (const char *m, const char *k) {
+ return(oauth_sign_hmac_sha1_raw (m, strlen(m), k, strlen(k)));
+}
+
+char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl) {
+ unsigned char result[EVP_MAX_MD_SIZE];
+ unsigned int resultlen = 0;
+
+ HMAC(EVP_sha1(), k, kl,
+ (unsigned char*) m, ml,
+ result, &resultlen);
+
+ return(oauth_encode_base64(resultlen, result));
+}
+
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+#include <openssl/ssl.h>
+
+char *oauth_sign_rsa_sha1 (const char *m, const char *k) {
+ unsigned char *sig = NULL;
+ unsigned char *passphrase = NULL;
+ unsigned int len=0;
+ EVP_MD_CTX md_ctx;
+
+ EVP_PKEY *pkey;
+ BIO *in;
+ in = BIO_new_mem_buf((unsigned char*) k, strlen(k));
+ pkey = PEM_read_bio_PrivateKey(in, NULL, 0, passphrase); // generate sign
+ BIO_free(in);
+
+ if (pkey == NULL) {
+ //fprintf(stderr, "liboauth/OpenSSL: can not read private key\n");
+ return xstrdup("liboauth/OpenSSL: can not read private key");
+ }
+
+ len = EVP_PKEY_size(pkey);
+ sig = (unsigned char*)xmalloc((len+1)*sizeof(char));
+
+ EVP_SignInit(&md_ctx, EVP_sha1());
+ EVP_SignUpdate(&md_ctx, m, strlen(m));
+ if (EVP_SignFinal (&md_ctx, sig, &len, pkey)) {
+ char *tmp;
+ sig[len] = '\0';
+ tmp = oauth_encode_base64(len,sig);
+ OPENSSL_free(sig);
+ EVP_PKEY_free(pkey);
+ return tmp;
+ }
+ return xstrdup("liboauth/OpenSSL: rsa-sha1 signing failed");
+}
+
+int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *s) {
+ EVP_MD_CTX md_ctx;
+ EVP_PKEY *pkey;
+ BIO *in;
+ X509 *cert = NULL;
+ unsigned char *b64d;
+ int slen, err;
+
+ in = BIO_new_mem_buf((unsigned char*)c, strlen(c));
+ cert = PEM_read_bio_X509(in, NULL, 0, NULL);
+ if (cert) {
+ pkey = (EVP_PKEY *) X509_get_pubkey(cert);
+ X509_free(cert);
+ } else {
+ pkey = PEM_read_bio_PUBKEY(in, NULL, 0, NULL);
+ }
+ BIO_free(in);
+ if (pkey == NULL) {
+ //fprintf(stderr, "could not read cert/pubkey.\n");
+ return -2;
+ }
+
+ b64d= (unsigned char*) xmalloc(sizeof(char)*strlen(s));
+ slen = oauth_decode_base64(b64d, s);
+
+ EVP_VerifyInit(&md_ctx, EVP_sha1());
+ EVP_VerifyUpdate(&md_ctx, m, strlen(m));
+ err = EVP_VerifyFinal(&md_ctx, b64d, slen, pkey);
+ EVP_MD_CTX_cleanup(&md_ctx);
+ EVP_PKEY_free(pkey);
+ free(b64d);
+ return (err);
+}
+
+
+/**
+ * http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
+ */
+char *oauth_body_hash_file(char *filename) {
+ unsigned char fb[BUFSIZ];
+ EVP_MD_CTX ctx;
+ size_t len=0;
+ unsigned char *md;
+ FILE *F= fopen(filename, "r");
+ if (!F) return NULL;
+
+ EVP_MD_CTX_init(&ctx);
+ EVP_DigestInit(&ctx,EVP_sha1());
+ while (!feof(F) && (len=fread(fb,sizeof(char),BUFSIZ, F))>0) {
+ EVP_DigestUpdate(&ctx, fb, len);
+ }
+ fclose(F);
+ len=0;
+ md=(unsigned char*) xcalloc(EVP_MD_size(EVP_sha1()),sizeof(unsigned char));
+ EVP_DigestFinal(&ctx, md,(unsigned int*) &len);
+ EVP_MD_CTX_cleanup(&ctx);
+ return oauth_body_hash_encode(len, md);
+}
+
+char *oauth_body_hash_data(size_t length, const char *data) {
+ EVP_MD_CTX ctx;
+ size_t len=0;
+ unsigned char *md;
+ md=(unsigned char*) xcalloc(EVP_MD_size(EVP_sha1()),sizeof(unsigned char));
+ EVP_MD_CTX_init(&ctx);
+ EVP_DigestInit(&ctx,EVP_sha1());
+ EVP_DigestUpdate(&ctx, data, length);
+ EVP_DigestFinal(&ctx, md,(unsigned int*) &len);
+ EVP_MD_CTX_cleanup(&ctx);
+ return oauth_body_hash_encode(len, md);
+}
+
+#endif
+
+// vi: sts=2 sw=2 ts=2
diff --git a/protocols/Twitter/src/oauth/oauth.c b/protocols/Twitter/src/oauth/oauth.c
new file mode 100644
index 0000000000..0f205572dd
--- /dev/null
+++ b/protocols/Twitter/src/oauth/oauth.c
@@ -0,0 +1,921 @@
+/*
+ * OAuth string functions in POSIX-C.
+ *
+ * Copyright 2007-2011 Robin Gareus <robin@gareus.org>
+ *
+ * The base64 functions are by Jan-Henrik Haukeland, <hauk@tildeslash.com>
+ * and un/escape_url() was inspired by libcurl's curl_escape under ISC-license
+ * many thanks to Daniel Stenberg <daniel@haxx.se>.
+ *
+ * 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 HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#define WIPE_MEMORY ///< overwrite sensitve data before free()ing it.
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <math.h>
+#include <ctype.h> // isxdigit
+
+#include "xmalloc.h"
+#include "oauth.h"
+
+#ifndef WIN32 // getpid() on POSIX systems
+#include <sys/types.h>
+#include <unistd.h>
+#else
+#define snprintf _snprintf
+#define strncasecmp strnicmp
+#endif
+
+/**
+ * Base64 encode one byte
+ */
+char oauth_b64_encode(unsigned char u) {
+ if(u < 26) return 'A'+u;
+ if(u < 52) return 'a'+(u-26);
+ if(u < 62) return '0'+(u-52);
+ if(u == 62) return '+';
+ return '/';
+}
+
+/**
+ * Decode a single base64 character.
+ */
+unsigned char oauth_b64_decode(char c) {
+ if(c >= 'A' && c <= 'Z') return(c - 'A');
+ if(c >= 'a' && c <= 'z') return(c - 'a' + 26);
+ if(c >= '0' && c <= '9') return(c - '0' + 52);
+ if(c == '+') return 62;
+ return 63;
+}
+
+/**
+ * Return TRUE if 'c' is a valid base64 character, otherwise FALSE
+ */
+int oauth_b64_is_base64(char c) {
+ if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+ (c >= '0' && c <= '9') || (c == '+') ||
+ (c == '/') || (c == '=')) {
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Base64 encode and return size data in 'src'. The caller must free the
+ * returned string.
+ *
+ * @param size The size of the data in src
+ * @param src The data to be base64 encode
+ * @return encoded string otherwise NULL
+ */
+char *oauth_encode_base64(int size, const unsigned char *src) {
+ int i;
+ char *out, *p;
+
+ if(!src) return NULL;
+ if(!size) size= strlen((char *)src);
+ out= (char*) xcalloc(sizeof(char), size*4/3+4);
+ p= out;
+
+ for(i=0; i<size; i+=3) {
+ unsigned char b1=0, b2=0, b3=0, b4=0, b5=0, b6=0, b7=0;
+ b1= src[i];
+ if(i+1<size) b2= src[i+1];
+ if(i+2<size) b3= src[i+2];
+
+ b4= b1>>2;
+ b5= ((b1&0x3)<<4)|(b2>>4);
+ b6= ((b2&0xf)<<2)|(b3>>6);
+ b7= b3&0x3f;
+
+ *p++= oauth_b64_encode(b4);
+ *p++= oauth_b64_encode(b5);
+
+ if(i+1<size) *p++= oauth_b64_encode(b6);
+ else *p++= '=';
+
+ if(i+2<size) *p++= oauth_b64_encode(b7);
+ else *p++= '=';
+ }
+ return out;
+}
+
+/**
+ * Decode the base64 encoded string 'src' into the memory pointed to by
+ * 'dest'.
+ *
+ * @param dest Pointer to memory for holding the decoded string.
+ * Must be large enough to receive the decoded string.
+ * @param src A base64 encoded string.
+ * @return the length of the decoded string if decode
+ * succeeded otherwise 0.
+ */
+int oauth_decode_base64(unsigned char *dest, const char *src) {
+ if(src && *src) {
+ unsigned char *p= dest;
+ int k, l= strlen(src)+1;
+ unsigned char *buf= (unsigned char*) xcalloc(sizeof(unsigned char), l);
+
+ /* Ignore non base64 chars as per the POSIX standard */
+ for(k=0, l=0; src[k]; k++) {
+ if(oauth_b64_is_base64(src[k])) {
+ buf[l++]= src[k];
+ }
+ }
+
+ for(k=0; k<l; k+=4) {
+ char c1='A', c2='A', c3='A', c4='A';
+ unsigned char b1=0, b2=0, b3=0, b4=0;
+ c1= buf[k];
+
+ if(k+1<l) c2= buf[k+1];
+ if(k+2<l) c3= buf[k+2];
+ if(k+3<l) c4= buf[k+3];
+
+ b1= oauth_b64_decode(c1);
+ b2= oauth_b64_decode(c2);
+ b3= oauth_b64_decode(c3);
+ b4= oauth_b64_decode(c4);
+
+ *p++=((b1<<2)|(b2>>4) );
+
+ if(c3 != '=') *p++=(((b2&0xf)<<4)|(b3>>2) );
+ if(c4 != '=') *p++=(((b3&0x3)<<6)|b4 );
+ }
+ free(buf);
+ dest[p-dest]='\0';
+ return(p-dest);
+ }
+ return 0;
+}
+
+/**
+ * Escape 'string' according to RFC3986 and
+ * http://oauth.net/core/1.0/#encoding_parameters.
+ *
+ * @param string The data to be encoded
+ * @return encoded string otherwise NULL
+ * The caller must free the returned string.
+ */
+char *oauth_url_escape(const char *string) {
+ size_t alloc, newlen;
+ char *ns = NULL, *testing_ptr = NULL;
+ unsigned char in;
+ size_t strindex=0;
+ size_t length;
+
+ if (!string) return xstrdup("");
+
+ alloc = strlen(string)+1;
+ newlen = alloc;
+
+ ns = (char*) xmalloc(alloc);
+
+ length = alloc-1;
+ while(length--) {
+ in = *string;
+
+ switch(in){
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9':
+ case 'a': case 'b': case 'c': case 'd': case 'e':
+ case 'f': case 'g': case 'h': case 'i': case 'j':
+ case 'k': case 'l': case 'm': case 'n': case 'o':
+ case 'p': case 'q': case 'r': case 's': case 't':
+ case 'u': case 'v': case 'w': case 'x': case 'y': case 'z':
+ case 'A': case 'B': case 'C': case 'D': case 'E':
+ case 'F': case 'G': case 'H': case 'I': case 'J':
+ case 'K': case 'L': case 'M': case 'N': case 'O':
+ case 'P': case 'Q': case 'R': case 'S': case 'T':
+ case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z':
+ case '_': case '~': case '.': case '-':
+ ns[strindex++]=in;
+ break;
+ default:
+ newlen += 2; /* this'll become a %XX */
+ if(newlen > alloc) {
+ alloc *= 2;
+ testing_ptr = (char*) xrealloc(ns, alloc);
+ ns = testing_ptr;
+ }
+ snprintf(&ns[strindex], 4, "%%%02X", in);
+ strindex+=3;
+ break;
+ }
+ string++;
+ }
+ ns[strindex]=0;
+ return ns;
+}
+
+#ifndef ISXDIGIT
+# define ISXDIGIT(x) (isxdigit((int) ((unsigned char)x)))
+#endif
+
+/**
+ * Parse RFC3986 encoded 'string' back to unescaped version.
+ *
+ * @param string The data to be unescaped
+ * @param olen unless NULL the length of the returned string is stored there.
+ * @return decoded string or NULL
+ * The caller must free the returned string.
+ */
+char *oauth_url_unescape(const char *string, size_t *olen) {
+ size_t alloc, strindex=0;
+ char *ns = NULL;
+ unsigned char in;
+ long hex;
+
+ if (!string) return NULL;
+ alloc = strlen(string)+1;
+ ns = (char*) xmalloc(alloc);
+
+ while(--alloc > 0) {
+ in = *string;
+ if(('%' == in) && ISXDIGIT(string[1]) && ISXDIGIT(string[2])) {
+ char hexstr[3]; // '%XX'
+ hexstr[0] = string[1];
+ hexstr[1] = string[2];
+ hexstr[2] = 0;
+ hex = strtol(hexstr, NULL, 16);
+ in = (unsigned char)hex; /* hex is always < 256 */
+ string+=2;
+ alloc-=2;
+ }
+ ns[strindex++] = in;
+ string++;
+ }
+ ns[strindex]=0;
+ if(olen) *olen = strindex;
+ return ns;
+}
+
+/**
+ * returns plaintext signature for the given key.
+ *
+ * the returned string needs to be freed by the caller
+ *
+ * @param m message to be signed
+ * @param k key used for signing
+ * @return signature string
+ */
+char *oauth_sign_plaintext (const char *m, const char *k) {
+ return(oauth_url_escape(k));
+}
+
+/**
+ * encode strings and concatenate with '&' separator.
+ * The number of strings to be concatenated must be
+ * given as first argument.
+ * all arguments thereafter must be of type (char *)
+ *
+ * @param len the number of arguments to follow this parameter
+ * @param ... string to escape and added (may be NULL)
+ *
+ * @return pointer to memory holding the concatenated
+ * strings - needs to be free(d) by the caller. or NULL
+ * in case we ran out of memory.
+ */
+char *oauth_catenc(int len, ...) {
+ va_list va;
+ int i;
+ char *rv = (char*) xmalloc(sizeof(char));
+ *rv='\0';
+ va_start(va, len);
+ for(i=0;i<len;i++) {
+ char *arg = va_arg(va, char *);
+ char *enc;
+ int len;
+ enc = oauth_url_escape(arg);
+ if(!enc) break;
+ len = strlen(enc) + 1 + ((i>0)?1:0);
+ if(rv) len+=strlen(rv);
+ rv=(char*) xrealloc(rv,len*sizeof(char));
+
+ if(i>0) strcat(rv, "&");
+ strcat(rv, enc);
+ free(enc);
+ }
+ va_end(va);
+ return(rv);
+}
+
+/**
+ * splits the given url into a parameter array.
+ * (see \ref oauth_serialize_url and \ref oauth_serialize_url_parameters for the reverse)
+ *
+ * NOTE: Request-parameters-values may include an ampersand character.
+ * However if unescaped this function will use them as parameter delimiter.
+ * If you need to make such a request, this function since version 0.3.5 allows
+ * to use the ASCII SOH (0x01) character as alias for '&' (0x26).
+ * (the motivation is convenience: SOH is /untypeable/ and much more
+ * unlikely to appear than '&' - If you plan to sign fancy URLs you
+ * should not split a query-string, but rather provide the parameter array
+ * directly to \ref oauth_serialize_url)
+ *
+ * @param url the url or query-string to parse.
+ * @param argv pointer to a (char *) array where the results are stored.
+ * The array is re-allocated to match the number of parameters and each
+ * parameter-string is allocated with strdup. - The memory needs to be freed
+ * by the caller.
+ * @param qesc use query parameter escape (vs post-param-escape) - if set
+ * to 1 all '+' are treated as spaces ' '
+ *
+ * @return number of parameter(s) in array.
+ */
+int oauth_split_post_paramters(const char *url, char ***argv, short qesc) {
+ int argc=0;
+ char *token, *tmp, *t1;
+ if (!argv) return 0;
+ if (!url) return 0;
+ t1=xstrdup(url);
+
+ // '+' represents a space, in a URL query string
+ while ((qesc&1) && (tmp=strchr(t1,'+'))) *tmp=' ';
+
+ tmp=t1;
+ while((token=strtok(tmp,"&?"))) {
+ if(!strncasecmp("oauth_signature=",token,16)) continue;
+ (*argv)=(char**) xrealloc(*argv,sizeof(char*)*(argc+1));
+ while (!(qesc&2) && (tmp=strchr(token,'\001'))) *tmp='&';
+ if (argc>0 || (qesc&4))
+ (*argv)[argc]=oauth_url_unescape(token, NULL);
+ else
+ (*argv)[argc]=xstrdup(token);
+ if (argc==0 && strstr(token, ":/")) {
+ // HTTP does not allow empty absolute paths, so the URL
+ // 'http://example.com' is equivalent to 'http://example.com/' and should
+ // be treated as such for the purposes of OAuth signing (rfc2616, section 3.2.1)
+ // see http://groups.google.com/group/oauth/browse_thread/thread/c44b6f061bfd98c?hl=en
+ char *slash=strstr(token, ":/");
+ while (slash && *(++slash) == '/') ; // skip slashes eg /xxx:[\/]*/
+#if 0
+ // skip possibly unescaped slashes in the userinfo - they're not allowed by RFC2396 but have been seen.
+ // the hostname/IP may only contain alphanumeric characters - so we're safe there.
+ if (slash && strchr(slash,'@')) slash=strchr(slash,'@');
+#endif
+ if (slash && !strchr(slash,'/')) {
+#ifdef DEBUG_OAUTH
+ fprintf(stderr, "\nliboauth: added trailing slash to URL: '%s'\n\n", token);
+#endif
+ free((*argv)[argc]);
+ (*argv)[argc]= (char*) xmalloc(sizeof(char)*(2+strlen(token)));
+ strcpy((*argv)[argc],token);
+ strcat((*argv)[argc],"/");
+ }
+ }
+ if (argc==0 && (tmp=strstr((*argv)[argc],":80/"))) {
+ memmove(tmp, tmp+3, strlen(tmp+2));
+ }
+ tmp=NULL;
+ argc++;
+ }
+
+ free(t1);
+ return argc;
+}
+
+int oauth_split_url_parameters(const char *url, char ***argv) {
+ return oauth_split_post_paramters(url, argv, 1);
+}
+
+/**
+ * build a url query string from an array.
+ *
+ * @param argc the total number of elements in the array
+ * @param start element in the array at which to start concatenating.
+ * @param argv parameter-array to concatenate.
+ * @return url string needs to be freed by the caller.
+ *
+ */
+char *oauth_serialize_url (int argc, int start, char **argv) {
+ return oauth_serialize_url_sep( argc, start, argv, "&", 0);
+}
+
+/**
+ * encode query parameters from an array.
+ *
+ * @param argc the total number of elements in the array
+ * @param start element in the array at which to start concatenating.
+ * @param argv parameter-array to concatenate.
+ * @param sep separator for parameters (usually "&")
+ * @param mod - bitwise modifiers:
+ * 1: skip all values that start with "oauth_"
+ * 2: skip all values that don't start with "oauth_"
+ * 4: add double quotation marks around values (use with sep=", " to generate HTTP Authorization header).
+ * @return url string needs to be freed by the caller.
+ */
+char *oauth_serialize_url_sep (int argc, int start, char **argv, char *sep, int mod) {
+ char *tmp, *t1;
+ int i;
+ int first=1;
+ int seplen=strlen(sep);
+ char *query = (char*) xmalloc(sizeof(char));
+ *query='\0';
+ for(i=start; i< argc; i++) {
+ int len = 0;
+ if ((mod&1)==1 && (strncmp(argv[i],"oauth_",6) == 0 || strncmp(argv[i],"x_oauth_",8) == 0) ) continue;
+ if ((mod&2)==2 && (strncmp(argv[i],"oauth_",6) != 0 && strncmp(argv[i],"x_oauth_",8) != 0) && i!=0) continue;
+
+ if (query) len+=strlen(query);
+
+ if (i==start && i==0 && strstr(argv[i], ":/")) {
+ tmp=xstrdup(argv[i]);
+#if 1 // encode white-space in the base-url
+ while ((t1=strchr(tmp,' '))) {
+# if 0
+ *t1='+';
+# else
+ size_t off = t1-tmp;
+ char *t2 = (char*) xmalloc(sizeof(char)*(3+strlen(tmp)));
+ strcpy(t2, tmp);
+ strcpy(t2+off+2, tmp+off);
+ *(t2+off)='%'; *(t2+off+1)='2'; *(t2+off+2)='0';
+ free(tmp);
+ tmp=t2;
+# endif
+#endif
+ }
+ len+=strlen(tmp);
+ } else if(!(t1=strchr(argv[i], '='))) {
+ // see http://oauth.net/core/1.0/#anchor14
+ // escape parameter names and arguments but not the '='
+ tmp=xstrdup(argv[i]);
+ tmp=(char*) xrealloc(tmp,(strlen(tmp)+2)*sizeof(char));
+ strcat(tmp,"=");
+ len+=strlen(tmp);
+ } else {
+ *t1=0;
+ tmp = oauth_url_escape(argv[i]);
+ *t1='=';
+ t1 = oauth_url_escape((t1+1));
+ tmp=(char*) xrealloc(tmp,(strlen(tmp)+strlen(t1)+2+(mod&4?2:0))*sizeof(char));
+ strcat(tmp,"=");
+ if (mod&4) strcat(tmp,"\"");
+ strcat(tmp,t1);
+ if (mod&4) strcat(tmp,"\"");
+ free(t1);
+ len+=strlen(tmp);
+ }
+ len+=seplen+1;
+ query=(char*) xrealloc(query,len*sizeof(char));
+ strcat(query, ((i==start||first)?"":sep));
+ first=0;
+ strcat(query, tmp);
+ if (i==start && i==0 && strstr(tmp, ":/")) {
+ strcat(query, "?");
+ first=1;
+ }
+ free(tmp);
+ }
+ return (query);
+}
+
+/**
+ * build a query parameter string from an array.
+ *
+ * This function is a shortcut for \ref oauth_serialize_url (argc, 1, argv).
+ * It strips the leading host/path, which is usually the first
+ * element when using oauth_split_url_parameters on an URL.
+ *
+ * @param argc the total number of elements in the array
+ * @param argv parameter-array to concatenate.
+ * @return url string needs to be freed by the caller.
+ */
+char *oauth_serialize_url_parameters (int argc, char **argv) {
+ return oauth_serialize_url(argc, 1, argv);
+}
+
+/**
+ * generate a random string between 15 and 32 chars length
+ * and return a pointer to it. The value needs to be freed by the
+ * caller
+ *
+ * @return zero terminated random string.
+ */
+#if !defined HAVE_OPENSSL_HMAC_H && !defined USE_NSS
+/* pre liboauth-0.7.2 and possible future versions that don't use OpenSSL or NSS */
+char *oauth_gen_nonce() {
+ char *nc;
+ static int rndinit = 1;
+ const char *chars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_";
+ unsigned int max = strlen( chars );
+ int i, len;
+
+ if(rndinit) {srand(time(NULL)
+#ifndef WIN32 // quick windows check.
+ * getpid()
+#endif
+ ); rndinit=0;} // seed random number generator - FIXME: we can do better ;)
+
+ len=15+floor(rand()*16.0/(double)RAND_MAX);
+ nc = (char*) xmalloc((len+1)*sizeof(char));
+ for(i=0;i<len; i++) {
+ nc[i] = chars[ rand() % max ];
+ }
+ nc[i]='\0';
+ return (nc);
+}
+#else // OpenSSL or NSS random number generator
+#ifdef USE_NSS
+ void oauth_init_nss(); //decladed in hash.c
+# include "pk11pub.h"
+# define MY_RAND PK11_GenerateRandom
+# define MY_SRAND oauth_init_nss();
+#else
+# ifdef _GNU_SOURCE
+/* Note: the OpenSSL/GPL exemption stated
+ * verbosely in hash.c applies to this code as well. */
+# endif
+# include <openssl/rand.h>
+# define MY_RAND RAND_bytes
+# define MY_SRAND ;
+#endif
+char *oauth_gen_nonce() {
+ char *nc;
+ unsigned char buf;
+ const char *chars = "abcdefghijklmnopqrstuvwxyz"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789_";
+ unsigned int max = strlen(chars);
+ int i, len;
+
+ MY_SRAND
+ MY_RAND(&buf, 1);
+ len=15+(((short)buf)&0x0f);
+ nc = (char*) xmalloc((len+1)*sizeof(char));
+ for(i=0;i<len; i++) {
+ MY_RAND(&buf, 1);
+ nc[i] = chars[ ((short)buf) % max ];
+ }
+ nc[i]='\0';
+ return (nc);
+}
+#endif
+
+/**
+ * string compare function for oauth parameters.
+ *
+ * used with qsort. needed to normalize request parameters.
+ * see http://oauth.net/core/1.0/#anchor14
+ */
+int oauth_cmpstringp(const void *p1, const void *p2) {
+ char *v1,*v2;
+ char *t1,*t2;
+ int rv;
+ // TODO: this is not fast - we should escape the
+ // array elements (once) before sorting.
+ v1=oauth_url_escape(* (char * const *)p1);
+ v2=oauth_url_escape(* (char * const *)p2);
+
+ // '=' signs are not "%3D" !
+ if ((t1=strstr(v1,"%3D"))) {
+ t1[0]='\0'; t1[1]='='; t1[2]='=';
+ }
+ if ((t2=strstr(v2,"%3D"))) {
+ t2[0]='\0'; t2[1]='='; t2[2]='=';
+ }
+
+ // compare parameter names
+ rv=strcmp(v1,v2);
+ if (rv!=0) {
+ if (v1) free(v1);
+ if (v2) free(v2);
+ return rv;
+ }
+
+ // if parameter names are equal, sort by value.
+ if (t1) t1[0]='=';
+ if (t2) t2[0]='=';
+ if (t1 && t2) rv=strcmp(t1,t2);
+ else if (!t1 && !t2) rv=0;
+ else if (!t1) rv=-1;
+ else rv=1;
+
+ if (v1) free(v1);
+ if (v2) free(v2);
+ return rv;
+}
+
+/**
+ * search array for parameter key.
+ * @param argv length of array to search
+ * @param argc parameter array to search
+ * @param key key of parameter to check.
+ *
+ * @return FALSE (0) if array does not contain a parameter with given key, TRUE (1) otherwise.
+ */
+int oauth_param_exists(char **argv, int argc, char *key) {
+ int i;
+ size_t l= strlen(key);
+ for (i=0;i<argc;i++)
+ if (strlen(argv[i])>l && !strncmp(argv[i],key,l) && argv[i][l] == '=') return 1;
+ return 0;
+}
+
+/**
+ * add query parameter to array
+ *
+ * @param argcp pointer to array length int
+ * @param argvp pointer to array values
+ * @param addparam parameter to add (eg. "foo=bar")
+ */
+void oauth_add_param_to_array(int *argcp, char ***argvp, const char *addparam) {
+ (*argvp)=(char**) xrealloc(*argvp,sizeof(char*)*((*argcp)+1));
+ (*argvp)[(*argcp)++]= (char*) xstrdup(addparam);
+}
+
+/**
+ *
+ */
+void oauth_add_protocol(int *argcp, char ***argvp,
+ OAuthMethod method,
+ const char *c_key, //< consumer key - posted plain text
+ const char *t_key //< token key - posted plain text in URL
+ ){
+ char oarg[1024];
+
+ // add OAuth specific arguments
+ if (!oauth_param_exists(*argvp,*argcp,"oauth_nonce")) {
+ char *tmp;
+ snprintf(oarg, 1024, "oauth_nonce=%s", (tmp=oauth_gen_nonce()));
+ oauth_add_param_to_array(argcp, argvp, oarg);
+ free(tmp);
+ }
+
+ if (!oauth_param_exists(*argvp,*argcp,"oauth_timestamp")) {
+ snprintf(oarg, 1024, "oauth_timestamp=%li", (long int) time(NULL));
+ oauth_add_param_to_array(argcp, argvp, oarg);
+ }
+
+ if (t_key) {
+ snprintf(oarg, 1024, "oauth_token=%s", t_key);
+ oauth_add_param_to_array(argcp, argvp, oarg);
+ }
+
+ snprintf(oarg, 1024, "oauth_consumer_key=%s", c_key);
+ oauth_add_param_to_array(argcp, argvp, oarg);
+
+ snprintf(oarg, 1024, "oauth_signature_method=%s",
+ method==0?"HMAC-SHA1":method==1?"RSA-SHA1":"PLAINTEXT");
+ oauth_add_param_to_array(argcp, argvp, oarg);
+
+ if (!oauth_param_exists(*argvp,*argcp,"oauth_version")) {
+ snprintf(oarg, 1024, "oauth_version=1.0");
+ oauth_add_param_to_array(argcp, argvp, oarg);
+ }
+
+#if 0 // oauth_version 1.0 Rev A
+ if (!oauth_param_exists(argv,argc,"oauth_callback")) {
+ snprintf(oarg, 1024, "oauth_callback=oob");
+ oauth_add_param_to_array(argcp, argvp, oarg);
+ }
+#endif
+
+}
+
+char *oauth_sign_url (const char *url, char **postargs,
+ OAuthMethod method,
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) {
+ return oauth_sign_url2(url, postargs,
+ method, NULL,
+ c_key, c_secret,
+ t_key, t_secret);
+}
+
+char *oauth_sign_url2 (const char *url, char **postargs,
+ OAuthMethod method,
+ const char *http_method, //< HTTP request method
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) {
+ int argc;
+ char **argv = NULL;
+ char *rv;
+
+ if (postargs)
+ argc = oauth_split_post_paramters(url, &argv, 0);
+ else
+ argc = oauth_split_url_parameters(url, &argv);
+
+ rv=oauth_sign_array2(&argc, &argv, postargs,
+ method, http_method,
+ c_key, c_secret, t_key, t_secret);
+
+ oauth_free_array(&argc, &argv);
+ return(rv);
+}
+
+char *oauth_sign_array (int *argcp, char***argvp,
+ char **postargs,
+ OAuthMethod method,
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) {
+ return oauth_sign_array2 (argcp, argvp,
+ postargs, method,
+ NULL,
+ c_key, c_secret,
+ t_key, t_secret);
+}
+
+void oauth_sign_array2_process (int *argcp, char***argvp,
+ char **postargs,
+ OAuthMethod method,
+ const char *http_method, //< HTTP request method
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) {
+ char oarg[1024];
+ char *query;
+ char *okey, *odat, *sign;
+ char *http_request_method;
+
+ if (!http_method) {
+ http_request_method = xstrdup(postargs?"POST":"GET");
+ } else {
+ int i;
+ http_request_method = xstrdup(http_method);
+ for (i=0;i<strlen(http_request_method);i++)
+ http_request_method[i]=toupper(http_request_method[i]);
+ }
+
+ // add required OAuth protocol parameters
+ oauth_add_protocol(argcp, argvp, method, c_key, t_key);
+
+ // sort parameters
+ qsort(&(*argvp)[1], (*argcp)-1, sizeof(char *), oauth_cmpstringp);
+
+ // serialize URL - base-url
+ query= oauth_serialize_url_parameters(*argcp, *argvp);
+
+ // generate signature
+ okey = oauth_catenc(2, c_secret, t_secret);
+ odat = oauth_catenc(3, http_request_method, (*argvp)[0], query); // base-string
+ free(http_request_method);
+#ifdef DEBUG_OAUTH
+ fprintf (stderr, "\nliboauth: data to sign='%s'\n\n", odat);
+ fprintf (stderr, "\nliboauth: key='%s'\n\n", okey);
+#endif
+ switch(method) {
+ case OA_RSA:
+ sign = oauth_sign_rsa_sha1(odat,okey); // XXX okey needs to be RSA key!
+ break;
+ case OA_PLAINTEXT:
+ sign = oauth_sign_plaintext(odat,okey);
+ break;
+ default:
+ sign = oauth_sign_hmac_sha1(odat,okey);
+ }
+#ifdef WIPE_MEMORY
+ memset(okey,0, strlen(okey));
+ memset(odat,0, strlen(odat));
+#endif
+ free(odat);
+ free(okey);
+
+ // append signature to query args.
+ snprintf(oarg, 1024, "oauth_signature=%s",sign);
+ oauth_add_param_to_array(argcp, argvp, oarg);
+ free(sign);
+ if(query) free(query);
+}
+
+char *oauth_sign_array2 (int *argcp, char***argvp,
+ char **postargs,
+ OAuthMethod method,
+ const char *http_method, //< HTTP request method
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) {
+
+ char *result;
+ oauth_sign_array2_process(argcp, argvp, postargs, method, http_method, c_key, c_secret, t_key, t_secret);
+
+ // build URL params
+ result = oauth_serialize_url(*argcp, (postargs?1:0), *argvp);
+
+ if(postargs) {
+ *postargs = result;
+ result = xstrdup((*argvp)[0]);
+ }
+
+ return result;
+}
+
+
+/**
+ * free array args
+ *
+ * @param argcp pointer to array length int
+ * @param argvp pointer to array values to be free()d
+ */
+void oauth_free_array(int *argcp, char ***argvp) {
+ int i;
+ for (i=0;i<(*argcp);i++) {
+ free((*argvp)[i]);
+ }
+ if(*argvp) free(*argvp);
+}
+
+/**
+ * base64 encode digest, free it and return a URL parameter
+ * with the oauth_body_hash
+ */
+char *oauth_body_hash_encode(size_t len, unsigned char *digest) {
+ char *sign=oauth_encode_base64(len,digest);
+ char *sig_url = (char*)xmalloc(17+strlen(sign));
+ sprintf(sig_url,"oauth_body_hash=%s", sign);
+ free(sign);
+ free(digest);
+ return sig_url;
+}
+
+
+/**
+ * compare two strings in constant-time (as to not let an
+ * attacker guess how many leading chars are correct:
+ * http://rdist.root.org/2010/01/07/timing-independent-array-comparison/ )
+ *
+ * @param a string to compare
+ * @param b string to compare
+ * @param len_a length of string a
+ * @param len_b length of string b
+ *
+ * returns 0 (false) if strings are not equal, and 1 (true) if strings are equal.
+ */
+int oauth_time_independent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b) {
+ int diff, i, j;
+ if (a == NULL) return (b == NULL);
+ else if (b == NULL) return 0;
+ else if (len_b == 0) return (len_a == 0);
+ diff = len_a ^ len_b;
+ j=0;
+ for (i=0; i<len_a; ++i) {
+ diff |= a[i] ^ b[j];
+ j = (j+1) % len_b;
+ }
+ return diff == 0;
+}
+int oauth_time_indepenent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b) {
+ return oauth_time_independent_equals_n(a, b, len_a, len_b);
+}
+
+int oauth_time_independent_equals(const char* a, const char* b) {
+ return oauth_time_independent_equals_n (a, b, a?strlen(a):0, b?strlen(b):0);
+}
+
+int oauth_time_indepenent_equals(const char* a, const char* b) {
+ return oauth_time_independent_equals_n (a, b, a?strlen(a):0, b?strlen(b):0);
+}
+
+/**
+ * xep-0235 - TODO
+ */
+char *oauth_sign_xmpp (const char *xml,
+ OAuthMethod method,
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) {
+
+ return NULL;
+}
+
+// vi: sts=2 sw=2 ts=2
diff --git a/protocols/Twitter/src/oauth/oauth.h b/protocols/Twitter/src/oauth/oauth.h
new file mode 100644
index 0000000000..4efb472fc8
--- /dev/null
+++ b/protocols/Twitter/src/oauth/oauth.h
@@ -0,0 +1,764 @@
+/**
+ * @brief OAuth.net implementation in POSIX-C.
+ * @file oauth.h
+ * @author Robin Gareus <robin@gareus.org>
+ *
+ * Copyright 2007-2011 Robin Gareus <robin@gareus.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 _OAUTH_H
+#define _OAUTH_H 1
+
+#ifndef DOXYGEN_IGNORE
+// liboauth version
+#define LIBOAUTH_VERSION "0.9.6"
+#define LIBOAUTH_VERSION_MAJOR 0
+#define LIBOAUTH_VERSION_MINOR 9
+#define LIBOAUTH_VERSION_MICRO 6
+
+//interface revision number
+//http://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html
+#define LIBOAUTH_CUR 8
+#define LIBOAUTH_REV 3
+#define LIBOAUTH_AGE 8
+#endif
+
+#ifdef __GNUC__
+# define OA_GCC_VERSION_AT_LEAST(x,y) (__GNUC__ > x || __GNUC__ == x && __GNUC_MINOR__ >= y)
+#else
+# define OA_GCC_VERSION_AT_LEAST(x,y) 0
+#endif
+
+#ifndef attribute_deprecated
+#if OA_GCC_VERSION_AT_LEAST(3,1)
+# define attribute_deprecated __attribute__((deprecated))
+#else
+# define attribute_deprecated
+#endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** \enum OAuthMethod
+ * signature method to used for signing the request.
+ */
+typedef enum {
+ OA_HMAC=0, ///< use HMAC-SHA1 request signing method
+ OA_RSA, ///< use RSA signature
+ OA_PLAINTEXT ///< use plain text signature (for testing only)
+ } OAuthMethod;
+
+/**
+ * Base64 encode and return size data in 'src'. The caller must free the
+ * returned string.
+ *
+ * @param size The size of the data in src
+ * @param src The data to be base64 encode
+ * @return encoded string otherwise NULL
+ */
+char *oauth_encode_base64(int size, const unsigned char *src);
+
+/**
+ * Decode the base64 encoded string 'src' into the memory pointed to by
+ * 'dest'.
+ *
+ * @param dest Pointer to memory for holding the decoded string.
+ * Must be large enough to receive the decoded string.
+ * @param src A base64 encoded string.
+ * @return the length of the decoded string if decode
+ * succeeded otherwise 0.
+ */
+int oauth_decode_base64(unsigned char *dest, const char *src);
+
+/**
+ * Escape 'string' according to RFC3986 and
+ * http://oauth.net/core/1.0/#encoding_parameters.
+ *
+ * @param string The data to be encoded
+ * @return encoded string otherwise NULL
+ * The caller must free the returned string.
+ */
+char *oauth_url_escape(const char *string);
+
+/**
+ * Parse RFC3986 encoded 'string' back to unescaped version.
+ *
+ * @param string The data to be unescaped
+ * @param olen unless NULL the length of the returned string is stored there.
+ * @return decoded string or NULL
+ * The caller must free the returned string.
+ */
+char *oauth_url_unescape(const char *string, size_t *olen);
+
+
+/**
+ * returns base64 encoded HMAC-SHA1 signature for
+ * given message and key.
+ * both data and key need to be urlencoded.
+ *
+ * the returned string needs to be freed by the caller
+ *
+ * @param m message to be signed
+ * @param k key used for signing
+ * @return signature string.
+ */
+char *oauth_sign_hmac_sha1 (const char *m, const char *k);
+
+/**
+ * same as \ref oauth_sign_hmac_sha1 but allows
+ * to specify length of message and key (in case they contain null chars).
+ *
+ * @param m message to be signed
+ * @param ml length of message
+ * @param k key used for signing
+ * @param kl length of key
+ * @return signature string.
+ */
+char *oauth_sign_hmac_sha1_raw (const char *m, const size_t ml, const char *k, const size_t kl);
+
+/**
+ * returns plaintext signature for the given key.
+ *
+ * the returned string needs to be freed by the caller
+ *
+ * @param m message to be signed
+ * @param k key used for signing
+ * @return signature string
+ */
+char *oauth_sign_plaintext (const char *m, const char *k);
+
+/**
+ * returns RSA-SHA1 signature for given data.
+ * the returned signature needs to be freed by the caller.
+ *
+ * @param m message to be signed
+ * @param k private-key PKCS and Base64-encoded
+ * @return base64 encoded signature string.
+ */
+char *oauth_sign_rsa_sha1 (const char *m, const char *k);
+
+/**
+ * verify RSA-SHA1 signature.
+ *
+ * returns the output of EVP_VerifyFinal() for a given message,
+ * cert/pubkey and signature.
+ *
+ * @param m message to be verified
+ * @param c public-key or x509 certificate
+ * @param s base64 encoded signature
+ * @return 1 for a correct signature, 0 for failure and -1 if some other error occurred
+ */
+int oauth_verify_rsa_sha1 (const char *m, const char *c, const char *s);
+
+/**
+ * url-escape strings and concatenate with '&' separator.
+ * The number of strings to be concatenated must be
+ * given as first argument.
+ * all arguments thereafter must be of type (char *)
+ *
+ * @param len the number of arguments to follow this parameter
+ *
+ * @return pointer to memory holding the concatenated
+ * strings - needs to be free(d) by the caller. or NULL
+ * in case we ran out of memory.
+ */
+char *oauth_catenc(int len, ...);
+
+/**
+ * splits the given url into a parameter array.
+ * (see \ref oauth_serialize_url and \ref oauth_serialize_url_parameters for the reverse)
+ * (see \ref oauth_split_post_paramters for a more generic version)
+ *
+ * @param url the url or query-string to parse; may be NULL
+ * @param argv pointer to a (char *) array where the results are stored.
+ * The array is re-allocated to match the number of parameters and each
+ * parameter-string is allocated with strdup. - The memory needs to be freed
+ * by the caller.
+ *
+ * @return number of parameter(s) in array.
+ */
+int oauth_split_url_parameters(const char *url, char ***argv);
+
+/**
+ * splits the given url into a parameter array.
+ * (see \ref oauth_serialize_url and \ref oauth_serialize_url_parameters for the reverse)
+ *
+ * @param url the url or query-string to parse.
+ * @param argv pointer to a (char *) array where the results are stored.
+ * The array is re-allocated to match the number of parameters and each
+ * parameter-string is allocated with strdup. - The memory needs to be freed
+ * by the caller.
+ * @param qesc use query parameter escape (vs post-param-escape) - if set
+ * to 1 all '+' are treated as spaces ' '
+ *
+ * @return number of parameter(s) in array.
+ */
+int oauth_split_post_paramters(const char *url, char ***argv, short qesc);
+
+/**
+ * build a url query string from an array.
+ *
+ * @param argc the total number of elements in the array
+ * @param start element in the array at which to start concatenating.
+ * @param argv parameter-array to concatenate.
+ * @return url string needs to be freed by the caller.
+ *
+ */
+char *oauth_serialize_url (int argc, int start, char **argv);
+
+/**
+ * encode query parameters from an array.
+ *
+ * @param argc the total number of elements in the array
+ * @param start element in the array at which to start concatenating.
+ * @param argv parameter-array to concatenate.
+ * @param sep separator for parameters (usually "&")
+ * @param mod - bitwise modifiers:
+ * 1: skip all values that start with "oauth_"
+ * 2: skip all values that don't start with "oauth_"
+ * 4: double quotation marks are added around values (use with sep ", " for HTTP Authorization header).
+ * @return url string needs to be freed by the caller.
+ */
+char *oauth_serialize_url_sep (int argc, int start, char **argv, char *sep, int mod);
+
+/**
+ * build a query parameter string from an array.
+ *
+ * This function is a shortcut for \ref oauth_serialize_url (argc, 1, argv).
+ * It strips the leading host/path, which is usually the first
+ * element when using oauth_split_url_parameters on an URL.
+ *
+ * @param argc the total number of elements in the array
+ * @param argv parameter-array to concatenate.
+ * @return url string needs to be freed by the caller.
+ */
+char *oauth_serialize_url_parameters (int argc, char **argv);
+
+/**
+ * generate a random string between 15 and 32 chars length
+ * and return a pointer to it. The value needs to be freed by the
+ * caller
+ *
+ * @return zero terminated random string.
+ */
+char *oauth_gen_nonce();
+
+/**
+ * string compare function for oauth parameters.
+ *
+ * used with qsort. needed to normalize request parameters.
+ * see http://oauth.net/core/1.0/#anchor14
+ */
+int oauth_cmpstringp(const void *p1, const void *p2);
+
+
+/**
+ * search array for parameter key.
+ * @param argv length of array to search
+ * @param argc parameter array to search
+ * @param key key of parameter to check.
+ *
+ * @return FALSE (0) if array does not contain a parameter with given key, TRUE (1) otherwise.
+ */
+int oauth_param_exists(char **argv, int argc, char *key);
+
+/**
+ * add query parameter to array
+ *
+ * @param argcp pointer to array length int
+ * @param argvp pointer to array values
+ * @param addparam parameter to add (eg. "foo=bar")
+ */
+void oauth_add_param_to_array(int *argcp, char ***argvp, const char *addparam);
+
+/**
+ * free array args
+ *
+ * @param argcp pointer to array length int
+ * @param argvp pointer to array values to be free()d
+ */
+void oauth_free_array(int *argcp, char ***argvp);
+
+/**
+ * compare two strings in constant-time (as to not let an
+ * attacker guess how many leading chars are correct:
+ * http://rdist.root.org/2010/01/07/timing-independent-array-comparison/ )
+ *
+ * @param a string to compare
+ * @param b string to compare
+ * @param len_a length of string a
+ * @param len_b length of string b
+ *
+ * returns 0 (false) if strings are not equal, and 1 (true) if strings are equal.
+ */
+int oauth_time_independent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b);
+
+/**
+ * @deprecated Use oauth_time_independent_equals_n() instead.
+ */
+int oauth_time_indepenent_equals_n(const char* a, const char* b, size_t len_a, size_t len_b) attribute_deprecated;
+
+/**
+ * compare two strings in constant-time.
+ * wrapper to \ref oauth_time_independent_equals_n
+ * which calls strlen() for each argument.
+ *
+ * @param a string to compare
+ * @param b string to compare
+ *
+ * returns 0 (false) if strings are not equal, and 1 (true) if strings are equal.
+ */
+int oauth_time_independent_equals(const char* a, const char* b);
+
+/**
+ * @deprecated Use oauth_time_independent_equals() instead.
+ */
+int oauth_time_indepenent_equals(const char* a, const char* b) attribute_deprecated;
+
+/**
+ * calculate OAuth-signature for a given HTTP request URL, parameters and oauth-tokens.
+ *
+ * if 'postargs' is NULL a "GET" request is signed and the
+ * signed URL is returned. Else this fn will modify 'postargs'
+ * to point to memory that contains the signed POST-variables
+ * and returns the base URL.
+ *
+ * both, the return value and (if given) 'postargs' need to be freed
+ * by the caller.
+ *
+ * @param url The request URL to be signed. append all GET or POST
+ * query-parameters separated by either '?' or '&' to this parameter.
+ *
+ * @param postargs This parameter points to an area where the return value
+ * is stored. If 'postargs' is NULL, no value is stored.
+ *
+ * @param method specify the signature method to use. It is of type
+ * \ref OAuthMethod and most likely \ref OA_HMAC.
+ *
+ * @param http_method The HTTP request method to use (ie "GET", "PUT",..)
+ * If NULL is given as 'http_method' this defaults to "GET" when
+ * 'postargs' is also NULL and when postargs is not NULL "POST" is used.
+ *
+ * @param c_key consumer key
+ * @param c_secret consumer secret
+ * @param t_key token key
+ * @param t_secret token secret
+ *
+ * @return the signed url or NULL if an error occurred.
+ *
+ */
+char *oauth_sign_url2 (const char *url, char **postargs,
+ OAuthMethod method,
+ const char *http_method, //< HTTP request method
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ );
+
+/**
+ * @deprecated Use oauth_sign_url2() instead.
+ */
+char *oauth_sign_url (const char *url, char **postargs,
+ OAuthMethod method,
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) attribute_deprecated;
+
+
+/**
+ * the back-end behind by /ref oauth_sign_array2.
+ * however it does not serialize the signed URL again.
+ * The user needs to call /ref oauth_serialize_url (oA)
+ * and /ref oauth_free_array to do so.
+ *
+ * This allows to split parts of the URL to be used for
+ * OAuth HTTP Authorization header:
+ * see http://oauth.net/core/1.0a/#consumer_req_param
+ * the oauthtest2 example code does so.
+ *
+ *
+ * @param argcp pointer to array length int
+ * @param argvp pointer to array values
+ * (argv[0]="http://example.org:80/" argv[1]="first=QueryParamater" ..
+ * the array is modified: fi. oauth_ parameters are added)
+ * These arrays can be generated with /ref oauth_split_url_parameters
+ * or /ref oauth_split_post_paramters.
+ *
+ * @param postargs This parameter points to an area where the return value
+ * is stored. If 'postargs' is NULL, no value is stored.
+ *
+ * @param method specify the signature method to use. It is of type
+ * \ref OAuthMethod and most likely \ref OA_HMAC.
+ *
+ * @param http_method The HTTP request method to use (ie "GET", "PUT",..)
+ * If NULL is given as 'http_method' this defaults to "GET" when
+ * 'postargs' is also NULL and when postargs is not NULL "POST" is used.
+ *
+ * @param c_key consumer key
+ * @param c_secret consumer secret
+ * @param t_key token key
+ * @param t_secret token secret
+ *
+ * @return void
+ *
+ */
+void oauth_sign_array2_process (int *argcp, char***argvp,
+ char **postargs,
+ OAuthMethod method,
+ const char *http_method, //< HTTP request method
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ );
+
+/**
+ * same as /ref oauth_sign_url
+ * with the url already split into parameter array
+ *
+ * @param argcp pointer to array length int
+ * @param argvp pointer to array values
+ * (argv[0]="http://example.org:80/" argv[1]="first=QueryParamater" ..
+ * the array is modified: fi. oauth_ parameters are added)
+ * These arrays can be generated with /ref oauth_split_url_parameters
+ * or /ref oauth_split_post_paramters.
+ *
+ * @param postargs This parameter points to an area where the return value
+ * is stored. If 'postargs' is NULL, no value is stored.
+ *
+ * @param method specify the signature method to use. It is of type
+ * \ref OAuthMethod and most likely \ref OA_HMAC.
+ *
+ * @param http_method The HTTP request method to use (ie "GET", "PUT",..)
+ * If NULL is given as 'http_method' this defaults to "GET" when
+ * 'postargs' is also NULL and when postargs is not NULL "POST" is used.
+ *
+ * @param c_key consumer key
+ * @param c_secret consumer secret
+ * @param t_key token key
+ * @param t_secret token secret
+ *
+ * @return the signed url or NULL if an error occurred.
+ */
+char *oauth_sign_array2 (int *argcp, char***argvp,
+ char **postargs,
+ OAuthMethod method,
+ const char *http_method, //< HTTP request method
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ );
+
+/**
+ * @deprecated Use oauth_sign_array2() instead.
+ */
+char *oauth_sign_array (int *argcp, char***argvp,
+ char **postargs,
+ OAuthMethod method,
+ const char *c_key, //< consumer key - posted plain text
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_key, //< token key - posted plain text in URL
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ ) attribute_deprecated;
+
+
+/**
+ * calculate body hash (sha1sum) of given file and return
+ * a oauth_body_hash=xxxx parameter to be added to the request.
+ * The returned string needs to be freed by the calling function.
+ *
+ * see
+ * http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
+ *
+ * @param filename the filename to calculate the hash for
+ *
+ * @return URL oauth_body_hash parameter string
+ */
+char *oauth_body_hash_file(char *filename);
+
+/**
+ * calculate body hash (sha1sum) of given data and return
+ * a oauth_body_hash=xxxx parameter to be added to the request.
+ * The returned string needs to be freed by the calling function.
+ * The returned string is not yet url-escaped and suitable to be
+ * passed as argument to \ref oauth_catenc.
+ *
+ * see
+ * http://oauth.googlecode.com/svn/spec/ext/body_hash/1.0/oauth-bodyhash.html
+ *
+ * @param length length of the data parameter in bytes
+ * @param data to calculate the hash for
+ *
+ * @return URL oauth_body_hash parameter string
+ */
+char *oauth_body_hash_data(size_t length, const char *data);
+
+/**
+ * base64 encode digest, free it and return a URL parameter
+ * with the oauth_body_hash. The returned hash needs to be freed by the
+ * calling function. The returned string is not yet url-escaped and
+ * thus suitable to be passed to \ref oauth_catenc.
+ *
+ * @param len length of the digest to encode
+ * @param digest hash value to encode
+ *
+ * @return URL oauth_body_hash parameter string
+ */
+char *oauth_body_hash_encode(size_t len, unsigned char *digest);
+
+/**
+ * xep-0235 - TODO
+ */
+char *oauth_sign_xmpp (const char *xml,
+ OAuthMethod method,
+ const char *c_secret, //< consumer secret - used as 1st part of secret-key
+ const char *t_secret //< token secret - used as 2st part of secret-key
+ );
+
+/**
+ * do a HTTP GET request, wait for it to finish
+ * and return the content of the reply.
+ * (requires libcurl or a command-line HTTP client)
+ *
+ * If compiled <b>without</b> libcurl this function calls
+ * a command-line executable defined in the environment variable
+ * OAUTH_HTTP_GET_CMD - it defaults to
+ * <tt>curl -sA 'liboauth-agent/0.1' '%%u'</tt>
+ * where %%u is replaced with the URL and query parameters.
+ *
+ * bash & wget example:
+ * <tt>export OAUTH_HTTP_CMD="wget -q -U 'liboauth-agent/0.1' '%u' "</tt>
+ *
+ * WARNING: this is a tentative function. it's convenient and handy for testing
+ * or developing OAuth code. But don't rely on this function
+ * to become a stable part of this API. It does not do
+ * much error checking or handling for one thing..
+ *
+ * NOTE: \a u and \a q are just concatenated with a '?' in between,
+ * unless \a q is NULL. in which case only \a u will be used.
+ *
+ * @param u base url to get
+ * @param q query string to send along with the HTTP request or NULL.
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_http_get (const char *u, const char *q);
+
+/**
+ * do a HTTP GET request, wait for it to finish
+ * and return the content of the reply.
+ *
+ * (requires libcurl)
+ *
+ * This is equivalent to /ref oauth_http_get but allows to
+ * specifiy a custom HTTP header and has
+ * has no support for commandline-curl.
+ *
+ * If liboauth is compiled <b>without</b> libcurl this function
+ * always returns NULL.
+ *
+ * @param u base url to get
+ * @param q query string to send along with the HTTP request or NULL.
+ * @param customheader specify custom HTTP header (or NULL for none)
+ * Multiple header elements can be passed separating them with "\r\n"
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_http_get2 (const char *u, const char *q, const char *customheader);
+
+
+/**
+ * do a HTTP POST request, wait for it to finish
+ * and return the content of the reply.
+ * (requires libcurl or a command-line HTTP client)
+ *
+ * If compiled <b>without</b> libcurl this function calls
+ * a command-line executable defined in the environment variable
+ * OAUTH_HTTP_CMD - it defaults to
+ * <tt>curl -sA 'liboauth-agent/0.1' -d '%%p' '%%u'</tt>
+ * where %%p is replaced with the postargs and %%u is replaced with
+ * the URL.
+ *
+ * bash & wget example:
+ * <tt>export OAUTH_HTTP_CMD="wget -q -U 'liboauth-agent/0.1' --post-data='%p' '%u' "</tt>
+ *
+ * NOTE: This function uses the curl's default HTTP-POST Content-Type:
+ * application/x-www-form-urlencoded which is the only option allowed
+ * by oauth core 1.0 spec. Experimental code can use the Environment variable
+ * to transmit custom HTTP headers or parameters.
+ *
+ * WARNING: this is a tentative function. it's convenient and handy for testing
+ * or developing OAuth code. But don't rely on this function
+ * to become a stable part of this API. It does not do
+ * much error checking for one thing..
+ *
+ * @param u url to query
+ * @param p postargs to send along with the HTTP request.
+ * @return replied content from HTTP server. needs to be freed by caller.
+ */
+char *oauth_http_post (const char *u, const char *p);
+
+/**
+ * do a HTTP POST request, wait for it to finish
+ * and return the content of the reply.
+ * (requires libcurl)
+ *
+ * It's equivalent to /ref oauth_http_post, but offers
+ * the possibility to specify a custom HTTP header and
+ * has no support for commandline-curl.
+ *
+ * If liboauth is compiled <b>without</b> libcurl this function
+ * always returns NULL.
+ *
+ * @param u url to query
+ * @param p postargs to send along with the HTTP request.
+ * @param customheader specify custom HTTP header (or NULL for none)
+ * Multiple header elements can be passed separating them with "\r\n"
+ * @return replied content from HTTP server. needs to be freed by caller.
+ */
+char *oauth_http_post2 (const char *u, const char *p, const char *customheader);
+
+
+/**
+ * http post raw data from file.
+ * the returned string needs to be freed by the caller
+ * (requires libcurl)
+ *
+ * see dislaimer: /ref oauth_http_post
+ *
+ * @param u url to retrieve
+ * @param fn filename of the file to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default).
+ * Multiple header elements can be passed separating them with "\r\n"
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_post_file (const char *u, const char *fn, const size_t len, const char *customheader);
+
+/**
+ * http post raw data
+ * the returned string needs to be freed by the caller
+ * (requires libcurl)
+ *
+ * see dislaimer: /ref oauth_http_post
+ *
+ * @param u url to retrieve
+ * @param data data to post
+ * @param len length of the data in bytes.
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * Multiple header elements can be passed separating them with "\r\n"
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_post_data (const char *u, const char *data, size_t len, const char *customheader);
+
+/**
+ * http post raw data, with callback.
+ * the returned string needs to be freed by the caller
+ * (requires libcurl)
+ *
+ * Invokes the callback - in no particular order - when HTTP-request status updates occur.
+ * The callback is called with:
+ * void * callback_data: supplied on function call.
+ * int type: 0=data received, 1=data sent.
+ * size_t size: amount of data received or amount of data sent so far
+ * size_t totalsize: original amount of data to send, or amount of data received
+ *
+ * @param u url to retrieve
+ * @param data data to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * Multiple header elements can be passed separating them with "\r\n"
+ * @param callback specify the callback function
+ * @param callback_data specify data to pass to the callback function
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_post_data_with_callback (const char *u,
+ const char *data,
+ size_t len,
+ const char *customheader,
+ void (*callback)(void*,int,size_t,size_t),
+ void *callback_data);
+
+/**
+ * http send raw data. similar to /ref oauth_http_post but provides
+ * for specifying the HTTP request method.
+ *
+ * the returned string needs to be freed by the caller
+ * (requires libcurl)
+ *
+ * see dislaimer: /ref oauth_http_post
+ *
+ * @param u url to retrieve
+ * @param data data to post
+ * @param len length of the data in bytes.
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * Multiple header elements can be passed separating them with "\r\n"
+ * @param httpMethod specify http verb ("GET"/"POST"/"PUT"/"DELETE") to be used. if httpMethod is NULL, a POST is executed.
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_send_data (const char *u,
+ const char *data,
+ size_t len,
+ const char *customheader,
+ const char *httpMethod);
+
+/**
+ * http post raw data, with callback.
+ * the returned string needs to be freed by the caller
+ * (requires libcurl)
+ *
+ * Invokes the callback - in no particular order - when HTTP-request status updates occur.
+ * The callback is called with:
+ * void * callback_data: supplied on function call.
+ * int type: 0=data received, 1=data sent.
+ * size_t size: amount of data received or amount of data sent so far
+ * size_t totalsize: original amount of data to send, or amount of data received
+ *
+ * @param u url to retrieve
+ * @param data data to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * Multiple header elements can be passed separating them with "\r\n"
+ * @param callback specify the callback function
+ * @param callback_data specify data to pass to the callback function
+ * @param httpMethod specify http verb ("GET"/"POST"/"PUT"/"DELETE") to be used.
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_send_data_with_callback (const char *u,
+ const char *data,
+ size_t len,
+ const char *customheader,
+ void (*callback)(void*,int,size_t,size_t),
+ void *callback_data,
+ const char *httpMethod);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif
+/* vi:set ts=8 sts=2 sw=2: */
diff --git a/protocols/Twitter/src/oauth/oauth_http.c b/protocols/Twitter/src/oauth/oauth_http.c
new file mode 100644
index 0000000000..0e06b10dae
--- /dev/null
+++ b/protocols/Twitter/src/oauth/oauth_http.c
@@ -0,0 +1,728 @@
+/*
+ * OAuth http functions in POSIX-C.
+ *
+ * Copyright 2007, 2008, 2009, 2010 Robin Gareus <robin@gareus.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 HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef WIN32
+# define snprintf _snprintf
+#endif
+
+#include "xmalloc.h"
+#include "oauth.h"
+
+#define OAUTH_USER_AGENT "liboauth-agent/" VERSION
+
+#ifdef HAVE_CURL /* HTTP requests via libcurl */
+#include <curl/curl.h>
+#include <sys/stat.h>
+
+# define GLOBAL_CURL_ENVIROMENT_OPTIONS \
+ if (getenv("CURLOPT_PROXYAUTH")){ \
+ curl_easy_setopt(curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); \
+ } \
+ if (getenv("CURLOPT_SSL_VERIFYPEER")){ \
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, (long) atol(getenv("CURLOPT_SSL_VERIFYPEER")) ); \
+ } \
+ if (getenv("CURLOPT_CAINFO")){ \
+ curl_easy_setopt(curl, CURLOPT_CAINFO, getenv("CURLOPT_CAINFO") ); \
+ } \
+ if (getenv("CURLOPT_FOLLOWLOCATION")){ \
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, (long) atol(getenv("CURLOPT_FOLLOWLOCATION")) ); \
+ } \
+ if (getenv("CURLOPT_FAILONERROR")){ \
+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, (long) atol(getenv("CURLOPT_FAILONERROR")) ); \
+ }
+
+struct MemoryStruct {
+ char *data;
+ size_t size; //< bytes remaining (r), bytes accumulated (w)
+
+ size_t start_size; //< only used with ..AndCall()
+ void (*callback)(void*,int,size_t,size_t); //< only used with ..AndCall()
+ void *callback_data; //< only used with ..AndCall()
+};
+
+static size_t
+WriteMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) {
+ size_t realsize = size * nmemb;
+ struct MemoryStruct *mem = (struct MemoryStruct *)data;
+
+ mem->data = (char *)xrealloc(mem->data, mem->size + realsize + 1);
+ if (mem->data) {
+ memcpy(&(mem->data[mem->size]), ptr, realsize);
+ mem->size += realsize;
+ mem->data[mem->size] = 0;
+ }
+ return realsize;
+}
+
+static size_t
+ReadMemoryCallback(void *ptr, size_t size, size_t nmemb, void *data) {
+ struct MemoryStruct *mem = (struct MemoryStruct *)data;
+ size_t realsize = size * nmemb;
+ if (realsize > mem->size) realsize = mem->size;
+ memcpy(ptr, mem->data, realsize);
+ mem->size -= realsize;
+ mem->data += realsize;
+ return realsize;
+}
+
+static size_t
+WriteMemoryCallbackAndCall(void *ptr, size_t size, size_t nmemb, void *data) {
+ struct MemoryStruct *mem = (struct MemoryStruct *)data;
+ size_t ret=WriteMemoryCallback(ptr,size,nmemb,data);
+ mem->callback(mem->callback_data,0,mem->size,mem->size);
+ return ret;
+}
+
+static size_t
+ReadMemoryCallbackAndCall(void *ptr, size_t size, size_t nmemb, void *data) {
+ struct MemoryStruct *mem = (struct MemoryStruct *)data;
+ size_t ret=ReadMemoryCallback(ptr,size,nmemb,data);
+ mem->callback(mem->callback_data,1,mem->start_size-mem->size,mem->start_size);
+ return ret;
+}
+
+/**
+ * cURL http post function.
+ * the returned string (if not NULL) needs to be freed by the caller
+ *
+ * @param u url to retrieve
+ * @param p post parameters
+ * @param customheader specify custom HTTP header (or NULL for none)
+ * @return returned HTTP
+ */
+char *oauth_curl_post (const char *u, const char *p, const char *customheader) {
+ CURL *curl;
+ CURLcode res;
+ struct curl_slist *slist=NULL;
+
+ struct MemoryStruct chunk;
+ chunk.data=NULL;
+ chunk.size = 0;
+
+ curl = curl_easy_init();
+ if(!curl) return NULL;
+ curl_easy_setopt(curl, CURLOPT_URL, u);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, p);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
+ if (customheader) {
+ slist = curl_slist_append(slist, customheader);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ }
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT);
+#ifdef OAUTH_CURL_TIMEOUT
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT);
+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+#endif
+ GLOBAL_CURL_ENVIROMENT_OPTIONS;
+ res = curl_easy_perform(curl);
+ curl_slist_free_all(slist);
+ if (res) {
+ return NULL;
+ }
+
+ curl_easy_cleanup(curl);
+ return (chunk.data);
+}
+
+/**
+ * cURL http get function.
+ * the returned string (if not NULL) needs to be freed by the caller
+ *
+ * @param u url to retrieve
+ * @param q optional query parameters
+ * @param customheader specify custom HTTP header (or NULL for none)
+ * @return returned HTTP
+ */
+char *oauth_curl_get (const char *u, const char *q, const char *customheader) {
+ CURL *curl;
+ CURLcode res;
+ struct curl_slist *slist=NULL;
+ char *t1=NULL;
+ struct MemoryStruct chunk;
+
+ if (q) {
+ t1=(char*)xmalloc(sizeof(char)*(strlen(u)+strlen(q)+2));
+ strcpy(t1,u); strcat(t1,"?"); strcat(t1,q);
+ }
+
+ chunk.data=NULL;
+ chunk.size = 0;
+
+ curl = curl_easy_init();
+ if(!curl) return NULL;
+ curl_easy_setopt(curl, CURLOPT_URL, q?t1:u);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
+ if (customheader) {
+ slist = curl_slist_append(slist, customheader);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ }
+#if 0 // TODO - support request methods..
+ if (0)
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "HEAD");
+ else if (0)
+ curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
+#endif
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT);
+#ifdef OAUTH_CURL_TIMEOUT
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT);
+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+#endif
+ GLOBAL_CURL_ENVIROMENT_OPTIONS;
+ res = curl_easy_perform(curl);
+ curl_slist_free_all(slist);
+ if (q) free(t1);
+ curl_easy_cleanup(curl);
+
+ if (res) {
+ return NULL;
+ }
+ return (chunk.data);
+}
+
+/**
+ * cURL http post raw data from file.
+ * the returned string needs to be freed by the caller
+ *
+ * @param u url to retrieve
+ * @param fn filename of the file to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * @return returned HTTP or NULL on error
+ */
+char *oauth_curl_post_file (const char *u, const char *fn, size_t len, const char *customheader) {
+ CURL *curl;
+ CURLcode res;
+ struct curl_slist *slist=NULL;
+ struct MemoryStruct chunk;
+ FILE *f;
+
+ chunk.data=NULL;
+ chunk.size=0;
+
+ if (customheader)
+ slist = curl_slist_append(slist, customheader);
+ else
+ slist = curl_slist_append(slist, "Content-Type: image/jpeg;");
+
+ if (!len) {
+ struct stat statbuf;
+ if (stat(fn, &statbuf) == -1) return(NULL);
+ len = statbuf.st_size;
+ }
+
+ f = fopen(fn,"r");
+ if (!f) return NULL;
+
+ curl = curl_easy_init();
+ if(!curl) return NULL;
+ curl_easy_setopt(curl, CURLOPT_URL, u);
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ curl_easy_setopt(curl, CURLOPT_READDATA, f);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT);
+#ifdef OAUTH_CURL_TIMEOUT
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT);
+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+#endif
+ GLOBAL_CURL_ENVIROMENT_OPTIONS;
+ res = curl_easy_perform(curl);
+ curl_slist_free_all(slist);
+ if (res) {
+ // error
+ return NULL;
+ }
+ fclose(f);
+
+ curl_easy_cleanup(curl);
+ return (chunk.data);
+}
+
+/**
+ * http send raw data, with callback.
+ * the returned string needs to be freed by the caller
+ *
+ * more documentation in oauth.h
+ *
+ * @param u url to retrieve
+ * @param data data to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * @param callback specify the callback function
+ * @param callback_data specify data to pass to the callback function
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_curl_send_data_with_callback (const char *u, const char *data, size_t len, const char *customheader, void (*callback)(void*,int,size_t,size_t), void *callback_data, const char *httpMethod) {
+ CURL *curl;
+ CURLcode res;
+ struct curl_slist *slist=NULL;
+ struct MemoryStruct chunk;
+ struct MemoryStruct rdnfo;
+
+ chunk.data=NULL;
+ chunk.size=0;
+ chunk.start_size=0;
+ chunk.callback=callback;
+ chunk.callback_data=callback_data;
+ rdnfo.data=(char *)data;
+ rdnfo.size=len;
+ rdnfo.start_size=len;
+ rdnfo.callback=callback;
+ rdnfo.callback_data=callback_data;
+
+ if (customheader)
+ slist = curl_slist_append(slist, customheader);
+ else
+ slist = curl_slist_append(slist, "Content-Type: image/jpeg;");
+
+ curl = curl_easy_init();
+ if(!curl) return NULL;
+ curl_easy_setopt(curl, CURLOPT_URL, u);
+ curl_easy_setopt(curl, CURLOPT_POST, 1);
+ if (httpMethod) curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, httpMethod);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, slist);
+ curl_easy_setopt(curl, CURLOPT_READDATA, (void *)&rdnfo);
+ if (callback)
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadMemoryCallbackAndCall);
+ else
+ curl_easy_setopt(curl, CURLOPT_READFUNCTION, ReadMemoryCallback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);
+ if (callback)
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallbackAndCall);
+ else
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, OAUTH_USER_AGENT);
+#ifdef OAUTH_CURL_TIMEOUT
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, OAUTH_CURL_TIMEOUT);
+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+#endif
+ GLOBAL_CURL_ENVIROMENT_OPTIONS;
+ res = curl_easy_perform(curl);
+ curl_slist_free_all(slist);
+ if (res) {
+ // error
+ return NULL;
+ }
+
+ curl_easy_cleanup(curl);
+ return (chunk.data);
+}
+
+/**
+ * http post raw data.
+ * the returned string needs to be freed by the caller
+ *
+ * more documentation in oauth.h
+ *
+ * @param u url to retrieve
+ * @param data data to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_curl_post_data(const char *u, const char *data, size_t len, const char *customheader) {
+ return oauth_curl_send_data_with_callback(u, data, len, customheader, NULL, NULL, NULL);
+}
+
+char *oauth_curl_send_data (const char *u, const char *data, size_t len, const char *customheader, const char *httpMethod) {
+ return oauth_curl_send_data_with_callback(u, data, len, customheader, NULL, NULL, httpMethod);
+}
+
+char *oauth_curl_post_data_with_callback (const char *u, const char *data, size_t len, const char *customheader, void (*callback)(void*,int,size_t,size_t), void *callback_data) {
+ return oauth_curl_send_data_with_callback(u, data, len, customheader, callback, callback_data, NULL);
+}
+
+#endif // libcURL.
+
+
+#ifdef HAVE_SHELL_CURL /* HTTP requests via command-line curl */
+
+// command line presets and ENV variable name
+#define _OAUTH_ENV_HTTPCMD "OAUTH_HTTP_CMD"
+#define _OAUTH_ENV_HTTPGET "OAUTH_HTTP_GET_CMD"
+
+#ifdef OAUTH_CURL_TIMEOUT
+
+#define cpxstr(s) cpstr(s)
+#define cpstr(s) #s
+
+#ifndef _OAUTH_DEF_HTTPCMD
+# define _OAUTH_DEF_HTTPCMD "curl -sA '"OAUTH_USER_AGENT"' -m "cpxstr(OAUTH_CURL_TIMEOUT)" -d '%p' '%u' "
+//alternative: "wget -q -U 'liboauth-agent/0.1' --post-data='%p' '%u' "
+#endif
+
+#ifndef _OAUTH_DEF_HTTPGET
+# define _OAUTH_DEF_HTTPGET "curl -sA '"OAUTH_USER_AGENT"' -m "cpxstr(OAUTH_CURL_TIMEOUT)" '%u' "
+//alternative: "wget -q -U 'liboauth-agent/0.1' '%u' "
+#endif
+
+#else // no timeout
+
+#ifndef _OAUTH_DEF_HTTPCMD
+# define _OAUTH_DEF_HTTPCMD "curl -sA '"OAUTH_USER_AGENT"' -d '%p' '%u' "
+//alternative: "wget -q -U 'liboauth-agent/0.1' --post-data='%p' '%u' "
+#endif
+
+#ifndef _OAUTH_DEF_HTTPGET
+# define _OAUTH_DEF_HTTPGET "curl -sA '"OAUTH_USER_AGENT"' '%u' "
+//alternative: "wget -q -U 'liboauth-agent/0.1' '%u' "
+#endif
+
+#endif
+
+#include <stdio.h>
+
+/**
+ * escape URL for use in String Quotes (aka shell single quotes).
+ * the returned string needs to be free()d by the calling function
+ *
+ * WARNING: this function only escapes single-quotes (')
+ *
+ *
+ * RFC2396 defines the following
+ * reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
+ * besides alphanum the following are allowed as unreserved:
+ * mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+ *
+ * checking `echo '-_.!~*()'` it seems we
+ * just need to escape the tick (') itself from "'" to "'\''"
+ *
+ * In C shell, the "!" character may need a backslash before it.
+ * It depends on the characters next to it. If it is surrounded by spaces,
+ * you don't need to use a backslash.
+ * (here: we'd always need to escape it for c shell)
+ * @todo: escape '!' for c-shell curl/wget commandlines
+ *
+ * @param cmd URI string or parameter to be escaped
+ * @return escaped parameter
+ */
+char *oauth_escape_shell (const char *cmd) {
+ char *esc = xstrdup(cmd);
+ char *tmp = esc;
+ int idx;
+ while ((tmp=strchr(tmp,'\''))) {
+ idx = tmp-esc;
+ esc=(char*)xrealloc(esc,(strlen(esc)+5)*sizeof(char));
+ memmove(esc+idx+4,esc+idx+1, strlen(esc+idx));
+ esc[idx+1]='\\'; esc[idx+2]='\''; esc[idx+3]='\'';
+ tmp=esc+(idx+4);
+ }
+
+// TODO escape '!' if CSHELL ?!
+
+ return esc;
+}
+
+/**
+ * execute command via shell and return it's output.
+ * This is used to call 'curl' or 'wget'.
+ * the command is uses <em>as is</em> and needs to be propery escaped.
+ *
+ * @param cmd the commandline to execute
+ * @return stdout string that needs to be freed or NULL if there's no output
+ */
+char *oauth_exec_shell (const char *cmd) {
+#ifdef DEBUG_OAUTH
+ printf("DEBUG: executing: %s\n",cmd);
+#endif
+ FILE *in = popen (cmd, "r");
+ size_t len = 0;
+ size_t alloc = 0;
+ char *data = NULL;
+ int rcv = 1;
+ while (in && rcv > 0 && !feof(in)) {
+ alloc +=1024;
+ data = (char*)xrealloc(data, alloc * sizeof(char));
+ rcv = fread(data + (alloc-1024), sizeof(char), 1024, in);
+ len += rcv;
+ }
+ pclose(in);
+#ifdef DEBUG_OAUTH
+ printf("DEBUG: read %i bytes\n",len);
+#endif
+ data[len]=0;
+#ifdef DEBUG_OAUTH
+ if (data) printf("DEBUG: return: %s\n",data);
+ else printf("DEBUG: NULL data\n");
+#endif
+ return (data);
+}
+
+/**
+ * send POST via a command line HTTP client, wait for it to finish
+ * and return the content of the reply. requires a command-line HTTP client
+ *
+ * see \ref oauth_http_post
+ *
+ * @param u url to query
+ * @param p postargs to send along with the HTTP request.
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_exec_post (const char *u, const char *p) {
+ char cmd[BUFSIZ];
+ char *t1,*t2;
+ char *cmdtpl = getenv(_OAUTH_ENV_HTTPCMD);
+ if (!cmdtpl) cmdtpl = xstrdup (_OAUTH_DEF_HTTPCMD);
+ else cmdtpl = xstrdup (cmdtpl); // clone getenv() string.
+
+ // add URL and post param - error if no '%p' or '%u' present in definition
+ t1=strstr(cmdtpl, "%p");
+ t2=strstr(cmdtpl, "%u");
+ if (!t1 || !t2) {
+ fprintf(stderr, "\nliboauth: invalid HTTP command. set the '%s' environment variable.\n\n",_OAUTH_ENV_HTTPCMD);
+ return(NULL);
+ }
+ // TODO: check if there are exactly two '%' in cmdtpl
+ *(++t1)= 's'; *(++t2)= 's';
+ if (t1>t2) {
+ t1=oauth_escape_shell(u);
+ t2=oauth_escape_shell(p);
+ } else {
+ t1=oauth_escape_shell(p);
+ t2=oauth_escape_shell(u);
+ }
+ snprintf(cmd, BUFSIZ, cmdtpl, t1, t2);
+ free(cmdtpl);
+ free(t1); free(t2);
+ return oauth_exec_shell(cmd);
+}
+
+/**
+ * send GET via a command line HTTP client
+ * and return the content of the reply..
+ * requires a command-line HTTP client.
+ *
+ * Note: u and q are just concatenated with a '?' in between unless q is NULL. in which case only u will be used.
+ *
+ * see \ref oauth_http_get
+ *
+ * @param u base url to get
+ * @param q query string to send along with the HTTP request.
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_exec_get (const char *u, const char *q) {
+ char cmd[BUFSIZ];
+ char *cmdtpl, *t1, *e1;
+
+ if (!u) return (NULL);
+
+ cmdtpl = getenv(_OAUTH_ENV_HTTPGET);
+ if (!cmdtpl) cmdtpl = xstrdup (_OAUTH_DEF_HTTPGET);
+ else cmdtpl = xstrdup (cmdtpl); // clone getenv() string.
+
+ // add URL and post param - error if no '%p' or '%u' present in definition
+ t1=strstr(cmdtpl, "%u");
+ if (!t1) {
+ fprintf(stderr, "\nliboauth: invalid HTTP command. set the '%s' environment variable.\n\n",_OAUTH_ENV_HTTPGET);
+ return(NULL);
+ }
+ *(++t1)= 's';
+
+ e1 = oauth_escape_shell(u);
+ if (q) {
+ char *e2;
+ e2 = oauth_escape_shell(q);
+ t1=(char*)xmalloc(sizeof(char)*(strlen(e1)+strlen(e2)+2));
+ strcpy(t1,e1); strcat(t1,"?"); strcat(t1,e2);
+ free(e2);
+ }
+ snprintf(cmd, BUFSIZ, cmdtpl, q?t1:e1);
+ free(cmdtpl);
+ free(e1);
+ if (q) free(t1);
+ return oauth_exec_shell(cmd);
+}
+#endif // command-line curl.
+
+/* wrapper functions */
+
+/**
+ * do a HTTP GET request, wait for it to finish
+ * and return the content of the reply.
+ * (requires libcurl or a command-line HTTP client)
+ *
+ * more documentation in oauth.h
+ *
+ * @param u base url to get
+ * @param q query string to send along with the HTTP request or NULL.
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_http_get (const char *u, const char *q) {
+#ifdef HAVE_CURL
+ return oauth_curl_get(u,q,NULL);
+#elif defined(HAVE_SHELL_CURL)
+ return oauth_exec_get(u,q);
+#else
+ return NULL;
+#endif
+}
+
+/**
+ * do a HTTP GET request, wait for it to finish
+ * and return the content of the reply.
+ * (requires libcurl)
+ *
+ * @param u base url to get
+ * @param q query string to send along with the HTTP request or NULL.
+ * @param customheader specify custom HTTP header (or NULL for none)
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_http_get2 (const char *u, const char *q, const char *customheader) {
+#ifdef HAVE_CURL
+ return oauth_curl_get(u,q,customheader);
+#else
+ return NULL;
+#endif
+}
+
+/**
+ * do a HTTP POST request, wait for it to finish
+ * and return the content of the reply.
+ * (requires libcurl or a command-line HTTP client)
+ *
+ * more documentation in oauth.h
+ *
+ * @param u url to query
+ * @param p postargs to send along with the HTTP request.
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_http_post (const char *u, const char *p) {
+#ifdef HAVE_CURL
+ return oauth_curl_post(u,p,NULL);
+#elif defined(HAVE_SHELL_CURL)
+ return oauth_exec_post(u,p);
+#else
+ return NULL;
+#endif
+}
+
+
+/**
+ * do a HTTP POST request, wait for it to finish
+ * and return the content of the reply.
+ * (requires libcurl)
+ *
+ * more documentation in oauth.h
+ *
+ * @param u url to query
+ * @param p postargs to send along with the HTTP request.
+ * @param customheader specify custom HTTP header (or NULL for none)
+ * @return In case of an error NULL is returned; otherwise a pointer to the
+ * replied content from HTTP server. latter needs to be freed by caller.
+ */
+char *oauth_http_post2 (const char *u, const char *p, const char *customheader) {
+#ifdef HAVE_CURL
+ return oauth_curl_post(u,p,customheader);
+#else
+ return NULL;
+#endif
+}
+
+/**
+ * http post raw data from file.
+ * the returned string needs to be freed by the caller
+ *
+ * more documentation in oauth.h
+ *
+ * @param u url to retrieve
+ * @param fn filename of the file to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_post_file (const char *u, const char *fn, const size_t len, const char *customheader){
+#ifdef HAVE_CURL
+ return oauth_curl_post_file (u, fn, len, customheader);
+#elif defined(HAVE_SHELL_CURL)
+ fprintf(stderr, "\nliboauth: oauth_post_file requires libcurl. libcurl is not available.\n\n");
+ return NULL;
+#else
+ return NULL;
+#endif
+}
+
+/**
+ * http post raw data.
+ * the returned string needs to be freed by the caller
+ *
+ * more documentation in oauth.h
+ *
+ * @param u url to retrieve
+ * @param data data to post along
+ * @param len length of the file in bytes. set to '0' for autodetection
+ * @param customheader specify custom HTTP header (or NULL for default)
+ * @return returned HTTP reply or NULL on error
+ */
+char *oauth_post_data (const char *u, const char *data, size_t len, const char *customheader) {
+#ifdef HAVE_CURL
+ return oauth_curl_post_data (u, data, len, customheader);
+#elif defined(HAVE_SHELL_CURL)
+ fprintf(stderr, "\nliboauth: oauth_post_file requires libcurl. libcurl is not available.\n\n");
+ return NULL;
+#else
+ return (NULL);
+#endif
+}
+
+char *oauth_send_data (const char *u, const char *data, size_t len, const char *customheader, const char *httpMethod) {
+#ifdef HAVE_CURL
+ return oauth_curl_send_data (u, data, len, customheader, httpMethod);
+#elif defined(HAVE_SHELL_CURL)
+ fprintf(stderr, "\nliboauth: oauth_send_file requires libcurl. libcurl is not available.\n\n");
+ return NULL;
+#else
+ return (NULL);
+#endif
+}
+
+char *oauth_post_data_with_callback (const char *u, const char *data, size_t len, const char *customheader, void (*callback)(void*,int,size_t,size_t), void *callback_data) {
+#ifdef HAVE_CURL
+ return oauth_curl_post_data_with_callback(u, data, len, customheader, callback, callback_data);
+#elif defined(HAVE_SHELL_CURL)
+ fprintf(stderr, "\nliboauth: oauth_post_data_with_callback requires libcurl.\n\n");
+ return NULL;
+#else
+ return (NULL);
+#endif
+}
+/* vi:set ts=8 sts=2 sw=2: */
diff --git a/protocols/Twitter/src/oauth/sha1.c b/protocols/Twitter/src/oauth/sha1.c
new file mode 100644
index 0000000000..c3189008ac
--- /dev/null
+++ b/protocols/Twitter/src/oauth/sha1.c
@@ -0,0 +1,317 @@
+/* This code is public-domain - it is based on libcrypt
+ * placed in the public domain by Wei Dai and other contributors.
+ */
+// gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test
+
+#include <stdint.h>
+#include <string.h>
+
+/* header */
+
+#define HASH_LENGTH 20
+#define BLOCK_LENGTH 64
+
+union _buffer {
+ uint8_t b[BLOCK_LENGTH];
+ uint32_t w[BLOCK_LENGTH/4];
+};
+
+union _state {
+ uint8_t b[HASH_LENGTH];
+ uint32_t w[HASH_LENGTH/4];
+};
+
+typedef struct sha1nfo {
+ union _buffer buffer;
+ uint8_t bufferOffset;
+ union _state state;
+ uint32_t byteCount;
+ uint8_t keyBuffer[BLOCK_LENGTH];
+ uint8_t innerHash[HASH_LENGTH];
+} sha1nfo;
+
+/* public API - prototypes - TODO: doxygen*/
+
+/**
+ */
+void sha1_init(sha1nfo *s);
+/**
+ */
+void sha1_writebyte(sha1nfo *s, uint8_t data);
+/**
+ */
+void sha1_write(sha1nfo *s, const char *data, size_t len);
+/**
+ */
+uint8_t* sha1_result(sha1nfo *s);
+/**
+ */
+void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength);
+/**
+ */
+uint8_t* sha1_resultHmac(sha1nfo *s);
+
+
+/* code */
+#define SHA1_K0 0x5a827999
+#define SHA1_K20 0x6ed9eba1
+#define SHA1_K40 0x8f1bbcdc
+#define SHA1_K60 0xca62c1d6
+
+const uint8_t sha1InitState[] = {
+ 0x01,0x23,0x45,0x67, // H0
+ 0x89,0xab,0xcd,0xef, // H1
+ 0xfe,0xdc,0xba,0x98, // H2
+ 0x76,0x54,0x32,0x10, // H3
+ 0xf0,0xe1,0xd2,0xc3 // H4
+};
+
+void sha1_init(sha1nfo *s) {
+ memcpy(s->state.b,sha1InitState,HASH_LENGTH);
+ s->byteCount = 0;
+ s->bufferOffset = 0;
+}
+
+uint32_t sha1_rol32(uint32_t number, uint8_t bits) {
+ return ((number << bits) | (number >> (32-bits)));
+}
+
+void sha1_hashBlock(sha1nfo *s) {
+ uint8_t i;
+ uint32_t a,b,c,d,e,t;
+
+ a=s->state.w[0];
+ b=s->state.w[1];
+ c=s->state.w[2];
+ d=s->state.w[3];
+ e=s->state.w[4];
+ for (i=0; i<80; i++) {
+ if (i>=16) {
+ t = s->buffer.w[(i+13)&15] ^ s->buffer.w[(i+8)&15] ^ s->buffer.w[(i+2)&15] ^ s->buffer.w[i&15];
+ s->buffer.w[i&15] = sha1_rol32(t,1);
+ }
+ if (i<20) {
+ t = (d ^ (b & (c ^ d))) + SHA1_K0;
+ } else if (i<40) {
+ t = (b ^ c ^ d) + SHA1_K20;
+ } else if (i<60) {
+ t = ((b & c) | (d & (b | c))) + SHA1_K40;
+ } else {
+ t = (b ^ c ^ d) + SHA1_K60;
+ }
+ t+=sha1_rol32(a,5) + e + s->buffer.w[i&15];
+ e=d;
+ d=c;
+ c=sha1_rol32(b,30);
+ b=a;
+ a=t;
+ }
+ s->state.w[0] += a;
+ s->state.w[1] += b;
+ s->state.w[2] += c;
+ s->state.w[3] += d;
+ s->state.w[4] += e;
+}
+
+void sha1_addUncounted(sha1nfo *s, uint8_t data) {
+ s->buffer.b[s->bufferOffset ^ 3] = data;
+ s->bufferOffset++;
+ if (s->bufferOffset == BLOCK_LENGTH) {
+ sha1_hashBlock(s);
+ s->bufferOffset = 0;
+ }
+}
+
+void sha1_writebyte(sha1nfo *s, uint8_t data) {
+ ++s->byteCount;
+ sha1_addUncounted(s, data);
+}
+
+void sha1_write(sha1nfo *s, const char *data, size_t len) {
+ for (;len--;) sha1_writebyte(s, (uint8_t) *data++);
+}
+
+void sha1_pad(sha1nfo *s) {
+ // Implement SHA-1 padding (fips180-2 §5.1.1)
+
+ // Pad with 0x80 followed by 0x00 until the end of the block
+ sha1_addUncounted(s, 0x80);
+ while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00);
+
+ // Append length in the last 8 bytes
+ sha1_addUncounted(s, 0); // We're only using 32 bit lengths
+ sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths
+ sha1_addUncounted(s, 0); // So zero pad the top bits
+ sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8
+ sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as
+ sha1_addUncounted(s, s->byteCount >> 13); // byte.
+ sha1_addUncounted(s, s->byteCount >> 5);
+ sha1_addUncounted(s, s->byteCount << 3);
+}
+
+uint8_t* sha1_result(sha1nfo *s) {
+ int i;
+ // Pad to complete the last block
+ sha1_pad(s);
+
+ // Swap byte order back
+ for (i=0; i<5; i++) {
+ uint32_t a,b;
+ a=s->state.w[i];
+ b=a<<24;
+ b|=(a<<8) & 0x00ff0000;
+ b|=(a>>8) & 0x0000ff00;
+ b|=a>>24;
+ s->state.w[i]=b;
+ }
+
+ // Return pointer to hash (20 characters)
+ return s->state.b;
+}
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5c
+
+void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) {
+ uint8_t i;
+ memset(s->keyBuffer, 0, BLOCK_LENGTH);
+ if (keyLength > BLOCK_LENGTH) {
+ // Hash long keys
+ sha1_init(s);
+ for (;keyLength--;) sha1_writebyte(s, *key++);
+ memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH);
+ } else {
+ // Block length keys are used as is
+ memcpy(s->keyBuffer, key, keyLength);
+ }
+ // Start inner hash
+ sha1_init(s);
+ for (i=0; i<BLOCK_LENGTH; i++) {
+ sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_IPAD);
+ }
+}
+
+uint8_t* sha1_resultHmac(sha1nfo *s) {
+ uint8_t i;
+ // Complete inner hash
+ memcpy(s->innerHash,sha1_result(s),HASH_LENGTH);
+ // Calculate outer hash
+ sha1_init(s);
+ for (i=0; i<BLOCK_LENGTH; i++) sha1_writebyte(s, s->keyBuffer[i] ^ HMAC_OPAD);
+ for (i=0; i<HASH_LENGTH; i++) sha1_writebyte(s, s->innerHash[i]);
+ return sha1_result(s);
+}
+
+/* self-test */
+
+#if SHA1TEST
+#include <stdio.h>
+
+uint8_t hmacKey1[]={
+ 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f,
+ 0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,
+ 0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,
+ 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f
+};
+uint8_t hmacKey2[]={
+ 0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,
+ 0x40,0x41,0x42,0x43
+};
+uint8_t hmacKey3[]={
+ 0x50,0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,
+ 0x60,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,
+ 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+ 0xa0,0xa1,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xab,0xac,0xad,0xae,0xaf,
+ 0xb0,0xb1,0xb2,0xb3
+};
+uint8_t hmacKey4[]={
+ 0x70,0x71,0x72,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,
+ 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
+ 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
+ 0xa0
+};
+
+void printHash(uint8_t* hash) {
+ int i;
+ for (i=0; i<20; i++) {
+ printf("%02x", hash[i]);
+ }
+ printf("\n");
+}
+
+
+int main (int argc, char **argv) {
+ uint32_t a;
+ sha1nfo s;
+
+ // SHA tests
+ printf("Test: FIPS 180-2 C.1 and RFC3174 7.3 TEST1\n");
+ printf("Expect:a9993e364706816aba3e25717850c26c9cd0d89d\n");
+ printf("Result:");
+ sha1_init(&s);
+ sha1_write(&s, "abc", 3);
+ printHash(sha1_result(&s));
+ printf("\n\n");
+
+ printf("Test: FIPS 180-2 C.2 and RFC3174 7.3 TEST2\n");
+ printf("Expect:84983e441c3bd26ebaae4aa1f95129e5e54670f1\n");
+ printf("Result:");
+ sha1_init(&s);
+ sha1_write(&s, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 56);
+ printHash(sha1_result(&s));
+ printf("\n\n");
+
+ printf("Test: RFC3174 7.3 TEST4\n");
+ printf("Expect:dea356a2cddd90c7a7ecedc5ebb563934f460452\n");
+ printf("Result:");
+ sha1_init(&s);
+ for (a=0; a<80; a++) sha1_write(&s, "01234567", 8);
+ printHash(sha1_result(&s));
+ printf("\n\n");
+
+ // HMAC tests
+ printf("Test: FIPS 198a A.1\n");
+ printf("Expect:4f4ca3d5d68ba7cc0a1208c9c61e9c5da0403c0a\n");
+ printf("Result:");
+ sha1_initHmac(&s, hmacKey1, 64);
+ sha1_write(&s, "Sample #1",9);
+ printHash(sha1_resultHmac(&s));
+ printf("\n\n");
+
+ printf("Test: FIPS 198a A.2\n");
+ printf("Expect:0922d3405faa3d194f82a45830737d5cc6c75d24\n");
+ printf("Result:");
+ sha1_initHmac(&s, hmacKey2, 20);
+ sha1_write(&s, "Sample #2", 9);
+ printHash(sha1_resultHmac(&s));
+ printf("\n\n");
+
+ printf("Test: FIPS 198a A.3\n");
+ printf("Expect:bcf41eab8bb2d802f3d05caf7cb092ecf8d1a3aa\n");
+ printf("Result:");
+ sha1_initHmac(&s, hmacKey3,100);
+ sha1_write(&s, "Sample #3", 9);
+ printHash(sha1_resultHmac(&s));
+ printf("\n\n");
+
+ printf("Test: FIPS 198a A.4\n");
+ printf("Expect:9ea886efe268dbecce420c7524df32e0751a2a26\n");
+ printf("Result:");
+ sha1_initHmac(&s, hmacKey4,49);
+ sha1_write(&s, "Sample #4", 9);
+ printHash(sha1_resultHmac(&s));
+ printf("\n\n");
+
+ // Long tests
+ printf("Test: FIPS 180-2 C.3 and RFC3174 7.3 TEST3\n");
+ printf("Expect:34aa973cd4c4daa4f61eeb2bdbad27316534016f\n");
+ printf("Result:");
+ sha1_init(&s);
+ for (a=0; a<1000000; a++) sha1_writebyte(&s, 'a');
+ printHash(sha1_result(&s));
+
+ return 0;
+}
+#endif /* self-test */
diff --git a/protocols/Twitter/src/oauth/xmalloc.c b/protocols/Twitter/src/oauth/xmalloc.c
new file mode 100644
index 0000000000..f831e13895
--- /dev/null
+++ b/protocols/Twitter/src/oauth/xmalloc.c
@@ -0,0 +1,60 @@
+/* xmalloc.c -- memory allocation including 'out of memory' checks
+ *
+ * Copyright 2010 Robin Gareus <robin@gareus.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.
+ *
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <string.h>
+#include <stdlib.h>
+
+static void *xmalloc_fatal(size_t size) {
+ if (size==0) return NULL;
+ fprintf(stderr, "Out of memory.");
+ exit(1);
+}
+
+void *xmalloc (size_t size) {
+ void *ptr = malloc (size);
+ if (ptr == NULL) return xmalloc_fatal(size);
+ return ptr;
+}
+
+void *xcalloc (size_t nmemb, size_t size) {
+ void *ptr = calloc (nmemb, size);
+ if (ptr == NULL) return xmalloc_fatal(nmemb*size);
+ return ptr;
+}
+
+void *xrealloc (void *ptr, size_t size) {
+ void *p = realloc (ptr, size);
+ if (p == NULL) return xmalloc_fatal(size);
+ return p;
+}
+
+char *xstrdup (const char *s) {
+ void *ptr = xmalloc(strlen(s)+1);
+ strcpy (ptr, s);
+ return (char*) ptr;
+}
+
+// vi: sts=2 sw=2 ts=2
diff --git a/protocols/Twitter/src/oauth/xmalloc.h b/protocols/Twitter/src/oauth/xmalloc.h
new file mode 100644
index 0000000000..7ec9b61fdd
--- /dev/null
+++ b/protocols/Twitter/src/oauth/xmalloc.h
@@ -0,0 +1,10 @@
+#ifndef _OAUTH_XMALLOC_H
+#define _OAUTH_XMALLOC_H 1
+
+/* Prototypes for functions defined in xmalloc.c */
+void *xmalloc (size_t size);
+void *xcalloc (size_t nmemb, size_t size);
+void *xrealloc (void *ptr, size_t size);
+char *xstrdup (const char *s);
+
+#endif
diff --git a/protocols/Twitter/src/proto.cpp b/protocols/Twitter/src/proto.cpp
new file mode 100644
index 0000000000..ae843d8b07
--- /dev/null
+++ b/protocols/Twitter/src/proto.cpp
@@ -0,0 +1,590 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "proto.h"
+
+#include "utility.h"
+#include "theme.h"
+#include "ui.h"
+#include "oauth.dev.h"
+
+#include "m_folders.h"
+#include "m_historyevents.h"
+
+#include <algorithm>
+#include <cstdio>
+#include <ctime>
+#include <direct.h>
+
+TwitterProto::TwitterProto(const char *proto_name,const TCHAR *username)
+{
+ m_szProtoName = mir_strdup (proto_name);
+ m_szModuleName = mir_strdup (proto_name);
+ m_tszUserName = mir_tstrdup(username);
+
+ CreateProtoService(m_szModuleName,PS_CREATEACCMGRUI, &TwitterProto::SvcCreateAccMgrUI,this);
+ CreateProtoService(m_szModuleName,PS_GETNAME, &TwitterProto::GetName, this);
+ CreateProtoService(m_szModuleName,PS_GETSTATUS,&TwitterProto::GetStatus, this);
+
+ CreateProtoService(m_szModuleName,PS_JOINCHAT, &TwitterProto::OnJoinChat, this);
+ CreateProtoService(m_szModuleName,PS_LEAVECHAT,&TwitterProto::OnLeaveChat,this);
+
+ CreateProtoService(m_szModuleName,PS_GETMYAVATAR,&TwitterProto::GetAvatar,this);
+ CreateProtoService(m_szModuleName,PS_SETMYAVATAR,&TwitterProto::SetAvatar,this);
+
+ HookProtoEvent(ME_DB_CONTACT_DELETED, &TwitterProto::OnContactDeleted, this);
+ HookProtoEvent(ME_CLIST_PREBUILDSTATUSMENU, &TwitterProto::OnBuildStatusMenu, this);
+ HookProtoEvent(ME_OPT_INITIALISE, &TwitterProto::OnOptionsInit, this);
+
+ TCHAR *profile = Utils_ReplaceVarsT( _T("%miranda_avatarcache%"));
+ def_avatar_folder_ = std::tstring(profile) + _T("\\") + m_tszUserName;
+ mir_free(profile);
+ hAvatarFolder_ = FoldersRegisterCustomPathT(m_szModuleName, "Avatars", def_avatar_folder_.c_str());
+
+ // Initialize hotkeys
+ char text[512];
+ HOTKEYDESC hkd = {sizeof(hkd)};
+ hkd.cbSize = sizeof(hkd);
+ hkd.pszName = text;
+ hkd.pszService = text;
+ hkd.pszSection = m_szModuleName; // Section title; TODO: use username?
+
+ mir_snprintf(text,SIZEOF(text),"%s/Tweet",m_szModuleName);
+ hkd.pszDescription = "Send Tweet";
+ Hotkey_Register(&hkd);
+
+ signon_lock_ = CreateMutex(0,false,0);
+ avatar_lock_ = CreateMutex(0,false,0);
+ twitter_lock_ = CreateMutex(0,false,0);
+
+ SetAllContactStatuses(ID_STATUS_OFFLINE); // In case we crashed last time
+
+ // set Tokens and stuff
+
+ //mirandas keys
+ ConsumerKey = OAUTH_CONSUMER_KEY;
+ ConsumerSecret = OAUTH_CONSUMER_SECRET;
+
+ //codebrook's keys
+ //wstring ConsumerKey = L"T6XLGzrkfsJAgU59dbIjSA";
+ //wstring ConsumerSecret = L"xsvm2NAksjsJGw63RMWAtec3Lz5uiBusfVt48gbdKLg";
+
+ AccessUrl = L"http://twitter.com/oauth/access_token";
+ AuthorizeUrl = L"http://twitter.com/oauth/authorize?oauth_token=%s";
+ RequestUrl = L"http://twitter.com/oauth/request_token?some_other_parameter=hello&another_one=goodbye#meep"; // threw in some parameters for fun, and to test UrlGetQuery
+ UserTimelineUrl = L"http://twitter.com/statuses/user_timeline.json";
+}
+
+TwitterProto::~TwitterProto()
+{
+ twit_.Disconnect();
+
+ if(hNetlib_)
+ Netlib_CloseHandle(hNetlib_);
+ if(hAvatarNetlib_)
+ Netlib_CloseHandle(hAvatarNetlib_);
+
+ CloseHandle(twitter_lock_);
+ CloseHandle(avatar_lock_);
+ CloseHandle(signon_lock_);
+
+ mir_free(m_szProtoName);
+ mir_free(m_szModuleName);
+ mir_free(m_tszUserName);
+}
+
+// *************************
+
+DWORD_PTR TwitterProto::GetCaps(int type,HANDLE hContact)
+{
+ switch(type)
+ {
+ case PFLAGNUM_1:
+ return PF1_IM | PF1_MODEMSGRECV | PF1_BASICSEARCH | PF1_SEARCHBYEMAIL |
+ PF1_SERVERCLIST | PF1_CHANGEINFO;
+ case PFLAGNUM_2:
+ return PF2_ONLINE;
+ case PFLAGNUM_3:
+ return PF2_ONLINE;
+ case PFLAGNUM_4:
+ return PF4_NOCUSTOMAUTH | PF4_IMSENDUTF | PF4_AVATARS;
+ case PFLAG_MAXLENOFMESSAGE:
+ return 159; // 140 + <max length of a users name (15 apparently)> + 4 ("RT @"). this allows for the new style retweets
+ case PFLAG_UNIQUEIDTEXT:
+ return (int) "Username";
+ case PFLAG_UNIQUEIDSETTING:
+ return (int) TWITTER_KEY_UN;
+ }
+ return 0;
+}
+
+HICON TwitterProto::GetIcon(int index)
+{
+ if(LOWORD(index) == PLI_PROTOCOL)
+ {
+ HICON ico = (HICON)CallService(MS_SKIN2_GETICON,0,(LPARAM)"Twitter_twitter");
+ return CopyIcon(ico);
+ }
+ else
+ return 0;
+}
+
+// *************************
+
+int TwitterProto::RecvMsg(HANDLE hContact,PROTORECVEVENT *pre)
+{
+ return Proto_RecvMessage(hContact, pre);
+}
+
+// *************************
+
+struct send_direct
+{
+ send_direct(HANDLE hContact,const std::string &msg) : hContact(hContact),msg(msg) {}
+ HANDLE hContact;
+ std::string msg;
+};
+
+void TwitterProto::SendSuccess(void *p)
+{
+ if(p == 0)
+ return;
+ send_direct *data = static_cast<send_direct*>(p);
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(data->hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ {
+ ScopedLock s(twitter_lock_);
+ twit_.send_direct(dbv.pszVal,data->msg);
+
+ ProtoBroadcastAck(m_szModuleName,data->hContact,ACKTYPE_MESSAGE,ACKRESULT_SUCCESS,
+ (HANDLE)1,0);
+ DBFreeVariant(&dbv);
+ }
+
+ delete data;
+}
+
+int TwitterProto::SendMsg(HANDLE hContact,int flags,const char *msg)
+{
+ if(m_iStatus != ID_STATUS_ONLINE)
+ return 0;
+
+ TCHAR* tszMsg;
+ if ( flags & PREF_UTF )
+ tszMsg = mir_utf8decodeT( msg );
+ else if ( flags & PREF_UNICODE )
+ tszMsg = mir_u2t(( wchar_t* )&msg[ strlen( msg )+1 ] );
+ else
+ tszMsg = mir_a2t( msg );
+
+ ForkThread(&TwitterProto::SendSuccess, this,new send_direct(hContact,msg));
+ return 1;
+}
+
+// *************************
+
+int TwitterProto::SetStatus(int new_status)
+{
+ int old_status = m_iStatus;
+ if(new_status == m_iStatus)
+ return 0;
+
+ m_iDesiredStatus = new_status;
+ // 40072 - 40080 are the "online" statuses, basically every status except offline. see statusmodes.h
+ if(new_status >= 40072 && new_status <= 40080)
+ {
+
+ m_iDesiredStatus = ID_STATUS_ONLINE; //i think i have to set this so it forces the twitter proto to be online (and not away, DND, etc)
+
+ // if we're already connecting and they want to go online, BAIL! we're already trying to connect you dumbass
+ if(old_status == ID_STATUS_CONNECTING)
+ return 0;
+
+ // if we're already connected, and we change to another connected status, don't try and reconnect!
+ if (old_status >= 40072 && old_status <= 40080)
+ return 0;
+
+ // i think here we tell the proto interface struct that we're connecting, just so it knows
+ m_iStatus = ID_STATUS_CONNECTING;
+ // ok.. here i think we're telling the core that this protocol something.. but why?
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_SUCCESS,
+ (HANDLE)old_status,m_iStatus);
+
+ ForkThread(&TwitterProto::SignOn,this);
+ }
+ else if(new_status == ID_STATUS_OFFLINE)
+ {
+ twit_.Disconnect();
+ m_iStatus = m_iDesiredStatus;
+ SetAllContactStatuses(ID_STATUS_OFFLINE);
+
+ ProtoBroadcastAck(m_szModuleName,0,ACKTYPE_STATUS,ACKRESULT_SUCCESS,
+ (HANDLE)old_status,m_iStatus);
+ }
+
+ return 0;
+}
+
+// *************************
+
+int TwitterProto::OnEvent(PROTOEVENTTYPE event,WPARAM wParam,LPARAM lParam)
+{
+ switch(event)
+ {
+ case EV_PROTO_ONLOAD: return OnModulesLoaded(wParam,lParam);
+ case EV_PROTO_ONEXIT: return OnPreShutdown (wParam,lParam);
+ case EV_PROTO_ONOPTIONS: return OnOptionsInit (wParam,lParam);
+ }
+
+ return 1;
+}
+
+// *************************
+
+int TwitterProto::SvcCreateAccMgrUI(WPARAM wParam,LPARAM lParam)
+{
+ return (int)CreateDialogParam(g_hInstance,MAKEINTRESOURCE(IDD_TWITTERACCOUNT),
+ (HWND)lParam, first_run_dialog, (LPARAM)this );
+}
+
+int TwitterProto::GetName(WPARAM wParam,LPARAM lParam)
+{
+ lstrcpynA(reinterpret_cast<char*>(lParam), m_szProtoName, (int)wParam);
+ return 0;
+}
+
+int TwitterProto::GetStatus(WPARAM wParam,LPARAM lParam)
+{
+ return m_iStatus;
+}
+
+int TwitterProto::ReplyToTweet(WPARAM wParam,LPARAM lParam)
+{
+ // TODO: support replying to tweets instead of just users
+ HANDLE hContact = reinterpret_cast<HANDLE>(wParam);
+
+ HWND hDlg = CreateDialogParam(g_hInstance,MAKEINTRESOURCE(IDD_TWEET),
+ (HWND)0,tweet_proc,reinterpret_cast<LPARAM>(this));
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ {
+ SendMessage(hDlg,WM_SETREPLY,reinterpret_cast<WPARAM>(dbv.pszVal),0);
+ DBFreeVariant(&dbv);
+ }
+
+ ShowWindow(hDlg,SW_SHOW);
+
+ return 0;
+}
+
+int TwitterProto::VisitHomepage(WPARAM wParam,LPARAM lParam)
+{
+ HANDLE hContact = reinterpret_cast<HANDLE>(wParam);
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(hContact,m_szModuleName,"Homepage",&dbv))
+ {
+ CallService(MS_UTILS_OPENURL,1,reinterpret_cast<LPARAM>(dbv.pszVal));
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ // TODO: remove this
+ if( !DBGetContactSettingString(hContact,m_szModuleName,TWITTER_KEY_UN,&dbv))
+ {
+ std::string url = profile_base_url(twit_.get_base_url())+
+ http::url_encode(dbv.pszVal);
+ DBWriteContactSettingString(hContact,m_szModuleName,"Homepage",url.c_str());
+
+ CallService(MS_UTILS_OPENURL,1,reinterpret_cast<LPARAM>(url.c_str()));
+ DBFreeVariant(&dbv);
+ }
+ }
+
+ return 0;
+}
+
+// *************************
+
+int TwitterProto::OnBuildStatusMenu(WPARAM wParam,LPARAM lParam)
+{
+ HGENMENU hRoot = pcli->pfnGetProtocolMenu(m_szModuleName);
+ if (hRoot == NULL)
+ return 0;
+
+ CLISTMENUITEM mi = {sizeof(mi)};
+
+ char text[200];
+ strcpy(text,m_szModuleName);
+ char *tDest = text+strlen(text);
+ mi.pszService = text;
+
+ mi.hParentMenu = hRoot;
+ mi.flags = CMIF_ICONFROMICOLIB|CMIF_ROOTHANDLE;
+ mi.position = 1001;
+
+ HANDLE m_hMenuRoot = Menu_AddStatusMenuItem(&mi);
+
+ // TODO: Disable this menu item when offline
+ // "Send Tweet..."
+ CreateProtoService(m_szModuleName,"/Tweet",&TwitterProto::OnTweet,this);
+ strcpy(tDest,"/Tweet");
+ mi.pszName = LPGEN("Send Tweet...");
+ mi.popupPosition = 200001;
+ mi.icolibItem = GetIconHandle("tweet");
+ HANDLE m_hMenuBookmarks = Menu_AddStatusMenuItem(&mi);
+ return 0;
+}
+
+int TwitterProto::OnOptionsInit(WPARAM wParam,LPARAM lParam)
+{
+ OPTIONSDIALOGPAGE odp = {sizeof(odp)};
+ odp.position = 271828;
+ odp.hInstance = g_hInstance;
+ odp.ptszGroup = LPGENT("Network");
+ odp.ptszTitle = m_tszUserName;
+ odp.dwInitParam = LPARAM(this);
+ odp.flags = ODPF_BOLDGROUPS | ODPF_TCHAR;
+
+ odp.ptszTab = LPGENT("Basic");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS);
+ odp.pfnDlgProc = options_proc;
+ Options_AddPage(wParam, &odp);
+
+ if(ServiceExists(MS_POPUP_ADDPOPUPT))
+ {
+ odp.ptszTab = LPGENT("Popups");
+ odp.pszTemplate = MAKEINTRESOURCEA(IDD_OPTIONS_POPUPS);
+ odp.pfnDlgProc = popup_options_proc;
+ Options_AddPage(wParam, &odp);
+ }
+
+ return 0;
+}
+
+int TwitterProto::OnTweet(WPARAM wParam,LPARAM lParam)
+{
+ if(m_iStatus != ID_STATUS_ONLINE)
+ return 1;
+
+ HWND hDlg = CreateDialogParam(g_hInstance,MAKEINTRESOURCE(IDD_TWEET),
+ (HWND)0,tweet_proc,reinterpret_cast<LPARAM>(this));
+ ShowWindow(hDlg,SW_SHOW);
+ return 0;
+}
+
+int TwitterProto::OnModulesLoaded(WPARAM wParam,LPARAM lParam)
+{
+ TCHAR descr[512];
+ NETLIBUSER nlu = {sizeof(nlu)};
+ nlu.flags = NUF_OUTGOING | NUF_INCOMING | NUF_HTTPCONNS | NUF_TCHAR;
+ nlu.szSettingsModule = m_szModuleName;
+
+ // Create standard network connection
+ mir_sntprintf(descr,SIZEOF(descr),TranslateT("%s server connection"),m_tszUserName);
+ nlu.ptszDescriptiveName = descr;
+ hNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER,0,(LPARAM)&nlu);
+ if(hNetlib_ == 0)
+ MessageBoxA(0,"Unable to get Netlib connection for Twitter","",0);
+
+ // Create avatar network connection (TODO: probably remove this)
+ char module[512];
+ mir_snprintf(module,SIZEOF(module),"%sAv",m_szModuleName);
+ nlu.szSettingsModule = module;
+ mir_sntprintf(descr,SIZEOF(descr),TranslateT("%s avatar connection"),m_tszUserName);
+ nlu.ptszDescriptiveName = descr;
+ hAvatarNetlib_ = (HANDLE)CallService(MS_NETLIB_REGISTERUSER,0,(LPARAM)&nlu);
+ if(hAvatarNetlib_ == 0)
+ MessageBoxA(0,"Unable to get avatar Netlib connection for Twitter","",0);
+
+ twit_.set_handle(hNetlib_);
+
+ GCREGISTER gcr = {sizeof(gcr)};
+ gcr.pszModule = m_szModuleName;
+ gcr.pszModuleDispName = m_szModuleName;
+ gcr.iMaxText = 159;
+ CallService(MS_GC_REGISTER,0,reinterpret_cast<LPARAM>(&gcr));
+
+ if (ServiceExists(MS_HISTORYEVENTS_REGISTER))
+ {
+ HISTORY_EVENT_HANDLER heh = {0};
+ heh.cbSize = sizeof(heh);
+ heh.module = m_szModuleName;
+ heh.name = "tweet";
+ heh.description = "Tweet";
+ heh.eventType = TWITTER_DB_EVENT_TYPE_TWEET;
+ heh.defaultIconName = "Twitter_tweet";
+ heh.flags = HISTORYEVENTS_FLAG_SHOW_IM_SRMM
+ | HISTORYEVENTS_FLAG_EXPECT_CONTACT_NAME_BEFORE
+// Not sure: | HISTORYEVENTS_FLAG_FLASH_MSG_WINDOW
+ | HISTORYEVENTS_REGISTERED_IN_ICOLIB;
+ CallService(MS_HISTORYEVENTS_REGISTER, (WPARAM) &heh, 0);
+ }
+ else
+ {
+ DBEVENTTYPEDESCR evt = {sizeof(evt)};
+ evt.eventType = TWITTER_DB_EVENT_TYPE_TWEET;
+ evt.module = m_szModuleName;
+ evt.descr = "Tweet";
+ evt.flags = DETF_HISTORY | DETF_MSGWINDOW;
+ CallService(MS_DB_EVENT_REGISTERTYPE,0,reinterpret_cast<LPARAM>(&evt));
+ }
+
+ return 0;
+}
+
+int TwitterProto::OnPreShutdown(WPARAM wParam,LPARAM lParam)
+{
+ Netlib_Shutdown(hNetlib_);
+ Netlib_Shutdown(hAvatarNetlib_);
+ return 0;
+}
+
+int TwitterProto::OnPrebuildContactMenu(WPARAM wParam,LPARAM lParam)
+{
+ HANDLE hContact = reinterpret_cast<HANDLE>(wParam);
+ if(IsMyContact(hContact))
+ ShowContactMenus(true);
+
+ return 0;
+}
+
+int TwitterProto::ShowPinDialog()
+{
+ HWND hDlg = (HWND)DialogBoxParam(g_hInstance,MAKEINTRESOURCE(IDD_TWITTERPIN),
+ (HWND)0,pin_proc,reinterpret_cast<LPARAM>(this));
+ ShowWindow(hDlg,SW_SHOW);
+ return 0;
+}
+
+void TwitterProto::ShowPopup(const wchar_t *text, int Error)
+{
+ POPUPDATAT popup = {};
+ _sntprintf(popup.lptzContactName,MAX_CONTACTNAME,TranslateT("%s Protocol"),m_tszUserName);
+ wcs_to_tcs(CP_UTF8,text,popup.lptzText,MAX_SECONDLINE);
+
+ if (Error) {
+ popup.iSeconds = -1;
+ popup.colorBack = 0x000000FF;
+ popup.colorText = 0x00FFFFFF;
+ }
+
+ if(ServiceExists(MS_POPUP_ADDPOPUPT))
+ CallService(MS_POPUP_ADDPOPUPT,reinterpret_cast<WPARAM>(&popup),0);
+ else
+ MessageBox(0,popup.lptzText,popup.lptzContactName,0);
+}
+
+void TwitterProto::ShowPopup(const char *text, int Error)
+{
+ POPUPDATAT popup = {};
+ _sntprintf(popup.lptzContactName,MAX_CONTACTNAME,TranslateT("%s Protocol"),m_tszUserName);
+ mbcs_to_tcs(CP_UTF8,text,popup.lptzText,MAX_SECONDLINE);
+ if (Error) {
+ popup.iSeconds = -1;
+ popup.colorBack = 0x000000FF;
+ popup.colorText = 0x00FFFFFF;
+ }
+
+ if(ServiceExists(MS_POPUP_ADDPOPUPT))
+ CallService(MS_POPUP_ADDPOPUPT,reinterpret_cast<WPARAM>(&popup),0);
+ else
+ MessageBox(0,popup.lptzText,popup.lptzContactName,0);
+}
+
+void TwitterProto::LOG(TCHAR *fmt,...)
+{
+ if (!hNetlib_)
+ return;
+
+ va_list va;
+ va_start(va,fmt);
+
+ TCHAR text[1024];
+ mir_vsntprintf(text,SIZEOF(text),fmt,va);
+ va_end(va);
+
+ CallService(MS_NETLIB_LOG, (WPARAM)hNetlib_, (LPARAM)( char* )_T2A(text));
+}
+
+// TODO: the more I think about it, the more I think all twit.* methods should
+// be in MessageLoop
+void TwitterProto::SendTweetWorker(void *p)
+{
+ if (p == 0)
+ return;
+
+ char *text = static_cast<char*>(p);
+ if (strlen(text) > 140) { // looks like the chat max outgoing msg thing doesn't work, so i'll do it here.
+ char * errorPopup = new char[280]; // i hate c strings ... i should use std::string here. why did i use char* ??? need to delete[] or use std::String
+ sprintf(errorPopup, "Don't be crazy! Everyone knows the max tweet size is 140, and you're trying to fit %d chars in there?", strlen(text));
+ ShowPopup(errorPopup, 1);
+ delete[] errorPopup;
+ return;
+ }
+
+ ScopedLock s(twitter_lock_);
+ twit_.set_status(text);
+
+ mir_free(text);
+}
+
+void TwitterProto::UpdateSettings()
+{
+ if(db_get_b(0,m_szModuleName,TWITTER_KEY_CHATFEED,0))
+ {
+ if(!in_chat_)
+ OnJoinChat(0,0);
+ }
+ else
+ {
+ if(in_chat_)
+ OnLeaveChat(0,0);
+
+ for(HANDLE hContact = db_find_first();
+ hContact;
+ hContact = db_find_next(hContact))
+ {
+ if(!IsMyContact(hContact,true))
+ continue;
+
+ if(db_get_b(hContact,m_szModuleName,"ChatRoom",0))
+ CallService(MS_DB_CONTACT_DELETE,reinterpret_cast<WPARAM>(hContact),0);
+ }
+ }
+}
+
+std::tstring TwitterProto::GetAvatarFolder()
+{
+ TCHAR path[MAX_PATH];
+ if(hAvatarFolder_ && FoldersGetCustomPathT(hAvatarFolder_,path,SIZEOF(path), _T("")) == 0)
+ return path;
+
+ return def_avatar_folder_;
+}
+
+int TwitterProto::GetAvatar(WPARAM,LPARAM)
+{
+ return 0;
+}
+
+int TwitterProto::SetAvatar(WPARAM,LPARAM)
+{
+ return 0;
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/proto.h b/protocols/Twitter/src/proto.h
new file mode 100644
index 0000000000..31acca9768
--- /dev/null
+++ b/protocols/Twitter/src/proto.h
@@ -0,0 +1,191 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#include "common.h"
+#include "utility.h"
+//#include "tc2.h"
+#include "stdafx.h"
+#include "oauth.h"
+
+#include <m_protoint.h>
+
+class TwitterProto : public PROTO_INTERFACE, public MZeroedObject
+{
+public:
+ TwitterProto(const char *,const TCHAR *);
+ ~TwitterProto();
+
+ inline const char * ModuleName() const
+ {
+ return m_szModuleName;
+ }
+
+ //PROTO_INTERFACE
+
+ virtual HANDLE __cdecl AddToList(int,PROTOSEARCHRESULT *);
+ virtual HANDLE __cdecl AddToListByEvent(int,int,HANDLE);
+
+ virtual int __cdecl Authorize(HANDLE);
+ virtual int __cdecl AuthDeny(HANDLE,const TCHAR *);
+ virtual int __cdecl AuthRecv(HANDLE,PROTORECVEVENT *);
+ virtual int __cdecl AuthRequest(HANDLE,const TCHAR *);
+
+ virtual HANDLE __cdecl ChangeInfo(int,void *);
+
+ virtual HANDLE __cdecl FileAllow(HANDLE,HANDLE,const TCHAR *);
+ virtual int __cdecl FileCancel(HANDLE,HANDLE);
+ virtual int __cdecl FileDeny(HANDLE,HANDLE,const TCHAR *);
+ virtual int __cdecl FileResume(HANDLE,int *,const TCHAR **);
+
+ virtual DWORD_PTR __cdecl GetCaps(int,HANDLE = 0);
+ virtual HICON __cdecl GetIcon(int);
+ virtual int __cdecl GetInfo(HANDLE,int);
+
+ virtual HANDLE __cdecl SearchBasic(const TCHAR *);
+ virtual HANDLE __cdecl SearchByEmail(const TCHAR *);
+ virtual HANDLE __cdecl SearchByName(const TCHAR *,const TCHAR *,const TCHAR *);
+ virtual HWND __cdecl SearchAdvanced(HWND);
+ virtual HWND __cdecl CreateExtendedSearchUI(HWND);
+
+ virtual int __cdecl RecvContacts(HANDLE,PROTORECVEVENT *);
+ virtual int __cdecl RecvFile(HANDLE,PROTORECVFILET *);
+ virtual int __cdecl RecvMsg(HANDLE,PROTORECVEVENT *);
+ virtual int __cdecl RecvUrl(HANDLE,PROTORECVEVENT *);
+
+ virtual int __cdecl SendContacts(HANDLE,int,int,HANDLE *);
+ virtual HANDLE __cdecl SendFile(HANDLE,const TCHAR *,TCHAR **);
+ virtual int __cdecl SendMsg(HANDLE,int,const char *);
+ virtual int __cdecl SendUrl(HANDLE,int,const char *);
+
+ virtual int __cdecl SetApparentMode(HANDLE,int);
+ virtual int __cdecl SetStatus(int);
+
+ virtual HANDLE __cdecl GetAwayMsg(HANDLE);
+ virtual int __cdecl RecvAwayMsg(HANDLE,int,PROTORECVEVENT *);
+ virtual int __cdecl SendAwayMsg(HANDLE,HANDLE,const char *);
+ virtual int __cdecl SetAwayMsg(int,const TCHAR *);
+
+ virtual int __cdecl UserIsTyping(HANDLE,int);
+
+ virtual int __cdecl OnEvent(PROTOEVENTTYPE,WPARAM,LPARAM);
+
+ void UpdateSettings();
+
+ // Services
+ int __cdecl SvcCreateAccMgrUI(WPARAM,LPARAM);
+ int __cdecl GetName(WPARAM,LPARAM);
+ int __cdecl GetStatus(WPARAM,LPARAM);
+ int __cdecl ReplyToTweet(WPARAM,LPARAM);
+ int __cdecl VisitHomepage(WPARAM,LPARAM);
+ int __cdecl GetAvatar(WPARAM,LPARAM);
+ int __cdecl SetAvatar(WPARAM,LPARAM);
+
+ // Events
+ int __cdecl OnContactDeleted(WPARAM,LPARAM);
+ int __cdecl OnBuildStatusMenu(WPARAM,LPARAM);
+ int __cdecl OnOptionsInit(WPARAM,LPARAM);
+ int __cdecl OnTweet(WPARAM,LPARAM);
+ int __cdecl OnModulesLoaded(WPARAM,LPARAM);
+ int __cdecl OnPreShutdown(WPARAM,LPARAM);
+ int __cdecl OnPrebuildContactMenu(WPARAM,LPARAM);
+ int __cdecl OnChatOutgoing(WPARAM,LPARAM);
+ int __cdecl OnJoinChat(WPARAM,LPARAM);
+ int __cdecl OnLeaveChat(WPARAM,LPARAM);
+
+ void __cdecl SendTweetWorker(void *);
+private:
+ // Worker threads
+ void __cdecl AddToListWorker(void *p);
+ void __cdecl SendSuccess(void *);
+ void __cdecl DoSearch(void *);
+ void __cdecl SignOn(void *);
+ void __cdecl MessageLoop(void *);
+ void __cdecl GetAwayMsgWorker(void *);
+ void __cdecl UpdateAvatarWorker(void *);
+ void __cdecl UpdateInfoWorker(void *);
+
+ bool NegotiateConnection();
+
+ void UpdateStatuses(bool pre_read,bool popups, bool tweetToMsg);
+ void UpdateMessages(bool pre_read);
+ void UpdateFriends();
+ void UpdateAvatar(HANDLE,const std::string &,bool force=false);
+
+ int ShowPinDialog();
+ void ShowPopup(const wchar_t *, int Error = 0);
+ void ShowPopup(const char *, int Error = 0);
+ void ShowContactPopup(HANDLE,const std::string &);
+
+ bool IsMyContact(HANDLE,bool include_chat = false);
+ HANDLE UsernameToHContact(const char *);
+ HANDLE AddToClientList(const char *,const char *);
+ void SetAllContactStatuses(int);
+
+ void LOG(TCHAR *fmt,...);
+ __inline void WLOG(TCHAR* first, const tstring& last) { LOG(first, last.c_str()); }
+ static void CALLBACK APC_callback(ULONG_PTR p);
+
+ void UpdateChat(const twitter_user &update);
+ void AddChatContact(const char *name,const char *nick=0);
+ void DeleteChatContact(const char *name);
+ void SetChatStatus(int);
+
+ void TwitterProto::resetOAuthKeys();
+
+ std::tstring GetAvatarFolder();
+
+ HANDLE signon_lock_;
+ HANDLE avatar_lock_;
+ HANDLE twitter_lock_;
+
+ HANDLE hNetlib_;
+ HANDLE hAvatarNetlib_;
+ HANDLE hMsgLoop_;
+ mir_twitter twit_;
+
+ twitter_id since_id_;
+ twitter_id dm_since_id_;
+
+ std::tstring def_avatar_folder_;
+ HANDLE hAvatarFolder_;
+
+ bool in_chat_;
+
+ int disconnectionCount;
+
+ //mirandas keys
+ wstring ConsumerKey;
+ wstring ConsumerSecret;
+
+ // various twitter api URLs
+ wstring AccessUrl;
+ wstring AuthorizeUrl;
+ wstring RequestUrl;
+ wstring UserTimelineUrl;
+};
+
+// TODO: remove this
+inline std::string profile_base_url(const std::string &url)
+{
+ size_t x = url.find("://");
+ if(x == std::string::npos)
+ return url.substr(0,url.find('/')+1);
+ else
+ return url.substr(0,url.find('/',x+3)+1);
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/resource.h b/protocols/Twitter/src/resource.h
new file mode 100644
index 0000000000..909c859e88
--- /dev/null
+++ b/protocols/Twitter/src/resource.h
@@ -0,0 +1,50 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by twitter.rc
+//
+#define IDD_TWITTERACCOUNT 101
+#define IDI_TWITTER 102
+#define IDD_TWEET 103
+#define IDD_OPTIONS 104
+#define IDD_OPTIONS_POPUPS 107
+#define IDD_TWITTERPIN 109
+#define IDC_NEWACCOUNTLINK 1001
+#define IDC_UN 1002
+#define IDC_PW 1003
+#define IDC_TWEETMSG 1004
+#define IDC_CHARACTERS 1005
+#define IDC_USERDETAILS 1006
+#define IDC_MISC 1007
+#define IDC_CHATFEED 1008
+#define IDC_BASEURL 1009
+#define IDC_POLLRATE 1010
+#define IDC_COLBACK 1011
+#define IDC_COLTEXT 1012
+#define IDC_TIMEOUT_DEFAULT 1013
+#define IDC_TIMEOUT_CUSTOM 1014
+#define IDC_TIMEOUT_SPIN 1015
+#define IDC_TIMEOUT 1016
+#define IDC_TIMEOUT_PERMANENT 1017
+#define IDC_COL_WINDOWS 1018
+#define IDC_COL_POPUP 1019
+#define IDC_COL_CUSTOM 1020
+#define IDC_SHOWPOPUPS 1021
+#define IDC_PREVIEW 1022
+#define IDC_NOSIGNONPOPUPS 1023
+#define IDC_RECONNECT 1024
+#define IDC_SERVER 1025
+#define IDC_PIN 1026
+#define IDC_GROUP 1027
+#define IDC_USERNAME 1028
+#define IDC_TWEET_MSG 1029
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 110
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1030
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/protocols/Twitter/src/stdafx.h b/protocols/Twitter/src/stdafx.h
new file mode 100644
index 0000000000..6e216fce33
--- /dev/null
+++ b/protocols/Twitter/src/stdafx.h
@@ -0,0 +1,33 @@
+// stdafx.h : include file for standard system include files,
+// or project specific include files that are used frequently, but
+// are changed infrequently
+//
+
+#pragma once
+
+#include "targetver.h"
+
+#include <Windows.h>
+//#include <WinInet.h>
+#include <Shlwapi.h>
+#include <Wincrypt.h>
+#include <stdio.h>
+#include <tchar.h>
+#include <time.h>
+
+#include <string>
+#include <list>
+#include <vector>
+#include <map>
+#include <fstream>
+
+typedef std::basic_string<TCHAR> tstring;
+//#define SIZEOF(x) (sizeof(x)/sizeof(*x))
+
+#include "StringConv.h"
+#include "StringUtil.h"
+#include "Base64Coder.h"
+
+#include "Debug.h"
+
+#include "win2k.h"
diff --git a/protocols/Twitter/src/stubs.cpp b/protocols/Twitter/src/stubs.cpp
new file mode 100644
index 0000000000..472138abe3
--- /dev/null
+++ b/protocols/Twitter/src/stubs.cpp
@@ -0,0 +1,139 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "proto.h"
+
+HANDLE TwitterProto::AddToListByEvent(int flags,int iContact,HANDLE hDbEvent)
+{
+ return NULL;
+}
+
+int TwitterProto::Authorize(HANDLE hDbEvent)
+{
+ return 1;
+}
+
+int TwitterProto::AuthDeny(HANDLE hDbEvent,const TCHAR *reason)
+{
+ return 1;
+}
+
+int TwitterProto::AuthRecv(HANDLE hContact,PROTORECVEVENT *)
+{
+ return 1;
+}
+
+int TwitterProto::AuthRequest(HANDLE hContact,const TCHAR *message)
+{
+ return 1;
+}
+
+HANDLE TwitterProto::ChangeInfo(int type,void *info_data)
+{
+ MessageBoxA(0,"ChangeInfo","",0);
+ return NULL;
+}
+
+HANDLE TwitterProto::FileAllow(HANDLE hContact,HANDLE hTransfer,const TCHAR *path)
+{
+ return NULL;
+}
+
+int TwitterProto::FileCancel(HANDLE hContact,HANDLE hTransfer)
+{
+ return 1;
+}
+
+int TwitterProto::FileDeny(HANDLE hContact,HANDLE hTransfer,const TCHAR *reason)
+{
+ return 1;
+}
+
+int TwitterProto::FileResume(HANDLE hTransfer,int *action,const TCHAR **filename)
+{
+ return 1;
+}
+
+HANDLE TwitterProto::SearchByName(const TCHAR *nick,const TCHAR *first_name, const TCHAR *last_name)
+{
+ return NULL;
+}
+
+HWND TwitterProto::SearchAdvanced(HWND owner)
+{
+ return NULL;
+}
+
+HWND TwitterProto::CreateExtendedSearchUI(HWND owner)
+{
+ return NULL;
+}
+
+int TwitterProto::RecvContacts(HANDLE hContact,PROTORECVEVENT *)
+{
+ return 1;
+}
+
+int TwitterProto::RecvFile(HANDLE hContact,PROTORECVFILET *)
+{
+ return 1;
+}
+
+int TwitterProto::RecvUrl(HANDLE hContact,PROTORECVEVENT *)
+{
+ return 1;
+}
+
+int TwitterProto::SendContacts(HANDLE hContact,int flags,int nContacts,HANDLE *hContactsList)
+{
+ return 1;
+}
+
+HANDLE TwitterProto::SendFile(HANDLE hContact,const TCHAR *desc, TCHAR **files)
+{
+ return NULL;
+}
+
+int TwitterProto::SendUrl(HANDLE hContact,int flags,const char *url)
+{
+ return 1;
+}
+
+int TwitterProto::SetApparentMode(HANDLE hContact,int mode)
+{
+ return 1;
+}
+
+int TwitterProto::RecvAwayMsg(HANDLE hContact,int mode,PROTORECVEVENT *evt)
+{
+ return 1;
+}
+
+int TwitterProto::SendAwayMsg(HANDLE hContact,HANDLE hProcess,const char *msg)
+{
+ return 1;
+}
+
+int TwitterProto::SetAwayMsg(int status,const TCHAR *msg)
+{
+ return 1;
+}
+
+int TwitterProto::UserIsTyping(HANDLE hContact,int type)
+{
+ return 1;
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/targetver.h b/protocols/Twitter/src/targetver.h
new file mode 100644
index 0000000000..a38195a4ef
--- /dev/null
+++ b/protocols/Twitter/src/targetver.h
@@ -0,0 +1,13 @@
+#pragma once
+
+// The following macros define the minimum required platform. The minimum required platform
+// is the earliest version of Windows, Internet Explorer etc. that has the necessary features to run
+// your application. The macros work by enabling all features available on platform versions up to and
+// including the version specified.
+
+// Modify the following defines if you have to target a platform prior to the ones specified below.
+// Refer to MSDN for the latest info on corresponding values for different platforms.
+#ifndef _WIN32_WINNT // Specifies that the minimum required platform is Windows Vista.
+#define _WIN32_WINNT 0x0600 // Change this to the appropriate value to target other versions of Windows.
+#endif
+
diff --git a/protocols/Twitter/src/tc2.cpp b/protocols/Twitter/src/tc2.cpp
new file mode 100644
index 0000000000..6348e2703a
--- /dev/null
+++ b/protocols/Twitter/src/tc2.cpp
@@ -0,0 +1,805 @@
+/*
+
+Copyright (c) 2010 Brook Miles
+
+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.
+
+*/
+
+#include "stdafx.h"
+#include "tc2.h"
+#include "proto.h"
+
+//mirandas keys
+wstring ConsumerKey = L"AygxrTNGti27uVMz0hAbA";
+wstring ConsumerSecret = L"kjM8gL7oqPXya2rhI8dSoZW0RPyPOD1GxgYj6wA";
+
+//codebrook's keys
+//wstring ConsumerKey = L"T6XLGzrkfsJAgU59dbIjSA";
+//wstring ConsumerSecret = L"xsvm2NAksjsJGw63RMWAtec3Lz5uiBusfVt48gbdKLg";
+
+const wstring HostName = L"http://twitter.com";
+const wstring AccessUrl = L"http://twitter.com/oauth/access_token";
+const wstring AuthorizeUrl = L"http://twitter.com/oauth/authorize?oauth_token=%s";
+const wstring RequestUrl = L"http://twitter.com/oauth/request_token?some_other_parameter=hello&another_one=goodbye#meep"; // threw in some parameters for fun, and to test UrlGetQuery
+const wstring UserTimelineUrl = L"http://twitter.com/statuses/user_timeline.json";
+
+const wstring AuthFileName = L"tc2_saved.txt";
+
+
+using namespace std;
+
+// ConsumerKey and ConsumerSecret uniquely identify your application
+// TODO Go to http://twitter.com/oauth_clients/new and register your own Client application.
+// TODO Then replace the ConsumerKey and ConsumerSecret in the values below.
+
+wstring getHostName() { return HostName; }
+wstring getAccessUrl() { return AccessUrl; }
+wstring getAuthorizeUrl() { return AuthorizeUrl; }
+wstring getRequestUrl() { return RequestUrl; }
+wstring getUserTimelineUrl() { return UserTimelineUrl; }
+wstring getConsumerKey() { return ConsumerKey; }
+wstring getConsumerSecret() { return ConsumerSecret; }
+
+
+typedef std::map<wstring, wstring> OAuthParameters;
+
+/*wstring UrlGetQuery( const wstring& url )
+{
+ wstring query;
+ URL_COMPONENTS components = {sizeof(URL_COMPONENTS)};
+
+ wchar_t buf[1024*4] = {};
+
+ components.lpszExtraInfo = buf;
+ components.dwExtraInfoLength = SIZEOF(buf);
+
+ BOOL crackUrlOk = InternetCrackUrl(url.c_str(), url.size(), 0, &components);
+ _ASSERTE(crackUrlOk);
+ if(crackUrlOk)
+ {
+ query = components.lpszExtraInfo;
+
+ wstring::size_type q = query.find_first_of(L'?');
+ if(q != wstring::npos)
+ {
+ query = query.substr(q + 1);
+ }
+
+ wstring::size_type h = query.find_first_of(L'#');
+ if(h != wstring::npos)
+ {
+ query = query.substr(0, h);
+ }
+ }
+ return query;
+}*/
+
+OAuthParameters ParseQueryString( const wstring& url )
+{
+ OAuthParameters ret;
+
+ vector<wstring> queryParams;
+ Split(url, queryParams, L'&', false);
+
+ for(size_t i = 0; i < queryParams.size(); ++i)
+ {
+ vector<wstring> paramElements;
+ Split(queryParams[i], paramElements, L'=', true);
+ _ASSERTE(paramElements.size() == 2);
+ if(paramElements.size() == 2)
+ {
+ ret[paramElements[0]] = paramElements[1];
+ }
+ }
+ return ret;
+}
+
+wstring OAuthCreateNonce()
+{
+ wchar_t ALPHANUMERIC[] = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ wstring nonce;
+
+ for(int i = 0; i <= 16; ++i)
+ {
+ nonce += ALPHANUMERIC[rand() % (SIZEOF(ALPHANUMERIC) - 1)]; // don't count null terminator in array
+ }
+ return nonce;
+}
+
+wstring OAuthCreateTimestamp()
+{
+ __time64_t utcNow;
+ __time64_t ret = _time64(&utcNow);
+ _ASSERTE(ret != -1);
+
+ wchar_t buf[100] = {};
+ swprintf_s(buf, SIZEOF(buf), L"%I64u", utcNow);
+
+ return buf;
+}
+
+string HMACSHA1( const string& keyBytes, const string& data )
+{
+ // based on http://msdn.microsoft.com/en-us/library/aa382379%28v=VS.85%29.aspx
+
+ string hash;
+
+ //--------------------------------------------------------------------
+ // Declare variables.
+ //
+ // hProv: Handle to a cryptographic service provider (CSP).
+ // This example retrieves the default provider for
+ // the PROV_RSA_FULL provider type.
+ // hHash: Handle to the hash object needed to create a hash.
+ // hKey: Handle to a symmetric key. This example creates a
+ // key for the RC4 algorithm.
+ // hHmacHash: Handle to an HMAC hash.
+ // pbHash: Pointer to the hash.
+ // dwDataLen: Length, in bytes, of the hash.
+ // Data1: Password string used to create a symmetric key.
+ // Data2: Message string to be hashed.
+ // HmacInfo: Instance of an HMAC_INFO structure that contains
+ // information about the HMAC hash.
+ //
+ HCRYPTPROV hProv = NULL;
+ HCRYPTHASH hHash = NULL;
+ HCRYPTKEY hKey = NULL;
+ HCRYPTHASH hHmacHash = NULL;
+ PBYTE pbHash = NULL;
+ DWORD dwDataLen = 0;
+ //BYTE Data1[] = {0x70,0x61,0x73,0x73,0x77,0x6F,0x72,0x64};
+ //BYTE Data2[] = {0x6D,0x65,0x73,0x73,0x61,0x67,0x65};
+ HMAC_INFO HmacInfo;
+
+ //--------------------------------------------------------------------
+ // Zero the HMAC_INFO structure and use the SHA1 algorithm for
+ // hashing.
+
+ ZeroMemory(&HmacInfo, sizeof(HmacInfo));
+ HmacInfo.HashAlgid = CALG_SHA1;
+
+ //--------------------------------------------------------------------
+ // Acquire a handle to the default RSA cryptographic service provider.
+
+ if (!CryptAcquireContext(
+ &hProv, // handle of the CSP
+ NULL, // key container name
+ NULL, // CSP name
+ PROV_RSA_FULL, // provider type
+ CRYPT_VERIFYCONTEXT)) // no key access is requested
+ {
+ _TRACE(" Error in AcquireContext 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ //--------------------------------------------------------------------
+ // Derive a symmetric key from a hash object by performing the
+ // following steps:
+ // 1. Call CryptCreateHash to retrieve a handle to a hash object.
+ // 2. Call CryptHashData to add a text string (password) to the
+ // hash object.
+ // 3. Call CryptDeriveKey to create the symmetric key from the
+ // hashed password derived in step 2.
+ // You will use the key later to create an HMAC hash object.
+
+ if (!CryptCreateHash(
+ hProv, // handle of the CSP
+ CALG_SHA1, // hash algorithm to use
+ 0, // hash key
+ 0, // reserved
+ &hHash)) // address of hash object handle
+ {
+ _TRACE("Error in CryptCreateHash 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ if (!CryptHashData(
+ hHash, // handle of the hash object
+ (BYTE*)keyBytes.c_str(), // password to hash
+ keyBytes.size(), // number of bytes of data to add
+ 0)) // flags
+ {
+ _TRACE("Error in CryptHashData 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ // key creation based on
+ // http://mirror.leaseweb.com/NetBSD/NetBSD-release-5-0/src/dist/wpa/src/crypto/crypto_cryptoapi.c
+ struct {
+ BLOBHEADER hdr;
+ DWORD len;
+ BYTE key[1024]; // TODO might want to dynamically allocate this, Should Be Fine though
+ } key_blob;
+
+ key_blob.hdr.bType = PLAINTEXTKEYBLOB;
+ key_blob.hdr.bVersion = CUR_BLOB_VERSION;
+ key_blob.hdr.reserved = 0;
+ /*
+ * Note: RC2 is not really used, but that can be used to
+ * import HMAC keys of up to 16 byte long.
+ * CRYPT_IPSEC_HMAC_KEY flag for CryptImportKey() is needed to
+ * be able to import longer keys (HMAC-SHA1 uses 20-byte key).
+ */
+ key_blob.hdr.aiKeyAlg = CALG_RC2;
+ key_blob.len = keyBytes.size();
+ ZeroMemory(key_blob.key, sizeof(key_blob.key));
+
+ _ASSERTE(keyBytes.size() <= SIZEOF(key_blob.key));
+ CopyMemory(key_blob.key, keyBytes.c_str(), min(keyBytes.size(), SIZEOF(key_blob.key)));
+
+ if (!CryptImportKey(
+ hProv,
+ (BYTE *)&key_blob,
+ sizeof(key_blob),
+ 0,
+ CRYPT_IPSEC_HMAC_KEY,
+ &hKey))
+ {
+ _TRACE("Error in CryptImportKey 0x%08x \n", GetLastError());
+ goto ErrorExit;
+ }
+
+ //--------------------------------------------------------------------
+ // Create an HMAC by performing the following steps:
+ // 1. Call CryptCreateHash to create a hash object and retrieve
+ // a handle to it.
+ // 2. Call CryptSetHashParam to set the instance of the HMAC_INFO
+ // structure into the hash object.
+ // 3. Call CryptHashData to compute a hash of the message.
+ // 4. Call CryptGetHashParam to retrieve the size, in bytes, of
+ // the hash.
+ // 5. Call malloc to allocate memory for the hash.
+ // 6. Call CryptGetHashParam again to retrieve the HMAC hash.
+
+ if (!CryptCreateHash(
+ hProv, // handle of the CSP.
+ CALG_HMAC, // HMAC hash algorithm ID
+ hKey, // key for the hash (see above)
+ 0, // reserved
+ &hHmacHash)) // address of the hash handle
+ {
+ _TRACE("Error in CryptCreateHash 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ if (!CryptSetHashParam(
+ hHmacHash, // handle of the HMAC hash object
+ HP_HMAC_INFO, // setting an HMAC_INFO object
+ (BYTE*)&HmacInfo, // the HMAC_INFO object
+ 0)) // reserved
+ {
+ _TRACE("Error in CryptSetHashParam 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ if (!CryptHashData(
+ hHmacHash, // handle of the HMAC hash object
+ (BYTE*)data.c_str(), // message to hash
+ data.size(), // number of bytes of data to add
+ 0)) // flags
+ {
+ _TRACE("Error in CryptHashData 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ //--------------------------------------------------------------------
+ // Call CryptGetHashParam twice. Call it the first time to retrieve
+ // the size, in bytes, of the hash. Allocate memory. Then call
+ // CryptGetHashParam again to retrieve the hash value.
+
+ if (!CryptGetHashParam(
+ hHmacHash, // handle of the HMAC hash object
+ HP_HASHVAL, // query on the hash value
+ NULL, // filled on second call
+ &dwDataLen, // length, in bytes, of the hash
+ 0))
+ {
+ _TRACE("Error in CryptGetHashParam 0x%08x \n",
+ GetLastError());
+ goto ErrorExit;
+ }
+
+ pbHash = (BYTE*)malloc(dwDataLen);
+ if(NULL == pbHash)
+ {
+ _TRACE("unable to allocate memory\n");
+ goto ErrorExit;
+ }
+
+ if (!CryptGetHashParam(
+ hHmacHash, // handle of the HMAC hash object
+ HP_HASHVAL, // query on the hash value
+ pbHash, // pointer to the HMAC hash value
+ &dwDataLen, // length, in bytes, of the hash
+ 0))
+ {
+ _TRACE("Error in CryptGetHashParam 0x%08x \n", GetLastError());
+ goto ErrorExit;
+ }
+
+ for(DWORD i = 0 ; i < dwDataLen ; i++)
+ {
+ hash.push_back((char)pbHash[i]);
+ }
+
+ // Free resources.
+ // lol goto
+ErrorExit:
+ if(hHmacHash)
+ CryptDestroyHash(hHmacHash);
+ if(hKey)
+ CryptDestroyKey(hKey);
+ if(hHash)
+ CryptDestroyHash(hHash);
+ if(hProv)
+ CryptReleaseContext(hProv, 0);
+ if(pbHash)
+ free(pbHash);
+
+ return hash;
+}
+
+wstring Base64String( const string& hash )
+{
+ Base64Coder coder;
+ coder.Encode((BYTE*)hash.c_str(), hash.size());
+ wstring encoded = UTF8ToWide(coder.EncodedMessage());
+ return encoded;
+}
+
+// char2hex and urlencode from http://www.zedwood.com/article/111/cpp-urlencode-function
+// modified according to http://oauth.net/core/1.0a/#encoding_parameters
+//
+//5.1. Parameter Encoding
+//
+//All parameter names and values are escaped using the [RFC3986]
+//percent-encoding (%xx) mechanism. Characters not in the unreserved character set
+//MUST be encoded. Characters in the unreserved character set MUST NOT be encoded.
+//Hexadecimal characters in encodings MUST be upper case.
+//Text names and values MUST be encoded as UTF-8
+// octets before percent-encoding them per [RFC3629].
+//
+// unreserved = ALPHA, DIGIT, '-', '.', '_', '~'
+
+string char2hex( char dec )
+{
+ char dig1 = (dec&0xF0)>>4;
+ char dig2 = (dec&0x0F);
+ if ( 0<= dig1 && dig1<= 9) dig1+=48; //0,48 in ascii
+ if (10<= dig1 && dig1<=15) dig1+=65-10; //A,65 in ascii
+ if ( 0<= dig2 && dig2<= 9) dig2+=48;
+ if (10<= dig2 && dig2<=15) dig2+=65-10;
+
+ string r;
+ r.append( &dig1, 1);
+ r.append( &dig2, 1);
+ return r;
+}
+
+string urlencode(const string &c)
+{
+
+ string escaped;
+ int max = c.length();
+ for(int i=0; i<max; i++)
+ {
+ if ( (48 <= c[i] && c[i] <= 57) ||//0-9
+ (65 <= c[i] && c[i] <= 90) ||//ABC...XYZ
+ (97 <= c[i] && c[i] <= 122) || //abc...xyz
+ (c[i]=='~' || c[i]=='-' || c[i]=='_' || c[i]=='.')
+ )
+ {
+ escaped.append( &c[i], 1);
+ }
+ else
+ {
+ escaped.append("%");
+ escaped.append( char2hex(c[i]) );//converts char 255 to string "FF"
+ }
+ }
+ return escaped;
+}
+
+
+wstring UrlEncode( const wstring& url )
+{
+ // multiple encodings r sux
+ return UTF8ToWide(urlencode(WideToUTF8(url)));
+}
+
+wstring OAuthCreateSignature( const wstring& signatureBase, const wstring& consumerSecret, const wstring& requestTokenSecret )
+{
+ // URL encode key elements
+ wstring escapedConsumerSecret = UrlEncode(consumerSecret);
+ wstring escapedTokenSecret = UrlEncode(requestTokenSecret);
+
+ wstring key = escapedConsumerSecret + L"&" + escapedTokenSecret;
+ string keyBytes = WideToUTF8(key);
+
+ string data = WideToUTF8(signatureBase);
+ string hash = HMACSHA1(keyBytes, data);
+ wstring signature = Base64String(hash);
+
+ // URL encode the returned signature
+ signature = UrlEncode(signature);
+ return signature;
+}
+
+
+/*wstring OAuthConcatenateRequestElements( const wstring& httpMethod, wstring url, const wstring& parameters )
+{
+ wstring escapedUrl = UrlEncode(url);
+ WLOG("before OAUTHConcat, params are %s", parameters);
+ wstring escapedParameters = UrlEncode(parameters);
+ LOG(")))))))))))))))))))))))))))))))))))))))))))))))");
+ WLOG("after url encode, its %s", escapedParameters);
+ wstring ret = httpMethod + L"&" + escapedUrl + L"&" + escapedParameters;
+ return ret;
+}*/
+
+/*wstring OAuthNormalizeRequestParameters( const OAuthParameters& requestParameters )
+{
+ list<wstring> sorted;
+ for(OAuthParameters::const_iterator it = requestParameters.begin();
+ it != requestParameters.end();
+ ++it)
+ {
+ wstring param = it->first + L"=" + it->second;
+ sorted.push_back(param);
+ }
+ sorted.sort();
+
+ wstring params;
+ for(list<wstring>::iterator it = sorted.begin(); it != sorted.end(); ++it)
+ {
+ if(params.size() > 0)
+ {
+ params += L"&";
+ }
+ params += *it;
+ }
+
+ return params;
+}*/
+
+/*wstring OAuthNormalizeUrl( const wstring& url )
+{
+ wchar_t scheme[1024*4] = {};
+ wchar_t host[1024*4] = {};
+ wchar_t path[1024*4] = {};
+
+ URL_COMPONENTS components = { sizeof(URL_COMPONENTS) };
+
+ components.lpszScheme = scheme;
+ components.dwSchemeLength = SIZEOF(scheme);
+
+ components.lpszHostName = host;
+ components.dwHostNameLength = SIZEOF(host);
+
+ components.lpszUrlPath = path;
+ components.dwUrlPathLength = SIZEOF(path);
+
+ wstring normalUrl = url;
+
+ BOOL crackUrlOk = InternetCrackUrl(url.c_str(), url.size(), 0, &components);
+ _ASSERTE(crackUrlOk);
+ if(crackUrlOk)
+ {
+ wchar_t port[10] = {};
+
+ // The port number must only be included if it is non-standard
+ if((Compare(scheme, L"http", false) && components.nPort != 80) ||
+ (Compare(scheme, L"https", false) && components.nPort != 443))
+ {
+ swprintf_s(port, SIZEOF(port), L":%u", components.nPort);
+ }
+
+ // InternetCrackUrl includes ? and # elements in the path,
+ // which we need to strip off
+ wstring pathOnly = path;
+ wstring::size_type q = pathOnly.find_first_of(L"#?");
+ if(q != wstring::npos)
+ {
+ pathOnly = pathOnly.substr(0, q);
+ }
+
+ normalUrl = wstring(scheme) + L"://" + host + port + pathOnly;
+ }
+ return normalUrl;
+}*/
+
+/*OAuthParameters BuildSignedOAuthParameters( const OAuthParameters& requestParameters,
+ const wstring& url,
+ const wstring& httpMethod,
+ const wstring& consumerKey,
+ const wstring& consumerSecret,
+ const wstring& requestToken = L"",
+ const wstring& requestTokenSecret = L"",
+ const wstring& pin = L"" )
+{
+ wstring timestamp = OAuthCreateTimestamp();
+ wstring nonce = OAuthCreateNonce();
+
+ // create oauth requestParameters
+ OAuthParameters oauthParameters;
+
+ oauthParameters[L"oauth_timestamp"] = timestamp;
+ oauthParameters[L"oauth_nonce"] = nonce;
+ oauthParameters[L"oauth_version"] = L"1.0";
+ oauthParameters[L"oauth_signature_method"] = L"HMAC-SHA1";
+ oauthParameters[L"oauth_consumer_key"] = consumerKey;
+
+ // add the request token if found
+ if (!requestToken.empty())
+ {
+ oauthParameters[L"oauth_token"] = requestToken;
+ }
+
+ // add the authorization pin if found
+ if (!pin.empty())
+ {
+ oauthParameters[L"oauth_verifier"] = pin;
+ }
+
+ // create a parameter list containing both oauth and original parameters
+ // this will be used to create the parameter signature
+ OAuthParameters allParameters = requestParameters;
+ allParameters.insert(oauthParameters.begin(), oauthParameters.end());
+
+ // prepare a signature base, a carefully formatted string containing
+ // all of the necessary information needed to generate a valid signature
+ wstring normalUrl = OAuthNormalizeUrl(url);
+ wstring normalizedParameters = OAuthNormalizeRequestParameters(allParameters);
+ wstring signatureBase = OAuthConcatenateRequestElements(httpMethod, normalUrl, normalizedParameters);
+
+ // obtain a signature and add it to header requestParameters
+ wstring signature = OAuthCreateSignature(signatureBase, consumerSecret, requestTokenSecret);
+ oauthParameters[L"oauth_signature"] = signature;
+
+ return oauthParameters;
+}*/
+
+/*wstring OAuthWebRequestSubmit(
+ const OAuthParameters& parameters,
+ const wstring& url
+ )
+{
+ _TRACE("OAuthWebRequestSubmit(%s)", url.c_str());
+
+ wstring oauthHeader = L"Authorization: OAuth ";
+
+ for(OAuthParameters::const_iterator it = parameters.begin();
+ it != parameters.end();
+ ++it)
+ {
+ _TRACE("%s = %s", it->first.c_str(), it->second.c_str());
+
+ if(it != parameters.begin())
+ {
+ oauthHeader += L",";
+ }
+
+ wstring pair;
+ pair += it->first + L"=\"" + it->second + L"\"";
+ oauthHeader += pair;
+ }
+ oauthHeader += L"\r\n";
+
+ _TRACE("%s", oauthHeader.c_str());
+
+ wchar_t host[1024*4] = {};
+ wchar_t path[1024*4] = {};
+
+ URL_COMPONENTS components = { sizeof(URL_COMPONENTS) };
+
+ components.lpszHostName = host;
+ components.dwHostNameLength = SIZEOF(host);
+
+ components.lpszUrlPath = path;
+ components.dwUrlPathLength = SIZEOF(path);
+
+ wstring normalUrl = url;
+
+ BOOL crackUrlOk = InternetCrackUrl(url.c_str(), url.size(), 0, &components);
+ _ASSERTE(crackUrlOk);
+
+ wstring result;
+
+ // TODO you'd probably want to InternetOpen only once at app initialization
+ HINTERNET hINet = InternetOpen(L"tc2/1.0",
+ INTERNET_OPEN_TYPE_PRECONFIG,
+ NULL,
+ NULL,
+ 0 );
+ _ASSERTE( hINet != NULL );
+ if ( hINet != NULL )
+ {
+ // TODO add support for HTTPS requests
+ HINTERNET hConnection = InternetConnect(
+ hINet,
+ host,
+ components.nPort,
+ NULL,
+ NULL,
+ INTERNET_SERVICE_HTTP,
+ 0, 0 );
+ _ASSERTE(hConnection != NULL);
+ if ( hConnection != NULL)
+ {
+ // TODO add support for handling POST requests
+ HINTERNET hData = HttpOpenRequest( hConnection,
+ L"GET",
+ path,
+ NULL,
+ NULL,
+ NULL,
+ INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_RELOAD,
+ 0 );
+ _ASSERTE(hData != NULL);
+ if ( hData != NULL )
+ {
+ BOOL addHeadersOk = HttpAddRequestHeaders(hData,
+ oauthHeader.c_str(),
+ oauthHeader.size(),
+ 0);
+ _ASSERTE(addHeadersOk);
+
+ BOOL sendOk = HttpSendRequest( hData, NULL, 0, NULL, 0);
+ _ASSERTE(sendOk);
+
+ // TODO dynamically allocate return buffer
+ BYTE buffer[1024*32] = {};
+ DWORD dwRead = 0;
+ while(
+ InternetReadFile( hData, buffer, SIZEOF(buffer) - 1, &dwRead ) &&
+ dwRead > 0
+ )
+ {
+ buffer[dwRead] = 0;
+ result += UTF8ToWide((char*)buffer);
+ }
+
+ _TRACE("%s", result.c_str());
+
+ InternetCloseHandle(hData);
+ }
+ InternetCloseHandle(hConnection);
+ }
+ InternetCloseHandle(hINet);
+ }
+
+ return result;
+}*/
+
+// OAuthWebRequest used for all OAuth related queries
+//
+// consumerKey and consumerSecret - must be provided for every call, they identify the application
+// oauthToken and oauthTokenSecret - need to be provided for every call, except for the first token request before authorizing
+// pin - only used during authorization, when the user enters the PIN they received from the twitter website
+/*wstring OAuthWebRequestSubmit(
+ const wstring& url,
+ const wstring& httpMethod,
+ const wstring& consumerKey,
+ const wstring& consumerSecret,
+ const wstring& oauthToken,
+ const wstring& oauthTokenSecret,
+ const wstring& pin,
+ TwitterProto *proto
+ )
+{
+ wstring query = UrlGetQuery(url);
+ //Show
+ OAuthParameters originalParameters = ParseQueryString(query);
+
+ OAuthParameters oauthSignedParameters = BuildSignedOAuthParameters(
+ originalParameters,
+ url,
+ httpMethod,
+ consumerKey, consumerSecret,
+ oauthToken, oauthTokenSecret,
+ pin );
+ return OAuthWebRequestSubmit(oauthSignedParameters, url);
+}*/
+/*
+int testfunc(int argc, _TCHAR* argv[])
+{
+ CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
+ srand(_time32(NULL));
+
+ string savedAccessTokenString;
+
+ ifstream inFile;
+ inFile.open(AuthFileName.c_str());
+ inFile >> savedAccessTokenString;
+ inFile.close();
+
+ OAuthParameters savedParameters = ParseQueryString(UTF8ToWide(savedAccessTokenString));
+
+ wstring oauthAccessToken = savedParameters[L"oauth_token"];
+ wstring oauthAccessTokenSecret = savedParameters[L"oauth_token_secret"];
+ wstring screenName = savedParameters[L"screen_name"];
+
+ if( oauthAccessToken.empty() || oauthAccessTokenSecret.empty() )
+ {
+ // Overall OAuth flow based on
+ // Professional Twitter Development: With Examples in .NET 3.5 by Daniel Crenna
+
+ wstring requestToken = OAuthWebRequestSubmit(RequestUrl, L"GET", ConsumerKey, ConsumerSecret);
+
+ OAuthParameters response = ParseQueryString(requestToken);
+ wstring oauthToken = response[L"oauth_token"];
+ wstring oauthTokenSecret = response[L"oauth_token_secret"];
+ if(!oauthToken.empty())
+ {
+ wchar_t buf[1024] = {};
+ swprintf_s(buf, SIZEOF(buf), AuthorizeUrl.c_str(), oauthToken.c_str());
+
+ wprintf(L"Launching %s\r\n", buf);
+ ShellExecute(NULL, L"open", buf, NULL, NULL, SW_SHOWNORMAL);
+ }
+
+ // TODO ok, using debug-only trace function to prompt the user isn't a great idea
+ wchar_t pin[1024] = {};
+ wprintf(L"\r\n");
+ wprintf(L"Enter the PIN you receive after authorizing this program in your web browser: ");
+ _getws_s(pin, SIZEOF(pin));
+
+ // exchange the request token for an access token
+ wstring accessTokenString = OAuthWebRequestSubmit(AccessUrl, L"GET", ConsumerKey, ConsumerSecret,
+ oauthToken, oauthTokenSecret, pin);
+
+ OAuthParameters accessTokenParameters = ParseQueryString(accessTokenString);
+ oauthAccessToken = accessTokenParameters[L"oauth_token"];
+ oauthAccessTokenSecret = accessTokenParameters[L"oauth_token_secret"];
+ screenName = accessTokenParameters[L"screen_name"];
+
+ ofstream outFile;
+ outFile.open(AuthFileName.c_str(), ios_base::out | ios_base::trunc);
+ outFile << WideToUTF8(accessTokenString);
+ outFile.close();
+ }
+
+ wprintf(L"\r\n");
+ wprintf(L"Authorized screen_name: %s\r\n", screenName.c_str());
+ wprintf(L"Your oauth_token is: %s\r\n", oauthAccessToken.c_str());
+ wprintf(L"Your oauth_token_secret is: %s\r\n", oauthAccessTokenSecret.c_str());
+ wprintf(L"\r\n");
+
+ wprintf(L"Press enter to request your time line...\r\n");
+ getchar();
+
+ // access a protected API call on Twitter using our access token
+ wstring userTimeline = OAuthWebRequestSubmit(UserTimelineUrl, L"GET", ConsumerKey, ConsumerSecret,
+ oauthAccessToken, oauthAccessTokenSecret);
+
+ wprintf(L"\r\nYour timeline:\r\n%s\r\n", userTimeline.c_str());
+
+ wprintf(L"Press enter to exit...\r\n");
+ getchar();
+
+ return 0;
+}*/
+
diff --git a/protocols/Twitter/src/tc2.h b/protocols/Twitter/src/tc2.h
new file mode 100644
index 0000000000..bbf3884fd6
--- /dev/null
+++ b/protocols/Twitter/src/tc2.h
@@ -0,0 +1,83 @@
+/* aww yeah creating some crazy .h file
+
+*/
+
+#ifndef TC2_H
+#define TC2_H
+
+//#include "proto.h"
+#include "stdafx.h"
+#include "twitter.h"
+
+using namespace std;
+
+//typedef std::map<wstring, wstring> OAuthParameters;
+
+wstring getHostName();
+wstring getAccessUrl();
+wstring getAuthorizeUrl();
+wstring getRequestUrl();
+wstring getUserTimelineUrl();
+wstring getConsumerKey();
+wstring getConsumerSecret();
+
+//wstring UrlGetQuery( const wstring& url );
+
+OAuthParameters ParseQueryString( const wstring& url );
+
+wstring OAuthCreateNonce();
+
+wstring OAuthCreateTimestamp();
+
+string HMACSHA1( const string& keyBytes, const string& data );
+
+wstring Base64String( const string& hash );
+
+string char2hex( char dec );
+
+string urlencode(const string &c);
+
+wstring UrlEncode( const wstring& url );
+
+wstring OAuthCreateSignature( const wstring& signatureBase, const wstring& consumerSecret, const wstring& requestTokenSecret );
+
+wstring OAuthConcatenateRequestElements( const wstring& httpMethod, wstring url, const wstring& parameters );
+
+wstring OAuthNormalizeRequestParameters( const OAuthParameters& requestParameters );
+
+//wstring OAuthNormalizeUrl( const wstring& url );
+
+/*OAuthParameters BuildSignedOAuthParameters( const OAuthParameters& requestParameters,
+ const wstring& url,
+ const wstring& httpMethod,
+ const wstring& consumerKey,
+ const wstring& consumerSecret,
+ const wstring& requestToken,
+ const wstring& requestTokenSecret,
+ const wstring& pin );*/
+
+/*wstring OAuthWebRequestSubmit(
+ const OAuthParameters& parameters,
+ const wstring& url
+ );*/
+
+// OAuthWebRequest used for all OAuth related queries
+//
+// consumerKey and consumerSecret - must be provided for every call, they identify the application
+// oauthToken and oauthTokenSecret - need to be provided for every call, except for the first token request before authorizing
+// pin - only used during authorization, when the user enters the PIN they received from the twitter website
+/*wstring OAuthWebRequestSubmit(
+ const wstring& url,
+ const wstring& httpMethod,
+ const wstring& consumerKey,
+ const wstring& consumerSecret,
+ const wstring& oauthToken = L"",
+ const wstring& oauthTokenSecret = L"",
+ const wstring& pin = L""
+ );*/
+
+
+
+int testfunc(int argc, _TCHAR* argv[]);
+
+#endif
diff --git a/protocols/Twitter/src/theme.cpp b/protocols/Twitter/src/theme.cpp
new file mode 100644
index 0000000000..11dea3cb44
--- /dev/null
+++ b/protocols/Twitter/src/theme.cpp
@@ -0,0 +1,179 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "theme.h"
+#include "proto.h"
+
+extern OBJLIST<TwitterProto> g_Instances;
+
+struct
+{
+ const char* name;
+ const char* descr;
+ int defIconID;
+ const char* section;
+}
+static const icons[] =
+{
+ { "twitter", "Twitter Icon", IDI_TWITTER },
+ { "tweet", "Tweet", IDI_TWITTER },
+ { "reply", "Reply to Tweet", IDI_TWITTER },
+
+ { "homepage", "Visit Homepage", 0, "core_main_2" },
+};
+
+static HANDLE hIconLibItem[SIZEOF(icons)];
+
+// TODO: uninit
+void InitIcons(void)
+{
+ TCHAR szFile[MAX_PATH];
+ GetModuleFileName(g_hInstance, szFile, SIZEOF(szFile));
+
+ char setting_name[100];
+ char section_name[100];
+
+ SKINICONDESC sid = {0};
+ sid.cbSize = sizeof(SKINICONDESC);
+ sid.ptszDefaultFile = szFile;
+ sid.cx = sid.cy = 16;
+ sid.pszName = setting_name;
+ sid.pszSection = section_name;
+ sid.flags = SIDF_PATH_TCHAR;
+
+ for (int i=0; i<SIZEOF(icons); i++)
+ {
+ if(icons[i].defIconID)
+ {
+ mir_snprintf(setting_name,sizeof(setting_name),"%s_%s","Twitter",icons[i].name);
+
+ if (icons[i].section)
+ {
+ mir_snprintf(section_name,sizeof(section_name),"%s/%s/%s",LPGEN("Protocols"),
+ LPGEN("Twitter"), icons[i].section);
+ }
+ else
+ {
+ mir_snprintf(section_name,sizeof(section_name),"%s/%s",LPGEN("Protocols"),
+ LPGEN("Twitter"));
+ }
+
+ sid.pszDescription = (char*)icons[i].descr;
+ sid.iDefaultIndex = -icons[i].defIconID;
+ hIconLibItem[i] = Skin_AddIcon(&sid);
+ }
+ else // External icons
+ {
+ hIconLibItem[i] = (HANDLE)CallService(MS_SKIN2_GETICONHANDLE,0,
+ (LPARAM)icons[i].section);
+ }
+ }
+}
+
+HANDLE GetIconHandle(const char* name)
+{
+ for(size_t i=0; i<SIZEOF(icons); i++)
+ {
+ if(strcmp(icons[i].name,name) == 0)
+ return hIconLibItem[i];
+ }
+ return 0;
+}
+
+
+
+// Contact List menu stuff
+static HANDLE g_hMenuItems[2];
+static HANDLE g_hMenuEvts[3];
+
+// Helper functions
+static TwitterProto * GetInstanceByHContact(HANDLE hContact)
+{
+ char *proto = reinterpret_cast<char*>( CallService(MS_PROTO_GETCONTACTBASEPROTO,
+ reinterpret_cast<WPARAM>(hContact),0));
+ if(!proto)
+ return 0;
+
+ for(int i=0; i<g_Instances.getCount(); i++)
+ if(!strcmp(proto,g_Instances[i].m_szModuleName))
+ return &g_Instances[i];
+
+ return 0;
+}
+
+template<int (__cdecl TwitterProto::*Fcn)(WPARAM,LPARAM)>
+INT_PTR GlobalService(WPARAM wParam,LPARAM lParam)
+{
+ TwitterProto *proto = GetInstanceByHContact(reinterpret_cast<HANDLE>(wParam));
+ return proto ? (proto->*Fcn)(wParam,lParam) : 0;
+}
+
+static int PrebuildContactMenu(WPARAM wParam,LPARAM lParam)
+{
+ ShowContactMenus(false);
+
+ TwitterProto *proto = GetInstanceByHContact(reinterpret_cast<HANDLE>(wParam));
+ return proto ? proto->OnPrebuildContactMenu(wParam,lParam) : 0;
+}
+
+void InitContactMenus()
+{
+ g_hMenuEvts[0] = HookEvent(ME_CLIST_PREBUILDCONTACTMENU,
+ PrebuildContactMenu);
+
+ CLISTMENUITEM mi = {sizeof(mi)};
+ mi.flags = CMIF_NOTOFFLINE | CMIF_ICONFROMICOLIB;
+
+ mi.position=-2000006000;
+ mi.icolibItem = GetIconHandle("reply");
+ mi.pszName = LPGEN("Reply...");
+ mi.pszService = "Twitter/ReplyToTweet";
+ g_hMenuEvts[1] = CreateServiceFunction(mi.pszService, GlobalService<&TwitterProto::ReplyToTweet>);
+ g_hMenuItems[0] = Menu_AddContactMenuItem(&mi);
+
+ mi.position=-2000006000;
+ mi.icolibItem = GetIconHandle("homepage");
+ mi.pszName = LPGEN("Visit Homepage");
+ mi.pszService = "Twitter/VisitHomepage";
+ g_hMenuEvts[2] = CreateServiceFunction(mi.pszService,
+ GlobalService<&TwitterProto::VisitHomepage>);
+ g_hMenuItems[1] = Menu_AddContactMenuItem(&mi);
+}
+
+void UninitContactMenus()
+{
+ for(size_t i=0; i<SIZEOF(g_hMenuItems); i++)
+ CallService(MS_CLIST_REMOVECONTACTMENUITEM,(WPARAM)g_hMenuItems[i],0);
+
+ UnhookEvent(g_hMenuEvts[0]);
+ for(size_t i=1; i<SIZEOF(g_hMenuEvts); i++)
+ DestroyServiceFunction(g_hMenuEvts[i]);
+}
+
+void ShowContactMenus(bool show)
+{
+ for(size_t i=0; i<SIZEOF(g_hMenuItems); i++)
+ {
+ CLISTMENUITEM item = { sizeof(item) };
+ item.flags = CMIM_FLAGS | CMIF_NOTOFFLINE;
+ if(!show)
+ item.flags |= CMIF_HIDDEN;
+
+ CallService(MS_CLIST_MODIFYMENUITEM,reinterpret_cast<WPARAM>(g_hMenuItems[i]),
+ reinterpret_cast<LPARAM>(&item));
+ }
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/theme.h b/protocols/Twitter/src/theme.h
new file mode 100644
index 0000000000..fd331236fe
--- /dev/null
+++ b/protocols/Twitter/src/theme.h
@@ -0,0 +1,27 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#include "common.h"
+
+void InitIcons(void);
+HANDLE GetIconHandle(const char *name);
+
+void InitContactMenus(void);
+void UninitContactMenus(void);
+void ShowContactMenus(bool show); \ No newline at end of file
diff --git a/protocols/Twitter/src/tinyjson.hpp b/protocols/Twitter/src/tinyjson.hpp
new file mode 100644
index 0000000000..240a3503c5
--- /dev/null
+++ b/protocols/Twitter/src/tinyjson.hpp
@@ -0,0 +1,586 @@
+/*
+ * TinyJson 1.3.0
+ * A Minimalistic JSON Reader Based On Boost.Spirit, Boost.Any, and Boost.Smart_Ptr.
+ *
+ * Copyright (c) 2008 Thomas Jansen (thomas@beef.de)
+ *
+ * Distributed under the Boost Software License, Version 1.0. (See accompanying
+ * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+ *
+ * See http://blog.beef.de/projects/tinyjson/ for documentation.
+ *
+ * (view source with tab-size = 3)
+ *
+ * 16 Mar 2009 - allow root of JSON to be array (Jim Porter)
+ * 29 Mar 2008 - use strict_real_p for number parsing, small cleanup (Thomas Jansen)
+ * 26 Mar 2008 - made json::grammar a template (Boris Schaeling)
+ * 20 Mar 2008 - optimized by using boost::shared_ptr (Thomas Jansen)
+ * 29 Jan 2008 - Small bugfixes (Thomas Jansen)
+ * 04 Jan 2008 - Released to the public (Thomas Jansen)
+ * 13 Nov 2007 - initial release (Thomas Jansen) *
+ *
+ * 29 Mar 2008
+ */
+
+
+#ifndef TINYJSON_HPP
+#define TINYJSON_HPP
+
+#include <boost/shared_ptr.hpp>
+#include <boost/any.hpp>
+#include <boost/spirit/core.hpp>
+#include <boost/spirit/utility/loops.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <stack>
+#include <utility>
+#include <deque>
+#include <map>
+
+
+namespace json
+{
+ boost::spirit::int_parser<long long> const
+ longlong_p = boost::spirit::int_parser<long long>();
+
+ // ==========================================================================================================
+ // === U N I C O D E _ C O N V E R T ===
+ // ==========================================================================================================
+
+ template< typename Char >
+ struct unicodecvt
+ {
+ static std::basic_string< Char > convert(int iUnicode)
+ {
+ return std::basic_string< Char >(1, static_cast< Char >(iUnicode));
+ }
+ };
+
+
+ // ---[ TEMPLATE SPECIALIZATION FOR CHAR ]--------------------------------------------------------------------
+
+ template<>
+ struct unicodecvt< char >
+ {
+ static std::string convert(int iUnicode)
+ {
+ std::string strString;
+
+ if(iUnicode < 0x0080)
+ {
+ // character 0x0000 - 0x007f...
+
+ strString.push_back(0x00 | ((iUnicode & 0x007f) >> 0));
+ }
+ else if(iUnicode < 0x0800)
+ {
+ // character 0x0080 - 0x07ff...
+
+ strString.push_back(0xc0 | ((iUnicode & 0x07c0) >> 6));
+ strString.push_back(0x80 | ((iUnicode & 0x003f) >> 0));
+ }
+ else
+ {
+ // character 0x0800 - 0xffff...
+
+ strString.push_back(0xe0 | ((iUnicode & 0x00f000) >> 12));
+ strString.push_back(0x80 | ((iUnicode & 0x000fc0) >> 6));
+ strString.push_back(0x80 | ((iUnicode & 0x00003f) >> 0));
+ }
+
+ return strString;
+ }
+ };
+
+
+ // ==========================================================================================================
+ // === T H E J S O N G R A M M A R ===
+ // ==========================================================================================================
+
+ template< typename Char >
+ class grammar : public boost::spirit::grammar< grammar< Char > >
+ {
+ public:
+
+ // ---[ TYPEDEFINITIONS ]---------------------------------------------------------------------------------
+
+ typedef boost::shared_ptr< boost::any > variant; // pointer to a shared variant
+
+ typedef std::stack< variant > stack; // a stack of json variants
+ typedef std::pair< std::basic_string< Char >, variant > pair; // a pair as it appears in json
+
+ typedef std::deque< variant > array; // an array of json variants
+ typedef std::map< std::basic_string< Char >, variant > object; // an object with json pairs
+
+ protected:
+
+ // ---[ SEMANTIC ACTION: PUSH A STRING ON THE STACK (AND ENCODE AS UTF-8) ]-------------------------------
+
+ struct push_string
+ {
+ stack & m_stack;
+ push_string(stack & stack) : m_stack(stack) { }
+
+ template <typename Iterator>
+ void operator() (Iterator szStart, Iterator szEnd) const
+ {
+ // 1: skip the quotes...
+
+ ++szStart;
+ --szEnd;
+
+ // 2: traverse through the original string and check for escape codes..
+
+ std::basic_string< typename Iterator::value_type > strString;
+
+ while(szStart < szEnd)
+ {
+ // 2.1: if it's no escape code, just append to the resulting string...
+
+ if(*szStart != static_cast< typename Iterator::value_type >('\\'))
+ {
+ // 2.1.1: append the character...
+
+ strString.push_back(*szStart);
+ }
+ else
+ {
+ // 2.1.2: otherwise, check the escape code...
+
+ ++szStart;
+
+ switch(*szStart)
+ {
+ default:
+
+ strString.push_back(*szStart);
+ break;
+
+ case 'b':
+
+ strString.push_back(static_cast< typename Iterator::value_type >('\b'));
+ break;
+
+ case 'f':
+
+ strString.push_back(static_cast< typename Iterator::value_type >('\f'));
+ break;
+
+ case 'n':
+
+ strString.push_back(static_cast< typename Iterator::value_type >('\n'));
+ break;
+
+ case 'r':
+
+ strString.push_back(static_cast< typename Iterator::value_type >('\r'));
+ break;
+
+ case 't':
+
+ strString.push_back(static_cast< typename Iterator::value_type >('\t'));
+ break;
+
+ case 'u':
+ {
+ // 2.1.2.1: convert the following hex value into an int...
+
+ int iUnicode;
+ std::basic_istringstream< Char >(std::basic_string< typename Iterator::value_type >(&szStart[1], 4)) >> std::hex >> iUnicode;
+
+ szStart += 4;
+
+ // 2.1.2.2: append the unicode int...
+
+ strString.append(unicodecvt< typename Iterator::value_type >::convert(iUnicode));
+ }
+ }
+ }
+
+ // 2.2: go on with the next character...
+
+ ++szStart;
+ }
+
+ // 3: finally, push the string on the stack...
+
+ m_stack.push(variant(new boost::any(strString)));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: PUSH A REAL ON THE STACK ]-------------------------------------------------------
+
+ struct push_double
+ {
+ stack & m_stack;
+ push_double(stack & stack) : m_stack(stack) { }
+
+ void operator() (double dValue) const
+ {
+ m_stack.push(variant(new boost::any(dValue)));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: PUSH AN INT ON THE STACK ]-------------------------------------------------------
+
+ struct push_int
+ {
+ stack & m_stack;
+ push_int(stack & stack) : m_stack(stack) { }
+
+ void operator() (long long iValue) const
+ {
+ m_stack.push(variant(new boost::any(iValue)));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: PUSH A BOOLEAN ON THE STACK ]----------------------------------------------------
+
+ struct push_boolean
+ {
+ stack & m_stack;
+ push_boolean(stack & stack) : m_stack(stack) { }
+
+ template <typename Iterator>
+ void operator() (Iterator szStart, Iterator /* szEnd */ ) const
+ {
+ // 1: push a boolean that is "true" if the string starts with 't' and "false" otherwise...
+
+ m_stack.push(variant(new boost::any(*szStart == static_cast< typename Iterator::value_type >('t'))));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: PUSH A NULL VALUE ON THE STACK ]-------------------------------------------------
+
+ struct push_null
+ {
+ stack & m_stack;
+ push_null(stack & stack) : m_stack(stack) { }
+
+ template <typename Iterator>
+ void operator() (Iterator /* szStart */ , Iterator /* szEnd */ ) const
+ {
+ m_stack.push(variant(new boost::any()));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: CREATE A "JSON PAIR" ON THE STACK ]----------------------------------------------
+
+ struct create_pair
+ {
+ stack & m_stack;
+ create_pair(stack & stack) : m_stack(stack) { }
+
+ template <typename Iterator>
+ void operator() (Iterator /* szStart */, Iterator /* szEnd */ ) const
+ {
+ // 1: get the variant from the stack...
+
+ variant var = m_stack.top();
+ m_stack.pop();
+
+ // 2: get the name from the stack...
+
+ std::basic_string< typename Iterator::value_type > strName;
+
+ try
+ {
+ strName = boost::any_cast< std::basic_string< typename Iterator::value_type > >(*m_stack.top());
+ }
+ catch(boost::bad_any_cast &) { /* NOTHING */ }
+
+ m_stack.pop();
+
+ // 3: push a pair of both on the stack...
+
+ m_stack.push(variant(new boost::any(pair(strName, var))));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: BEGIN AN ARRAY ]-----------------------------------------------------------------
+
+ class array_delimiter { /* EMPTY CLASS */ };
+
+ struct begin_array
+ {
+ stack & m_stack;
+ begin_array(stack & stack) : m_stack(stack) { }
+
+ template <typename Iterator>
+ void operator() (Iterator /* cCharacter */) const
+ {
+ m_stack.push(variant(new boost::any(array_delimiter())));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: CREATE AN ARRAY FROM THE VALUES ON THE STACK ]-----------------------------------
+
+ struct end_array
+ {
+ stack & m_stack;
+ end_array(stack & stack) : m_stack(stack) { }
+
+ // - -[ functional operator ] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ template <typename Iterator>
+ void operator() (Iterator /* cCharacter */) const
+ {
+ // 1: create an array object and push everything in it, that's on the stack...
+
+ variant varArray(new boost::any(array()));
+
+ while(!m_stack.empty())
+ {
+ // 1.1: get the top most variant of the stack...
+
+ variant var = m_stack.top();
+ m_stack.pop();
+
+ // 1.2: is it the end of the array? if yes => break the loop...
+
+ if(boost::any_cast< array_delimiter >(var.get()) != NULL)
+ {
+ break;
+ }
+
+ // 1.3: otherwise, add to the array...
+
+ boost::any_cast< array >(varArray.get())->push_front(var);
+ }
+
+ // 2: finally, push the array at the end of the stack...
+
+ m_stack.push(varArray);
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: BEGIN AN OBJECT ]----------------------------------------------------------------
+
+ class object_delimiter { /* EMPTY CLASS */ };
+
+ struct begin_object
+ {
+ stack & m_stack;
+ begin_object(stack & stack) : m_stack(stack) { }
+
+ template <typename Iterator>
+ void operator() (Iterator /* cCharacter */) const
+ {
+ m_stack.push(variant(new boost::any(object_delimiter())));
+ }
+ };
+
+
+ // ---[ SEMANTIC ACTION: CREATE AN OBJECT FROM THE VALUES ON THE STACK ]----------------------------------
+
+ struct end_object
+ {
+ stack & m_stack;
+ end_object(stack & stack) : m_stack(stack) { }
+
+ template <typename Iterator>
+ void operator() (Iterator /* cCharacter */) const
+ {
+ // 1: create an array object and push everything in it, that's on the stack...
+
+ variant varObject(new boost::any(object()));
+
+ while(!m_stack.empty())
+ {
+ // 1.1: get the top most variant of the stack...
+
+ variant var = m_stack.top();
+ m_stack.pop();
+
+ // 1.2: is it the end of the array? if yes => break the loop...
+
+ if(boost::any_cast< object_delimiter >(var.get()) != NULL)
+ {
+ break;
+ }
+
+ // 1.3: if this is not a pair, we have a problem...
+
+ pair * pPair = boost::any_cast< pair >(var.get());
+ if(!pPair)
+ {
+ /* BIG PROBLEM!! */
+
+ continue;
+ }
+
+ // 1.4: set the child of this object...
+
+ boost::any_cast< object >(varObject.get())->insert(std::make_pair(pPair->first, pPair->second));
+ }
+
+ // 2: finally, push the array at the end of the stack...
+
+ m_stack.push(varObject);
+ }
+ };
+
+ public:
+
+ stack & m_stack;
+ grammar(stack & stack) : m_stack(stack) { }
+
+ // ---[ THE ACTUAL GRAMMAR DEFINITION ]-------------------------------------------------------------------
+
+ template <typename SCANNER>
+ class definition
+ {
+ boost::spirit::rule< SCANNER > m_start;
+ boost::spirit::rule< SCANNER > m_object;
+ boost::spirit::rule< SCANNER > m_array;
+ boost::spirit::rule< SCANNER > m_pair;
+ boost::spirit::rule< SCANNER > m_value;
+ boost::spirit::rule< SCANNER > m_string;
+ boost::spirit::rule< SCANNER > m_number;
+ boost::spirit::rule< SCANNER > m_boolean;
+ boost::spirit::rule< SCANNER > m_null;
+
+ public:
+
+ boost::spirit::rule< SCANNER > const & start() const { return m_start; }
+
+ // - -[ create the definition ] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+ definition(grammar const & self)
+ {
+ using namespace boost::spirit;
+
+ // 0: JSON can either be an object or an array
+
+ m_start
+ = m_object
+ | m_array;
+
+ // 1: an object is an unordered set of pairs (seperated by commas)...
+
+ m_object
+ = ch_p('{') [ begin_object(self.m_stack) ] >>
+ !(m_pair >> *(ch_p(',') >> m_pair)) >>
+ ch_p('}') [ end_object (self.m_stack) ];
+
+ // 2: an array is an ordered collection of values (seperated by commas)...
+
+ m_array
+ = ch_p('[') [ begin_array(self.m_stack) ] >>
+ !(m_value >> *(ch_p(',') >> m_value)) >>
+ ch_p(']') [ end_array (self.m_stack) ];
+
+ // 3: a pair is given by a name and a value...
+
+ m_pair
+ = ( m_string >> ch_p(':') >> m_value )
+ [ create_pair(self.m_stack) ]
+ ;
+
+ // 4: a value can be a string in double quotes, a number, a boolean, an object or an array.
+
+ m_value
+ = m_string
+ | m_number
+ | m_object
+ | m_array
+ | m_boolean
+ | m_null
+ ;
+
+ // 5: a string is a collection of zero or more unicode characters, wrapped in double quotes...
+
+ m_string
+ = lexeme_d
+ [
+ ( ch_p('"') >> *(
+ ( (anychar_p - (ch_p('"') | ch_p('\\')))
+ | ch_p('\\') >>
+ ( ch_p('\"')
+ | ch_p('\\')
+ | ch_p('/')
+ | ch_p('b')
+ | ch_p('f')
+ | ch_p('n')
+ | ch_p('r')
+ | ch_p('t')
+ | (ch_p('u') >> repeat_p(4)[ xdigit_p ])
+ )
+ )) >> ch_p('"')
+ )
+ [ push_string(self.m_stack) ]
+ ]
+ ;
+
+ // 6: a number is very much like a C or java number...
+
+ m_number
+ = strict_real_p [ push_double(self.m_stack) ]
+ | longlong_p [ push_int (self.m_stack) ]
+ ;
+
+ // 7: a boolean can be "true" or "false"...
+
+ m_boolean
+ = ( str_p("true")
+ | str_p("false")
+ )
+ [ push_boolean(self.m_stack) ]
+ ;
+
+ // 8: finally, a value also can be a 'null', i.e. an empty item...
+
+ m_null
+ = str_p("null")
+ [ push_null(self.m_stack) ]
+ ;
+ }
+ };
+ };
+
+
+ // ==========================================================================================================
+ // === T H E F I N A L P A R S I N G R O U T I N E ===
+ // ==========================================================================================================
+
+ template <typename Iterator>
+ typename json::grammar< typename Iterator::value_type >::variant parse(Iterator const & szFirst, Iterator const & szEnd)
+ {
+ // 1: parse the input...
+
+ json::grammar< typename Iterator::value_type >::stack st;
+ json::grammar< typename Iterator::value_type > gr(st);
+
+ boost::spirit::parse_info<Iterator> pi = boost::spirit::parse(szFirst, szEnd, gr, boost::spirit::space_p);
+
+ // 2: skip any spaces at the end of the parsed section...
+
+ while((pi.stop != szEnd) && (*pi.stop == static_cast< typename Iterator::value_type >(' ')))
+ {
+ ++pi.stop;
+ }
+
+ // 3: if the input's end wasn't reached or if there is more than one object on the stack => cancel...
+
+ if((pi.stop != szEnd) || (st.size() != 1))
+ {
+ return json::grammar< typename Iterator::value_type >::variant(new boost::any());
+ }
+
+ // 4: otherwise, return the result...
+
+ return st.top();
+ }
+};
+
+
+#endif // TINYJSON_HPP
diff --git a/protocols/Twitter/src/twitter.cpp b/protocols/Twitter/src/twitter.cpp
new file mode 100644
index 0000000000..b9a6c7242b
--- /dev/null
+++ b/protocols/Twitter/src/twitter.cpp
@@ -0,0 +1,477 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "twitter.h"
+//#include "tc2.h"
+
+#include <windows.h>
+#include <cstring>
+#include <sstream>
+#include <ctime>
+
+#include "tinyjson.hpp"
+#include <boost/lexical_cast.hpp>
+
+//#include "boost/spirit/include/classic_core.hpp"
+//#include "boost/spirit/include/classic_loops.hpp"
+
+typedef json::grammar<char> js;
+
+// utility functions
+
+template <typename T>
+static T cast_and_decode(boost::any &a,bool allow_null)
+{
+ if(allow_null && a.type() == typeid(void))
+ return T();
+ return boost::any_cast<T>(a);
+}
+
+template <>
+static std::string cast_and_decode<std::string>(boost::any &a,bool allow_null)
+{
+ if(allow_null && a.type() == typeid(void))
+ return std::string();
+ std::string s = boost::any_cast<std::string>(a);
+
+ // Twitter *only* encodes < and >, so decode them
+ size_t off;
+ while( (off = s.find("&lt;")) != std::string::npos)
+ s.replace(off,4,"<");
+ while( (off = s.find("&gt;")) != std::string::npos)
+ s.replace(off,4,">");
+
+ return s;
+}
+
+template <typename T>
+static T retrieve(const js::object &o,const std::string &key,bool allow_null = false)
+{
+ using boost::any_cast;
+
+ js::object::const_iterator i = o.find(key);
+ if(i == o.end())
+ throw std::exception( ("unable to retrieve key '"+key+"'").c_str());
+ try
+ {
+ return cast_and_decode<T>(*i->second,allow_null);
+ }
+ catch(const boost::bad_any_cast &)
+ {
+ throw std::exception( ("unable to cast key '"+key+"' to target type").c_str());
+ }
+}
+
+
+
+twitter::twitter() : base_url_("https://twitter.com/")
+{}
+
+bool twitter::set_credentials(const std::string &username, const std::wstring &consumerKey, const std::wstring &consumerSecret,
+ const std::wstring &oauthAccessToken, const std::wstring &oauthAccessTokenSecret, const std::wstring &pin, bool test)
+{
+ username_ = username;
+ consumerKey_ = consumerKey;
+ consumerSecret_ = consumerSecret;
+ oauthAccessToken_ = oauthAccessToken;
+ oauthAccessTokenSecret_ = oauthAccessTokenSecret;
+ pin_ = pin;
+
+ if(test)
+ return slurp(base_url_+"account/verify_credentials.json",http::get).code == 200;
+ else
+ return true;
+}
+
+http::response twitter::request_token() {
+ return slurp(base_url_+"oauth/request_token",http::get);
+}
+
+http::response twitter::request_access_tokens() {
+ return slurp(base_url_+"oauth/access_token", http::get);
+}
+
+void twitter::set_base_url(const std::string &base_url)
+{
+ base_url_ = base_url;
+}
+
+const std::string & twitter::get_username() const
+{
+ return username_;
+}
+
+const std::string & twitter::get_base_url() const
+{
+ return base_url_;
+}
+
+std::vector<twitter_user> twitter::get_friends()
+{
+ std::vector<twitter_user> friends;
+ http::response resp = slurp(base_url_+"statuses/friends.json",http::get);
+
+ if(resp.code != 200)
+ throw bad_response();
+
+ const js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::array))
+ throw std::exception("unable to parse response");
+
+ const js::array &list = boost::any_cast<js::array>(*var);
+ for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
+ {
+ if((*i)->type() == typeid(js::object))
+ {
+ const js::object &one = boost::any_cast<js::object>(**i);
+
+ twitter_user user;
+ user.username = retrieve<std::string>(one,"screen_name");
+ user.real_name = retrieve<std::string>(one,"name",true);
+ user.profile_image_url = retrieve<std::string>(one,"profile_image_url",true);
+
+ if(one.find("status") != one.end())
+ {
+ js::object &status = retrieve<js::object>(one,"status");
+ user.status.text = retrieve<std::string>(status,"text");
+
+ user.status.id = retrieve<long long>(status,"id");
+
+ std::string timestr = retrieve<std::string>(status,"created_at");
+ user.status.time = parse_time(timestr);
+ }
+
+ friends.push_back(user);
+ }
+ }
+
+ return friends;
+}
+
+bool twitter::get_info(const std::string &name,twitter_user *info)
+{
+ if(!info)
+ return false;
+
+ std::string url = base_url_+"users/show/"+http::url_encode(name)+".json";
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ const js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() == typeid(js::object))
+ {
+ const js::object &user_info = boost::any_cast<js::object>(*var);
+ if(user_info.find("error") != user_info.end())
+ return false;
+
+ info->username = retrieve<std::string>(user_info,"screen_name");
+ info->real_name = retrieve<std::string>(user_info,"name",true);
+ info->profile_image_url = retrieve<std::string>(user_info,"profile_image_url",true);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+bool twitter::get_info_by_email(const std::string &email,twitter_user *info)
+{
+ if(!info)
+ return false;
+
+ std::string url = base_url_+"users/show.json?email="+http::url_encode(email);
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() == typeid(js::object))
+ {
+ const js::object &user_info = boost::any_cast<js::object>(*var);
+ if(user_info.find("error") != user_info.end())
+ return false;
+
+ info->username = retrieve<std::string>(user_info,"screen_name");
+ info->real_name = retrieve<std::string>(user_info,"name",true);
+ info->profile_image_url = retrieve<std::string>(user_info,"profile_image_url",true);
+
+ return true;
+ }
+ else
+ return false;
+}
+
+twitter_user twitter::add_friend(const std::string &name)
+{
+ std::string url = base_url_+"friendships/create/"+http::url_encode(name)+".json";
+
+ twitter_user ret;
+ http::response resp = slurp(url,http::post);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::object))
+ throw std::exception("unable to parse response");
+
+ const js::object &user_info = boost::any_cast<js::object>(*var);
+ ret.username = retrieve<std::string>(user_info,"screen_name");
+ ret.real_name = retrieve<std::string>(user_info,"name",true);
+ ret.profile_image_url = retrieve<std::string>(user_info,"profile_image_url",true);
+
+ if(user_info.find("status") != user_info.end())
+ {
+ // TODO: fill in more fields
+ const js::object &status = retrieve<js::object>(user_info,"status");
+ ret.status.text = retrieve<std::string>(status,"text");
+ }
+
+ return ret;
+}
+
+void twitter::remove_friend(const std::string &name)
+{
+ std::string url = base_url_+"friendships/destroy/"+http::url_encode(name)+".json";
+
+ slurp(url,http::post);
+}
+
+void twitter::set_status(const std::string &text)
+{
+ if(text.size())
+ {
+/* slurp(base_url_+"statuses/update.json",http::post,
+ "status="+http::url_encode(text)+
+ "&source=mirandaim");*/
+
+ //MessageBox(NULL, UTF8ToWide(text).c_str(), NULL, MB_OK);
+ std::wstring wTweet = UTF8ToWide(text);
+ OAuthParameters postParams;
+ postParams[L"status"] = UrlEncode(wTweet);
+
+ slurp(base_url_+"statuses/update.json",http::post, postParams);
+ }
+}
+
+void twitter::send_direct(const std::string &name,const std::string &text)
+{
+ std::wstring temp = UTF8ToWide(text);
+ OAuthParameters postParams;
+ postParams[L"text"] = UrlEncode(temp);
+ postParams[L"screen_name"] = UTF8ToWide(name);
+ slurp(base_url_+"direct_messages/new.json", http::post, postParams);
+}
+
+std::vector<twitter_user> twitter::get_statuses(int count,twitter_id id)
+{
+ using boost::lexical_cast;
+ std::vector<twitter_user> statuses;
+
+ std::string url = base_url_+"statuses/home_timeline.json?count="+
+ lexical_cast<std::string>(count);
+ if(id != 0)
+ url += "&since_id="+boost::lexical_cast<std::string>(id);
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::array))
+ throw std::exception("unable to parse response");
+
+ const js::array &list = boost::any_cast<js::array>(*var);
+ for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
+ {
+ if((*i)->type() == typeid(js::object))
+ {
+ const js::object &one = boost::any_cast<js::object>(**i);
+ const js::object &user = retrieve<js::object>(one,"user");
+
+ twitter_user u;
+ u.username = retrieve<std::string>(user,"screen_name");
+ bool isTruncated = retrieve<bool>(one,"truncated");
+
+ if (isTruncated) { // the tweet will be truncated unless we take action. i hate you twitter API
+
+ // here we grab the "retweeted_status" um.. section? it's in here that all the info we need is
+ const js::object &Retweet = retrieve<js::object>(one,"retweeted_status");
+ const js::object &RTUser = retrieve<js::object>(Retweet,"user");
+
+ std::string retweeteesName = retrieve<std::string>(RTUser,"screen_name"); // the user that is being retweeted
+ std::string retweetText = retrieve<std::string>(Retweet,"text"); // their tweet in all it's untruncated glory
+ u.status.text = "RT @" + retweeteesName + " " + retweetText; // mash it together in some format people will understand
+ }
+ else { // if it's not truncated, then the twitter API returns the native RT correctly anyway,
+ u.status.text = retrieve<std::string>(one,"text"); // so we can just pretend it doesn't happen
+ }
+
+ u.status.id = retrieve<long long>(one,"id");
+ std::string timestr = retrieve<std::string>(one,"created_at");
+ u.status.time = parse_time(timestr);
+
+ statuses.push_back(u);
+ }
+ }
+
+
+ return statuses;
+}
+
+std::vector<twitter_user> twitter::get_direct(twitter_id id)
+{
+ std::vector<twitter_user> messages;
+
+ std::string url = base_url_+"direct_messages.json";
+ if(id != 0)
+ url += "?since_id="+boost::lexical_cast<std::string>(id);
+
+ http::response resp = slurp(url,http::get);
+ if(resp.code != 200)
+ throw bad_response();
+
+ js::variant var = json::parse( resp.data.begin(),resp.data.end());
+ if(var->type() != typeid(js::array))
+ throw std::exception("unable to parse response");
+
+ const js::array &list = boost::any_cast<js::array>(*var);
+ for(js::array::const_iterator i=list.begin(); i!=list.end(); ++i)
+ {
+ if((*i)->type() == typeid(js::object))
+ {
+ const js::object &one = boost::any_cast<js::object>(**i);
+
+ twitter_user u;
+ u.username = retrieve<std::string>(one,"sender_screen_name");
+
+ u.status.text = retrieve<std::string>(one,"text");
+ u.status.id = retrieve<long long>(one,"id");
+ std::string timestr = retrieve<std::string>(one,"created_at");
+ u.status.time = parse_time(timestr);
+
+ messages.push_back(u);
+ }
+ }
+
+
+ return messages;
+}
+
+string twitter::urlencode(const string &c)
+{
+
+ string escaped;
+ int max = c.length();
+ for(int i=0; i<max; i++)
+ {
+ if ( (48 <= c[i] && c[i] <= 57) ||//0-9
+ (65 <= c[i] && c[i] <= 90) ||//ABC...XYZ
+ (97 <= c[i] && c[i] <= 122) || //abc...xyz
+ (c[i]=='~' || c[i]=='-' || c[i]=='_' || c[i]=='.')
+ )
+ {
+ escaped.append( &c[i], 1);
+ }
+ else
+ {
+ escaped.append("%");
+ escaped.append( char2hex(c[i]));//converts char 255 to string "FF"
+ }
+ }
+ return escaped;
+}
+
+
+wstring twitter::UrlEncode( const wstring& url )
+{
+ // multiple encodings r sux
+ return UTF8ToWide(urlencode(WideToUTF8(url)));
+}
+
+// char2hex and urlencode from http://www.zedwood.com/article/111/cpp-urlencode-function
+// modified according to http://oauth.net/core/1.0a/#encoding_parameters
+//
+//5.1. Parameter Encoding
+//
+//All parameter names and values are escaped using the [RFC3986]
+//percent-encoding (%xx) mechanism. Characters not in the unreserved character set
+//MUST be encoded. Characters in the unreserved character set MUST NOT be encoded.
+//Hexadecimal characters in encodings MUST be upper case.
+//Text names and values MUST be encoded as UTF-8
+// octets before percent-encoding them per [RFC3629].
+//
+// unreserved = ALPHA, DIGIT, '-', '.', '_', '~'
+
+string twitter::char2hex( char dec )
+{
+ char dig1 = (dec&0xF0)>>4;
+ char dig2 = (dec&0x0F);
+ if ( 0<= dig1 && dig1<= 9) dig1+=48; //0,48 in ascii
+ if (10<= dig1 && dig1<=15) dig1+=65-10; //A,65 in ascii
+ if ( 0<= dig2 && dig2<= 9) dig2+=48;
+ if (10<= dig2 && dig2<=15) dig2+=65-10;
+
+ string r;
+ r.append( &dig1, 1);
+ r.append( &dig2, 1);
+ return r;
+}
+
+// Some Unices get this, now we do too!
+time_t timegm(struct tm *t)
+{
+ _tzset();
+ t->tm_sec -= _timezone;
+ t->tm_isdst = 0;
+
+ return mktime(t);
+}
+
+static char *month_names[] = { "Jan","Feb","Mar","Apr","May","Jun",
+ "Jul","Aug","Sep","Oct","Nov","Dec" };
+
+int parse_month(const char *m)
+{
+ for(size_t i=0; i<12; i++)
+ {
+ if(strcmp(month_names[i],m) == 0)
+ return i;
+ }
+ return -1;
+}
+
+time_t parse_time(const std::string &s)
+{
+ struct tm t;
+ char day[4],month[4];
+ char plus;
+ int zone;
+ if(sscanf(s.c_str(),"%3s %3s %d %d:%d:%d %c%d %d",
+ day,month,&t.tm_mday,&t.tm_hour,&t.tm_min,&t.tm_sec,
+ &plus,&zone,&t.tm_year) == 9)
+ {
+ t.tm_year -= 1900;
+ t.tm_mon = parse_month(month);
+ if(t.tm_mon == -1)
+ return 0;
+ return timegm(&t);
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/twitter.h b/protocols/Twitter/src/twitter.h
new file mode 100644
index 0000000000..10c1115967
--- /dev/null
+++ b/protocols/Twitter/src/twitter.h
@@ -0,0 +1,114 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#include <map>
+#include <vector>
+#include <string>
+
+using std::string;
+using std::wstring;
+using std::map;
+using std::vector;
+
+#include "http.h"
+#include "StringConv.h"
+#include "stdafx.h"
+
+#define tstring wstring
+
+
+typedef unsigned long long twitter_id;
+typedef std::map<std::wstring, std::wstring> OAuthParameters;
+
+struct twitter_status
+{
+ std::string text;
+ twitter_id id;
+ time_t time;
+};
+
+struct twitter_user
+{
+ std::string username;
+ std::string real_name;
+ std::string profile_image_url;
+ twitter_status status;
+};
+
+time_t parse_time(const std::string &);
+
+class bad_response : public std::exception
+{
+public:
+ virtual const char * what() const
+ {
+ return "bad http response";
+ }
+};
+
+class twitter
+{
+public:
+ typedef std::vector<twitter_user> status_list;
+ typedef std::map<std::string,status_list> status_map;
+
+ twitter();
+
+ bool twitter::set_credentials(const std::string&, const std::wstring&, const std::wstring&,
+ const std::wstring&, const std::wstring&, const std::wstring &, bool);
+
+ http::response twitter::request_token();
+ http::response twitter::request_access_tokens();
+
+
+ void set_base_url(const std::string &base_url);
+
+ const std::string & get_username() const;
+ const std::string & get_base_url() const;
+
+ bool get_info(const std::string &name,twitter_user *);
+ bool get_info_by_email(const std::string &email,twitter_user *);
+ std::vector<twitter_user> get_friends();
+
+ twitter_user add_friend(const std::string &name);
+ void remove_friend(const std::string &name);
+
+ void set_status(const std::string &text);
+ std::vector<twitter_user> get_statuses(int count=20,twitter_id id=0);
+
+ void send_direct(const std::string &name,const std::string &text);
+ std::vector<twitter_user> get_direct(twitter_id id=0);
+
+ std::string urlencode(const std::string &c);
+ std::wstring UrlEncode( const std::wstring& url );
+ std::string char2hex( char dec );
+
+protected:
+ virtual http::response slurp(const std::string &,http::method,
+ OAuthParameters postParams = OAuthParameters()) = 0;
+
+ std::string username_;
+ std::string password_;
+ std::string base_url_;
+ std::wstring consumerKey_;
+ std::wstring consumerSecret_;
+ std::wstring oauthAccessToken_;
+ std::wstring oauthAccessTokenSecret_;
+ std::wstring pin_;
+}; \ No newline at end of file
diff --git a/protocols/Twitter/src/ui.cpp b/protocols/Twitter/src/ui.cpp
new file mode 100644
index 0000000000..fa6899c13d
--- /dev/null
+++ b/protocols/Twitter/src/ui.cpp
@@ -0,0 +1,589 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "ui.h"
+
+#include <cstdio>
+#include <commctrl.h>
+
+#include "proto.h"
+#include "twitter.h"
+
+static const TCHAR *sites[] = {
+ _T("https://twitter.com/"),
+ _T("https://identi.ca/api/")
+};
+
+INT_PTR CALLBACK first_run_dialog(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ TwitterProto *proto;
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ proto = reinterpret_cast<TwitterProto*>(lParam);
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+
+ DBVARIANT dbv;
+
+ if( !DBGetContactSettingTString(0,proto->ModuleName(),TWITTER_KEY_GROUP,&dbv))
+ {
+ SetDlgItemText(hwndDlg,IDC_GROUP,dbv.ptszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ SetDlgItemText(hwndDlg,IDC_GROUP,L"Twitter");
+ }
+
+ if( !DBGetContactSettingString(0,proto->ModuleName(),TWITTER_KEY_UN,&dbv))
+ {
+ SetDlgItemTextA(hwndDlg,IDC_USERNAME,dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ /*if ( !DBGetContactSettingString(0,proto->ModuleName(),TWITTER_KEY_PASS,&dbv))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING,strlen(dbv.pszVal)+1,
+ reinterpret_cast<LPARAM>(dbv.pszVal));
+ SetDlgItemTextA(hwndDlg,IDC_PW,dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }*/
+
+ for(size_t i=0; i<SIZEOF(sites); i++)
+ {
+ SendDlgItemMessage(hwndDlg,IDC_SERVER,CB_ADDSTRING,0,
+ reinterpret_cast<LPARAM>(sites[i]));
+ }
+ if( !DBGetContactSettingString(0,proto->ModuleName(),TWITTER_KEY_BASEURL,&dbv))
+ {
+ SetDlgItemTextA(hwndDlg,IDC_SERVER,dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ SendDlgItemMessage(hwndDlg,IDC_SERVER,CB_SETCURSEL,0,0);
+ }
+
+ return true;
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDC_NEWACCOUNTLINK)
+ {
+ CallService(MS_UTILS_OPENURL,1,reinterpret_cast<LPARAM>
+ ("http://twitter.com/signup"));
+ return true;
+ }
+
+ if(GetWindowLongPtr(hwndDlg,GWLP_USERDATA)) // Window is done initializing
+ {
+ switch(HIWORD(wParam))
+ {
+ case EN_CHANGE:
+ case CBN_EDITCHANGE:
+ case CBN_SELCHANGE:
+ SendMessage(GetParent(hwndDlg),PSM_CHANGED,0,0);
+ }
+ }
+ break;
+
+ case WM_NOTIFY: // might be able to get rid of this bit?
+ if(reinterpret_cast<NMHDR*>(lParam)->code == PSN_APPLY)
+ {
+ proto = reinterpret_cast<TwitterProto*>(GetWindowLongPtr(hwndDlg,GWLP_USERDATA));
+ char str[128];
+ TCHAR tstr[128];
+
+ /*
+ GetDlgItemTextA(hwndDlg,IDC_UN,str,sizeof(str));
+ DBWriteContactSettingString(0,proto->ModuleName(),TWITTER_KEY_UN,str);
+
+ GetDlgItemTextA(hwndDlg,IDC_PW,str,sizeof(str));
+ CallService(MS_DB_CRYPT_ENCODESTRING,sizeof(str),reinterpret_cast<LPARAM>(str));
+ DBWriteContactSettingString(0,proto->ModuleName(),TWITTER_KEY_PASS,str);
+ */
+
+ GetDlgItemTextA(hwndDlg,IDC_SERVER,str,sizeof(str)-1);
+ if(str[strlen(str)-1] != '/')
+ strncat(str,"/",sizeof(str));
+ DBWriteContactSettingString(0,proto->ModuleName(),TWITTER_KEY_BASEURL,str);
+
+ GetDlgItemText(hwndDlg,IDC_GROUP,tstr,SIZEOF(tstr));
+ DBWriteContactSettingTString(0,proto->ModuleName(),TWITTER_KEY_GROUP,tstr);
+
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+INT_PTR CALLBACK tweet_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ TwitterProto *proto;
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ proto = reinterpret_cast<TwitterProto*>(lParam);
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+ SendDlgItemMessage(hwndDlg,IDC_TWEETMSG,EM_LIMITTEXT,140,0);
+ SetDlgItemText(hwndDlg,IDC_CHARACTERS,_T("140"));
+
+ // Set window title
+ TCHAR title[512];
+ mir_sntprintf(title,SIZEOF(title),_T("Send Tweet for %s"),proto->m_tszUserName);
+ SendMessage(hwndDlg,WM_SETTEXT,0,(LPARAM)title);
+
+ return true;
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDOK)
+ {
+ TCHAR msg[141];
+ proto = reinterpret_cast<TwitterProto*>(GetWindowLongPtr(hwndDlg,GWLP_USERDATA));
+
+ GetDlgItemText(hwndDlg,IDC_TWEETMSG,msg,SIZEOF(msg));
+ ShowWindow(hwndDlg,SW_HIDE);
+
+ char *narrow = mir_t2a_cp(msg,CP_UTF8);
+ ForkThread(&TwitterProto::SendTweetWorker, proto,narrow);
+
+ EndDialog(hwndDlg, wParam);
+ return true;
+ }
+ else if(LOWORD(wParam) == IDCANCEL)
+ {
+ EndDialog(hwndDlg, wParam);
+ return true;
+ }
+ else if(LOWORD(wParam) == IDC_TWEETMSG && HIWORD(wParam) == EN_CHANGE)
+ {
+ size_t len = SendDlgItemMessage(hwndDlg,IDC_TWEETMSG,WM_GETTEXTLENGTH,0,0);
+ char str[4];
+ _snprintf(str,sizeof(str),"%d",140-len);
+ SetDlgItemTextA(hwndDlg,IDC_CHARACTERS,str);
+
+ return true;
+ }
+
+ break;
+ case WM_SETREPLY:
+ {
+ char foo[512];
+ _snprintf(foo,sizeof(foo),"@%s ",(char*)wParam);
+ size_t len = strlen(foo);
+
+ SetDlgItemTextA(hwndDlg,IDC_TWEETMSG,foo);
+ SendDlgItemMessage(hwndDlg,IDC_TWEETMSG,EM_SETSEL,len,len);
+
+ char str[4];
+ _snprintf(str,sizeof(str),"%d",140-len);
+ SetDlgItemTextA(hwndDlg,IDC_CHARACTERS,str);
+
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+INT_PTR CALLBACK options_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ TwitterProto *proto;
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ proto = reinterpret_cast<TwitterProto*>(lParam);
+
+ DBVARIANT dbv;
+ if( !DBGetContactSettingString(0,proto->ModuleName(),TWITTER_KEY_UN,&dbv))
+ {
+ SetDlgItemTextA(hwndDlg,IDC_UN,dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+
+ /*if( !DBGetContactSettingString(0,proto->ModuleName(),TWITTER_KEY_PASS,&dbv))
+ {
+ CallService(MS_DB_CRYPT_DECODESTRING,strlen(dbv.pszVal)+1,
+ reinterpret_cast<LPARAM>(dbv.pszVal));
+ SetDlgItemTextA(hwndDlg,IDC_PW,dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }*/
+
+ CheckDlgButton(hwndDlg,IDC_CHATFEED,DBGetContactSettingByte(0,
+ proto->ModuleName(),TWITTER_KEY_CHATFEED,0));
+
+ for(size_t i=0; i<SIZEOF(sites); i++)
+ {
+ SendDlgItemMessage(hwndDlg,IDC_BASEURL,CB_ADDSTRING,0,
+ reinterpret_cast<LPARAM>(sites[i]));
+ }
+
+ if( !DBGetContactSettingString(0,proto->ModuleName(),TWITTER_KEY_BASEURL,&dbv))
+ {
+ SetDlgItemTextA(hwndDlg,IDC_BASEURL,dbv.pszVal);
+ DBFreeVariant(&dbv);
+ }
+ else
+ {
+ SendDlgItemMessage(hwndDlg,IDC_BASEURL,CB_SETCURSEL,0,0);
+ }
+
+ char pollrate_str[32];
+ mir_snprintf(pollrate_str,sizeof(pollrate_str),"%d",
+ DBGetContactSettingDword(0,proto->ModuleName(),TWITTER_KEY_POLLRATE,80));
+ SetDlgItemTextA(hwndDlg,IDC_POLLRATE,pollrate_str);
+
+ CheckDlgButton(hwndDlg,IDC_TWEET_MSG,DBGetContactSettingByte(0,
+ proto->ModuleName(),TWITTER_KEY_TWEET_TO_MSG,0));
+
+
+ // Do this last so that any events propagated by pre-filling the form don't
+ // instigate a PSM_CHANGED message
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+
+ break;
+ case WM_COMMAND:
+ if(GetWindowLongPtr(hwndDlg,GWLP_USERDATA)) // Window is done initializing
+ {
+ switch(HIWORD(wParam))
+ {
+ case EN_CHANGE:
+ case BN_CLICKED:
+ case CBN_EDITCHANGE:
+ case CBN_SELCHANGE:
+ switch(LOWORD(wParam))
+ {
+ case IDC_UN:
+ case IDC_PW:
+ case IDC_BASEURL:
+ ShowWindow(GetDlgItem(hwndDlg,IDC_RECONNECT),SW_SHOW);
+ }
+ SendMessage(GetParent(hwndDlg),PSM_CHANGED,0,0);
+ }
+ }
+
+ break;
+ case WM_NOTIFY:
+ if(reinterpret_cast<NMHDR*>(lParam)->code == PSN_APPLY)
+ {
+ proto = reinterpret_cast<TwitterProto*>(GetWindowLongPtr(hwndDlg,GWLP_USERDATA));
+ char str[128];
+
+ GetDlgItemTextA(hwndDlg,IDC_UN,str,sizeof(str));
+ DBWriteContactSettingString(0,proto->ModuleName(),TWITTER_KEY_UN,str);
+
+ /*GetDlgItemTextA(hwndDlg,IDC_PW,str,sizeof(str));
+ CallService(MS_DB_CRYPT_ENCODESTRING,sizeof(str),reinterpret_cast<LPARAM>(str));
+ DBWriteContactSettingString(0,proto->ModuleName(),TWITTER_KEY_PASS,str);*/
+
+ GetDlgItemTextA(hwndDlg,IDC_BASEURL,str,sizeof(str)-1);
+ if(str[strlen(str)-1] != '/')
+ strncat(str,"/",sizeof(str));
+ DBWriteContactSettingString(0,proto->ModuleName(),TWITTER_KEY_BASEURL,str);
+
+ DBWriteContactSettingByte(0,proto->ModuleName(),TWITTER_KEY_CHATFEED,
+ IsDlgButtonChecked(hwndDlg,IDC_CHATFEED));
+
+ GetDlgItemTextA(hwndDlg,IDC_POLLRATE,str,sizeof(str));
+ int rate = atoi(str);
+ if(rate == 0)
+ rate = 80;
+ DBWriteContactSettingDword(0,proto->ModuleName(),TWITTER_KEY_POLLRATE,rate);
+
+ DBWriteContactSettingByte(0,proto->ModuleName(),TWITTER_KEY_TWEET_TO_MSG,
+ IsDlgButtonChecked(hwndDlg,IDC_TWEET_MSG));
+
+ proto->UpdateSettings();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+namespace popup_options
+{
+ static int get_timeout(HWND hwndDlg)
+ {
+ if(IsDlgButtonChecked(hwndDlg,IDC_TIMEOUT_PERMANENT))
+ return -1;
+ else if(IsDlgButtonChecked(hwndDlg,IDC_TIMEOUT_CUSTOM))
+ {
+ char str[32];
+ GetDlgItemTextA(hwndDlg,IDC_TIMEOUT,str,sizeof(str));
+ return atoi(str);
+ }
+ else // Default checked (probably)
+ return 0;
+ }
+
+ static COLORREF get_text_color(HWND hwndDlg,bool for_db)
+ {
+ if(IsDlgButtonChecked(hwndDlg,IDC_COL_WINDOWS))
+ {
+ if(for_db)
+ return -1;
+ else
+ return GetSysColor(COLOR_WINDOWTEXT);
+ }
+ else if(IsDlgButtonChecked(hwndDlg,IDC_COL_CUSTOM))
+ return (COLORREF)SendDlgItemMessage(hwndDlg,IDC_COLTEXT,CPM_GETCOLOUR,0,0);
+ else // Default checked (probably)
+ return 0;
+ }
+
+ static COLORREF get_back_color(HWND hwndDlg,bool for_db)
+ {
+ if(IsDlgButtonChecked(hwndDlg,IDC_COL_WINDOWS))
+ {
+ if(for_db)
+ return -1;
+ else
+ return GetSysColor(COLOR_WINDOW);
+ }
+ else if(IsDlgButtonChecked(hwndDlg,IDC_COL_CUSTOM))
+ return (COLORREF)SendDlgItemMessage(hwndDlg,IDC_COLBACK,CPM_GETCOLOUR,0,0);
+ else // Default checked (probably)
+ return 0;
+ }
+
+ struct
+ {
+ TCHAR *name;
+ TCHAR *text;
+ } const quotes[] = {
+ { _T("Dorothy Parker"), _T("If, with the literate, I am\n")
+ _T("Impelled to try an epigram,\n")
+ _T("I never seek to take the credit;\n")
+ _T("We all assume that Oscar said it.") },
+ { _T("Steve Ballmer"), _T("I have never, honestly, thrown a chair in my life.") },
+ { _T("James Joyce"), _T("I think I would know Nora's fart anywhere. I think ")
+ _T("I could pick hers out in a roomful of farting women.") },
+ { _T("Brooke Shields"), _T("Smoking kills. If you're killed, you've lost a very ")
+ _T("important part of your life.") },
+ { _T("Yogi Berra"), _T("Always go to other peoples' funerals, otherwise ")
+ _T("they won't go to yours.") },
+ };
+
+ static void preview(HWND hwndDlg)
+ {
+ POPUPDATAT popup = {};
+
+ // Pick a random contact
+ HANDLE hContact = 0;
+ int n_contacts = (int)CallService(MS_DB_CONTACT_GETCOUNT,0,0);
+
+ if(n_contacts != 0)
+ {
+ int contact = rand() % n_contacts;
+ hContact = db_find_first();
+ for(int i=0; i<contact; i++)
+ hContact = db_find_next(hContact);
+ }
+
+ // Pick a random quote
+ int q = rand() % SIZEOF(quotes);
+ _tcsncpy(popup.lptzContactName,quotes[q].name,MAX_CONTACTNAME);
+ _tcsncpy(popup.lptzText, quotes[q].text,MAX_SECONDLINE);
+
+ popup.lchContact = hContact;
+ popup.iSeconds = get_timeout(hwndDlg);
+ popup.colorText = get_text_color(hwndDlg,false);
+ popup.colorBack = get_back_color(hwndDlg,false);
+
+ CallService(MS_POPUP_ADDPOPUPT,reinterpret_cast<WPARAM>(&popup),0);
+ }
+}
+
+void CheckAndUpdateDlgButton(HWND hWnd,int button,BOOL check)
+{
+ CheckDlgButton(hWnd,button,check);
+ SendMessage(hWnd,WM_COMMAND,MAKELONG(button,BN_CLICKED),
+ (LPARAM)GetDlgItem(hWnd,button));
+}
+
+INT_PTR CALLBACK popup_options_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ using namespace popup_options;
+ TwitterProto *proto;
+
+ int text_color,back_color,timeout;
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ proto = reinterpret_cast<TwitterProto*>(lParam);
+
+ CheckAndUpdateDlgButton(hwndDlg,IDC_SHOWPOPUPS,
+ db_get_b(0,proto->ModuleName(),TWITTER_KEY_POPUP_SHOW,0));
+ CheckDlgButton(hwndDlg,IDC_NOSIGNONPOPUPS,
+ !db_get_b(0,proto->ModuleName(),TWITTER_KEY_POPUP_SIGNON,0));
+
+
+ // ***** Get color information
+ back_color = db_get_dw(0,proto->ModuleName(),TWITTER_KEY_POPUP_COLBACK,0);
+ text_color = db_get_dw(0,proto->ModuleName(),TWITTER_KEY_POPUP_COLTEXT,0);
+
+ SendDlgItemMessage(hwndDlg,IDC_COLBACK,CPM_SETCOLOUR,0,RGB(255,255,255));
+ SendDlgItemMessage(hwndDlg,IDC_COLTEXT,CPM_SETCOLOUR,0,RGB( 0, 0, 0));
+
+ if(back_color == -1 && text_color == -1) // Windows defaults
+ CheckAndUpdateDlgButton(hwndDlg,IDC_COL_WINDOWS,true);
+ else if(back_color == 0 && text_color == 0) // Popup defaults
+ CheckAndUpdateDlgButton(hwndDlg,IDC_COL_POPUP,true);
+ else // Custom colors
+ {
+ CheckAndUpdateDlgButton(hwndDlg,IDC_COL_CUSTOM,true);
+ SendDlgItemMessage(hwndDlg,IDC_COLBACK,CPM_SETCOLOUR,0,back_color);
+ SendDlgItemMessage(hwndDlg,IDC_COLTEXT,CPM_SETCOLOUR,0,text_color);
+ }
+
+ // ***** Get timeout information
+ timeout = db_get_dw(0,proto->ModuleName(),TWITTER_KEY_POPUP_TIMEOUT,0);
+ SetDlgItemTextA(hwndDlg,IDC_TIMEOUT,"5");
+
+ if(timeout == 0)
+ CheckAndUpdateDlgButton(hwndDlg,IDC_TIMEOUT_DEFAULT,true);
+ else if(timeout < 0)
+ CheckAndUpdateDlgButton(hwndDlg,IDC_TIMEOUT_PERMANENT,true);
+ else
+ {
+ char str[32];
+ _snprintf(str,sizeof(str),"%d",timeout);
+ SetDlgItemTextA(hwndDlg,IDC_TIMEOUT,str);
+ CheckAndUpdateDlgButton(hwndDlg,IDC_TIMEOUT_CUSTOM,true);
+ }
+
+ SendDlgItemMessage(hwndDlg,IDC_TIMEOUT_SPIN,UDM_SETRANGE32,1,INT_MAX);
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+
+ return true;
+ case WM_COMMAND:
+ switch(HIWORD(wParam))
+ {
+ case BN_CLICKED:
+ switch(LOWORD(wParam))
+ {
+ case IDC_SHOWPOPUPS:
+ EnableWindow(GetDlgItem(hwndDlg,IDC_NOSIGNONPOPUPS),
+ IsDlgButtonChecked(hwndDlg,IDC_SHOWPOPUPS));
+ break;
+
+ case IDC_COL_CUSTOM:
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COLBACK),true);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COLTEXT),true);
+ break;
+ case IDC_COL_WINDOWS:
+ case IDC_COL_POPUP:
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COLBACK),false);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_COLTEXT),false);
+ break;
+
+ case IDC_TIMEOUT_CUSTOM:
+ EnableWindow(GetDlgItem(hwndDlg,IDC_TIMEOUT),true);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_TIMEOUT_SPIN),true);
+ break;
+ case IDC_TIMEOUT_DEFAULT:
+ case IDC_TIMEOUT_PERMANENT:
+ EnableWindow(GetDlgItem(hwndDlg,IDC_TIMEOUT),false);
+ EnableWindow(GetDlgItem(hwndDlg,IDC_TIMEOUT_SPIN),false);
+ break;
+
+ case IDC_PREVIEW:
+ preview(hwndDlg);
+ break;
+ }
+
+ case EN_CHANGE:
+ if(GetWindowLongPtr(hwndDlg,GWLP_USERDATA)) // Window is done initializing
+ SendMessage(GetParent(hwndDlg),PSM_CHANGED,0,0);
+ }
+ break;
+ case WM_NOTIFY:
+ if(reinterpret_cast<NMHDR*>(lParam)->code == PSN_APPLY)
+ {
+ proto = reinterpret_cast<TwitterProto*>(GetWindowLongPtr(hwndDlg,GWLP_USERDATA));
+
+ DBWriteContactSettingByte(0,proto->ModuleName(),TWITTER_KEY_POPUP_SHOW,
+ IsDlgButtonChecked(hwndDlg,IDC_SHOWPOPUPS));
+ DBWriteContactSettingByte(0,proto->ModuleName(),TWITTER_KEY_POPUP_SIGNON,
+ !IsDlgButtonChecked(hwndDlg,IDC_NOSIGNONPOPUPS));
+
+ // ***** Write color settings
+ DBWriteContactSettingDword(0,proto->ModuleName(),TWITTER_KEY_POPUP_COLBACK,
+ get_back_color(hwndDlg,true));
+ DBWriteContactSettingDword(0,proto->ModuleName(),TWITTER_KEY_POPUP_COLTEXT,
+ get_text_color(hwndDlg,true));
+
+ // ***** Write timeout setting
+ DBWriteContactSettingDword(0,proto->ModuleName(),TWITTER_KEY_POPUP_TIMEOUT,
+ get_timeout(hwndDlg));
+
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+
+INT_PTR CALLBACK pin_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam)
+{
+ TwitterProto *proto;
+
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+
+ SetWindowLongPtr(hwndDlg,GWLP_USERDATA,lParam);
+
+ return true;
+ case WM_COMMAND:
+ if(LOWORD(wParam) == IDOK)
+ {
+ proto = reinterpret_cast<TwitterProto*>(GetWindowLongPtr(hwndDlg,GWLP_USERDATA));
+ char str[128];
+
+ GetDlgItemTextA(hwndDlg,IDC_PIN,str,sizeof(str));
+
+ DBWriteContactSettingString(0,proto->ModuleName(),TWITTER_KEY_OAUTH_PIN,str);
+ EndDialog(hwndDlg, wParam);
+ return true;
+ }
+ else if(LOWORD(wParam) == IDCANCEL)
+ {
+ EndDialog(hwndDlg, wParam);
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
diff --git a/protocols/Twitter/src/ui.h b/protocols/Twitter/src/ui.h
new file mode 100644
index 0000000000..f5520bba40
--- /dev/null
+++ b/protocols/Twitter/src/ui.h
@@ -0,0 +1,26 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#include <windows.h>
+
+INT_PTR CALLBACK first_run_dialog(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK tweet_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK pin_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK options_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam);
+INT_PTR CALLBACK popup_options_proc(HWND hwndDlg,UINT msg,WPARAM wParam,LPARAM lParam); \ No newline at end of file
diff --git a/protocols/Twitter/src/utility.cpp b/protocols/Twitter/src/utility.cpp
new file mode 100644
index 0000000000..090bfebf09
--- /dev/null
+++ b/protocols/Twitter/src/utility.cpp
@@ -0,0 +1,231 @@
+/*
+Copyright © 2009 Jim Porter
+
+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 "utility.h"
+//#include "tc2.h"
+
+#include <io.h>
+
+std::string b64encode(const std::string &s)
+{
+ NETLIBBASE64 encode;
+ encode.cbDecoded = (int)s.length();
+ encode.pbDecoded = (BYTE*)s.c_str();
+ encode.cchEncoded = Netlib_GetBase64EncodedBufferSize(encode.cbDecoded);
+ encode.pszEncoded = new char[encode.cchEncoded+1];
+ CallService(MS_NETLIB_BASE64ENCODE,0,(LPARAM)&encode);
+ std::string ret = encode.pszEncoded;
+ delete[] encode.pszEncoded;
+
+ return ret;
+}
+
+http::response mir_twitter::slurp(const std::string &url,http::method meth,
+ OAuthParameters postParams)
+{
+ NETLIBHTTPREQUEST req = {sizeof(req)};
+ NETLIBHTTPREQUEST *resp;
+ req.requestType = (meth == http::get) ? REQUEST_GET:REQUEST_POST;
+ req.szUrl = const_cast<char*>(url.c_str());
+
+ //std::wstring url_WSTR(url.length(),L' ');
+ //std::copy(url.begin(), url.end(), url_WSTR.begin());
+ std::wstring url_WSTR = UTF8ToWide(url);
+ std::string pdata_STR;
+ std::wstring pdata_WSTR;
+
+ std::wstring auth;
+ if (meth == http::get) {
+ if (url_WSTR.size()>0) { WLOG("**SLURP::GET - we have a URL: %s", url_WSTR); }
+ if (consumerKey_.size()>0) { LOG("**SLURP::GET - we have a consumerKey"); }
+ if (consumerSecret_.size()>0) { LOG("**SLURP::GET - we have a consumerSecret"); }
+ if (oauthAccessToken_.size()>0) { LOG("**SLURP::GET - we have a oauthAccessToken"); }
+ if (oauthAccessTokenSecret_.size()>0) { LOG("**SLURP::GET - we have a oauthAccessTokenSecret"); }
+ if (pin_.size()>0) { LOG("**SLURP::GET - we have a pin"); }
+ //WLOG("consumerSEcret is %s", consumerSecret_);
+ //WLOG("oauthAccessTok is %s", oauthAccessToken_);
+ //WLOG("oautAccessTokSEc is %s", oauthAccessTokenSecret_);
+ //WLOG("pin is %s", pin_);
+
+ auth = OAuthWebRequestSubmit(url_WSTR, L"GET", NULL, consumerKey_, consumerSecret_,
+ oauthAccessToken_, oauthAccessTokenSecret_, pin_);
+ }
+ else if (meth == http::post) {
+
+ //OAuthParameters postParams;
+ if (url_WSTR.size()>0) { WLOG("**SLURP::POST - we have a URL: %s", url_WSTR); }
+ if (consumerKey_.size()>0) { LOG("**SLURP::POST - we have a consumerKey"); }
+ if (consumerSecret_.size()>0) { LOG("**SLURP::POST - we have a consumerSecret"); }
+ if (oauthAccessToken_.size()>0) { LOG("**SLURP::POST - we have a oauthAccessToken"); }
+ if (oauthAccessTokenSecret_.size()>0) { LOG("**SLURP::POST - we have a oauthAccessTokenSecret"); }
+ if (pin_.size()>0) { LOG("**SLURP::POST - we have a pin"); }
+
+ //WLOG("consumerKey is %s", consumerKey_);
+ //WLOG("consumerSEcret is %s", consumerSecret_);
+ //WLOG("oauthAccessTok is %s", oauthAccessToken_);
+ //WLOG("oautAccessTokSEc is %s", oauthAccessTokenSecret_);
+
+ //std::wstring pdata_WSTR(post_data.length(),L' ');
+ //std::copy(post_data.begin(), post_data.end(), pdata_WSTR.begin());
+
+ //postParams[L"status"] = UrlEncode(pdata_WSTR);
+ //postParams[L"source"] = L"mirandaim";
+
+ pdata_WSTR = BuildQueryString(postParams);
+
+ WLOG("**SLURP::POST - post data is: %s", pdata_WSTR);
+
+ auth = OAuthWebRequestSubmit(url_WSTR, L"POST", &postParams, consumerKey_, consumerSecret_,
+ oauthAccessToken_, oauthAccessTokenSecret_);
+ //WLOG("**SLURP::POST auth is %s", auth);
+ }
+ else {
+ LOG("**SLURP - There is something really wrong.. the http method was neither get or post.. WHY??");
+ }
+
+ //std::string auth_STR(auth.length(), ' ');
+ //std::copy(auth.begin(), auth.end(), auth_STR.begin());
+
+ std::string auth_STR = WideToUTF8(auth);
+
+ NETLIBHTTPHEADER hdr[3];
+ hdr[0].szName = "Authorization";
+ hdr[0].szValue = const_cast<char*>(auth_STR.c_str());
+
+ req.headers = hdr;
+ req.headersCount = 1;
+
+ if(meth == http::post)
+ {
+ hdr[1].szName = "Content-Type";
+ hdr[1].szValue = "application/x-www-form-urlencoded";
+ hdr[2].szName = "Cache-Control";
+ hdr[2].szValue = "no-cache";
+
+ //char *pdata_STR = new char[pdata_WSTR.length() + 1];
+ //sprintf(pdata_STR,"%ls",pdata_WSTR.c_str());
+
+ pdata_STR = WideToUTF8(pdata_WSTR);
+
+ req.headersCount = 3;
+ req.dataLength = (int)pdata_STR.size();
+ req.pData = const_cast<char*>(pdata_STR.c_str());
+ LOG("**SLURP::POST - req.pdata is %s", req.pData);
+ }
+
+ req.flags = NLHRF_HTTP11 | NLHRF_PERSISTENT | NLHRF_REDIRECT;
+ req.nlc = httpPOST_;
+ http::response resp_data;
+ LOG("**SLURP - just before calling HTTPTRANSACTION");
+ resp = reinterpret_cast<NETLIBHTTPREQUEST*>(CallService( MS_NETLIB_HTTPTRANSACTION,
+ reinterpret_cast<WPARAM>(handle_), reinterpret_cast<LPARAM>(&req)));
+ LOG("**SLURP - HTTPTRANSACTION complete.");
+ if(resp)
+ {
+ LOG("**SLURP - the server has responded!");
+ httpPOST_ = resp->nlc;
+ resp_data.code = resp->resultCode;
+ resp_data.data = resp->pData ? resp->pData:"";
+
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT,0,(LPARAM)resp);
+ }
+ else {
+ httpPOST_ = NULL;
+ LOG("SLURP - there was no response!");
+ }
+
+ return resp_data;
+}
+
+int mir_twitter::LOG(const char *fmt,...)
+{
+ va_list va;
+ char text[1024];
+ if (!handle_)
+ return 0;
+
+ va_start(va,fmt);
+ mir_vsnprintf(text,sizeof(text),fmt,va);
+ va_end(va);
+
+ return (int)CallService(MS_NETLIB_LOG,(WPARAM)handle_,(LPARAM)text);
+}
+
+int mir_twitter::WLOG(const char* first, const std::wstring last)
+{
+ char *str1 = new char[1024*96];
+ sprintf(str1,"%ls", last.c_str());
+
+ return LOG(first, str1);
+}
+
+bool save_url(HANDLE hNetlib,const std::string &url,const std::tstring &filename)
+{
+ NETLIBHTTPREQUEST req = {sizeof(req)};
+ NETLIBHTTPREQUEST *resp;
+ req.requestType = REQUEST_GET;
+ req.flags = NLHRF_HTTP11 | NLHRF_REDIRECT;
+ req.szUrl = const_cast<char*>(url.c_str());
+
+ resp = reinterpret_cast<NETLIBHTTPREQUEST*>(CallService( MS_NETLIB_HTTPTRANSACTION,
+ reinterpret_cast<WPARAM>(hNetlib), reinterpret_cast<LPARAM>(&req)));
+
+ if (resp)
+ {
+ if (resp->resultCode == 200)
+ {
+ // Create folder if necessary
+ std::tstring dir = filename.substr(0,filename.rfind('\\'));
+ if( _taccess(dir.c_str(),0))
+ CallService(MS_UTILS_CREATEDIRTREET, 0, (LPARAM)dir.c_str());
+
+ // Write to file
+ FILE *f = _tfopen(filename.c_str(), _T("wb"));
+ fwrite(resp->pData,1,resp->dataLength,f);
+ fclose(f);
+ }
+
+ CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT,0,(LPARAM)resp);
+ return resp->resultCode == 200;
+ }
+ else
+ return false;
+}
+
+static const struct
+{
+ char *ext;
+ int fmt;
+} formats[] = {
+ { ".png", PA_FORMAT_PNG },
+ { ".jpg", PA_FORMAT_JPEG },
+ { ".jpeg", PA_FORMAT_JPEG },
+ { ".ico", PA_FORMAT_ICON },
+ { ".bmp", PA_FORMAT_BMP },
+ { ".gif", PA_FORMAT_GIF },
+};
+
+int ext_to_format(const std::string &ext)
+{
+ for(size_t i=0; i<SIZEOF(formats); i++)
+ {
+ if(ext == formats[i].ext)
+ return formats[i].fmt;
+ }
+
+ return PA_FORMAT_UNKNOWN;
+} \ No newline at end of file
diff --git a/protocols/Twitter/src/utility.h b/protocols/Twitter/src/utility.h
new file mode 100644
index 0000000000..a8823291d1
--- /dev/null
+++ b/protocols/Twitter/src/utility.h
@@ -0,0 +1,158 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#include "common.h"
+#include "http.h"
+#include "twitter.h"
+
+template<typename T>
+void CreateProtoService(const char *module,const char *service,
+ int (__cdecl T::*serviceProc)(WPARAM,LPARAM),T *self)
+{
+ char temp[MAX_PATH*2];
+
+ mir_snprintf(temp,sizeof(temp),"%s%s",module,service);
+ CreateServiceFunctionObj(temp,( MIRANDASERVICEOBJ )*(void**)&serviceProc, self );
+}
+
+template<typename T>
+void HookProtoEvent(const char* evt, int (__cdecl T::*eventProc)(WPARAM,LPARAM), T *self)
+{
+ ::HookEventObj(evt,(MIRANDAHOOKOBJ)*(void**)&eventProc,self);
+}
+
+template<typename T>
+HANDLE ForkThreadEx(void (__cdecl T::*thread)(void*),T *self,void *data = 0)
+{
+ return reinterpret_cast<HANDLE>( mir_forkthreadowner(
+ (pThreadFuncOwner)*(void**)&thread,self,data,0));
+}
+
+template<typename T>
+void ForkThread(void (__cdecl T::*thread)(void*),T *self,void *data = 0)
+{
+ CloseHandle(ForkThreadEx(thread,self,data));
+}
+
+std::string b64encode(const std::string &s);
+
+class mir_twitter : public twitter
+{
+public:
+ mir_twitter() : twitter(), handle_(NULL), httpPOST_(NULL) {}
+ void set_handle(HANDLE h)
+ {
+ handle_ = h;
+ }
+
+ // OAuthWebRequest used for all OAuth related queries
+ //
+ // consumerKey and consumerSecret - must be provided for every call, they identify the application
+ // oauthToken and oauthTokenSecret - need to be provided for every call, except for the first token request before authorizing
+ // pin - only used during authorization, when the user enters the PIN they received from the twitter website
+ std::wstring OAuthWebRequestSubmit(
+ const std::wstring& url,
+ const std::wstring& httpMethod,
+ const OAuthParameters *postData,
+ const std::wstring& consumerKey,
+ const std::wstring& consumerSecret,
+ const std::wstring& oauthToken = L"",
+ const std::wstring& oauthTokenSecret = L"",
+ const std::wstring& pin = L""
+ );
+
+ std::wstring OAuthWebRequestSubmit(
+ const OAuthParameters& parameters,
+ const std::wstring& url
+ );
+
+ std::wstring UrlGetQuery( const std::wstring& url );
+
+ OAuthParameters BuildSignedOAuthParameters( const OAuthParameters& requestParameters,
+ const std::wstring& url,
+ const std::wstring& httpMethod,
+ const OAuthParameters *postData,
+ const std::wstring& consumerKey,
+ const std::wstring& consumerSecret,
+ const std::wstring& requestToken,
+ const std::wstring& requestTokenSecret,
+ const std::wstring& pin );
+
+ std::wstring BuildQueryString( const OAuthParameters &parameters ) ;
+ std::wstring OAuthConcatenateRequestElements( const std::wstring& httpMethod, std::wstring url, const std::wstring& parameters );
+ std::map<std::wstring, std::wstring> CrackURL(std::wstring );
+ std::wstring brook_httpsend(std::wstring, std::wstring, std::wstring, std::wstring);
+ void Disconnect(void) { if (httpPOST_) Netlib_CloseHandle(httpPOST_); httpPOST_ = NULL; }
+ std::wstring OAuthNormalizeUrl( const std::wstring& url );
+ std::wstring OAuthNormalizeRequestParameters( const OAuthParameters& requestParameters );
+ OAuthParameters ParseQueryString( const std::wstring& url );
+
+ std::wstring OAuthCreateNonce();
+ std::wstring OAuthCreateTimestamp();
+ std::string HMACSHA1( const std::string& keyBytes, const std::string& data );
+ std::wstring Base64String( const std::string& hash );
+ std::wstring OAuthCreateSignature( const std::wstring& signatureBase, const std::wstring& consumerSecret, const std::wstring& requestTokenSecret );
+
+protected:
+ http::response slurp(const std::string &,http::method, OAuthParameters );
+ int LOG(const char *fmt,...);
+ int WLOG(const char* first, const std::wstring last);
+
+ HANDLE httpPOST_;
+ HANDLE handle_;
+};
+
+inline void mbcs_to_tcs(UINT code_page,const char *mbstr,TCHAR *tstr,int tlen)
+{
+
+ MultiByteToWideChar(code_page,0,mbstr,-1,tstr,tlen);
+
+}
+
+inline void wcs_to_tcs(UINT code_page,const wchar_t *wstr,TCHAR *tstr,int tlen)
+{
+
+ wcsncpy(tstr,wstr,tlen);
+
+}
+
+class ScopedLock
+{
+public:
+ ScopedLock(HANDLE h) : handle_(h)
+ {
+ WaitForSingleObject(handle_,INFINITE);
+ }
+ ~ScopedLock()
+ {
+ if(handle_)
+ ReleaseMutex(handle_);
+ }
+
+ void Unlock()
+ {
+ ReleaseMutex(handle_);
+ handle_ = 0;
+ }
+private:
+ HANDLE handle_;
+};
+
+int ext_to_format(const std::string &ext);
+bool save_url(HANDLE hNetlib,const std::string &url,const std::tstring &filename); \ No newline at end of file
diff --git a/protocols/Twitter/src/version.h b/protocols/Twitter/src/version.h
new file mode 100644
index 0000000000..0196f658bb
--- /dev/null
+++ b/protocols/Twitter/src/version.h
@@ -0,0 +1,20 @@
+/*
+Copyright © 2009 Jim Porter
+
+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/>.
+*/
+
+#pragma once
+
+#define __VERSION_DWORD PLUGIN_MAKE_VERSION(1, 0, 0, 0) \ No newline at end of file