////////////////////////////////////////////////////////////////////// // wisapi: WISAPI extension library. #include #include #include #include #pragma hdrstop #include "version.h" using namespace Upp; #define TFILE "wisapi.t" #include enum { DEFAULT_PORT = 1571 }; static void AddHeaders(HttpQuery& query, const char *h) { while(*h) if((byte)*h > ' ') { String var; for(; *h && *h != ':'; h++) if(IsAlNum(*h)) var.Cat(ToUpper(*h)); else if(*h == '-' && !var.IsEmpty() && *var.Last() != '_') var.Cat('_'); String value; LOG("var = " << var); if(*h == ':') { while((byte)*++h <= ' ' && *h && *h != '\n') ; const char *b = h; while(*h && *h++ != '\n') ; const char *e = h; while(e > b && (byte)e[-1] < ' ') e--; value = String(b, e); LOG("value = " << value); } if(!IsNull(var)) { var.Insert(0, "$$"); String old = query.GetString(var); if(!IsNull(old) && !IsNull(value)) { old << ", " << value; value = old; } LOG("AddHeaders: " << var << "=" << value); query.Set(var, value); } } else h++; } class CheckedSection { public: CheckedSection() : lock(0) { InitializeCriticalSection(§ion); } ~CheckedSection() { DeleteCriticalSection(§ion); } void Enter() { EnterCriticalSection(§ion); AtomicInc(lock); } void Leave() { LeaveCriticalSection(§ion); AtomicDec(lock); } int Get() const { return lock; } class Lock { public: Lock(CheckedSection& cs) : cs(cs) { cs.Enter(); } ~Lock() { cs.Leave(); } private: CheckedSection& cs; }; private: CRITICAL_SECTION section; Atomic lock; }; /* class SHandle { public: SHandle(SC_HANDLE handle = 0) : handle(handle) {} ~SHandle() { if(handle) CloseServiceHandle(handle); } operator SC_HANDLE () const { return handle; } void Clear() { Attach(0); } SC_HANDLE Detach() { SC_HANDLE h = handle; handle = 0; return h; } void Attach(SC_HANDLE h) { if(handle) CloseServiceHandle(handle); handle = h; } SHandle& operator = (SC_HANDLE h) { Attach(h); return *this; } bool Query(SERVICE_STATUS& s) { return handle && QueryServiceStatus(handle, &s); } private: SC_HANDLE handle; }; struct ServiceInfo { String display_name; String service_name; dword state; }; template class Moveable; static Array EnumServices(SHandle& manager) { dword resume_handle = 0; dword more = 0; bool cont; Array list; do { dword done; union { ENUM_SERVICE_STATUS status[1]; char buffer[10000]; }; bool result = EnumServicesStatus(manager, SERVICE_WIN32, SERVICE_STATE_ALL, status, sizeof(buffer), &more, &done, &resume_handle); cont = (!result && GetLastError() == ERROR_MORE_DATA); if(!result && !cont) break; for(int i = 0; i < done; i++) { ServiceInfo& info = list.Add(); info.display_name = status[i].lpDisplayName; info.service_name = status[i].lpServiceName; info.state = status[i].ServiceStatus.dwCurrentState; } } while(cont); return list; } */ static HtmlTag HtmlSimpleRow() { return HtmlRow() % HtmlCell().ColSpan(2).Width(-100); } static Htmls HtmlSplitRow(const char *tag, const Htmls& content) { return HtmlRow() % (HtmlCell().Width(-30) / tag + "\n" + HtmlCell().Left().Width(-70) / content); } static Htmls HtmlValueRow(const char *tag, const Value& value) { return HtmlSplitRow(tag, HtmlBold() / StdFormat(value)); } static Htmls HtmlMenu(bool active, const char *text, const char *url, int wd) { HtmlTag table = HtmlTable().CellSpacing(0).Border(2) .BorderColor(Black).BgColor(active ? Yellow : WhiteGray); if(wd) table.Width(wd); return table % HtmlRow() % HtmlCell().Center() / HtmlLink(url) / HtmlFontColor(Black) / (active ? HtmlBold() / text : Htmls(text)); } static Htmls HtmlHardSpace(int count = 1) { StringBuffer s(count * 6); char *p = s; for(int i = 0; i < count; i++) { memcpy(p, " ", 6); p += 6; } return String(s); } static HtmlTag HtmlIsapiTable() { return HtmlTable().Width(-100).CellSpacing(0).CellPadding(1).Border(1).BgColor(WhiteGray) % HtmlRow() % HtmlCell() % HtmlTable().Width(-100).CellSpacing(2).CellPadding(0).Border(0).BgColor(WhiteGray); } static Htmls HtmlButton(const char *text, const char *id) { return HtmlSubmit(text).Name(id); } static String ReadServerVariable(EXTENSION_CONTROL_BLOCK *ecb, const char *varname) { byte buffer[1024]; DWORD needed = __countof(buffer); if(ecb->GetServerVariable(ecb->ConnID, (char *)varname, buffer, &needed)) return String(buffer, (int)needed - 1); StringBuffer strbuf(needed); if(ecb->GetServerVariable(ecb->ConnID, (char *)varname, strbuf, &needed)) { strbuf.SetLength((int)needed - 1); return strbuf; } return String::GetVoid(); } static void WriteHeader(EXTENSION_CONTROL_BLOCK *ecb, const char *response, const char *header) { // LOGHEXDUMP(header, strlen(header)); LOG("WriteHeader(response = " << (response ? response : "(null)") << ", headers = " << (header ? header : "(null)") << ")"); ecb->ServerSupportFunction(ecb->ConnID, HSE_REQ_SEND_RESPONSE_HEADER, (void *)response, 0, (dword *)header); } static void WriteClient(EXTENSION_CONTROL_BLOCK *ecb, const void *data, int count) { dword c = (count >= 0 ? count : (int)strlen((const char *)data)); // LOG("client output (" << (int)c << "bytes): " << (const char *)data << "(eocd)"); // LOGHEXDUMP(data, count); // LOG("(eocd raw)"); ecb->WriteClient(ecb->ConnID, (void *)data, &c, HSE_IO_SYNC); } static void WriteClient(EXTENSION_CONTROL_BLOCK *ecb, const String& data) { WriteClient(ecb, data, data.GetLength()); } static HINSTANCE dll_module; static String dll_filename; static bool log_isapi = true; static bool show_headers = false; static bool remote_admin = true; static bool isapi_english = true; static int isapi_timeout = 5000; static long log_id = 0; static CheckedSection log_lock; static int retry_msec = 1000; static void WriteLog(const String& s) { CheckedSection::Lock guard(log_lock); Stream& strm = VppLog(); int i = GetTickCount(); int l = InterlockedIncrement(&log_id); String out = NFormat("%d.%d, #%d, T%d: %s\n", i / 1000, (i / 100) % 10, l, (int)GetCurrentThreadId(), s); strm.Put(out); strm.Flush(); } #define ISAPILOG(x) if(log_isapi) WriteLog(String() << x); else ; #define ISAPIDEBUG(x) if(log_isapi) { ISAPILOG("<< " #x); x; ISAPILOG(">> " #x); } else { x; } //timeout_title[] = "Chyba: server je přetížen", //timeout_desc[] = "Připojení k internetovému serveru se nezdařilo,\n" //"protože server je plně vytížen vyřizováním požadavků ostatních klientů.\n" //"\n" //"

\n" //"Zkuste se prosím připojit později.\n"; static String GetError(const char *title, const char *desc) { return String() << "\n" "\n" "\n" "" << title << "\n" "\n" "\n" "

" << title << "

\n" "

\n" << desc << "\n" "\n" "\n"; } static String Trim(const char *s, const char *e) { while(s < e && (byte)*s <= ' ') s++; while(e > s && (byte)e[-1] <= ' ') e--; return String(s, e); } class WIsapiClient { public: WIsapiClient(); ~WIsapiClient(); dword Run(int cid, EXTENSION_CONTROL_BLOCK *ecb); private: void ReadPostData(HttpQuery& query, EXTENSION_CONTROL_BLOCK *ecb); bool Run(int cid, const HttpQuery& query, EXTENSION_CONTROL_BLOCK *ecb); Htmls Config(const HttpQuery& query); Htmls Body(String english); Htmls Configuration(String english); Htmls Login(); Htmls English(); Htmls Logging(); Htmls ShowHeaders(); Htmls Timing(); Htmls RemoteAdmin(); // HtmlTag Edit(Id id, int& value); Htmls Servers(); // Htmls Commit(); Htmls Statistics(); Htmls Services(); Htmls ServiceTable(); bool CanWrite(); void AddHost(const String& host); void RemoveHost(const String& host); void LoadConfig(); void SaveConfig(); private: class Connection { public: Connection(WIsapiClient& owner, const String& raw_host); bool Run(int conn_id, const HttpQuery& query, One& owner_lock, EXTENSION_CONTROL_BLOCK *ecb); String RawRun(const HttpQuery& query, EXTENSION_CONTROL_BLOCK *ecb, int& data_length); void Kill(); void Status(); void CONNLOG(const String& log); public: // configuration String raw_host; String host; int port; String status; dword retry_ticks; bool is_error; bool is_busy; // statistics Time run_time; double hit_count; int last_msecs; double total_msecs; double total_bytes; int max_msecs; double error_count; // state parameters int conn_id; CheckedSection conn_lock; WIsapiClient& owner; HttpQuery query; Socket socket; // server connection }; friend class Connection; Array connections; CheckedSection client_lock; Event free_event; Time run_time; double hit_count; double total_msecs; double total_bytes; double max_msecs; double error_count; HttpQuery cfg_query; String cfg_filename; String cfg_default_host; String cfg_error; String cfg_url; String cfg_password; int cfg_can_write; }; WIsapiClient::Connection::Connection(WIsapiClient& owner, const String& raw_host) : owner(owner), raw_host(raw_host) { const char *p = raw_host; while(*p && *p != '@') p++; host = String(raw_host.Begin(), p); port = DEFAULT_PORT; if(*p++ == '@' && IsDigit(*p)) port = stou(p); hit_count = total_msecs = total_bytes = error_count = 0; max_msecs = 0; last_msecs = 0; run_time = GetSysTime(); is_busy = false; is_error = false; retry_ticks = GetTickCount(); Status(); } void WIsapiClient::Connection::CONNLOG(const String& log) { ISAPILOG("Connection(" << conn_id << "): " << log); } void WIsapiClient::Connection::Status() { ISAPILOG("Connection::Status->OpenClient"); dword ticks = GetTickCount(); String response; ISAPILOG("Connection::Status->OpenClient, opening socket"); if(ClientSocket(socket, host, port)) { ISAPILOG("Connection::Status->ClientSocket open, sending STAT"); socket.Write("STAT\n"); response = socket.ReadUntil('\n'); if(!socket.Close()) { status = Socket::GetErrorText(); ISAPILOG("Connection::Status->" << status); is_error = true; } else { ISAPILOG("-> response=" << response); if(!strnicmp(response, "READY ", 6)) { status = response.Mid(6); is_error = false; return; } status = t_("Invalid server response: ") + response; } } else { ISAPILOG("Connection::Status->ClientSocket failed to open, " << Socket::GetErrorText()); status = NFormat(t_("Error opening socket %s:%d: %s"), host, port, Socket::GetErrorText()); } is_error = true; dword new_ticks = GetTickCount(); retry_ticks = new_ticks + minmax(retry_msec, 10 * (int)(new_ticks - ticks), 60000); ISAPILOG("retry ticks = " << (int)retry_ticks); } bool WIsapiClient::Connection::Run(int cid, const HttpQuery& new_query, One& owner_lock, EXTENSION_CONTROL_BLOCK *ecb) { { CheckedSection::Lock conn_guard(conn_lock); if(is_busy) { CONNLOG("connection busy, ignoring new request " + FormatInt(cid)); return false; } if(is_error) { int delta = GetTickCount() - retry_ticks; ISAPILOG("WIsapiClient::Connection; retry in " << delta); if(delta >= 0) Status(); if(is_error) return false; } conn_id = cid; is_busy = true; } CONNLOG(String() << "before leaving client lock: " << owner.client_lock.Get()); CONNLOG(String() << "owner lock = " << FormatIntHex(~owner_lock)); owner_lock.Clear(); // owner.client_lock.Leave(); CONNLOG(String() << "after leaving client lock: " << owner.client_lock.Get()); dword start_ticks = GetTickCount(); int data_length = 0; String error = RawRun(new_query, ecb, data_length); CONNLOG(String() << "before re-entering client lock: " << owner.client_lock.Get()); owner_lock = new CheckedSection::Lock(owner.client_lock); CONNLOG(String() << "after re-entering client lock: " << owner.client_lock.Get()); // owner.client_lock.Enter(); bool is_ok = IsNull(error); if(is_error = !is_ok) { CONNLOG(error); socket.Close(); } { CheckedSection::Lock conn_guard(conn_lock); hit_count++; owner.hit_count++; if(is_ok) { last_msecs = GetTickCount() - start_ticks; total_msecs += last_msecs; owner.total_msecs += last_msecs; total_bytes += data_length; owner.total_bytes += data_length; if(last_msecs > max_msecs) max_msecs = last_msecs; if(last_msecs > owner.max_msecs) owner.max_msecs = last_msecs; } else { error_count++; owner.error_count++; is_busy = false; } } if(is_ok) owner.free_event.Set(); return true; } String WIsapiClient::Connection::RawRun(const HttpQuery& new_query, EXTENSION_CONTROL_BLOCK *ecb, int& data_length) { CONNLOG("OpenClient"); if(!ClientSocket(socket, host, port)) return Socket::GetErrorText(); CONNLOG(String() << "OpenClient->socket " << socket.GetNumber()); query = new_query; StringStream out; out.Put("SAPI"); out.PutIL(0); out % query; out.Seek(4); out.PutIL((int)(out.GetSize() - 8)); socket.Write(out.GetResult()); // socket.StopWrite(); CONNLOG("executing"); String line = socket.ReadUntil('\n'); if(socket.IsError()) return Socket::GetErrorText(); CONNLOG("-> response=" + line); int trim = line.GetLength(); while(trim > 0 && line[trim - 1] <= ' ') trim--; line.Trim(trim); if(trim < 5 || memicmp(line, "HTTP/", 5)) return NFormat(t_("Invalid server response: %s\n" "
...\n"), HtmlTag("TT") / ToHtml(line)); const char *p = line.Begin() + 5; while(*p && *p != ' ') p++; String status_line; if(*p == ' ' && IsDigit(*++p)) { status_line = p; } int content_length = 0; String headers; for(;;) { line = socket.ReadUntil('\n'); if(socket.IsError()) return Socket::GetErrorText(); CONNLOG(String() << "-> header=" << line); for(p = line; *p && (byte)*p <= ' '; p++) ; const char *b = p, *e = line.End(); while(e > b && (byte)e[-1] < ' ') e--; if(b >= e) { headers.Cat("\r\n"); break; } static const char cl[] = "content-length:"; enum { CL_LENGTH = sizeof(cl) - 1, MAX_DATA_SIZE = 10000000, }; if(!strnicmp(p, cl, CL_LENGTH)) { for(p += CL_LENGTH; *p == ' '; p++) ; if(IsDigit(*p)) content_length = minmax(stou(p), 0, MAX_DATA_SIZE); } headers.Cat(b, (int)(e - b)); headers.Cat("\r\n"); } String body; while(body.GetLength() < content_length) { String part = socket.Read(isapi_timeout); if(part.IsEmpty()) return NFormat("Partial input received, %d out of %d for #%d", body.GetLength(), content_length, conn_id); body.Cat(part); } CONNLOG(String() << "-> transmitted " << body.GetLength() << " bytes"); socket.Close(); int old_id = conn_id; { CheckedSection::Lock guard(conn_lock); is_busy = false; CONNLOG("-> connection freed"); } owner.free_event.Set(); WriteHeader(ecb, !IsNull(status_line) ? ~status_line : NULL, headers); WriteClient(ecb, body); data_length = headers.GetLength() + body.GetLength(); return Null; } WIsapiClient::WIsapiClient() { RLOGBLOCK("WIsapiClient constructor"); cfg_filename = ForceExt(dll_filename, ".cfg"); cfg_default_host << "localhost@" << (int)DEFAULT_PORT; hit_count = total_msecs = total_bytes = max_msecs = error_count = 0; run_time = GetSysTime(); show_headers = false; LoadConfig(); } WIsapiClient::~WIsapiClient() { } void WIsapiClient::ReadPostData(HttpQuery& query, EXTENSION_CONTROL_BLOCK *ecb) { unsigned left = ecb->cbTotalBytes; unsigned length = left; StringBuffer b(length); char *out = b; if(ecb->cbAvailable) { unsigned count = min(ecb->cbAvailable, left); memcpy(out, ecb->lpbData, count); out += count; left -= count; } while(left > 0) { dword count = left; if(!ecb->ReadClient(ecb->ConnID, out, &count) || count > left) { ISAPILOG(">>> HttpExtensionProc: POST fetch error"); return; // error reading client data } left -= count; out += count; } String buffer = b; if(buffer.Find('\n') < 0) { query.SetURL(buffer); return; } GetHttpPostData(query, buffer); } bool WIsapiClient::Run(int cid, const HttpQuery& query, EXTENSION_CONTROL_BLOCK *ecb) { dword ticks = GetTickCount(); ISAPILOG("WIsapiClient(" << cid << "): assigning request"); enum { ASSIGN_TIMEOUT = 60000 }; do { ISAPILOG("WIsapiClient(" << cid << "): before entering client lock: " << client_lock.Get()); One lock = new CheckedSection::Lock(client_lock); ISAPILOG("WIsapiClient(" << cid << "): after entering client lock: " << client_lock.Get()); // client_lock.Enter(); if(connections.IsEmpty()) return false; int repcnt = connections.GetCount(); int i = 0; if(repcnt > 0) { int ticks = connections[0].last_msecs; for(int t = 1; t < connections.GetCount(); t++) if(connections[t].last_msecs < ticks) ticks = connections[i = t].last_msecs; } while(--repcnt >= 0) { Connection& conn = connections[i]; ISAPILOG("WIsapiClient(" << cid << "): trying connection #" << i); if(conn.Run(cid, query, lock, ecb)) { // client_lock.Leave(); ISAPILOG("WIsapiClient(" << cid << "): request completed, lock = " << client_lock.Get()); return true; } if(++i >= connections.GetCount()) i = 0; } ISAPILOG("WIsapiClient(" << cid << "): before leaving client lock: " << client_lock.Get()); lock.Clear(); // client_lock.Leave(); ISAPILOG("WIsapiClient(" << cid << "): after leaving client lock: " << client_lock.Get()); ISAPIDEBUG(free_event.Wait(500);) } while(int(GetTickCount() - ticks) <= ASSIGN_TIMEOUT); ISAPILOG("WIsapiClient(" << cid << "): request assignment timeout\n"); return false; } dword WIsapiClient::Run(int cid, EXTENSION_CONTROL_BLOCK *ecb) { /* bool empty = false; for(int i = 0; i < connections.GetCount(); i++) if(!connections[i].ecb) { empty = true; break; } if(!empty) { ISAPILOG("ECB busy"); return HSE_STATUS_SUCCESS; } */ int start_time = GetTickCount(); ISAPILOG("WIsapiClient(" << cid << ")"); ecb->dwHttpStatusCode = HTTP_STATUS_OK; // ISAPILOG("ALL_HTTP:\n" << ReadServerVariable(ecb, "ALL_HTTP")); // ISAPILOG("ALL_RAW:\n" << ReadServerVariable(ecb, "ALL_RAW")); // ISAPILOG("AUTH_PASSWORD:\n" << ReadServerVariable(ecb, "AUTH_PASSWORD")); // ISAPILOG("AUTH_TYPE:\n" << ReadServerVariable(ecb, "AUTH_TYPE")); // ISAPILOG("AUTH_USER:\n" << ReadServerVariable(ecb, "AUTH_USER")); // ISAPILOG("LOCAL_ADDR:\n" << ReadServerVariable(ecb, "LOCAL_ADDR")); HttpQuery query; AddHeaders(query, ReadServerVariable(ecb, "ALL_RAW")); query.Set("$$IPADDR", ReadServerVariable(ecb, "REMOTE_ADDR")); query.Set("$$WISAPI", dll_filename); query.Set("$$METHOD", ecb->lpszMethod); query.Set("$$QUERY", ecb->lpszQueryString); query.SetInt("$$HTTP_VERSION", 1000); query.SetURL(ecb->lpszQueryString); if(ecb->lpszPathInfo && *ecb->lpszPathInfo) query.Set("$$PATH", ecb->lpszPathInfo); else // query.Set(WID__PATH, "http://www.wmap.cz:1571/atlaszp"); query.Set("$$PATH", GetFileName(dll_filename)); query.Set("$$CONTENT_TYPE", ecb->lpszContentType); if(!stricmp(ecb->lpszMethod, "POST")) ReadPostData(query, ecb); ISAPILOG("WIsapiClient(" << cid << "): query = " << query); String out; if(query.Find("CONFIGURE") >= 0) { // config page out = Config(query); String header = HttpContentType(HttpTextHtml(CHARSET_DEFAULT)); header << "Content-length: " << out.GetLength() << "\r\n\r\n"; WriteHeader(ecb, NULL, header); WriteClient(ecb, out); return HSE_STATUS_SUCCESS; } if(Run(cid, query, ecb)) { ISAPILOG("end: status = " << (int)ecb->dwHttpStatusCode); return HSE_STATUS_SUCCESS; // ISAPILOG("-> accepted"); // ecb->dwHttpStatusCode = HTTP_STATUS_ACCEPTED; // return HSE_STATUS_PENDING; } CheckedSection::Lock guard(client_lock); if(connections.IsEmpty()) out << t_("Currently there are no servers connected.\n" "You can connect servers using ") << HtmlLink(query.GetString("$$PATH") + "?configure") / t_("the configuration page") << "."; else out << t_("No connected server can process the current request."); ISAPILOG("WIsapiClient(" << cid << "):->error page: " << out); out = GetHttpErrorPage(query, out, show_headers); String header = HttpContentType(HttpTextHtml(CHARSET_DEFAULT)); header << "Content-length: " << out.GetLength() << "\r\n\r\n"; WriteHeader(ecb, NULL, header); WriteClient(ecb, out); return HSE_STATUS_SUCCESS; } static const char *Title() { return t_("Web server connector configuration"); } Htmls WIsapiClient::Config(const HttpQuery& query) { CheckedSection::Lock guard(client_lock); cfg_url = GetHttpPath(cfg_query = query) + "?configure"; cfg_can_write = -1; cfg_error = Null; String eng = English(); return HtmlTitlePage(Title(), Body(eng)); } #if defined(CPU_AMD64) #define ARCHITECTURE "x64" #elif defined(CPU_IA32) #define ARCHITECTURE "x86" #else #define ARCHITECTURE "unknown" #endif Htmls WIsapiClient::Body(String eng) { Htmls body; body << HtmlTag("SMALL") / t_("Architecture: ") << HtmlTag("TT") / ToHtml(ARCHITECTURE) << "
\n" << HtmlTag("SMALL") / t_("Library location: ") << HtmlTag("TT") / ToHtml(dll_filename) << "
\n" << HtmlTag("SMALL") / (String() << t_("Version: ") << WISAPI_VERSION << t_(", release date: ") << WISAPI_DATE << "
\n" WISAPI_COPYRIGHT "

\n"); String s = cfg_query.GetString("CONFIGURE"); enum { CONFIG, STAT /*, SERV */ } page = CONFIG; switch(*s) { case 's': page = STAT; break; // case 'r': page = SERV; break; } Htmls menu; if(remote_admin) { menu << HtmlCell() / ::HtmlMenu(page == CONFIG, t_("Configuration"), cfg_url, 120) << HtmlCell() / HtmlHardSpace(2); } else page = STAT; menu << HtmlCell() / ::HtmlMenu(page == STAT, t_("Statistics"), cfg_url + "=s", 120) // << HtmlCell() / HtmlHardSpace(2) // << HtmlCell() / HtmlMenu(page == SERV, "Služby", cfg_url + "=r", 120) ; body << HtmlTable().CellSpacing(0) % HtmlRow() % menu << "

\n"; switch(page) { case CONFIG: body << Configuration(eng); break; case STAT: body << Statistics(); break; // case SERV: body << Services(); break; } return body; } Htmls WIsapiClient::Configuration(String eng) { Htmls content; content << Login() << HtmlSimpleRow() << eng << HtmlSimpleRow() << Logging() << HtmlSimpleRow() << ShowHeaders() << HtmlSimpleRow() << Timing() << HtmlSimpleRow() << RemoteAdmin() << HtmlSimpleRow() << Servers(); // content // must be the last one as Timing() and Servers() might modify some client data // << HtmlSimpleRow() << Commit(); if(!IsNull(cfg_error)) content = HtmlSimpleRow() / HtmlFontColor(LtRed) / (HtmlBig() / (t_("Error: ") + HtmlBold() / cfg_error)) + HtmlSimpleRow() + content; return HtmlTag("FORM").Attr("METHOD", "POST").Attr("ACTION", cfg_url) % HtmlIsapiTable() % content; } Htmls WIsapiClient::English() { int engstate = Null; if(!cfg_query.IsEmpty("ENGLISH")) engstate = 1; else if(!cfg_query.IsEmpty("CZECH")) engstate = 0; if(!IsNull(engstate) && CanWrite()) { bool new_eng = !!engstate; if(new_eng != isapi_english) { isapi_english = new_eng; SaveConfig(); SetLanguage(isapi_english ? LNG_ENGLISH : LNG_CZECH); } } return HtmlSplitRow(HtmlBig() / (t_("ISAPI library language: ") + HtmlFontColor(LtRed) / t_("english")), HtmlButton(isapi_english ? " Czech " : " Anglicky ", isapi_english ? "CZECH" : "ENGLISH")); } Htmls WIsapiClient::Logging() { int logstate = Null; if(!cfg_query.IsEmpty("LOGON")) logstate = 1; else if(!cfg_query.IsEmpty("LOGOFF")) logstate = 0; if(!IsNull(logstate) && CanWrite()) { bool new_log = !!logstate; if(new_log != log_isapi) { log_isapi = new_log; SaveConfig(); } } return HtmlSplitRow(HtmlBig() / (t_("Log-file: ") + HtmlFontColor(LtRed) / (log_isapi ? t_("yes") : t_("no"))), HtmlButton(log_isapi ? t_("Disable") : t_("Enable"), log_isapi ? "LOGOFF" : "LOGON")); } Htmls WIsapiClient::ShowHeaders() { int hdrstate = Null; if(!cfg_query.IsEmpty("HDRON")) hdrstate = 1; else if(!cfg_query.IsEmpty("HDROFF")) hdrstate = 0; if(!IsNull(hdrstate) && CanWrite()) { bool new_hdr = !!hdrstate; if(new_hdr != show_headers) { show_headers = new_hdr; SaveConfig(); } } return HtmlSplitRow(HtmlBig() / (t_("Show headers on error: ") + HtmlFontColor(LtRed) / (show_headers ? t_("yes") : t_("no"))), HtmlButton(show_headers ? t_("Disable") : t_("Enable"), show_headers ? "HDROFF" : "HDRON")); } Htmls WIsapiClient::RemoteAdmin() { bool noadmin = false; if(!cfg_query.IsEmpty("NOADMIN1") && CanWrite()) { remote_admin = false; SaveConfig(); return HtmlSplitRow(HtmlBig() / (t_("Remote administration: ") + HtmlFontColor(LtRed) / t_("off")), Null); } if(!cfg_query.IsEmpty("NOADMIN") && CanWrite()) return HtmlSplitRow(HtmlBig() / HtmlFontColor(LtRed) / (t_("After confirmation the remote " "administration option will be disabled. You can re-enable remote " "administration by removing the line ADMIN=0 from the " "configuration file ") + cfg_filename + "."), HtmlButton(t_(" Confirm "), "NOADMIN1")); return HtmlSplitRow(HtmlBig() / (t_("Remote administration: ") + HtmlFontColor(LtRed) / t_("on")), HtmlButton(t_(" Disable "), "NOADMIN")); } Htmls WIsapiClient::Timing() { if(!cfg_query.IsEmpty("SAVE_TIMEOUT") && CanWrite()) { isapi_timeout = cfg_query.GetInt("TIMEOUT", 1000, 60000, 1000); SaveConfig(); } return HtmlSplitRow(HtmlBig() / (t_("Internal timeout (msec):")), HtmlEdit("TIMEOUT").Data(cfg_query.GetString("TIMEOUT", IntStr(isapi_timeout))) + "  " + HtmlButton(t_(" Commit "), "SAVE_TIMEOUT")); } /* HtmlTag WIsapiClient::Edit(Id id, int& value) { int vsec = value / 1000; int new_value = cfg_query.GetInt(id); if(!IsNull(new_value) && new_value != vsec) { if(new_value >= 1 && new_value <= 600) value = new_value * 1000; else cfg_error = "Hodnota je mimo povolený rozsah (1 - 600 s)."; } if(IsNull(new_value)) new_value = vsec; return HtmlEdit(id).Data(IntStr(new_value)).Size(6); } */ static Htmls HtmlTableRow(const Htmls& host, const Htmls& state, const Htmls& modf, Color c) { Htmls cells; cells << HtmlCell().Width(-30) % host; if(!modf.IsEmpty()) cells << HtmlCell().Width(-60) % state << HtmlCell().Width(-10) % modf; else cells << HtmlCell().ColSpan(3).Width(-100) % state; return HtmlRow().BgColor(c) % cells; } static const char *NoGenerators() { return t_("Currently no generators are connected."); } static Htmls NoGenMsg() { return HtmlFontColor(LtBlue) / HtmlTag("I") / NoGenerators(); } Htmls WIsapiClient::Servers() { int count = cfg_query.GetInt("HOSTCOUNT"); if(!IsNull(count)) { for(int i = 0; i < count; i++) if(!cfg_query.IsEmpty("RMHOST" + IntStr(i))) { // remove this host String hostid = cfg_query.GetString("HOST" + IntStr(i)); if(CanWrite()) try { RemoveHost(hostid); } catch(Exc e) { cfg_error = e; } break; } } if(!cfg_query.IsEmpty("ADDHOST")) { String ah = cfg_query.GetString("ADDHOST"); ISAPILOG("ADDHOST=<" << ah << "> (" << ah.GetLength() << " bytes)"); if(CanWrite()) try { // advance current port number String s = cfg_query.GetString("NEW_HOST"); AddHost(s); int i = s.Find('@'); int port = DEFAULT_PORT; if(i < 0) i = s.GetLength(); else { port = stou(s.Begin() + i + 1); if(port == -1) port = DEFAULT_PORT; } s.Trim(i); s << '@' << (port + 1); cfg_query.Set("NEW_HOST", s); } catch(Exc e) { cfg_error = e; } } Htmls table; table << HtmlTableRow(HtmlBold() / t_("Generator"), HtmlBold() / t_("Active state"), "", Yellow); // output connection parameters count = connections.GetCount(); table << HtmlHidden("HOSTCOUNT", count) << "\n"; for(int i = 0; i < count; i++) { Connection& conn = connections[i]; CheckedSection::Lock guard(conn.conn_lock); table << HtmlHidden("HOST" + IntStr(i), conn.raw_host) << "\n"; String status; if(conn.is_busy) status = t_("(working)") + conn.status; else if(conn.is_error) status = conn.status; else status = conn.status; table << HtmlTableRow(conn.raw_host, status, ~HtmlSubmit(t_("Remove")).Name((const char *)("RMHOST" + IntStr(i))), White); } DUMP(cfg_query.GetString("NEW_HOST")); DUMP(cfg_default_host); Htmls new_host; new_host << t_("Add generator: ") << HtmlEdit("NEW_HOST").Data(cfg_query.GetString("NEW_HOST", cfg_default_host)) << " " << HtmlSubmit(t_(" Insert ")).Name("ADDHOST"); Htmls s; s << HtmlSimpleRow() / HtmlBig() / t_("Active generators"); if(count > 0) s << HtmlSimpleRow() % (HtmlTable().CellSpacing(0).CellPadding(0) .BgColor(White).Border(0).Center().Width(-100) % table); else s << HtmlSimpleRow() / NoGenMsg(); s << HtmlSimpleRow() / new_host; return s; } Htmls WIsapiClient::Login() { Htmls s; s << HtmlSimpleRow() / HtmlBig() / t_("Admin password") << HtmlSplitRow(t_("Password:"), ~HtmlInput("PASSWORD").Name("PASSWORD").Data(cfg_query.GetString("PASSWORD", Null))); return s; } /* Htmls WIsapiClient::Commit() { Htmls text; text << HtmlSimpleRow() << HtmlSimpleRow().Left() % HtmlSubmit("Zapsat konfiguraci").Name(WID_SAVE_CONFIG); if(map.Find(ID_SAVE_CONFIG) >= 0 && CanWrite()) if(client.Save()) text << HtmlSimpleRow() << HtmlSimpleRow() % HtmlFontColor(LtBlue) / HtmlBig() / "Konfigurační soubor byl korektně uložen."; else error = "Chyba při zápisu konfiguračního souboru."; return text; } */ static Htmls HtmlStatGenRow(const Value& gen, const Value& num, const Value& byt, const Value& avg, const Value& max, const Value& err, const HtmlTag& tag = HtmlTag()) { return Htmls() << HtmlCell().Width(-25) / tag / StdFormat(gen) << HtmlCell().Width(-15) / tag / StdFormat(num) << HtmlCell().Width(-15) / tag / StdFormat(byt) << HtmlCell().Width(-15) / tag / StdFormat(avg) << HtmlCell().Width(-15) / tag / StdFormat(max) << HtmlCell().Width(-15) / tag / StdFormat(err); } Htmls WIsapiClient::Statistics() { Htmls content; double duration = (double)(GetSysTime() - run_time); int id = fround(duration); String dd; if(id < 60) dd << id << t_(" sec"); else if(id < 60 * 60) dd << id / 60 << t_(" min, ") << id % 60 << t_(" hrs, "); else { int hours = ffloor(duration / 3600); id = fround(duration - 3600.0 * hours); dd << hours << t_(" hrs, ") << id / 60 << t_(" min, ") << id % 60 << t_(" sec"); } content << HtmlSimpleRow() % HtmlBig() / t_("Overall statistics") << HtmlValueRow(t_("Server started:"), run_time) << HtmlValueRow(t_("Running time:"), dd) << HtmlValueRow(t_("Hit count:"), hit_count) << HtmlValueRow(t_("Error count:"), error_count) << HtmlValueRow(t_("Transferred data (KB):"), FormatDouble(total_bytes * 1e-3, 4, FD_REL)) << HtmlValueRow(t_("Average response (msecs):"), FormatDouble(total_msecs / max(hit_count - error_count, 1), 4, FD_REL)) << HtmlValueRow(t_("Slowest response (msecs):"), max_msecs) << HtmlSimpleRow() << HtmlSimpleRow() % HtmlBig() / t_("Statistics by generators") << HtmlSimpleRow(); int count = connections.GetCount(); if(count == 0) content << HtmlSimpleRow() % NoGenMsg(); else { Htmls table; table << HtmlRow().BgColor(Yellow) % HtmlStatGenRow(t_("Generator"), t_("Requests"), t_("Data (KB)"), t_("Response (msec)"), t_("Max (msec)"), t_("Errors"), HtmlBold()); for(int i = 0; i < count; i++) { Connection& conn = connections[i]; CheckedSection::Lock guard(conn.conn_lock); table << HtmlRow().BgColor(White) % HtmlStatGenRow(conn.raw_host, conn.hit_count, floor(conn.total_bytes * 1e-3), conn.total_msecs / max(conn.hit_count - conn.error_count, 1), conn.max_msecs, conn.error_count); } content << HtmlSimpleRow() % HtmlTable().CellSpacing(0).CellPadding(0) .BgColor(White).Border(0).Center().Width(-100) % table; } return HtmlIsapiTable() % content; } bool WIsapiClient::CanWrite() { if(cfg_can_write < 0) { String pwd = ToLower(cfg_query.GetString("PASSWORD")); if(pwd.IsEmpty()) { DUMP(cfg_query.GetString("PASSWORD")); cfg_can_write = 0; cfg_error = t_("To commit changes you must " "enter the password."); } else if(!(cfg_can_write = (pwd == ToLower(cfg_password)))) cfg_error = t_("Password invalid; check keyboard " "settings."); } return !!cfg_can_write; } /* Htmls WIsapiClient::Services() { Htmls content; content << Login() << HtmlSimpleRow() % HtmlBig("Instalované služby") << HtmlSimpleRow() % ServiceTable(); if(!IsNull(error)) content = HtmlSimpleRow() / HtmlFontColor(LtRed) / (HtmlBig("Chyba: " + HtmlBold(error))) + HtmlSimpleRow() + content; return HtmlTag("FORM").Attr("METHOD", "POST").Attr("ACTION", config_url + "=r") % HtmlIsapiTable() % content; } */ /* static Htmls HtmlServiceRow(Htmls cell1, Htmls cell2, Htmls cell3) { Htmls s; s << HtmlCell().Width(-50) / cell1 << HtmlCell().Width(-25) / cell2 << HtmlCell().Width(-25) / (cell3 + " "); return s; } */ /* Htmls WIsapiClient::ServiceTable() { SHandle mgr = OpenSCManager(0, 0, GENERIC_READ | GENERIC_WRITE); if(!mgr) { error = "Přístup ke správci služeb byl odmítnut."; return Htmls(); } Array services = EnumServices(mgr); SortX(services, XField(&ServiceInfo::display_name), StdCsNumSort); Htmls table = HtmlRow().BgColor(Yellow) % HtmlServiceRow(HtmlBold("Název služby"), HtmlBold("Stav"), Null); for(int i = 0; i < services.GetCount(); i++) { const ServiceInfo& s = services[i]; const char *state = "(?)"; switch(s.state) { case SERVICE_START_PENDING: state = "spouští se"; break; case SERVICE_RUNNING: state = "běží"; break; case SERVICE_STOP_PENDING: state = "vypíná se"; break; case SERVICE_STOPPED: state = "vypnutá"; break; case SERVICE_CONTINUE_PENDING: state = "obnovuje se"; break; case SERVICE_PAUSE_PENDING: state = "pozastavuje se"; break; case SERVICE_PAUSED: state = "pozastavena"; break; } table << HtmlRow() % HtmlServiceRow(ToHtml(s.display_name), state, Null); } return HtmlTable().Width(-100).CellSpacing(0).CellPadding(0).Border(0).BgColor(White) % table; } */ void WIsapiClient::AddHost(const String& host) { CheckedSection::Lock guard(client_lock); if(FindFieldIndex(connections, &Connection::raw_host, host) >= 0) throw Exc(NFormat(t_("Generator '%s' is already " "connected."), host)); connections.Add(new Connection(*this, host)); SaveConfig(); } void WIsapiClient::RemoveHost(const String& host) { CheckedSection::Lock guard(client_lock); int x = FindFieldIndex(connections, &Connection::raw_host, host); if(x < 0) throw Exc(NFormat(t_("Server '%s' not found."), host)); { CheckedSection::Lock conn_guard(connections[x].conn_lock); if(connections[x].is_busy) throw Exc(NFormat(t_("Cannot disconnect server '%s' " "while it's processing a request."), host)); } connections.Remove(x); } void WIsapiClient::SaveConfig() { CheckedSection::Lock guard(client_lock); StringStream cfg; cfg << "PASSWORD=" << cfg_password << "\n" "LOGGING=" << (log_isapi ? '1' : '0') << "\n" "HEADERS=" << (show_headers ? '1' : '0') << "\n" "ENGLISH=" << (isapi_english ? '1' : '0') << "\n" "TIMEOUT=" << isapi_timeout << "\n" "ADMIN=" << (remote_admin ? '1' : '0') << "\n" // "RETRY=" << retry_msec << "\n" ; for(int i = 0; i < connections.GetCount(); i++) cfg << "HOST=" << connections[i].raw_host << '\n'; DeleteFile(cfg_filename); if(!SaveFile(cfg_filename, cfg)) throw Exc(NFormat(t_("Error saving file '%s'."), cfg_filename)); SetFileAttributes(cfg_filename, FILE_ATTRIBUTE_HIDDEN); } void WIsapiClient::LoadConfig() { CheckedSection::Lock guard(client_lock); ASSERT(connections.IsEmpty()); VectorMap cfg = LoadKeyMapFile(cfg_filename); log_isapi = (*cfg.Get("LOGGING", Null) == '1'); show_headers = (*cfg.Get("HEADERS", "1") == '1'); remote_admin = (*cfg.Get("ADMIN", "1") == '1'); isapi_english = (*cfg.Get("ENGLISH", "1") == '1'); cfg_password = cfg.Get("PASSWORD", "wisapi"); // retry_msec = ::minmax(stoi(cfg("RETRY", "1000")), 0, 60000); isapi_timeout = ::minmax(ScanInt(cfg.Get("TIMEOUT", "1000")), 1000, 60000); SetLanguage(isapi_english ? LNG_ENGLISH : LNG_CZECH); for(int i = cfg.Find("HOST"); i >= 0; i = cfg.FindNext(i)) connections.Add(new Connection(*this, cfg[i])); // LOG("WIsapiClient::LoadConfig: retry = " << retry_msec); LOG("WIsapiClient::LoadConfig: timeout = " << isapi_timeout); } GLOBAL_VAR(One, client) extern "C" dword WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *ecb) { try { static long epid = 0; int cid = InterlockedIncrement(&epid); ISAPILOG("HttpExtensionProc, epid = " << cid << ", thread " << (int)GetCurrentThreadId()); return client()->Run(cid, ecb); } catch(Exc e) { ISAPILOG("HttpExtensionProc->Exc(" << e << ")"); ecb->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR; return HSE_STATUS_ERROR; } catch(...) { ISAPILOG("HttpExtensionProc->Exc (unknown)"); ecb->dwHttpStatusCode = HTTP_STATUS_SERVER_ERROR; return HSE_STATUS_ERROR; } } extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer) { pVer->dwExtensionVersion = MAKELONG(HSE_VERSION_MINOR, HSE_VERSION_MAJOR); strcpy(pVer->lpszExtensionDesc, "ISAPI extension: Copyright © 1999-2003 Ultimate Development spol. s r.o."); return TRUE; } extern "C" BOOL WINAPI DllMain(HANDLE module, DWORD reason, LPVOID lpReserved) { if(reason == DLL_PROCESS_ATTACH) { SetLanguage(LNG_ENGLISH); SetDefaultCharset(CHARSET_WIN1250); dll_module = (HINSTANCE)module; dll_filename = GetModuleFileName(dll_module); String lfn = ForceExt(dll_filename, "_log.txt"); DeleteFile(lfn); SetVppLogName(lfn); SetVppLogSizeLimit(500000); ISAPILOG("WISAPI DllMain/DLL_PROCESS_ATTACH: creating WIsapiClient object"); client() = new WIsapiClient; ISAPILOG("WISAPI DllMain/DLL_PROCESS_ATTACH: done"); } else if(reason == DLL_PROCESS_DETACH) { ISAPILOG("WISAPI DllMain/DLL_PROCESS_DETACH: destroying WIsapiClient object"); client().Clear(); ISAPILOG("WISAPI DllMain/DLL_PROCESS_DETACH: done"); } else if(reason == DLL_THREAD_ATTACH) { ISAPILOG("WISAPI DllMain/DLL_THREAD_ATTACH"); } else if(reason == DLL_THREAD_DETACH) { ISAPILOG("WISAPI DllMain/DLL_THREAD_DETACH"); } return TRUE; } #ifndef flagDLL CONSOLE_APP_MAIN { } #endif