!7 // for sub-bots 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. please include function serveInputStream. please include function servePartialContent. please include function serveRangeNotSatisfiable. sclass WebSocketWithWebRequest extends WebSocket { WebRequest request; *(NanoHTTPD.IHTTPSession session, WebRequest *request) { super(session); } IWebRequest webRequest() { ret request; } } sclass WebRequest implements IWebRequest { NanoHTTPD.IHTTPSession httpSession; S uri, subURI; SS params, files; S cookie, clientIP; Session session; bool isHttps, noSpam; *(NanoHTTPD.IHTTPSession *httpSession, S *uri, SS *params) { files = httpSession.getFiles(); clientIP = getClientIPFromHeaders(httpSession.getHeaders()); } public S uri() { ret uri; } public SS params() { ret params; } public SS files() { ret files; } public SS headers() { ret httpSession.getHeaders(); } public S cookie() { ret cookie; } public bool isHttps() { ret isHttps; } public bool isPost() { ret httpSession.getMethod() == NanoHTTPD.Method.POST; } User loggedInUser() { ret session == null ? null : session.user(); } S googleClientID() { S domain = lower(domain()); File jsonFile = googleClientSecretFileForDomain(domain); if (!fileExists(jsonFile)) null; Map map = decodeJSONMap(loadTextFile(jsonFile)); map = (Map) map.get("web"); ret (S) map.get("client_id"); } public void noSpam { if (noSpam) ret; set noSpam; print("noSpam count: " + simpleSpamClientDetect2_markNoSpam(clientIP, uri) + " (" + clientIP + "/" + uri + ")"); } } concept User > ConceptWithGlobalID { long lastSeen; } concept CookieUser > User { S cookie; toString { ret "[cookieUser " + md5(cookie) + "]"; } } concept GoogleUser > User { long googleLogInDate; S googleEmail, googleFirstName, googleLastName; bool googleEmailVerified; toString { ret googleFirstName + " " + googleLastName; } } concept Session { S cookie; User user; long accesses; User user() { if (user == null) cset(this, user := uniq CookieUser(+cookie)); ret user; } } abstract sclass DynEleu extends DynPrintLogAndEnabled { !include #1029492 // HTTP+HTTPS servers with WebSockets double clearSessionsInterval = 60.0; double clearSessionsTimeout = 300.0; switchable double maxModuleReloadWait = 20.0; switchable double strayWebSocketDelay = 5.0; // Eleu-wide sessions... I don't think anyone uses these anymore switchable bool useSessions; switchable bool shareCookiesBetweenApexDomains = true; // for noticing certificate changes in JavaX-Secret transient FileWatchService watchService; void start { super.start(); dbIndexing(Session, 'cookie, CookieUser, 'cookie, GoogleUser, 'googleEmail); dm_restartOnFieldChange enabled(); if (!enabled) ret; dm_startThread("Start Web Servers", r { start_webServers(serverSocketFactory_botCompanyEtc()); onWebServersStarted(); }); dm_doEvery(clearSessionsInterval, r clearSessions); ownResource(serverSocketFactory_autoUpdate()); } void clearSessions { Cl l = filter(list(Session), s -> s.accesses <= 1 && elapsedSeconds_timestamp(s.created) >= clearSessionsTimeout); if (nempty(l)) { print("Dropping " + nSessions(l)); deleteConcepts(l); } } WebSocket newWebSocket(NanoHTTPD.IHTTPSession handshake) enter { vmBus_send haveNewWebSocket(module(), handshake); S domain = mapGet(handshake.getHeaders(), "host"); S uri = handshake.getUri(); SS params = handshake.getParms(); print("Making WebSocket. domain: " + domain + ", uri: " + uri); WebRequest req = createWebRequest(handshake, uri, params); WebSocket ws = new WebSocketWithWebRequest(handshake, req); S module = moduleForDomainAndURI(domain, uri); if (nempty(module) && !dm_moduleIsStartingOrReloading(module)) pcall { dm_callOpt(module, 'handleWebSocket, ws); } else { sleepSeconds(strayWebSocketDelay); ws.close(); // Hopefully just a reload } ret ws; } O webServe(S uri, SS params) { WebRequest req = new(NanoHTTPD.currentSession!, uri, params); print("IP: " + req.clientIP); Pair spamCheck = spamBlocker.checkRequest(req.uri, req.clientIP); if (spamCheck.b) { print("Request from blocked IP: " + req.clientIP); sleepSeconds(60.0); ret print("go away"); } // fill more fields of WebRequest fillWebRequest(req); // log some printVars("webServe", +uri, userAgent := req.userAgent()); // Serve Let's encrypt challenges if (startsWith(uri, "/.well-known/")) { S fileName = "validation.txt"; /*if (swic(req.domain(), "www.")) fileName = "validation-www.txt";*/ ret loadTextFile(userDir(fileName)); } // Serve Google verification ifndef NoGoogleVerification if (eqic(uri, "/google-verify")) ret serveGoogleVerify(req); endifndef S module = moduleForDomainAndURI(req.domain(), uri); if (nempty(module)) { if (sleepWhile(() -> dm_moduleIsStartingOrReloading(module), max := maxModuleReloadWait)) ret subBot_serve500("Reload taking too long"); ret eleu_callModuleHTMLMethod(module, req); } ret subBot_serve404(); } // override me S moduleForDomain(S domain) { null; } // or me S moduleForDomainAndURI(S domain, S uri) { ret moduleForDomain(domain); } ifndef NoGoogleVerification O serveGoogleVerify(WebRequest req) { Payload payload = googleVerifyUserToken2(req.googleClientID(), req.params.get("token")); S email = payload == null ? null : payload.getEmail(); if (empty(email)) ret print("google-verify", "No"); // create/update an object for the user GoogleUser user = uniqCI_sync GoogleUser(googleEmail := email); cset(user, googleEmailVerified := payload.getEmailVerified(), googleFirstName := strOrNull(payload.get("given_name")), googleLastName := strOrNull(payload.get("family_name")), googleLogInDate := now()); // link user to session cset(req.session, +user); ret print("google-verify", payload.getEmail() + " " + (payload.getEmailVerified() ? "(verified)" : "(not verified)")); } endifndef ServeHttp_CookieHandler makeCookieHandler() { new ServeHttp_CookieHandler h; h.verbose = true; h.shareCookiesBetweenApexDomains = shareCookiesBetweenApexDomains; ret h; } void fillWebRequest(WebRequest req) { req.cookie = makeCookieHandler().handle(); req.isHttps = WebSocketHTTPD_current! == httpsServer; // Get session req.session = nempty(req.cookie) && useSessions ? uniq Session(cookie := req.cookie) : null; if (req.session != null) cset(req.session, accesses := req.session.accesses+1); print(session := req.session); } void enhanceFrame(Container c) { super.enhanceFrame(c); internalFrameMenuItem(c, "Show blocked IPs", rEnterThread { print("Blocked IPs: " + spamBlocker.blockedIPs); }); } void onWebServersStarted() {} WebRequest createWebRequest(NanoHTTPD.IHTTPSession httpSession, S uri, SS params) { ret new WebRequest(httpSession, uri, params); } } // end of module