/* Copyright (C) 2005 Kimmo Pekkola This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "../../Library/pcre-8.10/config.h" #include "../../Library/pcre-8.10/pcre.h" #include "../../Common/StringUtil.h" #include "../API/RainmeterAPI.h" void ShowError(void* rm, WCHAR* description); class ProxyCachePool { public: ProxyCachePool(LPCWSTR globalProxyName = nullptr) : m_GlobalProxyName((globalProxyName && *globalProxyName) ? globalProxyName : L"/auto") { m_GlobalProxyCache = new ProxyCache(CreateProxy(m_GlobalProxyName.c_str()), true); _wcslwr(&m_GlobalProxyName[0]); m_CacheMap[m_GlobalProxyName] = m_GlobalProxyCache; //DebugLog(L"* ADD-GLOBAL: key=%s, handle=0x%p, ref=new", m_GlobalProxyName.c_str(), m_GlobalProxyCache->GetCache()); } ~ProxyCachePool() { for (auto iter = m_CacheMap.begin(); iter != m_CacheMap.end(); ++iter) { ProxyCache* cache = (*iter).second; //DebugLog(L"* FORCE-REMOVE: key=%s, global=%i, ref=%i", (*iter).first.c_str(), cache->IsGlobal(), cache->GetRef()); delete cache; } } HINTERNET GetCache(const std::wstring& proxyName) { ProxyCache* cache = nullptr; if (proxyName.empty()) { // Use global proxy setting cache = m_GlobalProxyCache; } else { std::wstring key = proxyName; _wcslwr(&key[0]); auto iter = m_CacheMap.find(key); if (iter != m_CacheMap.end()) { cache = (*iter).second; } else // cache not found { // Create new proxy ProxyCache* cache = new ProxyCache(CreateProxy(proxyName.c_str())); m_CacheMap[key] = cache; //DebugLog(L"* ADD: key=%s, handle=0x%p, ref=new", key.c_str(), cache->GetCache()); return cache->GetCache(); } } // Use proxy cache cache->AddRef(); //DebugLog(L"* ADD-REF: key=%s, handle=0x%p, global=%i, ref=%i", // cache->IsGlobal() ? m_GlobalProxyName.c_str() : proxyName.c_str(), cache->GetCache(), cache->IsGlobal(), cache->GetRef()); return cache->GetCache(); } void RemoveCache(const std::wstring& proxyName) { std::wstring key = proxyName.empty() ? m_GlobalProxyName : proxyName; if (!proxyName.empty()) { _wcslwr(&key[0]); } auto iter = m_CacheMap.find(key); if (iter != m_CacheMap.end()) { ProxyCache* cache = (*iter).second; cache->Release(); //DebugLog(L"* REMOVE: key=%s, global=%i, ref=%i", key.c_str(), cache->IsGlobal(), cache->GetRef()); if (cache->IsInvalid()) { //DebugLog(L"* EMPTY-ERASE: key=%s", key.c_str()); m_CacheMap.erase(iter); delete cache; } } } private: HINTERNET CreateProxy(LPCWSTR proxyName) { DWORD proxyType; LPCWSTR proxyServer; if (_wcsicmp(proxyName, L"/auto") == 0) { proxyType = INTERNET_OPEN_TYPE_PRECONFIG; proxyServer = nullptr; } else if (_wcsicmp(proxyName, L"/none") == 0) { proxyType = INTERNET_OPEN_TYPE_DIRECT; proxyServer = nullptr; } else { proxyType = INTERNET_OPEN_TYPE_PROXY; proxyServer = proxyName; } HINTERNET handle = InternetOpen(L"Rainmeter WebParser plugin", proxyType, proxyServer, nullptr, 0); if (handle) { RmLogF(nullptr, LOG_DEBUG, L"WebParser: ProxyServer=\"%s\" (type=%s, handle=0x%p)", proxyName, proxyType == INTERNET_OPEN_TYPE_PRECONFIG ? L"PRECONFIG" : proxyType == INTERNET_OPEN_TYPE_DIRECT ? L"DIRECT" : L"PROXY", handle); } else { ShowError(nullptr, L"InternetOpen error"); } return handle; } class ProxyCache { public: ProxyCache(HINTERNET handle, bool isGlobal = false) : m_Handle(handle), m_IsGlobal(isGlobal), m_Ref(1) {} ~ProxyCache() { Dispose(); } void AddRef() { if (!IsInvalid()) { ++m_Ref; } } void Release() { if (m_Ref > 0) { --m_Ref; } if (IsInvalid()) { Dispose(); } } bool IsGlobal() { return m_IsGlobal; } bool IsInvalid() { return (m_Ref <= 0 && !IsGlobal()); } //int GetRef() { return m_Ref; } HINTERNET GetCache() { return m_Handle; } private: ProxyCache() {} ProxyCache(const ProxyCache& cache) {} void Dispose() { if (m_Handle) { InternetCloseHandle(m_Handle); m_Handle = nullptr; } } HINTERNET m_Handle; bool m_IsGlobal; int m_Ref; }; std::unordered_map m_CacheMap; ProxyCache* m_GlobalProxyCache; std::wstring m_GlobalProxyName; }; struct ProxySetting { std::wstring server; HINTERNET handle; ProxySetting() : handle() {} }; struct MeasureData { std::wstring url; std::wstring regExp; std::wstring resultString; std::wstring errorString; std::wstring finishAction; std::wstring downloadFolder; std::wstring downloadFile; std::wstring downloadedFile; std::wstring debugFileLocation; void* rm; void* skin; ProxySetting proxy; HANDLE threadHandle; HANDLE dlThreadHandle; int codepage; int stringIndex; int stringIndex2; int decodeCharacterReference; int debug; UINT updateRate; UINT updateCounter; bool download; bool forceReload; MeasureData() : rm(), skin(), threadHandle(), dlThreadHandle(), codepage(), stringIndex(), stringIndex2(), decodeCharacterReference(), debug(), updateRate(), updateCounter(), download(), forceReload() { } }; BYTE* DownloadUrl(HINTERNET handle, std::wstring& url, DWORD* dataSize, bool forceReload); unsigned __stdcall NetworkThreadProc(void* pParam); unsigned __stdcall NetworkDownloadThreadProc(void* pParam); void ParseData(MeasureData* measure, LPCSTR parseData, DWORD dwSize); CRITICAL_SECTION g_CriticalSection; ProxyCachePool* g_ProxyCachePool = nullptr; UINT g_InstanceCount = 0; static std::vector g_Measures; static bool g_Debug = false; static std::unordered_map g_CERs; #define OVECCOUNT 300 // should be a multiple of 3 std::string ConvertAsciiToUTF8(LPCSTR str, int strLen, int codepage) { std::string szUTF8; if (str && *str) { std::wstring wide = StringUtil::Widen(str, strLen, codepage); if (!wide.empty()) { szUTF8.swap(StringUtil::NarrowUTF8(wide)); } } return szUTF8; } void DecodeReferences(std::wstring& str, int opt) { // (opt <= 0 || opt > 3) : Do nothing. // (opt == 1) : Decode both numeric character references and character entity references. // (opt == 2) : Decode only numeric character references. // (opt == 3) : Decode only character entity references. if (opt >= 1 && opt <= 3) { std::wstring::size_type start = 0; while ((start = str.find(L'&', start)) != std::wstring::npos) { std::wstring::size_type end, pos; if ((end = str.find(L';', start)) == std::wstring::npos) break; pos = start + 1; if (pos == end) // &; - skip { start = end + 1; continue; } else if ((end - pos) > 10) // name (or number) is too long { ++start; continue; } if (str[pos] == L'#') // Numeric character reference { if (opt == 3 || // Decode only character entity references, ++pos == end) // &#; - skip { start = end + 1; continue; } int base; if (str[pos] == L'x' || str[pos] == L'X') { if (++pos == end) // &#x; or &#X; - skip { start = end + 1; continue; } base = 16; } else { base = 10; } std::wstring num(str, pos, end - pos); WCHAR* pch = nullptr; errno = 0; long ch = wcstol(num.c_str(), &pch, base); if (pch == nullptr || *pch != L'\0' || errno == ERANGE || ch <= 0 || ch >= 0xFFFE) // invalid character { start = pos; continue; } str.replace(start, end - start + 1, 1, (WCHAR)ch); ++start; } else // Character entity reference { if (opt == 2) // Decode only numeric character references - skip { start = end + 1; continue; } std::wstring name(str, pos, end - pos); std::unordered_map::const_iterator iter = g_CERs.find(name); if (iter != g_CERs.end()) { str.replace(start, end - start + 1, 1, (*iter).second); } ++start; } } } } void FillCharacterEntityReferences() { struct CER { WCHAR* name; WCHAR ch; }; // List from: // http://www.w3.org/TR/html4/sgml/entities.html // http://www.w3.org/TR/xhtml1/#C_16 static CER entities[] = { // for markup-significant and internationalization characters { L"quot", (WCHAR)34 }, { L"amp", (WCHAR)38 }, { L"apos", (WCHAR)39 }, { L"lt", (WCHAR)60 }, { L"gt", (WCHAR)62 }, { L"OElig", (WCHAR)338 }, { L"oelig", (WCHAR)339 }, { L"Scaron", (WCHAR)352 }, { L"scaron", (WCHAR)353 }, { L"Yuml", (WCHAR)376 }, { L"circ", (WCHAR)710 }, { L"tilde", (WCHAR)732 }, { L"ensp", (WCHAR)8194 }, { L"emsp", (WCHAR)8195 }, { L"thinsp", (WCHAR)8201 }, { L"zwnj", (WCHAR)8204 }, { L"zwj", (WCHAR)8205 }, { L"lrm", (WCHAR)8206 }, { L"rlm", (WCHAR)8207 }, { L"ndash", (WCHAR)8211 }, { L"mdash", (WCHAR)8212 }, { L"lsquo", (WCHAR)8216 }, { L"rsquo", (WCHAR)8217 }, { L"sbquo", (WCHAR)8218 }, { L"ldquo", (WCHAR)8220 }, { L"rdquo", (WCHAR)8221 }, { L"bdquo", (WCHAR)8222 }, { L"dagger", (WCHAR)8224 }, { L"Dagger", (WCHAR)8225 }, { L"permil", (WCHAR)8240 }, { L"lsaquo", (WCHAR)8249 }, { L"rsaquo", (WCHAR)8250 }, { L"euro", (WCHAR)8364 }, // for ISO 8859-1 characters { L"nbsp", (WCHAR)160 }, { L"iexcl", (WCHAR)161 }, { L"cent", (WCHAR)162 }, { L"pound", (WCHAR)163 }, { L"curren", (WCHAR)164 }, { L"yen", (WCHAR)165 }, { L"brvbar", (WCHAR)166 }, { L"sect", (WCHAR)167 }, { L"uml", (WCHAR)168 }, { L"copy", (WCHAR)169 }, { L"ordf", (WCHAR)170 }, { L"laquo", (WCHAR)171 }, { L"not", (WCHAR)172 }, { L"shy", (WCHAR)173 }, { L"reg", (WCHAR)174 }, { L"macr", (WCHAR)175 }, { L"deg", (WCHAR)176 }, { L"plusmn", (WCHAR)177 }, { L"sup2", (WCHAR)178 }, { L"sup3", (WCHAR)179 }, { L"acute", (WCHAR)180 }, { L"micro", (WCHAR)181 }, { L"para", (WCHAR)182 }, { L"middot", (WCHAR)183 }, { L"cedil", (WCHAR)184 }, { L"sup1", (WCHAR)185 }, { L"ordm", (WCHAR)186 }, { L"raquo", (WCHAR)187 }, { L"frac14", (WCHAR)188 }, { L"frac12", (WCHAR)189 }, { L"frac34", (WCHAR)190 }, { L"iquest", (WCHAR)191 }, { L"Agrave", (WCHAR)192 }, { L"Aacute", (WCHAR)193 }, { L"Acirc", (WCHAR)194 }, { L"Atilde", (WCHAR)195 }, { L"Auml", (WCHAR)196 }, { L"Aring", (WCHAR)197 }, { L"AElig", (WCHAR)198 }, { L"Ccedil", (WCHAR)199 }, { L"Egrave", (WCHAR)200 }, { L"Eacute", (WCHAR)201 }, { L"Ecirc", (WCHAR)202 }, { L"Euml", (WCHAR)203 }, { L"Igrave", (WCHAR)204 }, { L"Iacute", (WCHAR)205 }, { L"Icirc", (WCHAR)206 }, { L"Iuml", (WCHAR)207 }, { L"ETH", (WCHAR)208 }, { L"Ntilde", (WCHAR)209 }, { L"Ograve", (WCHAR)210 }, { L"Oacute", (WCHAR)211 }, { L"Ocirc", (WCHAR)212 }, { L"Otilde", (WCHAR)213 }, { L"Ouml", (WCHAR)214 }, { L"times", (WCHAR)215 }, { L"Oslash", (WCHAR)216 }, { L"Ugrave", (WCHAR)217 }, { L"Uacute", (WCHAR)218 }, { L"Ucirc", (WCHAR)219 }, { L"Uuml", (WCHAR)220 }, { L"Yacute", (WCHAR)221 }, { L"THORN", (WCHAR)222 }, { L"szlig", (WCHAR)223 }, { L"agrave", (WCHAR)224 }, { L"aacute", (WCHAR)225 }, { L"acirc", (WCHAR)226 }, { L"atilde", (WCHAR)227 }, { L"auml", (WCHAR)228 }, { L"aring", (WCHAR)229 }, { L"aelig", (WCHAR)230 }, { L"ccedil", (WCHAR)231 }, { L"egrave", (WCHAR)232 }, { L"eacute", (WCHAR)233 }, { L"ecirc", (WCHAR)234 }, { L"euml", (WCHAR)235 }, { L"igrave", (WCHAR)236 }, { L"iacute", (WCHAR)237 }, { L"icirc", (WCHAR)238 }, { L"iuml", (WCHAR)239 }, { L"eth", (WCHAR)240 }, { L"ntilde", (WCHAR)241 }, { L"ograve", (WCHAR)242 }, { L"oacute", (WCHAR)243 }, { L"ocirc", (WCHAR)244 }, { L"otilde", (WCHAR)245 }, { L"ouml", (WCHAR)246 }, { L"divide", (WCHAR)247 }, { L"oslash", (WCHAR)248 }, { L"ugrave", (WCHAR)249 }, { L"uacute", (WCHAR)250 }, { L"ucirc", (WCHAR)251 }, { L"uuml", (WCHAR)252 }, { L"yacute", (WCHAR)253 }, { L"thorn", (WCHAR)254 }, { L"yuml", (WCHAR)255 }, // for symbols, mathematical symbols, and Greek letters { L"fnof", (WCHAR)402 }, { L"Alpha", (WCHAR)913 }, { L"Beta", (WCHAR)914 }, { L"Gamma", (WCHAR)915 }, { L"Delta", (WCHAR)916 }, { L"Epsilon", (WCHAR)917 }, { L"Zeta", (WCHAR)918 }, { L"Eta", (WCHAR)919 }, { L"Theta", (WCHAR)920 }, { L"Iota", (WCHAR)921 }, { L"Kappa", (WCHAR)922 }, { L"Lambda", (WCHAR)923 }, { L"Mu", (WCHAR)924 }, { L"Nu", (WCHAR)925 }, { L"Xi", (WCHAR)926 }, { L"Omicron", (WCHAR)927 }, { L"Pi", (WCHAR)928 }, { L"Rho", (WCHAR)929 }, { L"Sigma", (WCHAR)931 }, { L"Tau", (WCHAR)932 }, { L"Upsilon", (WCHAR)933 }, { L"Phi", (WCHAR)934 }, { L"Chi", (WCHAR)935 }, { L"Psi", (WCHAR)936 }, { L"Omega", (WCHAR)937 }, { L"alpha", (WCHAR)945 }, { L"beta", (WCHAR)946 }, { L"gamma", (WCHAR)947 }, { L"delta", (WCHAR)948 }, { L"epsilon", (WCHAR)949 }, { L"zeta", (WCHAR)950 }, { L"eta", (WCHAR)951 }, { L"theta", (WCHAR)952 }, { L"iota", (WCHAR)953 }, { L"kappa", (WCHAR)954 }, { L"lambda", (WCHAR)955 }, { L"mu", (WCHAR)956 }, { L"nu", (WCHAR)957 }, { L"xi", (WCHAR)958 }, { L"omicron", (WCHAR)959 }, { L"pi", (WCHAR)960 }, { L"rho", (WCHAR)961 }, { L"sigmaf", (WCHAR)962 }, { L"sigma", (WCHAR)963 }, { L"tau", (WCHAR)964 }, { L"upsilon", (WCHAR)965 }, { L"phi", (WCHAR)966 }, { L"chi", (WCHAR)967 }, { L"psi", (WCHAR)968 }, { L"omega", (WCHAR)969 }, { L"thetasym", (WCHAR)977 }, { L"upsih", (WCHAR)978 }, { L"piv", (WCHAR)982 }, { L"bull", (WCHAR)8226 }, { L"hellip", (WCHAR)8230 }, { L"prime", (WCHAR)8242 }, { L"Prime", (WCHAR)8243 }, { L"oline", (WCHAR)8254 }, { L"frasl", (WCHAR)8260 }, { L"weierp", (WCHAR)8472 }, { L"image", (WCHAR)8465 }, { L"real", (WCHAR)8476 }, { L"trade", (WCHAR)8482 }, { L"alefsym", (WCHAR)8501 }, { L"larr", (WCHAR)8592 }, { L"uarr", (WCHAR)8593 }, { L"rarr", (WCHAR)8594 }, { L"darr", (WCHAR)8595 }, { L"harr", (WCHAR)8596 }, { L"crarr", (WCHAR)8629 }, { L"lArr", (WCHAR)8656 }, { L"uArr", (WCHAR)8657 }, { L"rArr", (WCHAR)8658 }, { L"dArr", (WCHAR)8659 }, { L"hArr", (WCHAR)8660 }, { L"forall", (WCHAR)8704 }, { L"part", (WCHAR)8706 }, { L"exist", (WCHAR)8707 }, { L"empty", (WCHAR)8709 }, { L"nabla", (WCHAR)8711 }, { L"isin", (WCHAR)8712 }, { L"notin", (WCHAR)8713 }, { L"ni", (WCHAR)8715 }, { L"prod", (WCHAR)8719 }, { L"sum", (WCHAR)8721 }, { L"minus", (WCHAR)8722 }, { L"lowast", (WCHAR)8727 }, { L"radic", (WCHAR)8730 }, { L"prop", (WCHAR)8733 }, { L"infin", (WCHAR)8734 }, { L"ang", (WCHAR)8736 }, { L"and", (WCHAR)8743 }, { L"or", (WCHAR)8744 }, { L"cap", (WCHAR)8745 }, { L"cup", (WCHAR)8746 }, { L"int", (WCHAR)8747 }, { L"there4", (WCHAR)8756 }, { L"sim", (WCHAR)8764 }, { L"cong", (WCHAR)8773 }, { L"asymp", (WCHAR)8776 }, { L"ne", (WCHAR)8800 }, { L"equiv", (WCHAR)8801 }, { L"le", (WCHAR)8804 }, { L"ge", (WCHAR)8805 }, { L"sub", (WCHAR)8834 }, { L"sup", (WCHAR)8835 }, { L"nsub", (WCHAR)8836 }, { L"sube", (WCHAR)8838 }, { L"supe", (WCHAR)8839 }, { L"oplus", (WCHAR)8853 }, { L"otimes", (WCHAR)8855 }, { L"perp", (WCHAR)8869 }, { L"sdot", (WCHAR)8901 }, { L"lceil", (WCHAR)8968 }, { L"rceil", (WCHAR)8969 }, { L"lfloor", (WCHAR)8970 }, { L"rfloor", (WCHAR)8971 }, { L"lang", (WCHAR)9001 }, { L"rang", (WCHAR)9002 }, { L"loz", (WCHAR)9674 }, { L"spades", (WCHAR)9824 }, { L"clubs", (WCHAR)9827 }, { L"hearts", (WCHAR)9829 }, { L"diams", (WCHAR)9830 } }; const int entityCount = _countof(entities); g_CERs.rehash(entityCount); for (int i = 0; i < entityCount; ++i) { g_CERs.insert(std::pair(entities[i].name, entities[i].ch)); } // for DEBUG //std::map::const_iterator iter = g_CERs.begin(); //for ( ; iter != g_CERs.end(); ++iter) //{ // WCHAR buffer[64]; // wsprintf(buffer, L"%s - %c", (*iter).first.c_str(), (*iter).second); // RmLog(LOG_DEBUG, buffer); //} } void SetupGlobalProxySetting() { if (!g_ProxyCachePool) { WCHAR buffer[MAX_PATH] = {0}; LPCWSTR file = RmGetSettingsFile(); GetPrivateProfileString(L"WebParser.dll", L"ProxyServer", nullptr, buffer, MAX_PATH, file); g_ProxyCachePool = new ProxyCachePool(buffer); } } void ClearGlobalProxySetting() { delete g_ProxyCachePool; g_ProxyCachePool = nullptr; } void SetupProxySetting(ProxySetting& setting, void* rm) { if (g_ProxyCachePool) { setting.server = RmReadString(rm, L"ProxyServer", L""); setting.handle = g_ProxyCachePool->GetCache(setting.server); } } void ClearProxySetting(ProxySetting& setting) { if (g_ProxyCachePool) { g_ProxyCachePool->RemoveCache(setting.server); } setting.handle = nullptr; setting.server.clear(); } PLUGIN_EXPORT void Initialize(void** data, void* rm) { MeasureData* measure = new MeasureData; *data = measure; g_Measures.push_back(measure); measure->skin = RmGetSkin(rm); measure->rm = rm; if (g_InstanceCount == 0) { InitializeCriticalSection(&g_CriticalSection); FillCharacterEntityReferences(); SetupGlobalProxySetting(); } SetupProxySetting(measure->proxy, rm); // No support for DynamicVariables ++g_InstanceCount; } PLUGIN_EXPORT void Reload(void* data, void* rm, double* maxValue) { MeasureData* measure = (MeasureData*)data; EnterCriticalSection(&g_CriticalSection); /* Read our own settings from the ini-file */ std::wstring url = RmReadString(rm, L"Url", L"", FALSE); std::wstring::size_type start = 0; while ((start = url.find(L"[&", start)) != std::wstring::npos) { std::wstring::size_type si = start + 1; std::wstring::size_type end = url.find(L']', si); if (end == std::wstring::npos) break; std::wstring var = L"["; var.append(url, si + 1, end - si); const WCHAR* result = RmReplaceVariables(rm, var.c_str()); const size_t resultLength = wcslen(result); url.replace(start, end - start + 1, result, resultLength); start += resultLength; } measure->url = url; measure->regExp = RmReadString(rm, L"RegExp", L""); measure->finishAction = RmReadString(rm, L"FinishAction", L"", FALSE); measure->errorString = RmReadString(rm, L"ErrorString", L""); int index = RmReadInt(rm, L"StringIndex", 0); measure->stringIndex = index < 0 ? 0 : index; index = RmReadInt(rm, L"StringIndex2", 0); measure->stringIndex2 = index < 0 ? 0 : index; measure->decodeCharacterReference = RmReadInt(rm, L"DecodeCharacterReference", 0); measure->updateRate = RmReadInt(rm, L"UpdateRate", 600); measure->forceReload = 0!=RmReadInt(rm, L"ForceReload", 0); measure->codepage = RmReadInt(rm, L"CodePage", 0); measure->download = 0!=RmReadInt(rm, L"Download", 0); if (measure->download) { measure->downloadFolder = RmPathToAbsolute(rm, L"DownloadFile\\"); measure->downloadFile = RmReadString(rm, L"DownloadFile", L""); } else { measure->downloadFile.clear(); } measure->debug = RmReadInt(rm, L"Debug", 0); if (measure->debug == 2) { measure->debugFileLocation = RmReadPath(rm, L"Debug2File", L"WebParserDump.txt"); RmLog(rm, LOG_DEBUG, measure->debugFileLocation.c_str()); } LeaveCriticalSection(&g_CriticalSection); } PLUGIN_EXPORT double Update(void* data) { MeasureData* measure = (MeasureData*)data; double value = 0; if (measure->download && measure->regExp.empty() && measure->url.find(L'[') == std::wstring::npos) { // If RegExp is empty download the file that is pointed by the Url if (measure->dlThreadHandle == 0) { if (measure->updateCounter == 0) { // Launch a new thread to fetch the web data unsigned int id; HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, measure, 0, &id); if (threadHandle) { measure->dlThreadHandle = threadHandle; } } measure->updateCounter++; if (measure->updateCounter >= measure->updateRate) { measure->updateCounter = 0; } } // Else download the file pointed by the result string (this is done later) } else { // Make sure that the thread is not writing to the result at the same time EnterCriticalSection(&g_CriticalSection); if (!measure->resultString.empty()) { value = wcstod(measure->resultString.c_str(), nullptr); } LeaveCriticalSection(&g_CriticalSection); if (measure->url.size() > 0 && measure->url.find(L'[') == std::wstring::npos) { // This is not a reference; need to update. if (measure->threadHandle == 0 && measure->dlThreadHandle == 0) { if (measure->updateCounter == 0) { // Launch a new thread to fetch the web data unsigned int id; HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkThreadProc, measure, 0, &id); if (threadHandle) { measure->threadHandle = threadHandle; } } measure->updateCounter++; if (measure->updateCounter >= measure->updateRate) { measure->updateCounter = 0; } } } } return value; } // Fetches the data from the net and parses the page unsigned __stdcall NetworkThreadProc(void* pParam) { MeasureData* measure = (MeasureData*)pParam; DWORD dwSize = 0; RmLogF(measure->rm, LOG_DEBUG, L"WebParser: Fetching: %s", measure->url.c_str()); BYTE* data = DownloadUrl(measure->proxy.handle, measure->url, &dwSize, measure->forceReload); if (!data) { ShowError(measure->rm, L"Fetch error"); } else { if (measure->debug == 2) { // Dump to a file FILE* file = _wfopen(measure->debugFileLocation.c_str(), L"wb"); if (file) { fwrite(data, sizeof(BYTE), dwSize, file); fclose(file); } else { RmLog(measure->rm, LOG_ERROR, L"WebParser: Failed to dump debug data"); } } ParseData(measure, (LPCSTR)data, dwSize); free(data); } EnterCriticalSection(&g_CriticalSection); CloseHandle(measure->threadHandle); measure->threadHandle = 0; LeaveCriticalSection(&g_CriticalSection); return 0; // thread completed successfully } void ParseData(MeasureData* measure, LPCSTR parseData, DWORD dwSize) { // Parse the value from the data pcre* re; const char* error; int erroffset; int ovector[OVECCOUNT]; int rc; int flags = PCRE_UTF8; if (measure->codepage == 0) { flags = 0; } // Compile the regular expression in the first argument re = pcre_compile( StringUtil::NarrowUTF8(measure->regExp).c_str(), // the pattern flags, // default options &error, // for error message &erroffset, // for error offset nullptr); // use default character tables if (re != nullptr) { // Compilation succeeded: match the subject in the second argument std::string utf8Data; if (measure->codepage == 1200) // 1200 = UTF-16LE { // Must convert the data to utf8 utf8Data = StringUtil::NarrowUTF8((LPCWSTR)parseData, dwSize / 2); parseData = utf8Data.c_str(); dwSize = (DWORD)utf8Data.length(); } else if (measure->codepage != CP_UTF8 && measure->codepage != 0) // 0 = CP_ACP { // Must convert the data to utf8 utf8Data = ConvertAsciiToUTF8(parseData, dwSize, measure->codepage); parseData = utf8Data.c_str(); dwSize = (DWORD)utf8Data.length(); } rc = pcre_exec( re, // the compiled pattern nullptr, // no extra data - we didn't study the pattern parseData, // the subject string dwSize, // the length of the subject 0, // start at offset 0 in the subject 0, // default options ovector, // output vector for substring information OVECCOUNT); // number of elements in the output vector if (rc >= 0) { if (rc == 0) { // The output vector wasn't big enough RmLog(measure->rm, LOG_ERROR, L"WebParser: Too many substrings"); } else { if (measure->stringIndex < rc) { if (measure->debug != 0) { for (int i = 0; i < rc; ++i) { const char* substring_start = parseData + ovector[2 * i]; int substring_length = ovector[2 * i + 1] - ovector[2 * i]; substring_length = min(substring_length, 256); const std::wstring value = StringUtil::WidenUTF8(substring_start, substring_length); RmLogF(measure->rm, LOG_DEBUG, L"WebParser: Index %2d: %s", i, value.c_str()); } } const char* substring_start = parseData + ovector[2 * measure->stringIndex]; int substring_length = ovector[2 * measure->stringIndex + 1] - ovector[2 * measure->stringIndex]; EnterCriticalSection(&g_CriticalSection); measure->resultString = StringUtil::WidenUTF8(substring_start, substring_length); DecodeReferences(measure->resultString, measure->decodeCharacterReference); LeaveCriticalSection(&g_CriticalSection); } else { RmLog(measure->rm, LOG_WARNING, L"WebParser: Not enough substrings"); // Clear the old result EnterCriticalSection(&g_CriticalSection); measure->resultString.clear(); if (measure->download) { if (measure->downloadFile.empty()) // cache mode { if (!measure->downloadedFile.empty()) { // Delete old downloaded file DeleteFile(measure->downloadedFile.c_str()); } } measure->downloadedFile.clear(); } LeaveCriticalSection(&g_CriticalSection); } // Update the references std::vector::iterator i = g_Measures.begin(); std::wstring compareStr = L"["; compareStr += RmGetMeasureName(measure->rm); compareStr += L']'; for ( ; i != g_Measures.end(); ++i) { if (measure->skin == (*i)->skin && StringUtil::CaseInsensitiveFind((*i)->url, compareStr) != std::wstring::npos) { if ((*i)->stringIndex < rc) { const char* substring_start = parseData + ovector[2 * (*i)->stringIndex]; int substring_length = ovector[2 * (*i)->stringIndex + 1] - ovector[2 * (*i)->stringIndex]; if (!(*i)->regExp.empty()) { // Change the index and parse the substring int index = (*i)->stringIndex; (*i)->stringIndex = (*i)->stringIndex2; ParseData((*i), substring_start, substring_length); (*i)->stringIndex = index; } else { // Set the result EnterCriticalSection(&g_CriticalSection); // Substitude the [measure] with result std::wstring result = StringUtil::WidenUTF8(substring_start, substring_length); (*i)->resultString = (*i)->url; (*i)->resultString.replace( StringUtil::CaseInsensitiveFind((*i)->resultString, compareStr), compareStr.size(), result); DecodeReferences((*i)->resultString, (*i)->decodeCharacterReference); // Start download threads for the references if ((*i)->download) { // Start the download thread unsigned int id; HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, (*i), 0, &id); if (threadHandle) { (*i)->dlThreadHandle = threadHandle; } } LeaveCriticalSection(&g_CriticalSection); } } else { RmLog((*i)->rm, LOG_WARNING, L"WebParser: Not enough substrings"); // Clear the old result EnterCriticalSection(&g_CriticalSection); (*i)->resultString.clear(); if ((*i)->download) { if ((*i)->downloadFile.empty()) // cache mode { if (!(*i)->downloadedFile.empty()) { // Delete old downloaded file DeleteFile((*i)->downloadedFile.c_str()); } } (*i)->downloadedFile.clear(); } LeaveCriticalSection(&g_CriticalSection); } } } } } else { // Matching failed: handle error cases RmLogF(measure->rm, LOG_ERROR, L"WebParser: RegExp matching error (%d)", rc); EnterCriticalSection(&g_CriticalSection); measure->resultString = measure->errorString; // Update the references std::vector::iterator i = g_Measures.begin(); std::wstring compareStr = L"["; compareStr += RmGetMeasureName(measure->rm); compareStr += L']'; for ( ; i != g_Measures.end(); ++i) { if ((StringUtil::CaseInsensitiveFind((*i)->url, compareStr) != std::wstring::npos) && (measure->skin == (*i)->skin)) { (*i)->resultString = (*i)->errorString; } } LeaveCriticalSection(&g_CriticalSection); } // Release memory used for the compiled pattern pcre_free(re); } else { // Compilation failed. RmLogF(measure->rm, LOG_ERROR, L"WebParser: RegExp error at offset %d: %S", erroffset, error); } if (measure->download) { // Start the download thread unsigned int id; HANDLE threadHandle = (HANDLE)_beginthreadex(nullptr, 0, NetworkDownloadThreadProc, measure, 0, &id); if (threadHandle) { measure->dlThreadHandle = threadHandle; } } else { if (!measure->finishAction.empty()) { RmExecute(measure->skin, measure->finishAction.c_str()); } } } // Downloads file from the net unsigned __stdcall NetworkDownloadThreadProc(void* pParam) { MeasureData* measure = (MeasureData*)pParam; const bool download = !measure->downloadFile.empty(); bool ready = false; std::wstring url; if (measure->regExp.empty() && measure->resultString.empty()) { if (!measure->url.empty() && measure->url[0] != L'[') { url = measure->url; } } else { EnterCriticalSection(&g_CriticalSection); url = measure->resultString; LeaveCriticalSection(&g_CriticalSection); std::wstring::size_type pos = url.find(L':'); if (pos == std::wstring::npos && !url.empty()) // No protocol { // Add the base url to the string if (url[0] == L'/') { // Absolute path pos = measure->url.find(L'/', 7); // Assume "http://" (=7) if (pos != std::wstring::npos) { std::wstring path(measure->url.substr(0, pos)); url = path + url; } } else { // Relative path pos = measure->url.rfind(L'/'); if (pos != std::wstring::npos) { std::wstring path(measure->url.substr(0, pos + 1)); url = path + url; } } } } if (!url.empty()) { // Create the filename WCHAR buffer[MAX_PATH] = {0}; std::wstring fullpath, directory; if (download) // download mode { PathCanonicalize(buffer, measure->downloadFile.c_str()); std::wstring path = buffer; std::wstring::size_type pos = path.find_first_not_of(L'\\'); if (pos != std::wstring::npos) { path.erase(0, pos); } PathCanonicalize(buffer, measure->downloadFolder.c_str()); CreateDirectory(buffer, nullptr); // Make sure that the folder exists wcscat(buffer, path.c_str()); if (buffer[wcslen(buffer)-1] != L'\\') // path is a file { fullpath = buffer; PathRemoveFileSpec(buffer); } PathAddBackslash(buffer); } else // cache mode { GetTempPath(MAX_PATH, buffer); wcscat(buffer, L"Rainmeter-Cache\\"); // "%TEMP%\Rainmeter-Cache\" } CreateDirectory(buffer, nullptr); // Make sure that the folder exists directory = buffer; if (fullpath.empty()) { fullpath = directory; std::wstring::size_type pos2 = url.find_first_of(L"?#"); std::wstring::size_type pos1 = url.find_last_of(L'/', pos2); pos1 = (pos1 != std::wstring::npos) ? pos1 + 1 : 0; std::wstring name; if (pos2 != std::wstring::npos) { name.assign(url, pos1, pos2 - pos1); } else { name.assign(url, pos1, url.length() - pos1); } if (!name.empty()) { // Replace reserved characters to "_" pos1 = 0; while ((pos1 = name.find_first_of(L"\\/:*?\"<>|", pos1)) != std::wstring::npos) { name[pos1] = L'_'; } fullpath += name; } else { fullpath += L"index"; } } ready = true; if (download) // download mode { if (!PathFileExists(directory.c_str()) || !PathIsDirectory(directory.c_str())) { ready = false; RmLogF( measure->rm, LOG_ERROR, L"WebParser: Directory does not exist: %s", directory.c_str()); } else if (PathIsDirectory(fullpath.c_str())) { ready = false; RmLogF( measure->rm, LOG_ERROR, L"WebParser: Path is a directory, not a file: %s", fullpath.c_str()); } else if (PathFileExists(fullpath.c_str())) { DWORD attr = GetFileAttributes(fullpath.c_str()); if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY)) { ready = false; RmLogF( measure->rm, LOG_ERROR, L"WebParser: File is read-only: %s", fullpath.c_str()); } } } else // cache mode { EnterCriticalSection(&g_CriticalSection); if (PathFileExists(fullpath.c_str())) { std::wstring::size_type pos = fullpath.find_last_of(L'.'); std::wstring path, ext; if (pos != std::wstring::npos) { path.assign(fullpath, 0, pos); ext.assign(fullpath, pos, fullpath.length() - pos); } else { path = fullpath; } // Assign a serial number int i = 1; do { wsprintf(buffer, L"_%i", i++); fullpath = path; fullpath += buffer; if (!ext.empty()) { fullpath += ext; } } while (PathFileExists(fullpath.c_str())); } // Create empty file HANDLE hFile = CreateFile(fullpath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile); LeaveCriticalSection(&g_CriticalSection); } if (ready) { // Delete IE cache before download if "SyncMode5" is not 3 (every visit to the page) { // Check "Temporary Internet Files" sync mode (SyncMode5) // Values: // Every visit to the page 3 // Every time you start Internet Explorer 2 // Automatically (default) 4 // Never 0 // http://support.microsoft.com/kb/263070/en HKEY hKey; LONG ret; DWORD mode; ret = RegOpenKeyEx(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings", 0, KEY_QUERY_VALUE, &hKey); if (ret == ERROR_SUCCESS) { DWORD size = sizeof(mode); ret = RegQueryValueEx(hKey, L"SyncMode5", nullptr, nullptr, (LPBYTE)&mode, &size); RegCloseKey(hKey); } if (ret != ERROR_SUCCESS || mode != 3) { std::wstring::size_type pos = url.find_first_of(L'#'); if (pos != std::wstring::npos) { DeleteUrlCacheEntry(url.substr(0, pos).c_str()); } else { DeleteUrlCacheEntry(url.c_str()); } } } RmLogF( measure->rm, LOG_DEBUG, L"WebParser: Downloading url '%s' to: %s", url.c_str(), fullpath.c_str()); HRESULT resultCoInitialize = CoInitialize(nullptr); // requires before calling URLDownloadToFile function // Download the file HRESULT result = URLDownloadToFile(nullptr, url.c_str(), fullpath.c_str(), 0, nullptr); if (result == S_OK) { EnterCriticalSection(&g_CriticalSection); if (!download) // cache mode { if (!measure->downloadedFile.empty()) { // Delete old downloaded file DeleteFile(measure->downloadedFile.c_str()); } } // Convert LFN to 8.3 filename if the path contains blank character if (fullpath.find_first_of(L' ') != std::wstring::npos) { DWORD size = GetShortPathName(fullpath.c_str(), buffer, MAX_PATH); if (size > 0 && size <= MAX_PATH) { fullpath = buffer; } } measure->downloadedFile = fullpath; LeaveCriticalSection(&g_CriticalSection); if (!measure->finishAction.empty()) { RmExecute(measure->skin, measure->finishAction.c_str()); } } else { ready = false; if (!download) // cache mode { // Delete empty file DeleteFile(fullpath.c_str()); } RmLogF( measure->rm, LOG_ERROR, L"WebParser: Download failed (res=0x%08X, COM=0x%08X): %s", result, resultCoInitialize, url.c_str()); } if (SUCCEEDED(resultCoInitialize)) { CoUninitialize(); } } else { RmLogF(measure->rm, LOG_ERROR, L"WebParser: Download failed: %s", url.c_str()); } } else { RmLog(measure->rm, LOG_ERROR, L"WebParser: Url is empty"); } if (!ready) // download failed { EnterCriticalSection(&g_CriticalSection); if (!download) // cache mode { if (!measure->downloadedFile.empty()) { // Delete old downloaded file DeleteFile(measure->downloadedFile.c_str()); } } // Clear old downloaded filename measure->downloadedFile.clear(); LeaveCriticalSection(&g_CriticalSection); } EnterCriticalSection(&g_CriticalSection); CloseHandle(measure->dlThreadHandle); measure->dlThreadHandle = 0; LeaveCriticalSection(&g_CriticalSection); return 0; // thread completed successfully } PLUGIN_EXPORT LPCWSTR GetString(void* data) { MeasureData* measure = (MeasureData*)data; static std::wstring resultString; EnterCriticalSection(&g_CriticalSection); if (measure->download) { resultString = measure->downloadedFile; } else { resultString = measure->resultString; } LeaveCriticalSection(&g_CriticalSection); return resultString.c_str(); } PLUGIN_EXPORT void Finalize(void* data) { MeasureData* measure = (MeasureData*)data; if (measure->threadHandle) { // Thread is killed inside critical section so that itself is not inside one when it is terminated EnterCriticalSection(&g_CriticalSection); TerminateThread(measure->threadHandle, 0); measure->threadHandle = nullptr; LeaveCriticalSection(&g_CriticalSection); } if (measure->dlThreadHandle) { // Thread is killed inside critical section so that itself is not inside one when it is terminated EnterCriticalSection(&g_CriticalSection); TerminateThread(measure->dlThreadHandle, 0); measure->dlThreadHandle = nullptr; LeaveCriticalSection(&g_CriticalSection); } if (measure->downloadFile.empty()) // cache mode { if (!measure->downloadedFile.empty()) { // Delete the file DeleteFile(measure->downloadedFile.c_str()); } } ClearProxySetting(measure->proxy); delete measure; std::vector::iterator iter = std::find(g_Measures.begin(), g_Measures.end(), measure); g_Measures.erase(iter); --g_InstanceCount; if (g_InstanceCount == 0) { // Last one, close all handles ClearGlobalProxySetting(); g_CERs.clear(); // Last instance deletes the critical section DeleteCriticalSection(&g_CriticalSection); } } /* Downloads the given url and returns the webpage as dynamically allocated string. You need to free the returned string after use! */ BYTE* DownloadUrl(HINTERNET handle, std::wstring& url, DWORD* dataSize, bool forceReload) { DWORD flags = INTERNET_FLAG_RESYNCHRONIZE; if (forceReload) { flags = INTERNET_FLAG_RELOAD; } HINTERNET hUrlDump = InternetOpenUrl(handle, url.c_str(), nullptr, 0, flags, 0); if (!hUrlDump) { if (_wcsnicmp(url.c_str(), L"file://", 7) == 0) // file scheme { const std::string urlACP = StringUtil::Narrow(url); hUrlDump = InternetOpenUrlA(handle, urlACP.c_str(), nullptr, 0, flags, 0); } if (!hUrlDump) { return nullptr; } } // Allocate buffer with 3 extra bytes for triple null termination in case the string is // invalid (e.g. when incorrectly using the UTF-16LE codepage for the data). const int CHUNK_SIZE = 8192; DWORD bufferSize = CHUNK_SIZE; BYTE* buffer = (BYTE*)malloc(bufferSize + 3); *dataSize = 0; // Read the data. do { DWORD readSize; if (!InternetReadFile(hUrlDump, buffer + *dataSize, bufferSize - *dataSize, &readSize)) { free(buffer); InternetCloseHandle(hUrlDump); return nullptr; } else if (readSize == 0) { // All data read. break; } *dataSize += readSize; bufferSize += CHUNK_SIZE; buffer = (BYTE*)realloc(buffer, bufferSize + 3); } while (true); InternetCloseHandle(hUrlDump); // Triple null terminate the buffer. buffer[*dataSize] = 0; buffer[*dataSize + 1] = 0; buffer[*dataSize + 2] = 0; return buffer; } /* Writes the last error to log. */ void ShowError(void* rm, WCHAR* description) { DWORD dwErr = GetLastError(); if (dwErr == ERROR_INTERNET_EXTENDED_ERROR) { WCHAR szBuffer[1024]; DWORD dwError, dwLen = 1024; const WCHAR* error = L"Unknown error"; if (InternetGetLastResponseInfo(&dwError, szBuffer, &dwLen)) { error = szBuffer; dwErr = dwError; } RmLogF(rm, LOG_ERROR, L"WebParser: (%s) %s (ErrorCode=%i)", description, error, dwErr); } else { LPVOID lpMsgBuf = nullptr; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_MAX_WIDTH_MASK, GetModuleHandle(L"wininet"), dwErr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language (LPTSTR) &lpMsgBuf, 0, nullptr ); const WCHAR* error = lpMsgBuf ? (WCHAR*)lpMsgBuf : L"Unknown error"; RmLogF(rm, LOG_ERROR, L"WebParser: (%s) %s (ErrorCode=%i)", description, error, dwErr); if (lpMsgBuf) LocalFree(lpMsgBuf); } } PLUGIN_EXPORT void ExecuteBang(void* data, LPCWSTR args) { MeasureData* measure = (MeasureData*)data; // Kill the threads (if any) and reset the update counter if (_wcsicmp(args, L"UPDATE") == 0) { if (measure->threadHandle) { // Thread is killed inside critical section so that itself is not inside one when it is terminated EnterCriticalSection(&g_CriticalSection); TerminateThread(measure->threadHandle, 0); measure->threadHandle = nullptr; LeaveCriticalSection(&g_CriticalSection); } if (measure->dlThreadHandle) { // Thread is killed inside critical section so that itself is not inside one when it is terminated EnterCriticalSection(&g_CriticalSection); TerminateThread(measure->dlThreadHandle, 0); measure->dlThreadHandle = nullptr; LeaveCriticalSection(&g_CriticalSection); } measure->updateCounter = 0; } }