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 @@ |
|||||||
|
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 @@ |
|||||||
/* © SRSoftware 2025 */ |
/* © SRSoftware 2025 */ |
||||||
package de.srsoftware.umbrella.user.model; |
package de.srsoftware.umbrella.core; |
||||||
|
|
||||||
import static de.srsoftware.umbrella.core.Constants.TOKEN; |
import static de.srsoftware.umbrella.core.Constants.TOKEN; |
||||||
|
|
||||||
import de.srsoftware.tools.SessionToken; |
import de.srsoftware.tools.SessionToken; |
||||||
import de.srsoftware.umbrella.core.AddableMap; |
|
||||||
import java.util.UUID; |
import java.util.UUID; |
||||||
|
|
||||||
public class Token implements CharSequence{ |
public class Token implements CharSequence{ |
||||||
@ -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 @@ |
|||||||
|
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