Browse Source
			
			
			
			
				
		Teile des Codes aus funktionierendem Projekt kpiert und angepasst, aber noch nicht getestetfeature/document
				 12 changed files with 239 additions and 35 deletions
			
			
		@ -0,0 +1,65 @@
				@@ -0,0 +1,65 @@
					 | 
				
			||||
package de.srsoftware.umbrella.core; | 
				
			||||
 | 
				
			||||
import com.sun.net.httpserver.HttpExchange; | 
				
			||||
import de.srsoftware.tools.Path; | 
				
			||||
import de.srsoftware.tools.PathHandler; | 
				
			||||
 | 
				
			||||
import java.io.ByteArrayOutputStream; | 
				
			||||
import java.io.IOException; | 
				
			||||
import java.util.List; | 
				
			||||
 | 
				
			||||
import static de.srsoftware.tools.MimeType.MIME_JSON; | 
				
			||||
import static de.srsoftware.tools.Optionals.nullable; | 
				
			||||
import static java.lang.System.Logger.Level.DEBUG; | 
				
			||||
import static java.lang.System.Logger.Level.WARNING; | 
				
			||||
import static java.net.HttpURLConnection.HTTP_NOT_FOUND; | 
				
			||||
 | 
				
			||||
public abstract class BaseHandler extends PathHandler { | 
				
			||||
 | 
				
			||||
	private static HttpExchange addCors(HttpExchange ex){ | 
				
			||||
		var headers = ex.getRequestHeaders(); | 
				
			||||
		var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny(); | 
				
			||||
		if (origin.isPresent()) { | 
				
			||||
			var url = origin.get(); | 
				
			||||
			headers = ex.getResponseHeaders(); | 
				
			||||
			headers.add("Allow-Origin", url); | 
				
			||||
			headers.add("Access-Control-Allow-Origin", url); | 
				
			||||
			headers.add("Access-Control-Allow-Headers", "Content-Type"); | 
				
			||||
			headers.add("Access-Control-Allow-Credentials", "true"); | 
				
			||||
			headers.add("Access-Control-Allow-Methods","GET, POST, PATCH"); | 
				
			||||
		} | 
				
			||||
		return ex; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public record Document(String mime, byte[] bytes){} | 
				
			||||
 | 
				
			||||
	public boolean load(Path path, HttpExchange ex) throws IOException { | 
				
			||||
		try { | 
				
			||||
			var doc = load(path.toString()); | 
				
			||||
			ex.getResponseHeaders().add(CONTENT_TYPE, doc.mime); | 
				
			||||
			return sendContent(ex,doc.bytes); | 
				
			||||
		} catch (UmbrellaException e) { | 
				
			||||
			throw new RuntimeException(e); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public Document load(String resourcePath) throws IOException, UmbrellaException { | 
				
			||||
		var url = getClass().getClassLoader().getResource(resourcePath); | 
				
			||||
		if (url == null) throw new UmbrellaException(HTTP_NOT_FOUND,"{0} not found!",resourcePath); | 
				
			||||
		LOG.log(DEBUG,"Trying to load {0}",url); | 
				
			||||
		var bos = new ByteArrayOutputStream(); | 
				
			||||
		var conn = url.openConnection(); | 
				
			||||
		var mime = conn.getContentType(); | 
				
			||||
		try (var stream = conn.getInputStream()){ | 
				
			||||
			stream.transferTo(bos); | 
				
			||||
			return new Document(mime,bos.toByteArray()); | 
				
			||||
		} catch (Exception e) { | 
				
			||||
			LOG.log(WARNING,"Failed to load {0}",url); | 
				
			||||
			throw new UmbrellaException(HTTP_NOT_FOUND,"Failed to load {0}",url); | 
				
			||||
		} | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	public boolean send(HttpExchange ex, UmbrellaException e) throws IOException { | 
				
			||||
		return sendContent(ex,e.statusCode(),e.getMessage()); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
@ -1,10 +1,10 @@
				@@ -1,10 +1,10 @@
					 | 
				
			||||
/* © SRSoftware 2025 */ | 
				
			||||
package de.srsoftware.umbrella.user.model; | 
				
			||||
package de.srsoftware.umbrella.core; | 
				
			||||
 | 
				
			||||
import static de.srsoftware.umbrella.core.Constants.TOKEN; | 
				
			||||
 | 
				
			||||
import de.srsoftware.tools.SessionToken; | 
				
			||||
import de.srsoftware.umbrella.core.AddableMap; | 
				
			||||
 | 
				
			||||
import java.util.UUID; | 
				
			||||
 | 
				
			||||
public class Token implements CharSequence{ | 
				
			||||
@ -0,0 +1,5 @@
				@@ -0,0 +1,5 @@
					 | 
				
			||||
# Legacy | 
				
			||||
 | 
				
			||||
This module aims to provide an API for legacy modules to communicate with. | 
				
			||||
 | 
				
			||||
As soon as all "old" PHP modules are replaced, this should no longer be required. | 
				
			||||
@ -0,0 +1,126 @@
				@@ -0,0 +1,126 @@
					 | 
				
			||||
package de.srsoftware.umbrella.legacy; | 
				
			||||
 | 
				
			||||
import com.sun.net.httpserver.HttpExchange; | 
				
			||||
import de.srsoftware.configuration.Configuration; | 
				
			||||
import de.srsoftware.tools.Path; | 
				
			||||
import de.srsoftware.tools.SessionToken; | 
				
			||||
import de.srsoftware.umbrella.core.BaseHandler; | 
				
			||||
import de.srsoftware.umbrella.core.Token; | 
				
			||||
import de.srsoftware.umbrella.core.UmbrellaException; | 
				
			||||
import de.srsoftware.umbrella.user.sqlite.SqliteDB; | 
				
			||||
 | 
				
			||||
import static de.srsoftware.tools.MimeType.MIME_FORM_URL; | 
				
			||||
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR; | 
				
			||||
import static de.srsoftware.umbrella.core.Constants.*; | 
				
			||||
import static de.srsoftware.umbrella.core.Constants.KEY; | 
				
			||||
import static de.srsoftware.umbrella.core.Paths.LOGOUT; | 
				
			||||
import static de.srsoftware.umbrella.core.Paths.SEARCH; | 
				
			||||
import static de.srsoftware.umbrella.core.Util.request; | 
				
			||||
import static java.net.HttpURLConnection.*; | 
				
			||||
import static java.time.temporal.ChronoUnit.DAYS; | 
				
			||||
 | 
				
			||||
import java.io.IOException; | 
				
			||||
import java.time.Instant; | 
				
			||||
import java.util.Map; | 
				
			||||
import java.util.Optional; | 
				
			||||
 | 
				
			||||
public class LegacyApi extends BaseHandler { | 
				
			||||
 | 
				
			||||
	private final SqliteDB users; | 
				
			||||
	private final Configuration config; | 
				
			||||
 | 
				
			||||
	public LegacyApi(SqliteDB userDb, Configuration config)  { | 
				
			||||
		users = userDb; | 
				
			||||
		this.config = config.subset("umbrella.modules").orElseThrow(() -> new RuntimeException("Missing configuration: umbrella.modules")); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	@Override | 
				
			||||
	public boolean doGet(Path path, HttpExchange ex) throws IOException { | 
				
			||||
		var head = path.pop(); | 
				
			||||
		return switch (head){ | 
				
			||||
			case null -> sendRedirect(ex, url(ex).replaceAll("/api/.*","")); | 
				
			||||
			case "common_templates" -> { | 
				
			||||
				allowOrigin(ex, "*"); // add CORS header
 | 
				
			||||
				yield load(path,ex); | 
				
			||||
			} | 
				
			||||
			case LOGIN -> getLegacyLogin(ex); | 
				
			||||
			case LOGOUT-> logout(ex); | 
				
			||||
			case SEARCH -> { | 
				
			||||
				try { | 
				
			||||
					yield legacySearch(ex); | 
				
			||||
				} catch (UmbrellaException e){ | 
				
			||||
					yield send(ex,e); | 
				
			||||
				} | 
				
			||||
			} | 
				
			||||
			default -> super.doGet(path,ex); | 
				
			||||
		}; | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private boolean legacySearch(HttpExchange ex) throws IOException, UmbrellaException { | 
				
			||||
		var optToken = SessionToken.from(ex).map(Token::of); | 
				
			||||
		if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex); | 
				
			||||
		var token = optToken.get(); | 
				
			||||
		var params = queryParam(ex); | 
				
			||||
		var key = params.get(KEY) instanceof String s ? s : null; | 
				
			||||
		if (key == null) throw new UmbrellaException(HTTP_BAD_REQUEST,"Missing search key"); | 
				
			||||
		var fulltext = key.contains("+") || "on".equals(params.get("fulltext")); | 
				
			||||
		StringBuilder searchResult = new StringBuilder(); | 
				
			||||
		if (fulltext){ | 
				
			||||
 | 
				
			||||
			for (var module : config.keys()){ | 
				
			||||
				var baseUrl = config.get(module + ".baseUrl"); | 
				
			||||
				if (baseUrl.isEmpty()) continue; | 
				
			||||
 | 
				
			||||
				var res = request(baseUrl.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,token.asBearer()); | 
				
			||||
				if (!(res instanceof String content) || content.isBlank()) continue; | 
				
			||||
				searchResult.append("<fieldset><label>").append(module).append("</label>"); | 
				
			||||
				searchResult.append(content).append("</fieldset>\n"); | 
				
			||||
			} | 
				
			||||
		} else { | 
				
			||||
			var bookmark = config.get("bookmark.baseUrl"); | 
				
			||||
			if (bookmark.isEmpty()) throw new UmbrellaException(500,"Tag search not available: Bookmark module not configured!"); | 
				
			||||
			var res = request(bookmark.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,null); | 
				
			||||
			if (!(res instanceof String content)) throw new UmbrellaException(500,"Search did not return html content!"); | 
				
			||||
			searchResult.append(content); | 
				
			||||
		} | 
				
			||||
		return sendContent(ex,searchResult.toString()); | 
				
			||||
	}; | 
				
			||||
 | 
				
			||||
	private boolean logout(HttpExchange ex) throws IOException { | 
				
			||||
		var returnTo = queryParam(ex).get("returnTo"); | 
				
			||||
		var optToken = SessionToken.from(ex).map(Token::of); | 
				
			||||
		if (optToken.isPresent()) try{ | 
				
			||||
			var token = optToken.get(); | 
				
			||||
			users.dropSession(token); | 
				
			||||
			var expiredToken = new SessionToken(token.toString(),"/", Instant.now().minus(1, DAYS),true); | 
				
			||||
			expiredToken.addTo(ex); | 
				
			||||
			if (returnTo instanceof String location) return sendRedirect(ex,location); | 
				
			||||
			return sendContent(ex, Map.of(REDIRECT,"home")); | 
				
			||||
		} catch (UmbrellaException e) { | 
				
			||||
			return send(ex,e); | 
				
			||||
		} | 
				
			||||
		if (returnTo instanceof String location) return sendRedirect(ex,location); | 
				
			||||
		return sendContent(ex,Map.of(REDIRECT,"home")); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	private boolean getLegacyLogin(HttpExchange ex) throws IOException { | 
				
			||||
		var returnTo = queryParam(ex).get("returnTo"); | 
				
			||||
		if (returnTo instanceof String url) { | 
				
			||||
			var token = SessionToken.from(ex).map(SessionToken::sessionId) | 
				
			||||
					.or(() -> getBearer(ex)) | 
				
			||||
					.map(Token::of); | 
				
			||||
			if (token.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex); | 
				
			||||
			return sendRedirect(ex, url + (url.contains("?") ? "&" : "?") + "token=" + token.get()); | 
				
			||||
		} | 
				
			||||
 | 
				
			||||
		var location = url(ex); | 
				
			||||
		location = location.replaceAll("/api/.*","/login"); | 
				
			||||
		if (returnTo != null) location += "?returnTo="+returnTo; | 
				
			||||
		return sendRedirect(ex,location); | 
				
			||||
	} | 
				
			||||
 | 
				
			||||
	@Override | 
				
			||||
	public boolean doPost(Path path, HttpExchange ex) throws IOException { | 
				
			||||
		return super.doPost(path, ex); | 
				
			||||
	} | 
				
			||||
} | 
				
			||||
					Loading…
					
					
				
		Reference in new issue