Update HttpServer & AuthenticationSystem to use Javalin

This commit is contained in:
Benj 2022-09-01 21:33:03 +08:00 committed by Melledy
parent bf9606222e
commit b5bed6ceef
19 changed files with 963 additions and 260 deletions

View File

@ -94,6 +94,7 @@ dependencies {
implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1' implementation group: 'com.github.davidmoten', name : 'rtree-multi', version: '0.1'
implementation group: 'io.javalin', name: 'javalin', version: '4.6.4' 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/') protobuf files('proto/')

View File

@ -2,8 +2,7 @@ package emu.grasscutter.auth;
import emu.grasscutter.game.Account; import emu.grasscutter.game.Account;
import emu.grasscutter.server.http.objects.*; import emu.grasscutter.server.http.objects.*;
import express.http.Request; import io.javalin.http.Context;
import express.http.Response;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
@ -71,8 +70,7 @@ public interface AuthenticationSystem {
*/ */
@Builder @AllArgsConstructor @Getter @Builder @AllArgsConstructor @Getter
class AuthenticationRequest { class AuthenticationRequest {
private final Request request; private final Context context;
@Nullable private final Response response;
@Nullable private final LoginAccountRequestJson passwordRequest; @Nullable private final LoginAccountRequestJson passwordRequest;
@Nullable private final LoginTokenRequestJson tokenRequest; @Nullable private final LoginTokenRequestJson tokenRequest;
@ -82,53 +80,51 @@ public interface AuthenticationSystem {
/** /**
* Generates an authentication request from a {@link LoginAccountRequestJson} object. * Generates an authentication request from a {@link LoginAccountRequestJson} object.
* @param request The Express request. * @param ctx The Javalin context.
* @param jsonData The JSON data. * @param jsonData The JSON data.
* @return An authentication request. * @return An authentication request.
*/ */
static AuthenticationRequest fromPasswordRequest(Request request, LoginAccountRequestJson jsonData) { static AuthenticationRequest fromPasswordRequest(Context ctx, LoginAccountRequestJson jsonData) {
return AuthenticationRequest.builder() return AuthenticationRequest.builder()
.request(request) .context(ctx)
.passwordRequest(jsonData) .passwordRequest(jsonData)
.build(); .build();
} }
/** /**
* Generates an authentication request from a {@link LoginTokenRequestJson} object. * Generates an authentication request from a {@link LoginTokenRequestJson} object.
* @param request The Express request. * @param ctx The Javalin context.
* @param jsonData The JSON data. * @param jsonData The JSON data.
* @return An authentication request. * @return An authentication request.
*/ */
static AuthenticationRequest fromTokenRequest(Request request, LoginTokenRequestJson jsonData) { static AuthenticationRequest fromTokenRequest(Context ctx, LoginTokenRequestJson jsonData) {
return AuthenticationRequest.builder() return AuthenticationRequest.builder()
.request(request) .context(ctx)
.tokenRequest(jsonData) .tokenRequest(jsonData)
.build(); .build();
} }
/** /**
* Generates an authentication request from a {@link ComboTokenReqJson} object. * Generates an authentication request from a {@link ComboTokenReqJson} object.
* @param request The Express request. * @param ctx The Javalin context.
* @param jsonData The JSON data. * @param jsonData The JSON data.
* @return An authentication request. * @return An authentication request.
*/ */
static AuthenticationRequest fromComboTokenRequest(Request request, ComboTokenReqJson jsonData, static AuthenticationRequest fromComboTokenRequest(Context ctx, ComboTokenReqJson jsonData,
ComboTokenReqJson.LoginTokenData tokenData) { ComboTokenReqJson.LoginTokenData tokenData) {
return AuthenticationRequest.builder() return AuthenticationRequest.builder()
.request(request) .context(ctx)
.sessionKeyRequest(jsonData) .sessionKeyRequest(jsonData)
.sessionKeyData(tokenData) .sessionKeyData(tokenData)
.build(); .build();
} }
/** /**
* Generates an authentication request from a {@link Response} object. * Generates an authentication request from a {@link Context} object.
* @param request The Express request. * @param ctx The Javalin context.
* @param response the Express response.
* @return An authentication request. * @return An authentication request.
*/ */
static AuthenticationRequest fromExternalRequest(Request request, Response response) { static AuthenticationRequest fromExternalRequest(Context ctx) {
return AuthenticationRequest.builder().request(request) return AuthenticationRequest.builder().context(ctx).build();
.response(response).build();
} }
} }

View File

@ -36,7 +36,7 @@ public final class DefaultAuthenticators {
int playerCount = Grasscutter.getGameServer().getPlayers().size(); int playerCount = Grasscutter.getGameServer().getPlayers().size();
boolean successfulLogin = false; boolean successfulLogin = false;
String address = request.getRequest().ip(); String address = request.getContext().ip();
String responseMessage = translate("messages.dispatch.account.username_error"); String responseMessage = translate("messages.dispatch.account.username_error");
String loggerMessage = ""; String loggerMessage = "";
@ -99,7 +99,7 @@ public final class DefaultAuthenticators {
int playerCount = Grasscutter.getGameServer().getPlayers().size(); int playerCount = Grasscutter.getGameServer().getPlayers().size();
boolean successfulLogin = false; boolean successfulLogin = false;
String address = request.getRequest().ip(); String address = request.getContext().ip();
String responseMessage = translate("messages.dispatch.account.username_error"); String responseMessage = translate("messages.dispatch.account.username_error");
String loggerMessage = ""; String loggerMessage = "";
String decryptedPassword = ""; String decryptedPassword = "";
@ -205,7 +205,7 @@ public final class DefaultAuthenticators {
assert requestData != null; assert requestData != null;
boolean successfulLogin; boolean successfulLogin;
String address = request.getRequest().ip(); String address = request.getContext().ip();
String loggerMessage; String loggerMessage;
int playerCount = Grasscutter.getGameServer().getPlayers().size(); int playerCount = Grasscutter.getGameServer().getPlayers().size();
@ -263,7 +263,7 @@ public final class DefaultAuthenticators {
assert loginData != null; assert loginData != null;
boolean successfulLogin; boolean successfulLogin;
String address = request.getRequest().ip(); String address = request.getContext().ip();
String loggerMessage; String loggerMessage;
int playerCount = Grasscutter.getGameServer().getPlayers().size(); int playerCount = Grasscutter.getGameServer().getPlayers().size();
@ -309,43 +309,37 @@ public final class DefaultAuthenticators {
public static class ExternalAuthentication implements ExternalAuthenticator { public static class ExternalAuthentication implements ExternalAuthenticator {
@Override @Override
public void handleLogin(AuthenticationRequest request) { public void handleLogin(AuthenticationRequest request) {
assert request.getResponse() != null; request.getContext().result("Authentication is not available with the default authentication method.");
request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override @Override
public void handleAccountCreation(AuthenticationRequest request) { public void handleAccountCreation(AuthenticationRequest request) {
assert request.getResponse() != null; request.getContext().result("Authentication is not available with the default authentication method.");
request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override @Override
public void handlePasswordReset(AuthenticationRequest request) { public void handlePasswordReset(AuthenticationRequest request) {
assert request.getResponse() != null; request.getContext().result("Authentication is not available with the default authentication method.");
request.getResponse().send("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 { public static class OAuthAuthentication implements OAuthAuthenticator {
@Override @Override
public void handleLogin(AuthenticationRequest request) { public void handleLogin(AuthenticationRequest request) {
assert request.getResponse() != null; request.getContext().result("Authentication is not available with the default authentication method.");
request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override @Override
public void handleRedirection(AuthenticationRequest request, ClientType type) { public void handleRedirection(AuthenticationRequest request, ClientType type) {
assert request.getResponse() != null; request.getContext().result("Authentication is not available with the default authentication method.");
request.getResponse().send("Authentication is not available with the default authentication method.");
} }
@Override @Override
public void handleTokenProcess(AuthenticationRequest request) { public void handleTokenProcess(AuthenticationRequest request) {
assert request.getResponse() != null; request.getContext().result("Authentication is not available with the default authentication method.");
request.getResponse().send("Authentication is not available with the default authentication method.");
} }
} }
} }

View File

@ -3,9 +3,9 @@ package emu.grasscutter.server.http;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerDebugMode;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import express.Express; import emu.grasscutter.utils.HttpUtils;
import express.http.MediaType;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.core.util.JavalinLogger;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
@ -21,14 +21,14 @@ import static emu.grasscutter.utils.Language.translate;
* (including dispatch, announcements, gacha, etc.) * (including dispatch, announcements, gacha, etc.)
*/ */
public final class HttpServer { public final class HttpServer {
private final Express express; private final Javalin javalin;
/** /**
* Configures the Express application. * Configures the Javalin application.
*/ */
public HttpServer() { public HttpServer() {
this.express = new Express(config -> { this.javalin = Javalin.create(config -> {
// Set the Express HTTP server. // Set the Javalin HTTP server.
config.server(HttpServer::createServer); config.server(HttpServer::createServer);
// Configure encryption/HTTPS/SSL. // Configure encryption/HTTPS/SSL.
@ -46,8 +46,7 @@ public final class HttpServer {
if (DISPATCH_INFO.logRequests == ServerDebugMode.ALL) if (DISPATCH_INFO.logRequests == ServerDebugMode.ALL)
config.enableDevLogging(); config.enableDevLogging();
// Disable compression on static files. // Static files should be added like this https://javalin.io/documentation#static-files
config.precompressStaticFiles = false;
}); });
} }
@ -100,7 +99,7 @@ public final class HttpServer {
* @return A Javalin instance. * @return A Javalin instance.
*/ */
public Javalin getHandle() { 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. try { // Create a router instance & apply routes.
var constructor = router.getDeclaredConstructor(types); // Get the constructor. var constructor = router.getDeclaredConstructor(types); // Get the constructor.
var routerInstance = constructor.newInstance(args); // Create instance. var routerInstance = constructor.newInstance(args); // Create instance.
routerInstance.applyRoutes(this.express, this.getHandle()); // Apply routes. routerInstance.applyRoutes(this.javalin); // Apply routes.
} catch (Exception exception) { } catch (Exception exception) {
Grasscutter.getLogger().warn(translate("messages.dispatch.router_error"), exception); Grasscutter.getLogger().warn(translate("messages.dispatch.router_error"), exception);
} return this; } return this;
@ -131,24 +130,24 @@ public final class HttpServer {
public void start() throws UnsupportedEncodingException { public void start() throws UnsupportedEncodingException {
// Attempt to start the HTTP server. // Attempt to start the HTTP server.
if (HTTP_INFO.bindAddress.equals("")) { if (HTTP_INFO.bindAddress.equals("")) {
this.express.listen(HTTP_INFO.bindPort); this.javalin.start(HTTP_INFO.bindPort);
}else { }else {
this.express.listen(HTTP_INFO.bindAddress, HTTP_INFO.bindPort); this.javalin.start(HTTP_INFO.bindAddress, HTTP_INFO.bindPort);
} }
// Log bind information. // 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. * Handles the '/' (index) endpoint on the Express application.
*/ */
public static class DefaultRequestRouter implements Router { public static class DefaultRequestRouter implements Router {
@Override public void applyRoutes(Express express, Javalin handle) { @Override public void applyRoutes(Javalin javalin) {
express.get("/", (request, response) -> { javalin.get("/", ctx -> {
File file = new File(HTTP_STATIC_FILES.indexFile); File file = new File(HTTP_STATIC_FILES.indexFile);
if (!file.exists()) if (!file.exists())
response.send(""" ctx.result("""
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
@ -159,9 +158,8 @@ public final class HttpServer {
""".formatted(translate("messages.status.welcome"))); """.formatted(translate("messages.status.welcome")));
else { else {
final var filePath = file.getPath(); 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));
response.type((fromExtension != null) ? fromExtension.getMIME() : "text/plain") ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "text/plain").result(FileUtils.read(filePath));
.send(FileUtils.read(filePath));
} }
}); });
} }
@ -171,8 +169,8 @@ public final class HttpServer {
* Handles unhandled endpoints on the Express application. * Handles unhandled endpoints on the Express application.
*/ */
public static class UnhandledRequestRouter implements Router { public static class UnhandledRequestRouter implements Router {
@Override public void applyRoutes(Express express, Javalin handle) { @Override public void applyRoutes(Javalin javalin) {
handle.error(404, context -> { javalin.error(404, context -> {
if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING) if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING)
Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", context.method(), context.url())); Grasscutter.getLogger().info(translate("messages.dispatch.unhandled_request_error", context.method(), context.url()));
context.contentType("text/html"); context.contentType("text/html");
@ -193,7 +191,7 @@ public final class HttpServer {
"""); """);
else { else {
final var filePath = file.getPath(); 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") context.contentType((fromExtension != null) ? fromExtension.getMIME() : "text/plain")
.result(FileUtils.read(filePath)); .result(FileUtils.read(filePath));
} }

View File

@ -1,16 +1,15 @@
package emu.grasscutter.server.http; package emu.grasscutter.server.http;
import express.Express;
import io.javalin.Javalin; import io.javalin.Javalin;
/** /**
* Defines routes for an {@link Express} instance. * Defines routes for an {@link Javalin} instance.
*/ */
public interface Router { public interface Router {
/** /**
* Called when the router is initialized by Express. * 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);
} }

View File

@ -2,16 +2,13 @@ package emu.grasscutter.server.http.dispatch;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.auth.AuthenticationSystem; import emu.grasscutter.auth.AuthenticationSystem;
import emu.grasscutter.auth.OAuthAuthenticator;
import emu.grasscutter.auth.OAuthAuthenticator.ClientType; import emu.grasscutter.auth.OAuthAuthenticator.ClientType;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import emu.grasscutter.server.http.objects.*; import emu.grasscutter.server.http.objects.*;
import emu.grasscutter.server.http.objects.ComboTokenReqJson.LoginTokenData; import emu.grasscutter.server.http.objects.ComboTokenReqJson.LoginTokenData;
import emu.grasscutter.utils.JsonUtils; import emu.grasscutter.utils.JsonUtils;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.Context;
import static emu.grasscutter.utils.Language.translate; 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) * Handles requests related to authentication. (aka dispatch)
*/ */
public final class DispatchHandler implements Router { 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). // 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). // 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). // 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). // External login (from other clients).
express.get("/authentication/type", (request, response) -> response.send(Grasscutter.getAuthenticationSystem().getClass().getSimpleName())); javalin.get("/authentication/type", ctx -> ctx.result(Grasscutter.getAuthenticationSystem().getClass().getSimpleName()));
express.post("/authentication/login", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() javalin.post("/authentication/login", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handleLogin(AuthenticationSystem.fromExternalRequest(request, response))); .handleLogin(AuthenticationSystem.fromExternalRequest(ctx)));
express.post("/authentication/register", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() javalin.post("/authentication/register", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handleAccountCreation(AuthenticationSystem.fromExternalRequest(request, response))); .handleAccountCreation(AuthenticationSystem.fromExternalRequest(ctx)));
express.post("/authentication/change_password", (request, response) -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator() javalin.post("/authentication/change_password", ctx -> Grasscutter.getAuthenticationSystem().getExternalAuthenticator()
.handlePasswordReset(AuthenticationSystem.fromExternalRequest(request, response))); .handlePasswordReset(AuthenticationSystem.fromExternalRequest(ctx)));
// External login (from OAuth2). // External login (from OAuth2).
express.post("/hk4e_global/mdk/shield/api/loginByThirdparty", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() javalin.post("/hk4e_global/mdk/shield/api/loginByThirdparty", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleLogin(AuthenticationSystem.fromExternalRequest(request, response))); .handleLogin(AuthenticationSystem.fromExternalRequest(ctx)));
express.get("/authentication/openid/redirect", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() javalin.get("/authentication/openid/redirect", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleTokenProcess(AuthenticationSystem.fromExternalRequest(request, response))); .handleTokenProcess(AuthenticationSystem.fromExternalRequest(ctx)));
express.get("/Api/twitter_login", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() javalin.get("/Api/twitter_login", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleRedirection(AuthenticationSystem.fromExternalRequest(request, response), ClientType.DESKTOP)); .handleRedirection(AuthenticationSystem.fromExternalRequest(ctx), ClientType.DESKTOP));
express.get("/sdkTwitterLogin.html", (request, response) -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator() javalin.get("/sdkTwitterLogin.html", ctx -> Grasscutter.getAuthenticationSystem().getOAuthAuthenticator()
.handleRedirection(AuthenticationSystem.fromExternalRequest(request, response), ClientType.MOBILE)); .handleRedirection(AuthenticationSystem.fromExternalRequest(ctx), ClientType.MOBILE));
} }
/** /**
* @route /hk4e_global/mdk/shield/api/login * @route /hk4e_global/mdk/shield/api/login
*/ */
private static void clientLogin(Request request, Response response) { private static void clientLogin(Context ctx) {
// Parse body data. // Parse body data.
String rawBodyData = request.ctx().body(); String rawBodyData = ctx.body();
var bodyData = JsonUtils.decode(rawBodyData, LoginAccountRequestJson.class); var bodyData = JsonUtils.decode(rawBodyData, LoginAccountRequestJson.class);
// Validate body data. // Validate body data.
@ -62,20 +59,20 @@ public final class DispatchHandler implements Router {
// Pass data to authentication handler. // Pass data to authentication handler.
var responseData = Grasscutter.getAuthenticationSystem() var responseData = Grasscutter.getAuthenticationSystem()
.getPasswordAuthenticator() .getPasswordAuthenticator()
.authenticate(AuthenticationSystem.fromPasswordRequest(request, bodyData)); .authenticate(AuthenticationSystem.fromPasswordRequest(ctx, bodyData));
// Send response. // Send response.
response.send(responseData); ctx.json(responseData);
// Log to console. // 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 * @route /hk4e_global/mdk/shield/api/verify
*/ */
private static void tokenLogin(Request request, Response response) { private static void tokenLogin(Context ctx) {
// Parse body data. // Parse body data.
String rawBodyData = request.ctx().body(); String rawBodyData = ctx.body();
var bodyData = JsonUtils.decode(rawBodyData, LoginTokenRequestJson.class); var bodyData = JsonUtils.decode(rawBodyData, LoginTokenRequestJson.class);
// Validate body data. // Validate body data.
@ -85,20 +82,20 @@ public final class DispatchHandler implements Router {
// Pass data to authentication handler. // Pass data to authentication handler.
var responseData = Grasscutter.getAuthenticationSystem() var responseData = Grasscutter.getAuthenticationSystem()
.getTokenAuthenticator() .getTokenAuthenticator()
.authenticate(AuthenticationSystem.fromTokenRequest(request, bodyData)); .authenticate(AuthenticationSystem.fromTokenRequest(ctx, bodyData));
// Send response. // Send response.
response.send(responseData); ctx.json(responseData);
// Log to console. // 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 * @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. // Parse body data.
String rawBodyData = request.ctx().body(); String rawBodyData = ctx.body();
var bodyData = JsonUtils.decode(rawBodyData, ComboTokenReqJson.class); var bodyData = JsonUtils.decode(rawBodyData, ComboTokenReqJson.class);
// Validate body data. // Validate body data.
@ -111,11 +108,11 @@ public final class DispatchHandler implements Router {
// Pass data to authentication handler. // Pass data to authentication handler.
var responseData = Grasscutter.getAuthenticationSystem() var responseData = Grasscutter.getAuthenticationSystem()
.getSessionKeyAuthenticator() .getSessionKeyAuthenticator()
.authenticate(AuthenticationSystem.fromComboTokenRequest(request, bodyData, tokenData)); .authenticate(AuthenticationSystem.fromComboTokenRequest(ctx, bodyData, tokenData));
// Send response. // Send response.
response.send(responseData); ctx.json(responseData);
// Log to console. // 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()));
} }
} }

View File

@ -11,20 +11,12 @@ import emu.grasscutter.server.event.dispatch.QueryCurrentRegionEvent;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import emu.grasscutter.server.http.objects.QueryCurRegionRspJson; import emu.grasscutter.server.http.objects.QueryCurRegionRspJson;
import emu.grasscutter.utils.Crypto; import emu.grasscutter.utils.Crypto;
import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.Context;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream; 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.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.security.Signature; import java.security.Signature;
@ -107,36 +99,36 @@ public final class RegionHandler implements Router {
regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray()); regionListResponse = Utils.base64Encode(updatedRegionList.toByteString().toByteArray());
} }
@Override public void applyRoutes(Express express, Javalin handle) { @Override public void applyRoutes(Javalin javalin) {
express.get("/query_region_list", RegionHandler::queryRegionList); javalin.get("/query_region_list", RegionHandler::queryRegionList);
express.get("/query_cur_region/:region", RegionHandler::queryCurrentRegion ); javalin.get("/query_cur_region/{region}", RegionHandler::queryCurrentRegion );
} }
/** /**
* @route /query_region_list * @route /query_region_list
*/ */
private static void queryRegionList(Request request, Response response) { private static void queryRegionList(Context ctx) {
// Invoke event. // Invoke event.
QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); event.call(); QueryAllRegionsEvent event = new QueryAllRegionsEvent(regionListResponse); event.call();
// Respond with event result. // Respond with event result.
response.send(event.getRegionList()); ctx.result(event.getRegionList());
// Log to console. // 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. // Get region to query.
String regionName = request.params("region"); String regionName = ctx.pathParam("region");
String versionName = request.query("version"); String versionName = ctx.queryParam("version");
var region = regions.get(regionName); var region = regions.get(regionName);
// Get region data. // Get region data.
String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw=="; String regionData = "CAESGE5vdCBGb3VuZCB2ZXJzaW9uIGNvbmZpZw==";
if (request.query().values().size() > 0) { if (ctx.queryParamMap().values().size() > 0) {
if (region != null) if (region != null)
regionData = region.getBase64(); regionData = region.getBase64();
} }
@ -150,18 +142,18 @@ public final class RegionHandler implements Router {
try { try {
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call();
if (request.query("dispatchSeed") == null) { if (ctx.queryParam("dispatchSeed") == null) {
// More love for UA Patch players // More love for UA Patch players
var rsp = new QueryCurRegionRspJson(); var rsp = new QueryCurRegionRspJson();
rsp.content = event.getRegionInfo(); rsp.content = event.getRegionInfo();
rsp.sign = "TW9yZSBsb3ZlIGZvciBVQSBQYXRjaCBwbGF5ZXJz"; rsp.sign = "TW9yZSBsb3ZlIGZvciBVQSBQYXRjaCBwbGF5ZXJz";
response.send(rsp); ctx.json(rsp);
return; return;
} }
String key_id = request.query("key_id"); String key_id = ctx.queryParam("key_id");
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); 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); 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()); var regionInfo = Utils.base64Decode(event.getRegionInfo());
@ -189,7 +181,7 @@ public final class RegionHandler implements Router {
rsp.content = Utils.base64Encode(encryptedRegionInfoStream.toByteArray()); rsp.content = Utils.base64Encode(encryptedRegionInfoStream.toByteArray());
rsp.sign = Utils.base64Encode(privateSignature.sign()); rsp.sign = Utils.base64Encode(privateSignature.sign());
response.send(rsp); ctx.json(rsp);
} }
catch (Exception e) { catch (Exception e) {
Grasscutter.getLogger().error("An error occurred while handling query_cur_region.", 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. // Invoke event.
QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call(); QueryCurrentRegionEvent event = new QueryCurrentRegionEvent(regionData); event.call();
// Respond with event result. // Respond with event result.
response.send(event.getRegionInfo()); ctx.result(event.getRegionInfo());
} }
// Log to console. // 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));
} }
/** /**

View File

@ -1,9 +1,8 @@
package emu.grasscutter.server.http.documentation; package emu.grasscutter.server.http.documentation;
import express.http.Request; import io.javalin.http.Context;
import express.http.Response;
interface DocumentationHandler { interface DocumentationHandler {
void handle(Request request, Response response); void handle(Context ctx);
} }

View File

@ -1,19 +1,21 @@
package emu.grasscutter.server.http.documentation; package emu.grasscutter.server.http.documentation;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import express.Express;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.Context;
public final class DocumentationServerHandler implements Router { public final class DocumentationServerHandler implements Router {
@Override @Override
public void applyRoutes(Express express, Javalin handle) { public void applyRoutes(Javalin javalin) {
final RootRequestHandler root = new RootRequestHandler(); final RootRequestHandler root = new RootRequestHandler();
final HandbookRequestHandler handbook = new HandbookRequestHandler(); final HandbookRequestHandler handbook = new HandbookRequestHandler();
final GachaMappingRequestHandler gachaMapping = new GachaMappingRequestHandler(); final GachaMappingRequestHandler gachaMapping = new GachaMappingRequestHandler();
express.get("/documentation/handbook", handbook::handle); // TODO: Removal
express.get("/documentation/gachamapping", gachaMapping::handle); // TODO: Forward /documentation requests to https://grasscutter.io/wiki
express.get("/documentation", root::handle); javalin.get("/documentation/handbook", handbook::handle);
javalin.get("/documentation/gachamapping", gachaMapping::handle);
javalin.get("/documentation", root::handle);
} }
} }

View File

@ -1,9 +1,9 @@
package emu.grasscutter.server.http.documentation; package emu.grasscutter.server.http.documentation;
import emu.grasscutter.tools.Tools; import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Language;
import express.http.Request; import io.javalin.http.Context;
import express.http.Response;
import static emu.grasscutter.config.Configuration.DOCUMENT_LANGUAGE; import static emu.grasscutter.config.Configuration.DOCUMENT_LANGUAGE;
@ -17,10 +17,8 @@ final class GachaMappingRequestHandler implements DocumentationHandler {
} }
@Override @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 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.contentType(HttpUtils.MediaType._json.getMIME()).result(gachaJsons.get(langIdx));
.ctx()
.result(gachaJsons.get(langIdx));
} }
} }

View File

@ -10,10 +10,10 @@ import emu.grasscutter.data.excels.ItemData;
import emu.grasscutter.data.excels.MonsterData; import emu.grasscutter.data.excels.MonsterData;
import emu.grasscutter.data.excels.SceneData; import emu.grasscutter.data.excels.SceneData;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Language; import emu.grasscutter.utils.Language;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import express.http.Request; import io.javalin.http.Context;
import express.http.Response;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -36,12 +36,13 @@ final class HandbookRequestHandler implements DocumentationHandler {
} }
@Override @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 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) { if (template == null) {
response.status(500); ctx.status(500);
} else { } else {
response.send(handbookHtmls.get(langIdx)); ctx.contentType(HttpUtils.MediaType._html.getMIME());
ctx.result(handbookHtmls.get(langIdx));
} }
} }

View File

@ -4,11 +4,11 @@ import static emu.grasscutter.config.Configuration.DATA;
import static emu.grasscutter.utils.Language.translate; import static emu.grasscutter.utils.Language.translate;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.ResourceLoader;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import express.http.Request; import io.javalin.http.Context;
import express.http.Response;
import java.io.File; import java.io.File;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -27,15 +27,16 @@ final class RootRequestHandler implements DocumentationHandler {
} }
@Override @Override
public void handle(Request request, Response response) { public void handle(Context ctx) {
if (template == null) { if (template == null) {
response.status(500); ctx.status(500);
return; return;
} }
String content = template.replace("{{TITLE}}", translate("documentation.index.title")) String content = template.replace("{{TITLE}}", translate("documentation.index.title"))
.replace("{{ITEM_HANDBOOK}}", translate("documentation.index.handbook")) .replace("{{ITEM_HANDBOOK}}", translate("documentation.index.handbook"))
.replace("{{ITEM_GACHA_MAPPING}}", translate("documentation.index.gacha_mapping")); .replace("{{ITEM_GACHA_MAPPING}}", translate("documentation.index.gacha_mapping"));
response.send(content); ctx.contentType(HttpUtils.MediaType._html.getMIME());
ctx.result(content);
} }
} }

View File

@ -5,44 +5,39 @@ import emu.grasscutter.data.DataLoader;
import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.objects.HttpJsonResponse;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Utils; 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.Javalin;
import io.javalin.http.Context;
import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Objects; import java.util.Objects;
/** /**
* Handles requests related to the announcements page. * Handles requests related to the announcements page.
*/ */
public final class AnnouncementsHandler implements Router { 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 // 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 // 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 // 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 // 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 // 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 = ""; 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 { try {
data = FileUtils.readToString(DataLoader.load("GameAnnouncement.json")); data = FileUtils.readToString(DataLoader.load("GameAnnouncement.json"));
} catch (Exception e) { } catch (Exception e) {
@ -50,7 +45,7 @@ public final class AnnouncementsHandler implements Router {
Grasscutter.getLogger().info("Unable to read file 'GameAnnouncementList.json'. \n" + e); 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 { try {
data = FileUtils.readToString(DataLoader.load("GameAnnouncementList.json")); data = FileUtils.readToString(DataLoader.load("GameAnnouncementList.json"));
} catch (Exception e) { } catch (Exception e) {
@ -59,11 +54,11 @@ public final class AnnouncementsHandler implements Router {
} }
} }
} else { } else {
response.send("{\"retcode\":404,\"message\":\"Unknown request path\"}"); ctx.result("{\"retcode\":404,\"message\":\"Unknown request path\"}");
} }
if (data.isEmpty()) { if (data.isEmpty()) {
response.send("{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}"); ctx.result("{\"retcode\":500,\"message\":\"Unable to fetch requsted content\"}");
return; return;
} }
@ -74,19 +69,19 @@ public final class AnnouncementsHandler implements Router {
data = data data = data
.replace("{{DISPATCH_PUBLIC}}", dispatchDomain) .replace("{{DISPATCH_PUBLIC}}", dispatchDomain)
.replace("{{SYSTEM_TIME}}", String.valueOf(System.currentTimeMillis())); .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) { private static void getPageResources(Context ctx) {
try (InputStream filestream = DataLoader.load(request.path())) { try (InputStream filestream = DataLoader.load(ctx.path())) {
String possibleFilename = Utils.toFilePath(DATA(request.path())); String possibleFilename = Utils.toFilePath(DATA(ctx.path()));
MediaType fromExtension = MediaType.getByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1)); HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(possibleFilename.substring(possibleFilename.lastIndexOf(".") + 1));
response.type((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream"); ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream");
response.send(filestream.readAllBytes()); ctx.result(filestream.readAllBytes());
} catch (Exception e) { } catch (Exception e) {
Grasscutter.getLogger().warn("File does not exist: " + request.path()); Grasscutter.getLogger().warn("File does not exist: " + ctx.path());
response.status(404); ctx.status(404);
} }
} }
} }

View File

@ -7,13 +7,11 @@ import emu.grasscutter.game.gacha.GachaBanner;
import emu.grasscutter.game.gacha.GachaSystem; import emu.grasscutter.game.gacha.GachaSystem;
import emu.grasscutter.game.player.Player; import emu.grasscutter.game.player.Player;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import emu.grasscutter.tools.Tools;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.HttpUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.Utils;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.Context;
import io.javalin.http.staticfiles.Location; import io.javalin.http.staticfiles.Location;
import java.io.File; import java.io.File;
@ -31,38 +29,38 @@ import static emu.grasscutter.utils.Language.translate;
public final class GachaHandler implements Router { public final class GachaHandler implements Router {
public static final String gachaMappings = DATA(Utils.toFilePath("gacha/mappings.js")); public static final String gachaMappings = DATA(Utils.toFilePath("gacha/mappings.js"));
@Override public void applyRoutes(Express express, Javalin handle) { @Override public void applyRoutes(Javalin javalin) {
express.get("/gacha", GachaHandler::gachaRecords); javalin.get("/gacha", GachaHandler::gachaRecords);
express.get("/gacha/details", GachaHandler::gachaDetails); 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"))); File recordsTemplate = new File(Utils.toFilePath(DATA("gacha/records.html")));
if (!recordsTemplate.exists()) { if (!recordsTemplate.exists()) {
Grasscutter.getLogger().warn("File does not exist: " + recordsTemplate); Grasscutter.getLogger().warn("File does not exist: " + recordsTemplate);
response.status(500); ctx.status(500);
return; return;
} }
String sessionKey = request.query("s"); String sessionKey = ctx.queryParam("s");
Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); Account account = DatabaseHelper.getAccountBySessionKey(sessionKey);
if (account == null) { if (account == null) {
response.status(403).send("Requested account was not found"); ctx.status(403).result("Requested account was not found");
return; return;
} }
Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId()); Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId());
if (player == null) { if (player == null) {
response.status(403).send("No player associated with requested account"); ctx.status(403).result("No player associated with requested account");
return; return;
} }
int page = 0, gachaType = 0; int page = 0, gachaType = 0;
if (request.query("p") != null) if (ctx.queryParam("p") != null)
page = Integer.parseInt(request.query("p")); page = Integer.parseInt(ctx.queryParam("p"));
if (request.query("gachaType") != null) if (ctx.queryParam("gachaType") != null)
gachaType = Integer.parseInt(request.query("gachaType")); gachaType = Integer.parseInt(ctx.queryParam("gachaType"));
String records = DatabaseHelper.getGachaRecords(player.getUid(), page, gachaType).toString(); String records = DatabaseHelper.getGachaRecords(player.getUid(), page, gachaType).toString();
long maxPage = DatabaseHelper.getGachaRecordsMaxPage(player.getUid(), page, gachaType); 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("{{DATE}}", translate(player, "gacha.records.date"))
.replace("{{ITEM}}", translate(player, "gacha.records.item")) .replace("{{ITEM}}", translate(player, "gacha.records.item"))
.replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale())); .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"))); File detailsTemplate = new File(Utils.toFilePath(DATA("gacha/details.html")));
if (!detailsTemplate.exists()) { if (!detailsTemplate.exists()) {
Grasscutter.getLogger().warn("File does not exist: " + detailsTemplate); Grasscutter.getLogger().warn("File does not exist: " + detailsTemplate);
response.status(500); ctx.status(500);
return; return;
} }
String sessionKey = request.query("s"); String sessionKey = ctx.queryParam("s");
Account account = DatabaseHelper.getAccountBySessionKey(sessionKey); Account account = DatabaseHelper.getAccountBySessionKey(sessionKey);
if (account == null) { if (account == null) {
response.status(403).send("Requested account was not found"); ctx.status(403).result("Requested account was not found");
return; return;
} }
Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId()); Player player = Grasscutter.getGameServer().getPlayerByAccountId(account.getId());
if (player == null) { if (player == null) {
response.status(403).send("No player associated with requested account"); ctx.status(403).result("No player associated with requested account");
return; return;
} }
@ -107,7 +106,7 @@ public final class GachaHandler implements Router {
.replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale())); .replace("{{LANGUAGE}}", Utils.getLanguageCode(account.getLocale()));
// Get the banner info for the banner we want. // 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(); GachaSystem manager = Grasscutter.getGameServer().getGachaSystem();
GachaBanner banner = manager.getGachaBanners().get(scheduleId); GachaBanner banner = manager.getGachaBanners().get(scheduleId);
@ -135,6 +134,7 @@ public final class GachaHandler implements Router {
template = template.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]"); template = template.replace("{{THREE_STARS}}", "[" + String.join(",", threeStarItems) + "]");
// Done. // Done.
response.send(template); ctx.contentType(HttpUtils.MediaType._html.getMIME());
ctx.result(template);
} }
} }

View File

@ -7,53 +7,52 @@ import emu.grasscutter.Grasscutter;
import emu.grasscutter.server.http.objects.HttpJsonResponse; import emu.grasscutter.server.http.objects.HttpJsonResponse;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import emu.grasscutter.server.http.objects.WebStaticVersionResponse; import emu.grasscutter.server.http.objects.WebStaticVersionResponse;
import express.Express; import emu.grasscutter.utils.HttpUtils;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.Context;
/** /**
* Handles all generic, hard-coded responses. * Handles all generic, hard-coded responses.
*/ */
public final class GenericHandler implements Router { 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 // 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 // hk4e-sdk-os.hoyoverse.com
// this could be either GET or POST based on the observation of different clients // 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 // 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 // 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 // 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 // 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? // Test api?
// abtest-api-data-sg.hoyoverse.com // 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 // log-upload-os.mihoyo.com
express.all("/log/sdk/upload", new HttpJsonResponse("{\"code\":0}")); HttpUtils.allRoutes(javalin, "/log/sdk/upload", new HttpJsonResponse("{\"code\":0}"));
express.all("/sdk/upload", new HttpJsonResponse("{\"code\":0}")); HttpUtils.allRoutes(javalin, "/sdk/upload", new HttpJsonResponse("{\"code\":0}"));
express.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}")); javalin.post("/sdk/dataUpload", new HttpJsonResponse("{\"code\":0}"));
// /perf/config/verify?device_id=xxx&platform=x&name=xxx // /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 // 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 playerCount = Grasscutter.getGameServer().getPlayers().size();
int maxPlayer = ACCOUNT.maxPlayer; int maxPlayer = ACCOUNT.maxPlayer;
String version = GameConstants.VERSION; 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 + "\"}}");
} }
} }

View File

@ -1,24 +1,22 @@
package emu.grasscutter.server.http.handlers; package emu.grasscutter.server.http.handlers;
import emu.grasscutter.server.http.Router; import emu.grasscutter.server.http.Router;
import express.Express;
import express.http.Request;
import express.http.Response;
import io.javalin.Javalin; import io.javalin.Javalin;
import io.javalin.http.Context;
/** /**
* Handles logging requests made to the server. * Handles logging requests made to the server.
*/ */
public final class LogHandler implements Router { public final class LogHandler implements Router {
@Override public void applyRoutes(Express express, Javalin handle) { @Override public void applyRoutes(Javalin javalin) {
// overseauspider.yuanshen.com // overseauspider.yuanshen.com
express.post("/log", LogHandler::log); javalin.post("/log", LogHandler::log);
// log-upload-os.mihoyo.com // 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. // TODO: Figure out how to dump request body and log to file.
response.send("{\"code\":0}"); ctx.result("{\"code\":0}");
} }
} }

View File

@ -6,14 +6,14 @@ import java.util.Objects;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.Grasscutter.ServerDebugMode; import emu.grasscutter.Grasscutter.ServerDebugMode;
import express.http.HttpContextHandler; import io.javalin.http.Context;
import express.http.Request; import io.javalin.http.Handler;
import express.http.Response; import org.jetbrains.annotations.NotNull;
import static emu.grasscutter.config.Configuration.*; import static emu.grasscutter.config.Configuration.*;
import static emu.grasscutter.utils.Language.translate; 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 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 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", "/common/hk4e_global/announcement/api/getAlertPic",
@ -33,11 +33,11 @@ public final class HttpJsonResponse implements HttpContextHandler {
} }
@Override @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 // 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()))) { if (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING && Arrays.stream(missingRoutes).anyMatch(x -> Objects.equals(x, ctx.endpointHandlerPath()))) {
Grasscutter.getLogger().info(translate("messages.dispatch.request", req.ip(), req.method(), req.baseUrl()) + (DISPATCH_INFO.logRequests == ServerDebugMode.MISSING ? "(MISSING)" : "")); 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);
} }
} }

View File

@ -1,42 +1,37 @@
package emu.grasscutter.server.http.objects; package emu.grasscutter.server.http.objects;
import emu.grasscutter.Grasscutter; import emu.grasscutter.Grasscutter;
import emu.grasscutter.data.DataLoader;
import emu.grasscutter.utils.FileUtils; import emu.grasscutter.utils.FileUtils;
import emu.grasscutter.utils.Utils; import emu.grasscutter.utils.HttpUtils;
import express.http.HttpContextHandler; import io.javalin.http.Context;
import express.http.MediaType; import io.javalin.http.Handler;
import express.http.Request;
import express.http.Response;
import io.javalin.core.util.FileUtil;
import static emu.grasscutter.config.Configuration.DATA;
import static emu.grasscutter.config.Configuration.DISPATCH_INFO; import static emu.grasscutter.config.Configuration.DISPATCH_INFO;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
public class WebStaticVersionResponse implements HttpContextHandler { public class WebStaticVersionResponse implements Handler {
@Override @Override
public void handle(Request request, Response response) throws IOException { public void handle(Context ctx) throws IOException {
String requestFor = request.path().substring(request.path().lastIndexOf("-") + 1); String requestFor = ctx.path().substring(ctx.path().lastIndexOf("-") + 1);
getPageResources("/webstatic/" + requestFor, response); getPageResources("/webstatic/" + requestFor, ctx);
return; return;
} }
private static void getPageResources(String path, Response response) { private static void getPageResources(String path, Context ctx) {
try (InputStream filestream = FileUtils.readResourceAsStream(path)) { try (InputStream filestream = FileUtils.readResourceAsStream(path)) {
MediaType fromExtension = MediaType.getByExtension(path.substring(path.lastIndexOf(".") + 1)); HttpUtils.MediaType fromExtension = HttpUtils.MediaType.getByExtension(path.substring(path.lastIndexOf(".") + 1));
response.type((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream"); ctx.contentType((fromExtension != null) ? fromExtension.getMIME() : "application/octet-stream");
response.send(filestream.readAllBytes()); ctx.result(filestream.readAllBytes());
} catch (Exception e) { } catch (Exception e) {
if (DISPATCH_INFO.logRequests == Grasscutter.ServerDebugMode.MISSING) { if (DISPATCH_INFO.logRequests == Grasscutter.ServerDebugMode.MISSING) {
Grasscutter.getLogger().warn("Webstatic File Missing: " + path); Grasscutter.getLogger().warn("Webstatic File Missing: " + path);
} }
response.status(404); ctx.status(404);
} }
} }
} }

View File

@ -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;
}
}
}