static Set blockedIPs = synchroHashSet(); // important: cleaned regularly in #1009210 static Set whiteListedIPs = synchroHashSet(); //static int blockedIPDelay = -1; // forever static int blockedIPDelay = 60; // seconds //static int spamInterval = 60000; //static double spamPerSecondThreshold = 2.0; // per IP + URI // for sub-bots to call through reflection please include function serveRedirect. please include function serve403. please include function serve404. please include function serve500. please include function serveFile. please include function serveFileWithName. please include function serveFile_maxCache. please include function serveByteArray. please include function serveByteArray_maxCache. static volatile int hitCount; // These are speaking URLs like "/images/" static new SS botNames; // see e.g. #1013896 static S anonymousDialogID = "web-anon"; static S webLogProgramID = "#1002576"; static S webLog = "webLog"; static int recentWebLogMax = 1000; static new LinkedList recentWebLog; static S homePageBotID = "#1002771"; static void webInit() { readLocally("hitCount"); readLocally("homePageBotID"); //readLocally("blockedIPs"); //set NanoHTTPD_debug; } static new Object webLock; static NanoHTTPD.Response serve(S uri, NanoHTTPD.Method method, Map header, Map params, Map files) { final long startTime = sysNow(); temp tempAfterwards(r { print(formatDateAndTime() + " | " + (sysNow()-startTime) + " ms") }); SS headers = getSession().getHeaders(); S clientIP = headers.get("remote-addr"); Pair pair = simpleSpamClientDetect2(clientIP, uri); //Pair pair = pair(clientIP, uri); //int ipCount = toInt(ipsAndUris.get(pair)); //ipsAndUris.put(pair, ipCount+1); bool whiteListed = whiteListedIPs.contains(clientIP); if (whiteListed) pair.b = false; bool bad = eq(uri, "/simulate-bad-client"); //int spamMax = iround(spamPerSecondThreshold*spamInterval/1000); int ipCount = pair.a; if (!bad && pair.b) { print("Blocking IP " + clientIP + ", count reached: " + ipCount); addBlockedIP(clientIP); bad = true; } if (blockedIPs.contains(clientIP)) { bad = true; if (blockedIPDelay < 0) print(formatDateAndTime() + " | Blocked IP!! " + clientIP + " " + l(nanoHttpd_badClients())); else { print(formatDateAndTime() + " | Blocked IP!! " + clientIP + (blockedIPDelay != 0 ? " Delaying " + blockedIPDelay + "s" : "")); //sleepSeconds(blockedIPDelay); } //ret serveHTML("No"); } if (bad) { NanoHTTPD.IHTTPSession httpSession = NanoHTTPD.currentSession!; if (httpSession == null || blockedIPDelay >= 0) { if (httpSession == null) print("No HTML session?"); sleepSeconds(blockedIPDelay); ret serve500("go away"); } httpSession.badClient(true); null; } S host = dropFrom(header.get("host"), ":"); print(gmtWithSeconds() + ": Serving HTTP " + quote(headers.get("host") + /*colonPortUnless80(subBot_currentPort())*/ + uri) + " to " + clientIP + " (anti-spam count: " + ipCount + "/" + simpleSpamClientDetect2_spamMax + (whiteListed ? ", white-listed" : "") + ")"); int hitID; synchronized(webLock) { ++hitCount; hitID = hitCount; if ((hitCount % 1000) == 1) { hitCount = (hitCount+999)/1000*1000; saveLocally("hitCount"); hitCount = hitID; } } pcall { webLog(litmap("time", now(), "request", hitID, "uri", uri, "method", str(method), "header", header, "params", params, "files", files)); } try { NanoHTTPD.Response response = serveNoLog(uri, method, header, params, files); // log that we responded pcall { webLog(litmap("time", now(), "response", hitID)); } ret response; } catch (Throwable e) { // log that there was an error pcall { webLog(litmap("time", now(), "response-error", hitID, getStackTrace(e))); } throw asRuntimeException(e); } } static NanoHTTPD.Response serveNoLog(S uri, NanoHTTPD.Method method, Map header, Map params, Map files) { actualURI.set(uri); if (nempty(files)) print("URI: " + uri + ", files: " + files); uploadFiles.set(files); if (!startsWith(uri, "/")) ret serve404("Bad URI"); uri = dropPrefixMandatory("/", uri); if (!loaded) { int numLoaded = l(bots), total = l(botIDs); // query dispatcher for number of loaded sub-bots S lbi = loadingBotID; if (sameSnippetID(lbi, "#1002317")) { int[] x = (int[]) callOpt(loadingBot, "numLoaded"); if (x != null) { numLoaded += x[0]; total += x[1]; } lbi = or((S) getOpt(loadingBot, "loadingBotID"), lbi); } S s = "LOADING, PLEASE TRY AGAIN. " + numLoaded + "/" + total + " bots loaded "; if (lbi != null) s += " (loading bot " + formatSnippetID(lbi) /*+ " - " + getSnippetTitle_overBot(lbi)*/ + ")"; ret serve500(s); } // count and set cookie O visitorsBot = getBot("#1002157"); if (visitorsBot != null) callHtmlMethod(visitorsBot, "/"); setDialogIDForWeb(); if (eq(uri, "") || eq(uri, "raw")) ret serveHomePage("/", params); S host = dropFrom(header.get("host"), ":"); if (domainIsUnderOneOf(host, "code.botcompany.de", "tomii.me")) ret serveHomePage(uri, params); // Blog Bot handles it if (eq(uri, "favicon.ico")) ret serveFile(loadLibrary(or2(trimLoadTextFile(javaxDataDir("eleu-favicon.txt")), #1101146)), "image/x-icon"); int i = uri.indexOf('/'); if (i < 0) { uri += "/"; i = l(uri)-1; } S firstPart = uri.substring(0, i); //print("uri: " + uri + ", i: " + i + ", firstPart: " + firstPart); if (botNames.containsKey(firstPart)) { uri = dropPrefix("#", botNames.get(firstPart)) + addPrefix("/", substring(uri, i)); i = uri.indexOf('/'); firstPart = i >= 0 ? uri.substring(0, i) : uri; //print("now firstPart: " + firstPart + ", uri: " + uri + ", i: " + i); } S rest = i >= 0 ? substr(uri, i) : "/"; // rest always starts with "/" if (possibleGlobalID(toLower(firstPart))) { ret serveBot(#1007510, "/raw/" + uri, params); /*AIConcept c = aiConceptsMap_cached().get(toLower(firstPart)); if (c != null) ret serveHTML("Concept found: " + c.globalID + " - " + c.name); else ret serveHTML("Concept not found: " + toLower(firstPart));*/ } if (!isInteger(firstPart)) ret serveHomePage("/" + uri, params); ret serveBot(firstPart, rest, params); } static NanoHTTPD.Response serveBot(S botID, S subUri, Map params) { sleepWhile(() -> isTrue_callOpt(getDispatcher(), 'isReloading, botID)); Class bot = getBot(botID); boolean raw = subUri.equals("/raw") || subUri.startsWith("/raw/"); if (raw) { subUri = substr(subUri, "/raw".length()); if (subUri.length() == 0) subUri = "/"; } if (bot != null) { //print("Bot " + botID + " methods: " + structure(allMethodNames(bot))); O botOut; try { botOut = callExtendedHtmlMethod(bot, subUri, params); } catch e { callOpt(bot, 'print, "Error while serving " + subUri + ": " + stackTraceToString(e)); throw rethrow(e); } if (botOut instanceof NanoHTTPD.Response) ret (NanoHTTPD.Response) botOut; if (eq(getClassName(botOut), NanoHTTPD.Response.class.getName())) fail("Wrong realm"); S botHtml = str(botOut); S html; if (raw) html = unnull(botHtml); else { if (botHtml == null) botHtml = "Bot has no HTML output."; S name = getSnippetTitle(botID); S title = htmlencode(name) + " [" + formatSnippetID(botID) + "]"; html = botHtml; Pair pTitle = hextracttag(html, 'title); if (pairBNotNull(pTitle)) title = join(contentsOfContainerTag(htmlTok(pTitle.b))); html = pTitle.a; html = "" + title + "" + "

Bot " + formatSnippetID(botID) + " - " + htmlencode(name) + "

\n" + botHtml + "\n"; } ret serveHTMLNoCache(html); } ret serve404(); } static NanoHTTPD.Response serveHomePage(S uri, Map params) { if (nempty(homePageBotID)) { O bot = getBot(homePageBotID); if (bot != null) /*pcall*/ { ret serveHTMLNoCache(callHtmlMethod2(bot, uri, params)); } } ret serveHTML(botsListCache!); } static TimedCache botsListCache = new(r serveBotsList, 10.0); static S serveBotsList() { new StringBuilder buf; buf.append(""); buf.append("TinyBrain Chat Bots"); buf.append(""); buf.append("

TinyBrain Bots

"); buf.append("
    "); L botsToList = litlist(getProgramID()); O dispatcher = getDispatcher(); if (dispatcher != null) botsToList.addAll((L) call(dispatcher, "getSubBotIDs")); botsToList.addAll(activeBots); for (S botID : botsToList) { botID = formatSnippetID(botID); S parsedID = "" + parseSnippetID(botID); S title = "?"; pcall { title = getSnippetTitle_overBot(botID); } S name = botID + " - " + htmlencode(title); buf.append("
  • " + name + " ("); buf.append("source)
  • "); } buf.append("
"); buf.append("

Hit count: " + hitCount + "

"); buf.append("

Last interactions:

"); int maxInteractionsToPrint = 10; for (int i = l(history)-1; i >= 0 && i >= l(history)-maxInteractionsToPrint; i--) { Map m = history.get(i); buf.append("

" + htmlencode(m.get("question")) + "
    " + htmlencode(m.get("answer")) + "

"); } buf.append(""); buf.append(""); ret str(buf); } // for sub-bots to call static NanoHTTPD.IHTTPSession getSession() { ret NanoHTTPD.currentSession.get(); } // for sub-bots to call static O getCookies() { NanoHTTPD.IHTTPSession session = NanoHTTPD.currentSession.get(); NanoHTTPD.CookieHandler cookies = session.getCookies(); ret cookies; } static void setDialogIDForWeb() { O session = getSession(); O cookieHandler = getCookies(); S cookie = cast call(cookieHandler, "read", "cookie"); //print("Web answer cookie: " + cookie); //print("Web answer question: " + quote(s)); S dialogID = nempty(cookie) ? "web-" + hashCookie(cookie) : anonymousDialogID; main.dialogID.set(dialogID); } static S webAnswer(S s) { ret webAnswer(s, true); } static S webAnswer(S s, boolean log) { setDialogIDForWeb(); originalLine.set(s); // drop the attn prefix ("!") s = dropPrefix("!", s).trim(); attn.set(true); dedicated.set(true); // user name is empty for now S a = null; pcall { a = answer(s, false); // generalRelease = false, all bots print("Web answer: " + quote(a)); if (empty(a)) a = "Hello :)"; } if (empty(a)) a = "(no response)"; if (log) { S user = getDialogID(); Map logEntry = litmap("question", s, "user", user, "answer", a); historyAdd(logEntry); } ret a; } static S hashCookie(S cookie) { ret md5(cookie).substring(0, 10); } static void webLog(Map data) { synchronized(webLock) { logStructure(getProgramFile(webLogProgramID, webLog), data); if (recentWebLogMax > 0) { if (l(recentWebLog) >= recentWebLogMax) recentWebLog.remove(0); recentWebLog.add(data); } } } static L getRecentWebLog() { synchronized(webLock) { ret cloneList(recentWebLog); } } static synchronized void addBlockedIP(S ip) { blockedIPs.add(ip); //save("blockedIPs"); } static synchronized void removeBlockedIP(S ip) { blockedIPs.remove(ip); //save("blockedIPs"); } static synchronized void clearBlockedIPs() { blockedIPs.clear(); }