From b5bed6ceefe596e2537c2cde0c61f7a27186a855 Mon Sep 17 00:00:00 2001 From: Benj Date: Thu, 1 Sep 2022 21:33:03 +0800 Subject: [PATCH] Update HttpServer & AuthenticationSystem to use Javalin --- build.gradle | 1 + .../auth/AuthenticationSystem.java | 38 +- .../auth/DefaultAuthenticators.java | 28 +- .../grasscutter/server/http/HttpServer.java | 42 +- .../emu/grasscutter/server/http/Router.java | 9 +- .../server/http/dispatch/DispatchHandler.java | 73 +- .../server/http/dispatch/RegionHandler.java | 44 +- .../documentation/DocumentationHandler.java | 5 +- .../DocumentationServerHandler.java | 12 +- .../GachaMappingRequestHandler.java | 10 +- .../documentation/HandbookRequestHandler.java | 11 +- .../documentation/RootRequestHandler.java | 13 +- .../http/handlers/AnnouncementsHandler.java | 51 +- .../server/http/handlers/GachaHandler.java | 50 +- .../server/http/handlers/GenericHandler.java | 37 +- .../server/http/handlers/LogHandler.java | 16 +- .../server/http/objects/HttpJsonResponse.java | 16 +- .../objects/WebStaticVersionResponse.java | 29 +- .../java/emu/grasscutter/utils/HttpUtils.java | 738 ++++++++++++++++++ 19 files changed, 963 insertions(+), 260 deletions(-) create mode 100644 src/main/java/emu/grasscutter/utils/HttpUtils.java diff --git a/build.gradle b/build.gradle index 1b800180f..fd79b73d5 100644 --- a/build.gradle +++ b/build.gradle @@ -94,6 +94,7 @@ dependencies { implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1' implementation group: 'io.javalin', name: 'javalin', version: '4.6.4' + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.13.3' protobuf files('proto/') diff --git a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java index 90f2648c5..828c4eca0 100644 --- a/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java +++ b/src/main/java/emu/grasscutter/auth/AuthenticationSystem.java @@ -2,8 +2,7 @@ package emu.grasscutter.auth; import emu.grasscutter.game.Account; import emu.grasscutter.server.http.objects.*; -import express.http.Request; -import express.http.Response; +import io.javalin.http.Context; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -15,7 +14,7 @@ import javax.annotation.Nullable; * Can be changed by plugins. */ public interface AuthenticationSystem { - + /** * Called when a user requests to make an account. * @param username The provided username. @@ -71,9 +70,8 @@ public interface AuthenticationSystem { */ @Builder @AllArgsConstructor @Getter class AuthenticationRequest { - private final Request request; - @Nullable private final Response response; - + private final Context context; + @Nullable private final LoginAccountRequestJson passwordRequest; @Nullable private final LoginTokenRequestJson tokenRequest; @Nullable private final ComboTokenReqJson sessionKeyRequest; @@ -82,53 +80,51 @@ public interface AuthenticationSystem { /** * Generates an authentication request from a {@link LoginAccountRequestJson} object. - * @param request The Express request. + * @param ctx The Javalin context. * @param jsonData The JSON data. * @return An authentication request. */ - static AuthenticationRequest fromPasswordRequest(Request request, LoginAccountRequestJson jsonData) { + static AuthenticationRequest fromPasswordRequest(Context ctx, LoginAccountRequestJson jsonData) { return AuthenticationRequest.builder() - .request(request) + .context(ctx) .passwordRequest(jsonData) .build(); } /** * Generates an authentication request from a {@link LoginTokenRequestJson} object. - * @param request The Express request. + * @param ctx The Javalin context. * @param jsonData The JSON data. * @return An authentication request. */ - static AuthenticationRequest fromTokenRequest(Request request, LoginTokenRequestJson jsonData) { + static AuthenticationRequest fromTokenRequest(Context ctx, LoginTokenRequestJson jsonData) { return AuthenticationRequest.builder() - .request(request) + .context(ctx) .tokenRequest(jsonData) .build(); } /** * Generates an authentication request from a {@link ComboTokenReqJson} object. - * @param request The Express request. + * @param ctx The Javalin context. * @param jsonData The JSON data. * @return An authentication request. */ - static AuthenticationRequest fromComboTokenRequest(Request request, ComboTokenReqJson jsonData, + static AuthenticationRequest fromComboTokenRequest(Context ctx, ComboTokenReqJson jsonData, ComboTokenReqJson.LoginTokenData tokenData) { return AuthenticationRequest.builder() - .request(request) + .context(ctx) .sessionKeyRequest(jsonData) .sessionKeyData(tokenData) .build(); } /** - * Generates an authentication request from a {@link Response} object. - * @param request The Express request. - * @param response the Express response. + * Generates an authentication request from a {@link Context} object. + * @param ctx The Javalin context. * @return An authentication request. */ - static AuthenticationRequest fromExternalRequest(Request request, Response response) { - return AuthenticationRequest.builder().request(request) - .response(response).build(); + static AuthenticationRequest fromExternalRequest(Context ctx) { + return AuthenticationRequest.builder().context(ctx).build(); } } diff --git a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java index cc9080044..85b9e8e29 100644 --- a/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java +++ b/src/main/java/emu/grasscutter/auth/DefaultAuthenticators.java @@ -36,7 +36,7 @@ public final class DefaultAuthenticators { int playerCount = Grasscutter.getGameServer().getPlayers().size(); boolean successfulLogin = false; - String address = request.getRequest().ip(); + String address = request.getContext().ip(); String responseMessage = translate("messages.dispatch.account.username_error"); String loggerMessage = ""; @@ -99,7 +99,7 @@ public final class DefaultAuthenticators { int playerCount = Grasscutter.getGameServer().getPlayers().size(); boolean successfulLogin = false; - String address = request.getRequest().ip(); + String address = request.getContext().ip(); String responseMessage = translate("messages.dispatch.account.username_error"); String loggerMessage = ""; String decryptedPassword = ""; @@ -205,7 +205,7 @@ public final class DefaultAuthenticators { assert requestData != null; boolean successfulLogin; - String address = request.getRequest().ip(); + String address = request.getContext().ip(); String loggerMessage; int playerCount = Grasscutter.getGameServer().getPlayers().size(); @@ -263,7 +263,7 @@ public final class DefaultAuthenticators { assert loginData != null; boolean successfulLogin; - String address = request.getRequest().ip(); + String address = request.getContext().ip(); String loggerMessage; int playerCount = Grasscutter.getGameServer().getPlayers().size(); @@ -309,43 +309,37 @@ public final class DefaultAuthenticators { public static class ExternalAuthentication implements ExternalAuthenticator { @Override public void handleLogin(AuthenticationRequest request) { - assert request.getResponse() != null; - request.getResponse().send("Authentication is not available with the default authentication method."); + request.getContext().result("Authentication is not available with the default authentication method."); } @Override public void handleAccountCreation(AuthenticationRequest request) { - assert request.getResponse() != null; - request.getResponse().send("Authentication is not available with the default authentication method."); + request.getContext().result("Authentication is not available with the default authentication method."); } @Override public void handlePasswordReset(AuthenticationRequest request) { - assert request.getResponse() != null; - request.getResponse().send("Authentication is not available with the default authentication method."); + request.getContext().result("Authentication is not available with the default authentication method."); } } /** - * Handles authentication requests from OAuth sources. + * Handles authentication requests from OAuth sources.Zenlith */ public static class OAuthAuthentication implements OAuthAuthenticator { @Override public void handleLogin(AuthenticationRequest request) { - assert request.getResponse() != null; - request.getResponse().send("Authentication is not available with the default authentication method."); + request.getContext().result("Authentication is not available with the default authentication method."); } @Override public void handleRedirection(AuthenticationRequest request, ClientType type) { - assert request.getResponse() != null; - request.getResponse().send("Authentication is not available with the default authentication method."); + request.getContext().result("Authentication is not available with the default authentication method."); } @Override public void handleTokenProcess(AuthenticationRequest request) { - assert request.getResponse() != null; - request.getResponse().send("Authentication is not available with the default authentication method."); + request.getContext().result("Authentication is not available with the default authentication method."); } } } diff --git a/src/main/java/emu/grasscutter/server/http/HttpServer.java b/src/main/java/emu/grasscutter/server/http/HttpServer.java index 1ad62fb26..b34a6e35b 100644 --- a/src/main/java/emu/grasscutter/server/http/HttpServer.java +++ b/src/main/java/emu/grasscutter/server/http/HttpServer.java @@ -3,9 +3,9 @@ package emu.grasscutter.server.http; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.utils.FileUtils; -import express.Express; -import express.http.MediaType; +import emu.grasscutter.utils.HttpUtils; import io.javalin.Javalin; +import io.javalin.core.util.JavalinLogger; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -21,14 +21,14 @@ import static emu.grasscutter.utils.Language.translate; * (including dispatch, announcements, gacha, etc.) */ public final class HttpServer { - private final Express express; + private final Javalin javalin; /** - * Configures the Express application. + * Configures the Javalin application. */ public HttpServer() { - this.express = new Express(config -> { - // Set the Express HTTP server. + this.javalin = Javalin.create(config -> { + // Set the Javalin HTTP server. config.server(HttpServer::createServer); // Configure encryption/HTTPS/SSL. @@ -46,8 +46,7 @@ public final class HttpServer { if (DISPATCH_INFO.logRequests == ServerDebugMode.ALL) config.enableDevLogging(); - // Disable compression on static files. - config.precompressStaticFiles = false; + // Static files should be added like this https://javalin.io/documentation#static-files }); } @@ -100,7 +99,7 @@ public final class HttpServer { * @return A Javalin instance. */ public Javalin getHandle() { - return this.express.raw(); + return this.javalin; } /** @@ -118,7 +117,7 @@ public final class HttpServer { try { // Create a router instance & apply routes. var constructor = router.getDeclaredConstructor(types); // Get the constructor. var routerInstance = constructor.newInstance(args); // Create instance. - routerInstance.applyRoutes(this.express, this.getHandle()); // Apply routes. + routerInstance.applyRoutes(this.javalin); // Apply routes. } catch (Exception exception) { Grasscutter.getLogger().warn(translate("messages.dispatch.router_error"), exception); } return this; @@ -131,24 +130,24 @@ public final class HttpServer { public void start() throws UnsupportedEncodingException { // Attempt to start the HTTP server. if (HTTP_INFO.bindAddress.equals("")) { - this.express.listen(HTTP_INFO.bindPort); + this.javalin.start(HTTP_INFO.bindPort); }else { - this.express.listen(HTTP_INFO.bindAddress, HTTP_INFO.bindPort); + this.javalin.start(HTTP_INFO.bindAddress, HTTP_INFO.bindPort); } // Log bind information. - Grasscutter.getLogger().info(translate("messages.dispatch.address_bind", HTTP_INFO.accessAddress, this.express.raw().port())); + Grasscutter.getLogger().info(translate("messages.dispatch.address_bind", HTTP_INFO.accessAddress, this.javalin.port())); } /** * Handles the '/' (index) endpoint on the Express application. */ public static class DefaultRequestRouter implements Router { - @Override public void applyRoutes(Express express, Javalin handle) { - express.get("/", (request, response) -> { + @Override public void applyRoutes(Javalin javalin) { + javalin.get("/", ctx -> { File file = new File(HTTP_STATIC_FILES.indexFile); if (!file.exists()) - response.send(""" + ctx.result(""" @@ -159,9 +158,8 @@ public final class HttpServer { """.formatted(translate("messages.status.welcome"))); else { final var filePath = file.getPath(); - final MediaType fromExtension = MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1)); - response.type((fromExtension != null) ? fromExtension.getMIME() : "text/plain") - .send(FileUtils.read(filePath)); + final HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1)); + ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "text/plain").result(FileUtils.read(filePath)); } }); } @@ -171,8 +169,8 @@ public final class HttpServer { * Handles unhandled endpoints on the Express application. */ public static class UnhandledRequestRouter implements Router { - @Override public void applyRoutes(Express express, Javalin handle) { - handle.error(404, context -> { + @Override public void applyRoutes(Javalin javalin) { + javalin.error(404, context -> { if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING) Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", context.method(), context.url())); context.contentType("text/html"); @@ -193,7 +191,7 @@ public final class HttpServer { """); else { final var filePath = file.getPath(); - final MediaType fromExtension = MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1)); + final HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(filePath.substring(filePath.lastIndexOf(".") + 1)); context.contentType((fromExtension != null) ? fromExtension.getMIME() : "text/plain") .result(FileUtils.read(filePath)); } diff --git a/src/main/java/emu/grasscutter/server/http/Router.java b/src/main/java/emu/grasscutter/server/http/Router.java index 1720d7ca0..3e3f2877b 100644 --- a/src/main/java/emu/grasscutter/server/http/Router.java +++ b/src/main/java/emu/grasscutter/server/http/Router.java @@ -1,16 +1,15 @@ package emu.grasscutter.server.http; -import express.Express; import io.javalin.Javalin; /** - * Defines routes for an {@link Express} instance. + * Defines routes for an {@link Javalin} instance. */ public interface Router { - + /** * Called when the router is initialized by Express. - * @param express An Express instance. + * @param javalin A Javalin instance. */ - void applyRoutes(Express express, Javalin handle); + void applyRoutes(Javalin javalin); } diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java index 6a93b5440..0678abd43 100644 --- a/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java +++ b/src/main/java/emu/grasscutter/server/http/dispatch/DispatchHandler.java @@ -2,16 +2,13 @@ package emu.grasscutter.server.http.dispatch; import emu.grasscutter.Grasscutter; import emu.grasscutter.auth.AuthenticationSystem; -import emu.grasscutter.auth.OAuthAuthenticator; import emu.grasscutter.auth.OAuthAuthenticator.ClientType; import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.objects.*; import emu.grasscutter.server.http.objects.ComboTokenReqJson.LoginTokenData; import emu.grasscutter.utils.JsonUtils; -import express.Express; -import express.http.Request; -import express.http.Response; import io.javalin.Javalin; +import io.javalin.http.Context; import static emu.grasscutter.utils.Language.translate; @@ -19,40 +16,40 @@ import static emu.grasscutter.utils.Language.translate; * Handles requests related to authentication. (aka dispatch) */ public final class DispatchHandler implements Router { - @Override public void applyRoutes(Express express, Javalin handle) { + @Override public void applyRoutes(Javalin javalin) { // Username & Password login (from client). - express.post("/hk4e_global/mdk/shield/api/login", DispatchHandler::clientLogin); + javalin.post("/hk4e_global/mdk/shield/api/login", DispatchHandler::clientLogin); // Cached token login (from registry). - express.post("/hk4e_global/mdk/shield/api/verify", DispatchHandler::tokenLogin); + javalin.post("/hk4e_global/mdk/shield/api/verify", DispatchHandler::tokenLogin); // Combo token login (from session key). - express.post("/hk4e_global/combo/granter/login/v2/login", DispatchHandler::sessionKeyLogin); + javalin.post("/hk4e_global/combo/granter/login/v2/login", DispatchHandler::sessionKeyLogin); // External login (from other clients). - express.get("/authentication/type", (request, response) -> response.send(Grasscutter.getAuthenticationSystem().getClass().getSimpleName())); - express.post("/authentication/login", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() - .handleLogin(AuthenticationSystem.fromExternalRequest(request, response))); - express.post("/authentication/register", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() - .handleAccountCreation(AuthenticationSystem.fromExternalRequest(request, response))); - express.post("/authentication/change_password", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() - .handlePasswordReset(AuthenticationSystem.fromExternalRequest(request, response))); + javalin.get("/authentication/type", ctx -> ctx.result(Grasscutter.getAuthenticationSystem().getClass().getSimpleName())); + javalin.post("/authentication/login", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() + .handleLogin(AuthenticationSystem.fromExternalRequest(ctx))); + javalin.post("/authentication/register", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() + .handleAccountCreation(AuthenticationSystem.fromExternalRequest(ctx))); + javalin.post("/authentication/change_password", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() + .handlePasswordReset(AuthenticationSystem.fromExternalRequest(ctx))); // External login (from OAuth2). - express.post("/hk4e_global/mdk/shield/api/loginByThirdparty", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() - .handleLogin(AuthenticationSystem.fromExternalRequest(request, response))); - express.get("/authentication/openid/redirect", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() - .handleTokenProcess(AuthenticationSystem.fromExternalRequest(request, response))); - express.get("/Api/twitter_login", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() - .handleRedirection(AuthenticationSystem.fromExternalRequest(request, response), ClientType.DESKTOP)); - express.get("/sdkTwitterLogin.html", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() - .handleRedirection(AuthenticationSystem.fromExternalRequest(request, response), ClientType.MOBILE)); + javalin.post("/hk4e_global/mdk/shield/api/loginByThirdparty", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() + .handleLogin(AuthenticationSystem.fromExternalRequest(ctx))); + javalin.get("/authentication/openid/redirect", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() + .handleTokenProcess(AuthenticationSystem.fromExternalRequest(ctx))); + javalin.get("/Api/twitter_login", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() + .handleRedirection(AuthenticationSystem.fromExternalRequest(ctx), ClientType.DESKTOP)); + javalin.get("/sdkTwitterLogin.html", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() + .handleRedirection(AuthenticationSystem.fromExternalRequest(ctx), ClientType.MOBILE)); } /** * @route /hk4e_global/mdk/shield/api/login */ - private static void clientLogin(Request request, Response response) { + private static void clientLogin(Context ctx) { // Parse body data. - String rawBodyData = request.ctx().body(); + String rawBodyData = ctx.body(); var bodyData = JsonUtils.decode(rawBodyData, LoginAccountRequestJson.class); // Validate body data. @@ -62,20 +59,20 @@ public final class DispatchHandler implements Router { // Pass data to authentication handler. var responseData = Grasscutter.getAuthenticationSystem() .getPasswordAuthenticator() - .authenticate(AuthenticationSystem.fromPasswordRequest(request, bodyData)); + .authenticate(AuthenticationSystem.fromPasswordRequest(ctx, bodyData)); // Send response. - response.send(responseData); + ctx.json(responseData); // Log to console. - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", ctx.ip())); } /** * @route /hk4e_global/mdk/shield/api/verify */ - private static void tokenLogin(Request request, Response response) { + private static void tokenLogin(Context ctx) { // Parse body data. - String rawBodyData = request.ctx().body(); + String rawBodyData = ctx.body(); var bodyData = JsonUtils.decode(rawBodyData, LoginTokenRequestJson.class); // Validate body data. @@ -85,20 +82,20 @@ public final class DispatchHandler implements Router { // Pass data to authentication handler. var responseData = Grasscutter.getAuthenticationSystem() .getTokenAuthenticator() - .authenticate(AuthenticationSystem.fromTokenRequest(request, bodyData)); + .authenticate(AuthenticationSystem.fromTokenRequest(ctx, bodyData)); // Send response. - response.send(responseData); + ctx.json(responseData); // Log to console. - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", ctx.ip())); } /** * @route /hk4e_global/combo/granter/login/v2/login */ - private static void sessionKeyLogin(Request request, Response response) { + private static void sessionKeyLogin(Context ctx) { // Parse body data. - String rawBodyData = request.ctx().body(); + String rawBodyData = ctx.body(); var bodyData = JsonUtils.decode(rawBodyData, ComboTokenReqJson.class); // Validate body data. @@ -111,11 +108,11 @@ public final class DispatchHandler implements Router { // Pass data to authentication handler. var responseData = Grasscutter.getAuthenticationSystem() .getSessionKeyAuthenticator() - .authenticate(AuthenticationSystem.fromComboTokenRequest(request, bodyData, tokenData)); + .authenticate(AuthenticationSystem.fromComboTokenRequest(ctx, bodyData, tokenData)); // Send response. - response.send(responseData); + ctx.json(responseData); // Log to console. - Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", request.ip())); + Grasscutter.getLogger().info(translate("messages.dispatch.account.login_attempt", ctx.ip())); } } diff --git a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java index 40a2c2037..8c4d98748 100644 --- a/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java +++ b/src/main/java/emu/grasscutter/server/http/dispatch/RegionHandler.java @@ -11,20 +11,12 @@ import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent; import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.objects.QueryCurRegionRspJson; import emu.grasscutter.utils.Crypto; -import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.Utils; -import express.Express; -import express.http.Request; -import express.http.Response; import io.javalin.Javalin; +import io.javalin.http.Context; import javax.crypto.Cipher; import java.io.ByteArrayOutputStream; -import java.security.KeyFactory; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.security.Signature; @@ -107,36 +99,36 @@ public final class RegionHandler implements Router { regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray()); } - @Override public void applyRoutes(Express express, Javalin handle) { - express.get("/query_region_list", RegionHandler::queryRegionList); - express.get("/query_cur_region/:region", RegionHandler::queryCurrentRegion ); + @Override public void applyRoutes(Javalin javalin) { + javalin.get("/query_region_list", RegionHandler::queryRegionList); + javalin.get("/query_cur_region/{region}", RegionHandler::queryCurrentRegion ); } /** * @route /query_region_list */ - private static void queryRegionList(Request request, Response response) { + private static void queryRegionList(Context ctx) { // Invoke event. QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); event.call(); // Respond with event result. - response.send(event.getRegionList()); + ctx.result(event.getRegionList()); // Log to console. - Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", request.ip())); + Grasscutter.getLogger().info(String.format("[Dispatch] Client %s request: query_region_list", ctx.ip())); } /** - * @route /query_cur_region/:region + * @route /query_cur_region/{region} */ - private static void queryCurrentRegion(Request request, Response response) { + private static void queryCurrentRegion(Context ctx) { // Get region to query. - String regionName = request.params("region"); - String versionName = request.query("version"); + String regionName = ctx.pathParam("region"); + String versionName = ctx.queryParam("version"); var region = regions.get(regionName); // Get region data. String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; - if (request.query().values().size() > 0) { + if (ctx.queryParamMap().values().size() > 0) { if (region != null) regionData = region.getBase64(); } @@ -150,18 +142,18 @@ public final class RegionHandler implements Router { try { QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); - if (request.query("dispatchSeed") == null) { + if (ctx.queryParam("dispatchSeed") == null) { // More love for UA Patch players var rsp = new QueryCurRegionRspJson(); rsp.content = event.getRegionInfo(); rsp.sign = "TW9yZSBsb3ZlIGZvciBVQSBQYXRjaCBwbGF5ZXJz"; - response.send(rsp); + ctx.json(rsp); return; } - String key_id = request.query("key_id"); + String key_id = ctx.queryParam("key_id"); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(Cipher.ENCRYPT_MODE, key_id.equals("3") ? Crypto.CUR_OS_ENCRYPT_KEY : Crypto.CUR_CN_ENCRYPT_KEY); var regionInfo = Utils.base64Decode(event.getRegionInfo()); @@ -189,7 +181,7 @@ public final class RegionHandler implements Router { rsp.content = Utils.base64Encode(encryptedRegionInfoStream.toByteArray()); rsp.sign = Utils.base64Encode(privateSignature.sign()); - response.send(rsp); + ctx.json(rsp); } catch (Exception e) { Grasscutter.getLogger().error("An error occurred while handling query_cur_region.", e); @@ -199,10 +191,10 @@ public final class RegionHandler implements Router { // Invoke event. QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); // Respond with event result. - response.send(event.getRegionInfo()); + ctx.result(event.getRegionInfo()); } // Log to console. - Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region/%s", request.ip(), regionName)); + Grasscutter.getLogger().info(String.format("Client %s request: query_cur_region/%s", ctx.ip(), regionName)); } /** diff --git a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java index de7f543a3..05468e954 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationHandler.java @@ -1,9 +1,8 @@ package emu.grasscutter.server.http.documentation; -import express.http.Request; -import express.http.Response; +import io.javalin.http.Context; interface DocumentationHandler { - void handle(Request request, Response response); + void handle(Context ctx); } diff --git a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java index 24c8236de..6e64286c2 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/DocumentationServerHandler.java @@ -1,19 +1,21 @@ package emu.grasscutter.server.http.documentation; import emu.grasscutter.server.http.Router; -import express.Express; import io.javalin.Javalin; +import io.javalin.http.Context; public final class DocumentationServerHandler implements Router { @Override - public void applyRoutes(Express express, Javalin handle) { + public void applyRoutes(Javalin javalin) { final RootRequestHandler root = new RootRequestHandler(); final HandbookRequestHandler handbook = new HandbookRequestHandler(); final GachaMappingRequestHandler gachaMapping = new GachaMappingRequestHandler(); - express.get("/documentation/handbook", handbook::handle); - express.get("/documentation/gachamapping", gachaMapping::handle); - express.get("/documentation", root::handle); + // TODO: Removal + // TODO: Forward /documentation requests to https://grasscutter.io/wiki + javalin.get("/documentation/handbook", handbook::handle); + javalin.get("/documentation/gachamapping", gachaMapping::handle); + javalin.get("/documentation", root::handle); } } diff --git a/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java index 6b821a3f1..51d52dc7a 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/GachaMappingRequestHandler.java @@ -1,9 +1,9 @@ package emu.grasscutter.server.http.documentation; import emu.grasscutter.tools.Tools; +import emu.grasscutter.utils.HttpUtils; import emu.grasscutter.utils.Language; -import express.http.Request; -import express.http.Response; +import io.javalin.http.Context; import static emu.grasscutter.config.Configuration.DOCUMENT_LANGUAGE; @@ -17,10 +17,8 @@ final class GachaMappingRequestHandler implements DocumentationHandler { } @Override - public void handle(Request request, Response response) { + public void handle(Context ctx) { final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow - response.set("Content-Type", "application/json") - .ctx() - .result(gachaJsons.get(langIdx)); + ctx.contentType(HttpUtils.MediaType._json.getMIME()).result(gachaJsons.get(langIdx)); } } diff --git a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java index cb260e3a2..c207461ee 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/HandbookRequestHandler.java @@ -10,10 +10,10 @@ import emu.grasscutter.data.excels.ItemData; import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.data.excels.SceneData; import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.HttpUtils; import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Utils; -import express.http.Request; -import express.http.Response; +import io.javalin.http.Context; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import java.io.File; import java.nio.charset.StandardCharsets; @@ -36,12 +36,13 @@ final class HandbookRequestHandler implements DocumentationHandler { } @Override - public void handle(Request request, Response response) { + public void handle(Context ctx) { final int langIdx = Language.TextStrings.MAP_LANGUAGES.getOrDefault(DOCUMENT_LANGUAGE, 0); // TODO: This should really be based off the client language somehow if (template == null) { - response.status(500); + ctx.status(500); } else { - response.send(handbookHtmls.get(langIdx)); + ctx.contentType(HttpUtils.MediaType._html.getMIME()); + ctx.result(handbookHtmls.get(langIdx)); } } diff --git a/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java b/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java index 7cf376979..2ff9dd2cf 100644 --- a/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java +++ b/src/main/java/emu/grasscutter/server/http/documentation/RootRequestHandler.java @@ -4,11 +4,11 @@ import static emu.grasscutter.config.Configuration.DATA; import static emu.grasscutter.utils.Language.translate; import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.ResourceLoader; import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.HttpUtils; import emu.grasscutter.utils.Utils; -import express.http.Request; -import express.http.Response; +import io.javalin.http.Context; + import java.io.File; import java.nio.charset.StandardCharsets; @@ -27,15 +27,16 @@ final class RootRequestHandler implements DocumentationHandler { } @Override - public void handle(Request request, Response response) { + public void handle(Context ctx) { if (template == null) { - response.status(500); + ctx.status(500); return; } String content = template.replace("{{TITLE}}", translate("documentation.index.title")) .replace("{{ITEM_HANDBOOK}}", translate("documentation.index.handbook")) .replace("{{ITEM_GACHA_MAPPING}}", translate("documentation.index.gacha_mapping")); - response.send(content); + ctx.contentType(HttpUtils.MediaType._html.getMIME()); + ctx.result(content); } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java index 69ebc6b96..5191c8869 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/AnnouncementsHandler.java @@ -5,44 +5,39 @@ import emu.grasscutter.data.DataLoader; import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.Router; import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.HttpUtils; import emu.grasscutter.utils.Utils; -import express.Express; -import express.http.MediaType; -import express.http.Request; -import express.http.Response; import io.javalin.Javalin; +import io.javalin.http.Context; import static emu.grasscutter.config.Configuration.*; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.nio.charset.StandardCharsets; import java.util.Objects; /** * Handles requests related to the announcements page. */ public final class AnnouncementsHandler implements Router { - @Override public void applyRoutes(Express express, Javalin handle) { + @Override public void applyRoutes(Javalin javalin) { // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); + HttpUtils.allRoutes(javalin, "/common/hk4e_global/announcement/api/getAlertPic", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"total\":0,\"list\":[]}}")); // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAlertAnn", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); + HttpUtils.allRoutes(javalin,"/common/hk4e_global/announcement/api/getAlertAnn", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"alert\":false,\"alert_id\":0,\"remind\":true}}")); // hk4e-api-os.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAnnList", AnnouncementsHandler::getAnnouncement); + HttpUtils.allRoutes(javalin,"/common/hk4e_global/announcement/api/getAnnList", AnnouncementsHandler::getAnnouncement); // hk4e-api-os-static.hoyoverse.com - express.all("/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement); + HttpUtils.allRoutes(javalin,"/common/hk4e_global/announcement/api/getAnnContent", AnnouncementsHandler::getAnnouncement); // hk4e-sdk-os.hoyoverse.com - express.all("/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); + HttpUtils.allRoutes(javalin,"/hk4e_global/mdk/shopwindow/shopwindow/listPriceTier", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"suggest_currency\":\"USD\",\"tiers\":[]}}")); - express.get("/hk4e/announcement/*", AnnouncementsHandler::getPageResources); + javalin.get("/hk4e/announcement/*", AnnouncementsHandler::getPageResources); } - private static void getAnnouncement(Request request, Response response) { + private static void getAnnouncement(Context ctx) { String data = ""; - if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnContent")) { + if (Objects.equals(ctx.endpointHandlerPath(), "/common/hk4e_global/announcement/api/getAnnContent")) { try { data = FileUtils.readToString(DataLoader.load("GameAnnouncement.json")); } catch (Exception e) { @@ -50,7 +45,7 @@ public final class AnnouncementsHandler implements Router { Grasscutter.getLogger().info("Unable to read file 'GameAnnouncementList.json'. \n" + e); } } - } else if (Objects.equals(request.baseUrl(), "/common/hk4e_global/announcement/api/getAnnList")) { + } else if (Objects.equals(ctx.endpointHandlerPath(), "/common/hk4e_global/announcement/api/getAnnList")) { try { data = FileUtils.readToString(DataLoader.load("GameAnnouncementList.json")); } catch (Exception e) { @@ -59,11 +54,11 @@ public final class AnnouncementsHandler implements Router { } } } else { - response.send("{\"retcode\":404,\"message\":\"Unknown request path\"}"); + ctx.result("{\"retcode\":404,\"message\":\"Unknown request path\"}"); } if (data.isEmpty()) { - response.send("{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}"); + ctx.result("{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}"); return; } @@ -74,19 +69,19 @@ public final class AnnouncementsHandler implements Router { data = data .replace("{{DISPATCH_PUBLIC}}", dispatchDomain) .replace("{{SYSTEM_TIME}}", String.valueOf(System.currentTimeMillis())); - response.send("{\"retcode\":0,\"message\":\"OK\",\"data\": " + data + "}"); + ctx.result("{\"retcode\":0,\"message\":\"OK\",\"data\": " + data + "}"); } - private static void getPageResources(Request request, Response response) { - try (InputStream filestream = DataLoader.load(request.path())) { - String possibleFilename = Utils.toFilePath(DATA(request.path())); + private static void getPageResources(Context ctx) { + try (InputStream filestream = DataLoader.load(ctx.path())) { + String possibleFilename = Utils.toFilePath(DATA(ctx.path())); - MediaType fromExtension = MediaType.getByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1)); - response.type((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream"); - response.send(filestream.readAllBytes()); + HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1)); + ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream"); + ctx.result(filestream.readAllBytes()); } catch (Exception e) { - Grasscutter.getLogger().warn("File does not exist: " + request.path()); - response.status(404); + Grasscutter.getLogger().warn("File does not exist: " + ctx.path()); + ctx.status(404); } } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java index 38548b1d6..9c883e6b2 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/GachaHandler.java @@ -7,13 +7,11 @@ import emu.grasscutter.game.gacha.GachaBanner; import emu.grasscutter.game.gacha.GachaSystem; import emu.grasscutter.game.player.Player; import emu.grasscutter.server.http.Router; -import emu.grasscutter.tools.Tools; import emu.grasscutter.utils.FileUtils; +import emu.grasscutter.utils.HttpUtils; import emu.grasscutter.utils.Utils; -import express.Express; -import express.http.Request; -import express.http.Response; import io.javalin.Javalin; +import io.javalin.http.Context; import io.javalin.http.staticfiles.Location; import java.io.File; @@ -31,38 +29,38 @@ import static emu.grasscutter.utils.Language.translate; public final class GachaHandler implements Router { public static final String gachaMappings = DATA(Utils.toFilePath("gacha/mappings.js")); - @Override public void applyRoutes(Express express, Javalin handle) { - express.get("/gacha", GachaHandler::gachaRecords); - express.get("/gacha/details", GachaHandler::gachaDetails); + @Override public void applyRoutes(Javalin javalin) { + javalin.get("/gacha", GachaHandler::gachaRecords); + javalin.get("/gacha/details", GachaHandler::gachaDetails); - express.useStaticFallback("/gacha/mappings", gachaMappings, Location.EXTERNAL); + javalin._conf.addSinglePageRoot("/gacha/mappings", gachaMappings, Location.EXTERNAL); } - private static void gachaRecords(Request request, Response response) { + private static void gachaRecords(Context ctx) { File recordsTemplate = new File(Utils.toFilePath(DATA("gacha/records.html"))); if (!recordsTemplate.exists()) { Grasscutter.getLogger().warn("File does not exist: " + recordsTemplate); - response.status(500); + ctx.status(500); return; } - String sessionKey = request.query("s"); + String sessionKey = ctx.queryParam("s"); Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); if (account == null) { - response.status(403).send("Requested account was not found"); + ctx.status(403).result("Requested account was not found"); return; } Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId()); if (player == null) { - response.status(403).send("No player associated with requested account"); + ctx.status(403).result("No player associated with requested account"); return; } int page = 0, gachaType = 0; - if (request.query("p") != null) - page = Integer.parseInt(request.query("p")); - if (request.query("gachaType") != null) - gachaType = Integer.parseInt(request.query("gachaType")); + if (ctx.queryParam("p") != null) + page = Integer.parseInt(ctx.queryParam("p")); + if (ctx.queryParam("gachaType") != null) + gachaType = Integer.parseInt(ctx.queryParam("gachaType")); String records = DatabaseHelper.getGachaRecords(player.getUid(), page, gachaType).toString(); long maxPage = DatabaseHelper.getGachaRecordsMaxPage(player.getUid(), page, gachaType); @@ -74,26 +72,27 @@ public final class GachaHandler implements Router { .replace("{{DATE}}", translate(player, "gacha.records.date")) .replace("{{ITEM}}", translate(player, "gacha.records.item")) .replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale())); - response.send(template); + ctx.contentType(HttpUtils.MediaType._html.getMIME()); + ctx.result(template); } - private static void gachaDetails(Request request, Response response) { + private static void gachaDetails(Context ctx) { File detailsTemplate = new File(Utils.toFilePath(DATA("gacha/details.html"))); if (!detailsTemplate.exists()) { Grasscutter.getLogger().warn("File does not exist: " + detailsTemplate); - response.status(500); + ctx.status(500); return; } - String sessionKey = request.query("s"); + String sessionKey = ctx.queryParam("s"); Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); if (account == null) { - response.status(403).send("Requested account was not found"); + ctx.status(403).result("Requested account was not found"); return; } Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId()); if (player == null) { - response.status(403).send("No player associated with requested account"); + ctx.status(403).result("No player associated with requested account"); return; } @@ -107,7 +106,7 @@ public final class GachaHandler implements Router { .replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale())); // Get the banner info for the banner we want. - int scheduleId = Integer.parseInt(request.query("scheduleId")); + int scheduleId = Integer.parseInt(ctx.queryParam("scheduleId")); GachaSystem manager = Grasscutter.getGameServer().getGachaSystem(); GachaBanner banner = manager.getGachaBanners().get(scheduleId); @@ -135,6 +134,7 @@ public final class GachaHandler implements Router { template = template.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]"); // Done. - response.send(template); + ctx.contentType(HttpUtils.MediaType._html.getMIME()); + ctx.result(template); } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java index 4c32d3d2a..222d3ec8f 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/GenericHandler.java @@ -7,53 +7,52 @@ import emu.grasscutter.Grasscutter; import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.objects.WebStaticVersionResponse; -import express.Express; -import express.http.Request; -import express.http.Response; +import emu.grasscutter.utils.HttpUtils; import io.javalin.Javalin; +import io.javalin.http.Context; /** * Handles all generic, hard-coded responses. */ public final class GenericHandler implements Router { - @Override public void applyRoutes(Express express, Javalin handle) { + @Override public void applyRoutes(Javalin javalin) { // hk4e-sdk-os.hoyoverse.com - express.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); + javalin.get("/hk4e_global/mdk/agreement/api/getAgreementInfos", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"marketing_agreements\":[]}}")); // hk4e-sdk-os.hoyoverse.com // this could be either GET or POST based on the observation of different clients - express.all("/hk4e_global/combo/granter/api/compareProtocolVersion", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); + HttpUtils.allRoutes(javalin, "/hk4e_global/combo/granter/api/compareProtocolVersion", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"modified\":true,\"protocol\":{\"id\":0,\"app_id\":4,\"language\":\"en\",\"user_proto\":\"\",\"priv_proto\":\"\",\"major\":7,\"minimum\":0,\"create_time\":\"0\",\"teenager_proto\":\"\",\"third_proto\":\"\"}}}")); // api-account-os.hoyoverse.com - express.post("/account/risky/api/check", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); + javalin.post("/account/risky/api/check", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":\"none\",\"action\":\"ACTION_NONE\",\"geetest\":null}}")); // sdk-os-static.hoyoverse.com - express.get("/combo/box/api/config/sdk/combo", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); + javalin.get("/combo/box/api/config/sdk/combo", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"vals\":{\"disable_email_bind_skip\":\"false\",\"email_bind_remind_interval\":\"7\",\"email_bind_remind\":\"true\"}}}")); // hk4e-sdk-os-static.hoyoverse.com - express.get("/hk4e_global/combo/granter/api/getConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); + javalin.get("/hk4e_global/combo/granter/api/getConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"protocol\":true,\"qr_enabled\":false,\"log_level\":\"INFO\",\"announce_url\":\"https://webstatic-sea.hoyoverse.com/hk4e/announcement/index.html?sdk_presentation_style=fullscreen\\u0026sdk_screen_transparent=true\\u0026game_biz=hk4e_global\\u0026auth_appid=announcement\\u0026game=hk4e#/\",\"push_alias_type\":2,\"disable_ysdk_guard\":false,\"enable_announce_pic_popup\":true}}")); // hk4e-sdk-os-static.hoyoverse.com - express.get("/hk4e_global/mdk/shield/api/loadConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); + javalin.get("/hk4e_global/mdk/shield/api/loadConfig", new HttpJsonResponse("{\"retcode\":0,\"message\":\"OK\",\"data\":{\"id\":6,\"game_key\":\"hk4e_global\",\"client\":\"PC\",\"identity\":\"I_IDENTITY\",\"guest\":false,\"ignore_versions\":\"\",\"scene\":\"S_NORMAL\",\"name\":\"原神海外\",\"disable_regist\":false,\"enable_email_captcha\":false,\"thirdparty\":[\"fb\",\"tw\"],\"disable_mmt\":false,\"server_guest\":false,\"thirdparty_ignore\":{\"tw\":\"\",\"fb\":\"\"},\"enable_ps_bind_account\":false,\"thirdparty_login_configs\":{\"tw\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800},\"fb\":{\"token_type\":\"TK_GAME_TOKEN\",\"game_token_expires_in\":604800}}}}")); // Test api? // abtest-api-data-sg.hoyoverse.com - express.post("/data_abtest_api/config/experiment/list", new HttpJsonResponse("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); + javalin.post("/data_abtest_api/config/experiment/list", new HttpJsonResponse("{\"retcode\":0,\"success\":true,\"message\":\"\",\"data\":[{\"code\":1000,\"type\":2,\"config_id\":\"14\",\"period_id\":\"6036_99\",\"version\":\"1\",\"configs\":{\"cardType\":\"old\"}}]}")); // log-upload-os.mihoyo.com - express.all("/log/sdk/upload", new HttpJsonResponse("{\"code\":0}")); - express.all("/sdk/upload", new HttpJsonResponse("{\"code\":0}")); - express.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}")); + HttpUtils.allRoutes(javalin, "/log/sdk/upload", new HttpJsonResponse("{\"code\":0}")); + HttpUtils.allRoutes(javalin, "/sdk/upload", new HttpJsonResponse("{\"code\":0}")); + javalin.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}")); // /perf/config/verify?device_id=xxx&platform=x&name=xxx - express.all("/perf/config/verify", new HttpJsonResponse("{\"code\":0}")); + HttpUtils.allRoutes(javalin, "/perf/config/verify", new HttpJsonResponse("{\"code\":0}")); // webstatic-sea.hoyoverse.com - express.get("/admin/mi18n/plat_oversea/*", new WebStaticVersionResponse()); + javalin.get("/admin/mi18n/plat_oversea/*", new WebStaticVersionResponse()); - express.get("/status/server", GenericHandler::serverStatus); + javalin.get("/status/server", GenericHandler::serverStatus); } - private static void serverStatus(Request request, Response response) { + private static void serverStatus(Context ctx) { int playerCount = Grasscutter.getGameServer().getPlayers().size(); int maxPlayer = ACCOUNT.maxPlayer; String version = GameConstants.VERSION; - response.send("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"maxPlayer\":" + maxPlayer + ",\"version\":\"" + version + "\"}}"); + ctx.result("{\"retcode\":0,\"status\":{\"playerCount\":" + playerCount + ",\"maxPlayer\":" + maxPlayer + ",\"version\":\"" + version + "\"}}"); } } diff --git a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java index 08025d365..37ac7ad45 100644 --- a/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java +++ b/src/main/java/emu/grasscutter/server/http/handlers/LogHandler.java @@ -1,24 +1,22 @@ package emu.grasscutter.server.http.handlers; import emu.grasscutter.server.http.Router; -import express.Express; -import express.http.Request; -import express.http.Response; import io.javalin.Javalin; +import io.javalin.http.Context; /** * Handles logging requests made to the server. */ public final class LogHandler implements Router { - @Override public void applyRoutes(Express express, Javalin handle) { + @Override public void applyRoutes(Javalin javalin) { // overseauspider.yuanshen.com - express.post("/log", LogHandler::log); + javalin.post("/log", LogHandler::log); // log-upload-os.mihoyo.com - express.post("/crash/dataUpload", LogHandler::log); + javalin.post("/crash/dataUpload", LogHandler::log); } - - private static void log(Request request, Response response) { + + private static void log(Context ctx) { // TODO: Figure out how to dump request body and log to file. - response.send("{\"code\":0}"); + ctx.result("{\"code\":0}"); } } diff --git a/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java index 327445f22..410e69ac9 100644 --- a/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java +++ b/src/main/java/emu/grasscutter/server/http/objects/HttpJsonResponse.java @@ -6,14 +6,14 @@ import java.util.Objects; import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter.ServerDebugMode; -import express.http.HttpContextHandler; -import express.http.Request; -import express.http.Response; +import io.javalin.http.Context; +import io.javalin.http.Handler; +import org.jetbrains.annotations.NotNull; import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.utils.Language.translate; -public final class HttpJsonResponse implements HttpContextHandler { +public final class HttpJsonResponse implements Handler { private final String response; private final String[] missingRoutes = { // TODO: When http requests for theses routes are found please remove it from this list and update the route request type in the DispatchServer "/common/hk4e_global/announcement/api/getAlertPic", @@ -33,11 +33,11 @@ public final class HttpJsonResponse implements HttpContextHandler { } @Override - public void handle(Request req, Response res) throws IOException { + public void handle(@NotNull Context ctx) throws Exception { // Checking for ALL here isn't required as when ALL is enabled enableDevLogging() gets enabled - if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, req.baseUrl()))) { - Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING ? "(MISSING)" : "")); + if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, ctx.endpointHandlerPath()))) { + Grasscutter.getLogger().info(translate("messages.dispatch.request", ctx.ip(), ctx.method(), ctx.endpointHandlerPath()) + (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING ? "(MISSING)" : "")); } - res.send(response); + ctx.result(response); } } diff --git a/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java b/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java index ec0f973fb..7d3480723 100644 --- a/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java +++ b/src/main/java/emu/grasscutter/server/http/objects/WebStaticVersionResponse.java @@ -1,42 +1,37 @@ package emu.grasscutter.server.http.objects; import emu.grasscutter.Grasscutter; -import emu.grasscutter.data.DataLoader; import emu.grasscutter.utils.FileUtils; -import emu.grasscutter.utils.Utils; -import express.http.HttpContextHandler; -import express.http.MediaType; -import express.http.Request; -import express.http.Response; -import io.javalin.core.util.FileUtil; +import emu.grasscutter.utils.HttpUtils; +import io.javalin.http.Context; +import io.javalin.http.Handler; -import static emu.grasscutter.config.Configuration.DATA; import static emu.grasscutter.config.Configuration.DISPATCH_INFO; import java.io.IOException; import java.io.InputStream; -public class WebStaticVersionResponse implements HttpContextHandler { +public class WebStaticVersionResponse implements Handler { @Override - public void handle(Request request, Response response) throws IOException { - String requestFor = request.path().substring(request.path().lastIndexOf("-") + 1); + public void handle(Context ctx) throws IOException { + String requestFor = ctx.path().substring(ctx.path().lastIndexOf("-") + 1); - getPageResources("/webstatic/" + requestFor, response); + getPageResources("/webstatic/" + requestFor, ctx); return; } - private static void getPageResources(String path, Response response) { + private static void getPageResources(String path, Context ctx) { try (InputStream filestream = FileUtils.readResourceAsStream(path)) { - MediaType fromExtension = MediaType.getByExtension(path.substring(path.lastIndexOf(".") + 1)); - response.type((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream"); - response.send(filestream.readAllBytes()); + HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(path.substring(path.lastIndexOf(".") + 1)); + ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream"); + ctx.result(filestream.readAllBytes()); } catch (Exception e) { if (DISPATCH_INFO.logRequests == Grasscutter.ServerDebugMode.MISSING) { Grasscutter.getLogger().warn("Webstatic File Missing: " + path); } - response.status(404); + ctx.status(404); } } } diff --git a/src/main/java/emu/grasscutter/utils/HttpUtils.java b/src/main/java/emu/grasscutter/utils/HttpUtils.java new file mode 100644 index 000000000..b1fc02b2a --- /dev/null +++ b/src/main/java/emu/grasscutter/utils/HttpUtils.java @@ -0,0 +1,738 @@ +package emu.grasscutter.utils; + +import io.javalin.Javalin; +import io.javalin.http.Handler; + +public final class HttpUtils { + + public static Javalin allRoutes(Javalin javalin, String path, Handler ctx) { + javalin.get(path, ctx); + javalin.post(path, ctx); + javalin.put(path, ctx); + javalin.patch(path, ctx); + javalin.delete(path, ctx); + + return javalin; + } + + /** + * @author Simon Reinisch + * Enum with all MediaTypes + * Original code: https://github.com/Aarkan1/java-express/blob/main/src/main/java/express/http/MediaType.java + */ + public enum MediaType { + + _aw("aw", "application/applixware"), + _atom("atom", "application/atom+xml "), + _atomcat("atomcat", "application/atomcat+xml"), + _atomsvc("atomsvc", "application/atomsvc+xml"), + _ccxml("ccxml", "application/ccxml+xml"), + _cdmia("cdmia", "application/cdmi-capability"), + _cdmic("cdmic", "application/cdmi-container"), + _cdmid("cdmid", "application/cdmi-domain"), + _cdmio("cdmio", "application/cdmi-object"), + _cdmiq("cdmiq", "application/cdmi-queue"), + _cu("cu", "application/cu-seeme"), + _davmount("davmount", "application/davmount+xml"), + _dssc("dssc", "application/dssc+der"), + _xdssc("xdssc", "application/dssc+xml"), + _es("es", "application/ecmascript"), + _emma("emma", "application/emma+xml"), + _epub("epub", "application/epub+zip"), + _exi("exi", "application/exi"), + _pfr("pfr", "application/font-tdpfr"), + _stk("stk", "application/hyperstudio"), + _ipfix("ipfix", "application/ipfix"), + _jar("jar", "application/java-archive"), + _ser("ser", "application/java-serialized-object"), + _class("class", "application/java-vm"), + _js("js", "application/javascript"), + _json("json", "application/json"), + _hqx("hqx", "application/mac-binhex40"), + _cpt("cpt", "application/mac-compactpro"), + _mads("mads", "application/mads+xml"), + _mrc("mrc", "application/marc"), + _mrcx("mrcx", "application/marcxml+xml"), + _ma("ma", "application/mathematica"), + _mathml("mathml", "application/mathml+xml"), + _mbox("mbox", "application/mbox"), + _mscml("mscml", "application/mediaservercontrol+xml"), + _meta4("meta4", "application/metalink4+xml"), + _mets("mets", "application/mets+xml"), + _mods("mods", "application/mods+xml"), + _m21("m21", "application/mp21"), + _doc("doc", "application/msword"), + _mxf("mxf", "application/mxf"), + _bin("bin", "application/octet-stream"), + _oda("oda", "application/oda"), + _opf("opf", "application/oebps-package+xml"), + _ogx("ogx", "application/ogg"), + _onetoc("onetoc", "application/onenote"), + _xer("xer", "application/patch-ops-error+xml"), + _pdf("pdf", "application/pdf"), + _prf("prf", "application/pics-rules"), + _p10("p10", "application/pkcs10"), + _p7m("p7m", "application/pkcs7-mime"), + _p7s("p7s", "application/pkcs7-signature"), + _p8("p8", "application/pkcs8"), + _ac("ac", "application/pkix-attr-cert"), + _cer("cer", "application/pkix-cert"), + _crl("crl", "application/pkix-crl"), + _pkipath("pkipath", "application/pkix-pkipath"), + _pki("pki", "application/pkixcmp"), + _pls("pls", "application/pls+xml"), + _ai("ai", "application/postscript"), + _cww("cww", "application/prs.cww"), + _pskcxml("pskcxml", "application/pskc+xml"), + _rdf("rdf", "application/rdf+xml"), + _rif("rif", "application/reginfo+xml"), + _rnc("rnc", "application/relax-ng-compact-syntax"), + _rl("rl", "application/resource-lists+xml"), + _rld("rld", "application/resource-lists-diff+xml"), + _rs("rs", "application/rls-services+xml"), + _rsd("rsd", "application/rsd+xml"), + _rss("rss", "application/rss+xml"), + _rtf("rtf", "application/rtf"), + _sbml("sbml", "application/sbml+xml"), + _scq("scq", "application/scvp-cv-request"), + _scs("scs", "application/scvp-cv-response"), + _spq("spq", "application/scvp-vp-request"), + _spp("spp", "application/scvp-vp-response"), + _sdp("sdp", "application/sdp"), + _setpay("setpay", "application/set-payment-initiation"), + _setreg("setreg", "application/set-registration-initiation"), + _shf("shf", "application/shf+xml"), + _smi("smi", "application/smil+xml"), + _rq("rq", "application/sparql-query"), + _srx("srx", "application/sparql-results+xml"), + _gram("gram", "application/srgs"), + _grxml("grxml", "application/srgs+xml"), + _sru("sru", "application/sru+xml"), + _ssml("ssml", "application/ssml+xml"), + _tei("tei", "application/tei+xml"), + _tfi("tfi", "application/thraud+xml"), + _tsd("tsd", "application/timestamped-data"), + _plb("plb", "application/vnd.3gpp.pic-bw-large"), + _psb("psb", "application/vnd.3gpp.pic-bw-small"), + _pvb("pvb", "application/vnd.3gpp.pic-bw-var"), + _tcap("tcap", "application/vnd.3gpp2.tcap"), + _pwn("pwn", "application/vnd.3m.post-it-notes"), + _aso("aso", "application/vnd.accpac.simply.aso"), + _imp("imp", "application/vnd.accpac.simply.imp"), + _acu("acu", "application/vnd.acucobol"), + _atc("atc", "application/vnd.acucorp"), + _air("air", "application/vnd.adobe.air-application-installer-package+zip"), + _fxp("fxp", "application/vnd.adobe.fxp"), + _xdp("xdp", "application/vnd.adobe.xdp+xml"), + _xfdf("xfdf", "application/vnd.adobe.xfdf"), + _ahead("ahead", "application/vnd.ahead.space"), + _azf("azf", "application/vnd.airzip.filesecure.azf"), + _azs("azs", "application/vnd.airzip.filesecure.azs"), + _azw("azw", "application/vnd.amazon.ebook"), + _acc("acc", "application/vnd.americandynamics.acc"), + _ami("ami", "application/vnd.amiga.ami"), + _apk("apk", "application/vnd.android.package-archive"), + _cii("cii", "application/vnd.anser-web-certificate-issue-initiation"), + _fti("fti", "application/vnd.anser-web-funds-transfer-initiation"), + _atx("atx", "application/vnd.antix.game-component"), + _mpkg("mpkg", "application/vnd.apple.installer+xml"), + _m3u8("m3u8", "application/vnd.apple.mpegurl"), + _swi("swi", "application/vnd.aristanetworks.swi"), + _aep("aep", "application/vnd.audiograph"), + _mpm("mpm", "application/vnd.blueice.multipass"), + _bmi("bmi", "application/vnd.bmi"), + _rep("rep", "application/vnd.businessobjects"), + _cdxml("cdxml", "application/vnd.chemdraw+xml"), + _mmd("mmd", "application/vnd.chipnuts.karaoke-mmd"), + _cdy("cdy", "application/vnd.cinderella"), + _cla("cla", "application/vnd.claymore"), + _rp9("rp9", "application/vnd.cloanto.rp9"), + _c4g("c4g", "application/vnd.clonk.c4group"), + _c11amc("c11amc", "application/vnd.cluetrust.cartomobile-config"), + _c11amz("c11amz", "application/vnd.cluetrust.cartomobile-config-pkg"), + _csp("csp", "application/vnd.commonspace"), + _cdbcmsg("cdbcmsg", "application/vnd.contact.cmsg"), + _cmc("cmc", "application/vnd.cosmocaller"), + _clkx("clkx", "application/vnd.crick.clicker"), + _clkk("clkk", "application/vnd.crick.clicker.keyboard"), + _clkp("clkp", "application/vnd.crick.clicker.palette"), + _clkt("clkt", "application/vnd.crick.clicker.template"), + _clkw("clkw", "application/vnd.crick.clicker.wordbank"), + _wbs("wbs", "application/vnd.criticaltools.wbs+xml"), + _pml("pml", "application/vnd.ctc-posml"), + _ppd("ppd", "application/vnd.cups-ppd"), + _car("car", "application/vnd.curl.car"), + _pcurl("pcurl", "application/vnd.curl.pcurl"), + _rdz("rdz", "application/vnd.data-vision.rdz"), + _fe_launch("fe_launch", "application/vnd.denovo.fcselayout-link"), + _dna("dna", "application/vnd.dna"), + _mlp("mlp", "application/vnd.dolby.mlp"), + _dpg("dpg", "application/vnd.dpgraph"), + _dfac("dfac", "application/vnd.dreamfactory"), + _ait("ait", "application/vnd.dvb.ait"), + _svc("svc", "application/vnd.dvb.service"), + _geo("geo", "application/vnd.dynageo"), + _mag("mag", "application/vnd.ecowin.chart"), + _nml("nml", "application/vnd.enliven"), + _esf("esf", "application/vnd.epson.esf"), + _msf("msf", "application/vnd.epson.msf"), + _qam("qam", "application/vnd.epson.quickanime"), + _slt("slt", "application/vnd.epson.salt"), + _ssf("ssf", "application/vnd.epson.ssf"), + _es3("es3", "application/vnd.eszigno3+xml"), + _ez2("ez2", "application/vnd.ezpix-album"), + _ez3("ez3", "application/vnd.ezpix-package"), + _fdf("fdf", "application/vnd.fdf"), + _seed("seed", "application/vnd.fdsn.seed"), + _gph("gph", "application/vnd.flographit"), + _ftc("ftc", "application/vnd.fluxtime.clip"), + _fm("fm", "application/vnd.framemaker"), + _fnc("fnc", "application/vnd.frogans.fnc"), + _ltf("ltf", "application/vnd.frogans.ltf"), + _fsc("fsc", "application/vnd.fsc.weblaunch"), + _oas("oas", "application/vnd.fujitsu.oasys"), + _oa2("oa2", "application/vnd.fujitsu.oasys2"), + _oa3("oa3", "application/vnd.fujitsu.oasys3"), + _fg5("fg5", "application/vnd.fujitsu.oasysgp"), + _bh2("bh2", "application/vnd.fujitsu.oasysprs"), + _ddd("ddd", "application/vnd.fujixerox.ddd"), + _xdw("xdw", "application/vnd.fujixerox.docuworks"), + _xbd("xbd", "application/vnd.fujixerox.docuworks.binder"), + _fzs("fzs", "application/vnd.fuzzysheet"), + _txd("txd", "application/vnd.genomatix.tuxedo"), + _ggb("ggb", "application/vnd.geogebra.file"), + _ggt("ggt", "application/vnd.geogebra.tool"), + _gex("gex", "application/vnd.geometry-explorer"), + _gxt("gxt", "application/vnd.geonext"), + _g2w("g2w", "application/vnd.geoplan"), + _g3w("g3w", "application/vnd.geospace"), + _gmx("gmx", "application/vnd.gmx"), + _kml("kml", "application/vnd.google-earth.kml+xml"), + _kmz("kmz", "application/vnd.google-earth.kmz"), + _gqf("gqf", "application/vnd.grafeq"), + _gac("gac", "application/vnd.groove-account"), + _ghf("ghf", "application/vnd.groove-help"), + _gim("gim", "application/vnd.groove-identity-message"), + _grv("grv", "application/vnd.groove-injector"), + _gtm("gtm", "application/vnd.groove-tool-message"), + _tpl("tpl", "application/vnd.groove-tool-template"), + _vcg("vcg", "application/vnd.groove-vcard"), + _hal("hal", "application/vnd.hal+xml"), + _zmm("zmm", "application/vnd.handheld-entertainment+xml"), + _hbci("hbci", "application/vnd.hbci"), + _les("les", "application/vnd.hhe.lesson-player"), + _hpgl("hpgl", "application/vnd.hp-hpgl"), + _hpid("hpid", "application/vnd.hp-hpid"), + _hps("hps", "application/vnd.hp-hps"), + _jlt("jlt", "application/vnd.hp-jlyt"), + _pcl("pcl", "application/vnd.hp-pcl"), + _pclxl("pclxl", "application/vnd.hp-pclxl"), + _sfd_hdstx("sfd-hdstx", "application/vnd.hydrostatix.sof-data"), + _x3d("x3d", "application/vnd.hzn-3d-crossword"), + _mpy("mpy", "application/vnd.ibm.minipay"), + _afp("afp", "application/vnd.ibm.modcap"), + _irm("irm", "application/vnd.ibm.rights-management"), + _sc("sc", "application/vnd.ibm.secure-container"), + _icc("icc", "application/vnd.iccprofile"), + _igl("igl", "application/vnd.igloader"), + _ivp("ivp", "application/vnd.immervision-ivp"), + _ivu("ivu", "application/vnd.immervision-ivu"), + _igm("igm", "application/vnd.insors.igm"), + _xpw("xpw", "application/vnd.intercon.formnet"), + _i2g("i2g", "application/vnd.intergeo"), + _qbo("qbo", "application/vnd.intu.qbo"), + _qfx("qfx", "application/vnd.intu.qfx"), + _rcprofile("rcprofile", "application/vnd.ipunplugged.rcprofile"), + _irp("irp", "application/vnd.irepository.package+xml"), + _xpr("xpr", "application/vnd.is-xpr"), + _fcs("fcs", "application/vnd.isac.fcs"), + _jam("jam", "application/vnd.jam"), + _rms("rms", "application/vnd.jcp.javame.midlet-rms"), + _jisp("jisp", "application/vnd.jisp"), + _joda("joda", "application/vnd.joost.joda-archive"), + _ktz("ktz", "application/vnd.kahootz"), + _karbon("karbon", "application/vnd.kde.karbon"), + _chrt("chrt", "application/vnd.kde.kchart"), + _kfo("kfo", "application/vnd.kde.kformula"), + _flw("flw", "application/vnd.kde.kivio"), + _kon("kon", "application/vnd.kde.kontour"), + _kpr("kpr", "application/vnd.kde.kpresenter"), + _ksp("ksp", "application/vnd.kde.kspread"), + _kwd("kwd", "application/vnd.kde.kword"), + _htke("htke", "application/vnd.kenameaapp"), + _kia("kia", "application/vnd.kidspiration"), + _kne("kne", "application/vnd.kinar"), + _skp("skp", "application/vnd.koan"), + _sse("sse", "application/vnd.kodak-descriptor"), + _lasxml("lasxml", "application/vnd.las.las+xml"), + _lbd("lbd", "application/vnd.llamagraphics.life-balance.desktop"), + _lbe("lbe", "application/vnd.llamagraphics.life-balance.exchange+xml"), + _123("123", "application/vnd.lotus-1-2-3"), + _apr("apr", "application/vnd.lotus-approach"), + _pre("pre", "application/vnd.lotus-freelance"), + _nsf("nsf", "application/vnd.lotus-notes"), + _org("org", "application/vnd.lotus-organizer"), + _scm("scm", "application/vnd.lotus-screencam"), + _lwp("lwp", "application/vnd.lotus-wordpro"), + _portpkg("portpkg", "application/vnd.macports.portpkg"), + _mcd("mcd", "application/vnd.mcd"), + _mc1("mc1", "application/vnd.medcalcdata"), + _cdkey("cdkey", "application/vnd.mediastation.cdkey"), + _mwf("mwf", "application/vnd.mfer"), + _mfm("mfm", "application/vnd.mfmp"), + _flo("flo", "application/vnd.micrografx.flo"), + _igx("igx", "application/vnd.micrografx.igx"), + _mif("mif", "application/vnd.mif"), + _daf("daf", "application/vnd.mobius.daf"), + _dis("dis", "application/vnd.mobius.dis"), + _mbk("mbk", "application/vnd.mobius.mbk"), + _mqy("mqy", "application/vnd.mobius.mqy"), + _msl("msl", "application/vnd.mobius.msl"), + _plc("plc", "application/vnd.mobius.plc"), + _txf("txf", "application/vnd.mobius.txf"), + _mpn("mpn", "application/vnd.mophun.application"), + _mpc("mpc", "application/vnd.mophun.certificate"), + _xul("xul", "application/vnd.mozilla.xul+xml"), + _cil("cil", "application/vnd.ms-artgalry"), + _cab("cab", "application/vnd.ms-cab-compressed"), + _xls("xls", "application/vnd.ms-excel"), + _xlam("xlam", "application/vnd.ms-excel.addin.macroenabled.12"), + _xlsb("xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"), + _xlsm("xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"), + _xltm("xltm", "application/vnd.ms-excel.template.macroenabled.12"), + _eot("eot", "application/vnd.ms-fontobject"), + _chm("chm", "application/vnd.ms-htmlhelp"), + _ims("ims", "application/vnd.ms-ims"), + _lrm("lrm", "application/vnd.ms-lrm"), + _thmx("thmx", "application/vnd.ms-officetheme"), + _cat("cat", "application/vnd.ms-pki.seccat"), + _stl("stl", "application/vnd.ms-pki.stl"), + _ppt("ppt", "application/vnd.ms-powerpoint"), + _ppam("ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"), + _pptm("pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12"), + _sldm("sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"), + _ppsm("ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12"), + _potm("potm", "application/vnd.ms-powerpoint.template.macroenabled.12"), + _mpp("mpp", "application/vnd.ms-project"), + _docm("docm", "application/vnd.ms-word.document.macroenabled.12"), + _dotm("dotm", "application/vnd.ms-word.template.macroenabled.12"), + _wps("wps", "application/vnd.ms-works"), + _wpl("wpl", "application/vnd.ms-wpl"), + _xps("xps", "application/vnd.ms-xpsdocument"), + _mseq("mseq", "application/vnd.mseq"), + _mus("mus", "application/vnd.musician"), + _msty("msty", "application/vnd.muvee.style"), + _nlu("nlu", "application/vnd.neurolanguage.nlu"), + _nnd("nnd", "application/vnd.noblenet-directory"), + _nns("nns", "application/vnd.noblenet-sealer"), + _nnw("nnw", "application/vnd.noblenet-web"), + _ngdat("ngdat", "application/vnd.nokia.n-gage.data"), + _n_gage("n-gage", "application/vnd.nokia.n-gage.symbian.install"), + _rpst("rpst", "application/vnd.nokia.radio-preset"), + _rpss("rpss", "application/vnd.nokia.radio-presets"), + _edm("edm", "application/vnd.novadigm.edm"), + _edx("edx", "application/vnd.novadigm.edx"), + _ext("ext", "application/vnd.novadigm.ext"), + _odc("odc", "application/vnd.oasis.opendocument.chart"), + _otc("otc", "application/vnd.oasis.opendocument.chart-template"), + _odb("odb", "application/vnd.oasis.opendocument.database"), + _odf("odf", "application/vnd.oasis.opendocument.formula"), + _odft("odft", "application/vnd.oasis.opendocument.formula-template"), + _odg("odg", "application/vnd.oasis.opendocument.graphics"), + _otg("otg", "application/vnd.oasis.opendocument.graphics-template"), + _odi("odi", "application/vnd.oasis.opendocument.image"), + _oti("oti", "application/vnd.oasis.opendocument.image-template"), + _odp("odp", "application/vnd.oasis.opendocument.presentation"), + _otp("otp", "application/vnd.oasis.opendocument.presentation-template"), + _ods("ods", "application/vnd.oasis.opendocument.spreadsheet"), + _ots("ots", "application/vnd.oasis.opendocument.spreadsheet-template"), + _odt("odt", "application/vnd.oasis.opendocument.text"), + _odm("odm", "application/vnd.oasis.opendocument.text-master"), + _ott("ott", "application/vnd.oasis.opendocument.text-template"), + _oth("oth", "application/vnd.oasis.opendocument.text-web"), + _xo("xo", "application/vnd.olpc-sugar"), + _dd2("dd2", "application/vnd.oma.dd2+xml"), + _oxt("oxt", "application/vnd.openofficeorg.extension"), + _pptx("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"), + _sldx("sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"), + _ppsx("ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"), + _potx("potx", "application/vnd.openxmlformats-officedocument.presentationml.template"), + _xlsx("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"), + _xltx("xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"), + _docx("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"), + _dotx("dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"), + _mgp("mgp", "application/vnd.osgeo.mapguide.package"), + _dp("dp", "application/vnd.osgi.dp"), + _pdb("pdb", "application/vnd.palm"), + _paw("paw", "application/vnd.pawaafile"), + _str("str", "application/vnd.pg.format"), + _ei6("ei6", "application/vnd.pg.osasli"), + _efif("efif", "application/vnd.picsel"), + _wg("wg", "application/vnd.pmi.widget"), + _plf("plf", "application/vnd.pocketlearn"), + _pbd("pbd", "application/vnd.powerbuilder6"), + _box("box", "application/vnd.previewsystems.box"), + _mgz("mgz", "application/vnd.proteus.magazine"), + _qps("qps", "application/vnd.publishare-delta-tree"), + _ptid("ptid", "application/vnd.pvi.ptid1"), + _qxd("qxd", "application/vnd.quark.quarkxpress"), + _bed("bed", "application/vnd.realvnc.bed"), + _mxl("mxl", "application/vnd.recordare.musicxml"), + _musicxml("musicxml", "application/vnd.recordare.musicxml+xml"), + _cryptonote("cryptonote", "application/vnd.rig.cryptonote"), + _cod("cod", "application/vnd.rim.cod"), + _rm("rm", "application/vnd.rn-realmedia"), + _link66("link66", "application/vnd.route66.link66+xml"), + _st("st", "application/vnd.sailingtracker.track"), + _see("see", "application/vnd.seemail"), + _sema("sema", "application/vnd.sema"), + _semd("semd", "application/vnd.semd"), + _semf("semf", "application/vnd.semf"), + _ifm("ifm", "application/vnd.shana.informed.formdata"), + _itp("itp", "application/vnd.shana.informed.formtemplate"), + _iif("iif", "application/vnd.shana.informed.interchange"), + _ipk("ipk", "application/vnd.shana.informed.package"), + _twd("twd", "application/vnd.simtech-mindmapper"), + _mmf("mmf", "application/vnd.smaf"), + _teacher("teacher", "application/vnd.smart.teacher"), + _sdkm("sdkm", "application/vnd.solent.sdkm+xml"), + _dxp("dxp", "application/vnd.spotfire.dxp"), + _sfs("sfs", "application/vnd.spotfire.sfs"), + _sdc("sdc", "application/vnd.stardivision.calc"), + _sda("sda", "application/vnd.stardivision.draw"), + _sdd("sdd", "application/vnd.stardivision.impress"), + _smf("smf", "application/vnd.stardivision.math"), + _sdw("sdw", "application/vnd.stardivision.writer"), + _sgl("sgl", "application/vnd.stardivision.writer-global"), + _sm("sm", "application/vnd.stepmania.stepchart"), + _sxc("sxc", "application/vnd.sun.xml.calc"), + _stc("stc", "application/vnd.sun.xml.calc.template"), + _sxd("sxd", "application/vnd.sun.xml.draw"), + _std("std", "application/vnd.sun.xml.draw.template"), + _sxi("sxi", "application/vnd.sun.xml.impress"), + _sti("sti", "application/vnd.sun.xml.impress.template"), + _sxm("sxm", "application/vnd.sun.xml.math"), + _sxw("sxw", "application/vnd.sun.xml.writer"), + _sxg("sxg", "application/vnd.sun.xml.writer.global"), + _stw("stw", "application/vnd.sun.xml.writer.template"), + _sus("sus", "application/vnd.sus-calendar"), + _svd("svd", "application/vnd.svd"), + _sis("sis", "application/vnd.symbian.install"), + _xsm("xsm", "application/vnd.syncml+xml"), + _bdm("bdm", "application/vnd.syncml.dm+wbxml"), + _xdm("xdm", "application/vnd.syncml.dm+xml"), + _tao("tao", "application/vnd.tao.intent-module-archive"), + _tmo("tmo", "application/vnd.tmobile-livetv"), + _tpt("tpt", "application/vnd.trid.tpt"), + _mxs("mxs", "application/vnd.triscape.mxs"), + _tra("tra", "application/vnd.trueapp"), + _ufd("ufd", "application/vnd.ufdl"), + _utz("utz", "application/vnd.uiq.theme"), + _umj("umj", "application/vnd.umajin"), + _unityweb("unityweb", "application/vnd.unity"), + _uoml("uoml", "application/vnd.uoml+xml"), + _vcx("vcx", "application/vnd.vcx"), + _vsd("vsd", "application/vnd.visio"), + _vsdx("vsdx", "application/vnd.visio2013"), + _vis("vis", "application/vnd.visionary"), + _vsf("vsf", "application/vnd.vsf"), + _wbxml("wbxml", "application/vnd.wap.wbxml"), + _wmlc("wmlc", "application/vnd.wap.wmlc"), + _wmlsc("wmlsc", "application/vnd.wap.wmlscriptc"), + _wtb("wtb", "application/vnd.webturbo"), + _nbp("nbp", "application/vnd.wolfram.player"), + _wpd("wpd", "application/vnd.wordperfect"), + _wqd("wqd", "application/vnd.wqd"), + _stf("stf", "application/vnd.wt.stf"), + _xar("xar", "application/vnd.xara"), + _xfdl("xfdl", "application/vnd.xfdl"), + _hvd("hvd", "application/vnd.yamaha.hv-dic"), + _hvs("hvs", "application/vnd.yamaha.hv-script"), + _hvp("hvp", "application/vnd.yamaha.hv-voice"), + _osf("osf", "application/vnd.yamaha.openscoreformat"), + _osfpvg("osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"), + _saf("saf", "application/vnd.yamaha.smaf-audio"), + _spf("spf", "application/vnd.yamaha.smaf-phrase"), + _cmp("cmp", "application/vnd.yellowriver-custom-menu"), + _zir("zir", "application/vnd.zul"), + _zaz("zaz", "application/vnd.zzazz.deck+xml"), + _vxml("vxml", "application/voicexml+xml"), + _wgt("wgt", "application/widget"), + _hlp("hlp", "application/winhlp"), + _wsdl("wsdl", "application/wsdl+xml"), + _wspolicy("wspolicy", "application/wspolicy+xml"), + _7z("7z", "application/x-7z-compressed"), + _abw("abw", "application/x-abiword"), + _ace("ace", "application/x-ace-compressed"), + _aab("aab", "application/x-authorware-bin"), + _aam("aam", "application/x-authorware-map"), + _aas("aas", "application/x-authorware-seg"), + _bcpio("bcpio", "application/x-bcpio"), + _torrent("torrent", "application/x-bittorrent"), + _bz("bz", "application/x-bzip"), + _bz2("bz2", "application/x-bzip2"), + _vcd("vcd", "application/x-cdlink"), + _chat("chat", "application/x-chat"), + _pgn("pgn", "application/x-chess-pgn"), + _cpio("cpio", "application/x-cpio"), + _csh("csh", "application/x-csh"), + _deb("deb", "application/x-debian-package"), + _dir("dir", "application/x-director"), + _wad("wad", "application/x-doom"), + _ncx("ncx", "application/x-dtbncx+xml"), + _dtb("dtb", "application/x-dtbook+xml"), + _res("res", "application/x-dtbresource+xml"), + _dvi("dvi", "application/x-dvi"), + _bdf("bdf", "application/x-font-bdf"), + _gsf("gsf", "application/x-font-ghostscript"), + _psf("psf", "application/x-font-linux-psf"), + _otf("otf", "application/x-font-otf"), + _pcf("pcf", "application/x-font-pcf"), + _snf("snf", "application/x-font-snf"), + _ttf("ttf", "application/x-font-ttf"), + _pfa("pfa", "application/x-font-type1"), + _woff("woff", "application/x-font-woff"), + _spl("spl", "application/x-futuresplash"), + _gnumeric("gnumeric", "application/x-gnumeric"), + _gtar("gtar", "application/x-gtar"), + _hdf("hdf", "application/x-hdf"), + _jnlp("jnlp", "application/x-java-jnlp-file"), + _latex("latex", "application/x-latex"), + _prc("prc", "application/x-mobipocket-ebook"), + _application("application", "application/x-ms-application"), + _wmd("wmd", "application/x-ms-wmd"), + _wmz("wmz", "application/x-ms-wmz"), + _xbap("xbap", "application/x-ms-xbap"), + _mdb("mdb", "application/x-msaccess"), + _obd("obd", "application/x-msbinder"), + _crd("crd", "application/x-mscardfile"), + _clp("clp", "application/x-msclip"), + _exe("exe", "application/x-msdownload"), + _mvb("mvb", "application/x-msmediaview"), + _wmf("wmf", "application/x-msmetafile"), + _mny("mny", "application/x-msmoney"), + _pub("pub", "application/x-mspublisher"), + _scd("scd", "application/x-msschedule"), + _trm("trm", "application/x-msterminal"), + _wri("wri", "application/x-mswrite"), + _nc("nc", "application/x-netcdf"), + _p12("p12", "application/x-pkcs12"), + _p7b("p7b", "application/x-pkcs7-certificates"), + _p7r("p7r", "application/x-pkcs7-certreqresp"), + _rar("rar", "application/x-rar-compressed"), + _sh("sh", "application/x-sh"), + _shar("shar", "application/x-shar"), + _swf("swf", "application/x-shockwave-flash"), + _xap("xap", "application/x-silverlight-app"), + _sit("sit", "application/x-stuffit"), + _sitx("sitx", "application/x-stuffitx"), + _sv4cpio("sv4cpio", "application/x-sv4cpio"), + _sv4crc("sv4crc", "application/x-sv4crc"), + _tar("tar", "application/x-tar"), + _tcl("tcl", "application/x-tcl"), + _tex("tex", "application/x-tex"), + _tfm("tfm", "application/x-tex-tfm"), + _texinfo("texinfo", "application/x-texinfo"), + _ustar("ustar", "application/x-ustar"), + _src("src", "application/x-wais-source"), + _der("der", "application/x-x509-ca-cert"), + _fig("fig", "application/x-xfig"), + _xpi("xpi", "application/x-xpinstall"), + _xdf("xdf", "application/xcap-diff+xml"), + _xenc("xenc", "application/xenc+xml"), + _xhtml("xhtml", "application/xhtml+xml"), + _xml("xml", "application/xml"), + _dtd("dtd", "application/xml-dtd"), + _xop("xop", "application/xop+xml"), + _xslt("xslt", "application/xslt+xml"), + _xspf("xspf", "application/xspf+xml"), + _mxml("mxml", "application/xv+xml"), + _yang("yang", "application/yang"), + _yin("yin", "application/yin+xml"), + _zip("zip", "application/zip"), + _adp("adp", "audio/adpcm"), + _au("au", "audio/basic"), + _mid("mid", "audio/midi"), + _mp4a("mp4a", "audio/mp4"), + _mpga("mpga", "audio/mpeg"), + _oga("oga", "audio/ogg"), + _uva("uva", "audio/vnd.dece.audio"), + _eol("eol", "audio/vnd.digital-winds"), + _dra("dra", "audio/vnd.dra"), + _dts("dts", "audio/vnd.dts"), + _dtshd("dtshd", "audio/vnd.dts.hd"), + _lvp("lvp", "audio/vnd.lucent.voice"), + _pya("pya", "audio/vnd.ms-playready.media.pya"), + _ecelp4800("ecelp4800", "audio/vnd.nuera.ecelp4800"), + _ecelp7470("ecelp7470", "audio/vnd.nuera.ecelp7470"), + _ecelp9600("ecelp9600", "audio/vnd.nuera.ecelp9600"), + _rip("rip", "audio/vnd.rip"), + _weba("weba", "audio/webm"), + _aac("aac", "audio/x-aac"), + _aif("aif", "audio/x-aiff"), + _m3u("m3u", "audio/x-mpegurl"), + _wax("wax", "audio/x-ms-wax"), + _wma("wma", "audio/x-ms-wma"), + _ram("ram", "audio/x-pn-realaudio"), + _rmp("rmp", "audio/x-pn-realaudio-plugin"), + _wav("wav", "audio/x-wav"), + _cdx("cdx", "chemical/x-cdx"), + _cif("cif", "chemical/x-cif"), + _cmdf("cmdf", "chemical/x-cmdf"), + _cml("cml", "chemical/x-cml"), + _csml("csml", "chemical/x-csml"), + _xyz("xyz", "chemical/x-xyz"), + _bmp("bmp", "image/bmp"), + _cgm("cgm", "image/cgm"), + _g3("g3", "image/g3fax"), + _gif("gif", "image/gif"), + _ief("ief", "image/ief"), + _jpeg("jpeg", "image/jpeg"), + _jpg("jpg", "image/jpeg"), + _pjpeg("pjpeg", "image/pjpeg"), + _ktx("ktx", "image/ktx"), + _png("png", "image/x-citrix-png"), + _btif("btif", "image/prs.btif"), + _svg("svg", "image/svg+xml"), + _tiff("tiff", "image/tiff"), + _psd("psd", "image/vnd.adobe.photoshop"), + _uvi("uvi", "image/vnd.dece.graphic"), + _sub("sub", "image/vnd.dvb.subtitle"), + _djvu("djvu", "image/vnd.djvu"), + _dwg("dwg", "image/vnd.dwg"), + _dxf("dxf", "image/vnd.dxf"), + _fbs("fbs", "image/vnd.fastbidsheet"), + _fpx("fpx", "image/vnd.fpx"), + _fst("fst", "image/vnd.fst"), + _mmr("mmr", "image/vnd.fujixerox.edmics-mmr"), + _rlc("rlc", "image/vnd.fujixerox.edmics-rlc"), + _mdi("mdi", "image/vnd.ms-modi"), + _npx("npx", "image/vnd.net-fpx"), + _wbmp("wbmp", "image/vnd.wap.wbmp"), + _xif("xif", "image/vnd.xiff"), + _webp("webp", "image/webp"), + _ras("ras", "image/x-cmu-raster"), + _cmx("cmx", "image/x-cmx"), + _fh("fh", "image/x-freehand"), + _ico("ico", "image/x-icon"), + _pcx("pcx", "image/x-pcx"), + _pic("pic", "image/x-pict"), + _pnm("pnm", "image/x-portable-anymap"), + _pbm("pbm", "image/x-portable-bitmap"), + _pgm("pgm", "image/x-portable-graymap"), + _ppm("ppm", "image/x-portable-pixmap"), + _rgb("rgb", "image/x-rgb"), + _xbm("xbm", "image/x-xbitmap"), + _xpm("xpm", "image/x-xpixmap"), + _xwd("xwd", "image/x-xwindowdump"), + _eml("eml", "message/rfc822"), + _igs("igs", "model/iges"), + _msh("msh", "model/mesh"), + _dae("dae", "model/vnd.collada+xml"), + _dwf("dwf", "model/vnd.dwf"), + _gdl("gdl", "model/vnd.gdl"), + _gtw("gtw", "model/vnd.gtw"), + _mts("mts", "model/vnd.mts"), + _vtu("vtu", "model/vnd.vtu"), + _wrl("wrl", "model/vrml"), + _ics("ics", "text/calendar"), + _css("css", "text/css"), + _csv("csv", "text/csv"), + _html("html", "text/html"), + _n3("n3", "text/n3"), + _txt("txt", "text/plain"), + _dsc("dsc", "text/prs.lines.tag"), + _rtx("rtx", "text/richtext"), + _sgml("sgml", "text/sgml"), + _tsv("tsv", "text/tab-separated-values"), + _t("t", "text/troff"), + _ttl("ttl", "text/turtle"), + _uri("uri", "text/uri-list"), + _curl("curl", "text/vnd.curl"), + _dcurl("dcurl", "text/vnd.curl.dcurl"), + _scurl("scurl", "text/vnd.curl.scurl"), + _mcurl("mcurl", "text/vnd.curl.mcurl"), + _fly("fly", "text/vnd.fly"), + _flx("flx", "text/vnd.fmi.flexstor"), + _gv("gv", "text/vnd.graphviz"), + _3dml("3dml", "text/vnd.in3d.3dml"), + _spot("spot", "text/vnd.in3d.spot"), + _jad("jad", "text/vnd.sun.j2me.app-descriptor"), + _wml("wml", "text/vnd.wap.wml"), + _wmls("wmls", "text/vnd.wap.wmlscript"), + _s("s", "text/x-asm"), + _c("c", "text/x-c"), + _f("f", "text/x-fortran"), + _p("p", "text/x-pascal"), + _java("java", "text/x-java-source"), + _etx("etx", "text/x-setext"), + _uu("uu", "text/x-uuencode"), + _vcs("vcs", "text/x-vcalendar"), + _vcf("vcf", "text/x-vcard"), + _3gp("3gp", "video/3gpp"), + _3g2("3g2", "video/3gpp2"), + _h261("h261", "video/h261"), + _h263("h263", "video/h263"), + _h264("h264", "video/h264"), + _jpgv("jpgv", "video/jpeg"), + _jpm("jpm", "video/jpm"), + _mj2("mj2", "video/mj2"), + _mp4("mp4", "video/mp4"), + _mpeg("mpeg", "video/mpeg"), + _ogv("ogv", "video/ogg"), + _qt("qt", "video/quicktime"), + _uvh("uvh", "video/vnd.dece.hd"), + _uvm("uvm", "video/vnd.dece.mobile"), + _uvp("uvp", "video/vnd.dece.pd"), + _uvs("uvs", "video/vnd.dece.sd"), + _uvv("uvv", "video/vnd.dece.video"), + _fvt("fvt", "video/vnd.fvt"), + _mxu("mxu", "video/vnd.mpegurl"), + _pyv("pyv", "video/vnd.ms-playready.media.pyv"), + _uvu("uvu", "video/vnd.uvvu.mp4"), + _viv("viv", "video/vnd.vivo"), + _webm("webm", "video/webm"), + _f4v("f4v", "video/x-f4v"), + _fli("fli", "video/x-fli"), + _flv("flv", "video/x-flv"), + _m4v("m4v", "video/x-m4v"), + _asf("asf", "video/x-ms-asf"), + _wm("wm", "video/x-ms-wm"), + _wmv("wmv", "video/x-ms-wmv"), + _wmx("wmx", "video/x-ms-wmx"), + _wvx("wvx", "video/x-ms-wvx"), + _avi("avi", "video/x-msvideo"), + _movie("movie", "video/x-sgi-movie"), + _ice("ice", "x-conference/x-cooltalk"), + _par("par", "text/plain-bas"), + _yaml("yaml", "text/yaml"), + _dmg("dmg", "application/x-apple-diskimage"), + _xww("form", "application/x-www-form-urlencoded"); + + private final String mime; + private final String extension; + + MediaType(String extension, String mime) { + this.mime = mime; + this.extension = extension; + } + + public static MediaType getByExtension(String extension) { + for (MediaType type : values()) { + if (type.extension.equals(extension)) { + return type; + } + } + return null; + } + + public String getMIME() { + return mime; + } + + public String getExtension() { + return extension; + } + } + +}