#ifndef __DUALEA_POLICY_HTTP_BRIDGE_MQH__ #define __DUALEA_POLICY_HTTP_BRIDGE_MQH__ #include #include "LogMiddleware.mqh" class CPolicyHttpBridge { private: string m_policy_file; // where EA reads policy from (Common Files path allowed) int m_reload_secs; // minimum interval between checks datetime m_last_check; long m_last_size; bool m_warned_whitelist; public: CPolicyHttpBridge(): m_policy_file(""), m_reload_secs(10), m_last_check(0), m_last_size(-1), m_warned_whitelist(false) {} void Configure(const string policy_file, const int reload_secs) { m_policy_file = policy_file; m_reload_secs = (reload_secs>0? reload_secs:10); m_last_check = 0; m_last_size = -1; } bool VerifyAccess(const string url) { string trimmed = url; StringTrimLeft(trimmed); StringTrimRight(trimmed); if(StringLen(trimmed) == 0) return true; char payload[]; ArrayResize(payload, 0); char response[]; string response_headers = ""; int status = WebRequest("HEAD", trimmed, "", 3000, payload, response, response_headers); if(status == -1) { int err = GetLastError(); if((err == 4014 || err == 5204) && !m_warned_whitelist) { string guidance = StringFormat( "Policy HTTP access blocked (error %d). Enable WebRequest in MT5: Tools → Options → Expert Advisors → 'Allow WebRequest for listed URL' and add %s. Then press OK and restart the EA.", err, trimmed); LOG(guidance); m_warned_whitelist = true; } return false; } if(status >= 400 && !m_warned_whitelist) { LOG(StringFormat("Policy HTTP verification returned status %d for %s", status, trimmed)); } return (status >= 200 && status < 500); } // Returns true if the on-disk policy file size changed since last check bool UpdateIfStale() { datetime now = TimeCurrent(); if(m_policy_file=="") return false; if(m_last_check!=0 && (now - m_last_check) < m_reload_secs) return false; m_last_check = now; // Check file in Common first int h = FileOpen(m_policy_file, FILE_READ|FILE_TXT|FILE_COMMON); if(h == INVALID_HANDLE) { // Fall back to user Files h = FileOpen(m_policy_file, FILE_READ|FILE_TXT); if(h == INVALID_HANDLE) { // File absent is not an error; simply no update return false; } } long sz = (long)FileSize(h); FileClose(h); if(sz != m_last_size) { m_last_size = sz; LOG(StringFormat("Policy file changed, size=%ld", sz)); return true; } return false; } // Pull policy JSON from HTTP endpoint and write to file. Requires MT5 WebRequest whitelist. bool Pull(const string url) { if(m_policy_file=="") return false; string headers = "Content-Type: application/json\r\n"; char data[]; ArrayResize(data, 0); char result[]; string result_headers; int status = WebRequest("GET", url, headers, 5000, data, result, result_headers); if(status==200 || status==201) { string body = CharArrayToString(result, 0, -1); int h = FileOpen(m_policy_file, FILE_WRITE|FILE_TXT|FILE_COMMON); if(h == INVALID_HANDLE) { h = FileOpen(m_policy_file, FILE_WRITE|FILE_TXT); } if(h == INVALID_HANDLE) { LOG(StringFormat("PolicyHttpBridge: Cannot open %s for write, error=%d", m_policy_file, GetLastError())); return false; } FileWriteString(h, body); FileClose(h); LOG(StringFormat("Policy pull success: %s (len=%d)", url, StringLen(body))); // Force stale so UpdateIfStale will report change on next call m_last_size = -1; return true; } if(status==-1) { LOG(StringFormat("Policy pull failed: %s (error=%d). Ensure URL is whitelisted in MT5 WebRequest.", url, GetLastError())); } else { LOG(StringFormat("Policy pull HTTP status: %d for %s", status, url)); } return false; } }; #endif // __DUALEA_POLICY_HTTP_BRIDGE_MQH__