mirror of
https://github.com/sirjonasxx/G-Earth.git
synced 2024-11-23 00:40:51 +01:00
Merge branch 'network-extensions-rewrite' of github.com:dorving/G-Earth into dorving-network-extensions-rewrite
This commit is contained in:
commit
df45404a36
@ -1,12 +1,14 @@
|
||||
package gearth.extensions;
|
||||
|
||||
import gearth.misc.HostInfo;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.connection.HClient;
|
||||
import gearth.services.Constants;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionInfo;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
@ -108,10 +110,10 @@ public abstract class Extension extends ExtensionBase {
|
||||
packet.fixLength();
|
||||
|
||||
|
||||
if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST) {
|
||||
if (packet.headerId() == Outgoing.InfoRequest.HEADER_ID) {
|
||||
ExtensionInfo info = getInfoAnnotations();
|
||||
|
||||
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO);
|
||||
HPacket response = new HPacket(Incoming.ExtensionInfo.HEADER_ID);
|
||||
response.appendString(info.Title())
|
||||
.appendString(info.Author())
|
||||
.appendString(info.Version())
|
||||
@ -124,7 +126,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
.appendBoolean(canDelete());
|
||||
writeToStream(response.toBytes());
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART) {
|
||||
else if (packet.headerId() == Outgoing.ConnectionStart.HEADER_ID) {
|
||||
String host = packet.readString();
|
||||
int connectionPort = packet.readInteger();
|
||||
String hotelVersion = packet.readString();
|
||||
@ -143,10 +145,10 @@ public abstract class Extension extends ExtensionBase {
|
||||
);
|
||||
onStartConnection();
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND) {
|
||||
else if (packet.headerId() == Outgoing.ConnectionEnd.HEADER_ID) {
|
||||
onEndConnection();
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.FLAGSCHECK) {
|
||||
else if (packet.headerId() == Outgoing.FlagsCheck.HEADER_ID) {
|
||||
// body = an array of G-Earths gearth flags
|
||||
if (flagRequestCallback != null) {
|
||||
int arraysize = packet.readInteger();
|
||||
@ -158,7 +160,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
}
|
||||
flagRequestCallback = null;
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT) {
|
||||
else if (packet.headerId() == Outgoing.Init.HEADER_ID) {
|
||||
delayed_init = packet.readBoolean();
|
||||
HostInfo hostInfo = HostInfo.fromPacket(packet);
|
||||
updateHostInfo(hostInfo);
|
||||
@ -167,21 +169,21 @@ public abstract class Extension extends ExtensionBase {
|
||||
}
|
||||
writeToConsole("green","Extension \"" + getInfoAnnotations().Title() + "\" successfully initialized", false);
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK) {
|
||||
else if (packet.headerId() == Outgoing.OnDoubleClick.HEADER_ID) {
|
||||
onClick();
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT) {
|
||||
else if (packet.headerId() == Outgoing.PacketIntercept.HEADER_ID) {
|
||||
String stringifiedMessage = packet.readLongString();
|
||||
HMessage habboMessage = new HMessage(stringifiedMessage);
|
||||
|
||||
modifyMessage(habboMessage);
|
||||
|
||||
HPacket response = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET);
|
||||
HPacket response = new HPacket(Incoming.ManipulatedPacket.MANIPULATED_PACKET);
|
||||
response.appendLongString(habboMessage.stringify());
|
||||
|
||||
writeToStream(response.toBytes());
|
||||
}
|
||||
else if (packet.headerId() == NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.UPDATEHOSTINFO) {
|
||||
else if (packet.headerId() == Outgoing.UpdateHostInfo.HEADER_ID) {
|
||||
HostInfo hostInfo = HostInfo.fromPacket(packet);
|
||||
updateHostInfo(hostInfo);
|
||||
}
|
||||
@ -231,7 +233,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
if (!packet.isPacketComplete()) packet.completePacket(packetInfoManager);
|
||||
if (!packet.isPacketComplete()) return false;
|
||||
|
||||
HPacket packet1 = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.SENDMESSAGE);
|
||||
HPacket packet1 = new HPacket(Incoming.SendMessage.HEADER_ID);
|
||||
packet1.appendByte(direction == HMessage.Direction.TOCLIENT ? (byte)0 : (byte)1);
|
||||
packet1.appendInt(packet.getBytesLength());
|
||||
packet1.appendBytes(packet.toBytes());
|
||||
@ -253,7 +255,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
if (this.flagRequestCallback != null) return false;
|
||||
this.flagRequestCallback = flagRequestCallback;
|
||||
try {
|
||||
writeToStream(new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS).toBytes());
|
||||
writeToStream(new HPacket(Incoming.RequestFlags.HEADER_ID).toBytes());
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@ -279,7 +281,7 @@ public abstract class Extension extends ExtensionBase {
|
||||
private void writeToConsole(String colorClass, String s, boolean mentionTitle) {
|
||||
String text = "[" + colorClass + "]" + (mentionTitle ? (getInfoAnnotations().Title() + " --> ") : "") + s;
|
||||
|
||||
HPacket packet = new HPacket(NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG);
|
||||
HPacket packet = new HPacket(Incoming.ExtensionConsoleLog.HEADER_ID);
|
||||
packet.appendString(text);
|
||||
try {
|
||||
writeToStream(packet.toBytes());
|
||||
|
@ -1,6 +1,6 @@
|
||||
package gearth.services.extension_handler.extensions.extensionproducers;
|
||||
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer;
|
||||
import gearth.services.extension_handler.extensions.implementations.simple.SimpleExtensionProducer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@ -9,13 +9,17 @@ import java.util.List;
|
||||
public class ExtensionProducerFactory {
|
||||
// returns one of every ExtensionProducer class we have created, to support all types of extensions
|
||||
|
||||
private final static NetworkExtensionServer EXTENSION_SERVER = new NetworkExtensionServer();
|
||||
|
||||
public static List<ExtensionProducer> getAll() {
|
||||
List<ExtensionProducer> all = new ArrayList<>();
|
||||
all.add(new NetworkExtensionsProducer());
|
||||
all.add(EXTENSION_SERVER);
|
||||
all.add(new SimpleExtensionProducer());
|
||||
|
||||
return all;
|
||||
}
|
||||
|
||||
|
||||
public static NetworkExtensionServer getExtensionServer() {
|
||||
return EXTENSION_SERVER;
|
||||
}
|
||||
}
|
||||
|
@ -1,258 +0,0 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.misc.HostInfo;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.connection.HClient;
|
||||
import gearth.services.extension_handler.extensions.ExtensionType;
|
||||
import gearth.services.extension_handler.extensions.GEarthExtension;
|
||||
import gearth.protocol.HPacket;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 21/06/18.
|
||||
*/
|
||||
public class NetworkExtension extends GEarthExtension {
|
||||
|
||||
private String title;
|
||||
private String author;
|
||||
private String version;
|
||||
private String description;
|
||||
|
||||
private boolean fireEventButtonVisible;
|
||||
private boolean leaveButtonVisible;
|
||||
private boolean deleteButtonVisible;
|
||||
|
||||
private boolean isInstalledExtension; // <- extension is in the extensions directory
|
||||
private String fileName;
|
||||
private String cookie;
|
||||
|
||||
private Socket connection;
|
||||
|
||||
NetworkExtension(HPacket extensionInfo, Socket connection) {
|
||||
this.title = extensionInfo.readString();
|
||||
this.author = extensionInfo.readString();
|
||||
this.version = extensionInfo.readString();
|
||||
this.description = extensionInfo.readString();
|
||||
this.fireEventButtonVisible = extensionInfo.readBoolean();
|
||||
|
||||
this.isInstalledExtension = extensionInfo.readBoolean();
|
||||
this.fileName = extensionInfo.readString();
|
||||
this.cookie = extensionInfo.readString();
|
||||
|
||||
this.leaveButtonVisible = extensionInfo.readBoolean();
|
||||
this.deleteButtonVisible = extensionInfo.readBoolean();
|
||||
|
||||
this.connection = connection;
|
||||
|
||||
NetworkExtension selff = this;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
InputStream inputStream = connection.getInputStream();
|
||||
DataInputStream dIn = new DataInputStream(inputStream);
|
||||
|
||||
while (!connection.isClosed()) {
|
||||
int length = dIn.readInt();
|
||||
byte[] headerandbody = new byte[length + 4];
|
||||
|
||||
int amountRead = 0;
|
||||
while (amountRead < length) {
|
||||
amountRead += dIn.read(headerandbody, 4 + amountRead, Math.min(dIn.available(), length - amountRead));
|
||||
}
|
||||
|
||||
HPacket message = new HPacket(headerandbody);
|
||||
message.fixLength();
|
||||
|
||||
synchronized (selff.extensionObservable) {
|
||||
if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.REQUESTFLAGS) {
|
||||
requestFlags();
|
||||
}
|
||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.SENDMESSAGE) {
|
||||
byte side = message.readByte();
|
||||
int byteLength = message.readInteger();
|
||||
byte[] packetAsByteArray = message.readBytes(byteLength);
|
||||
|
||||
HPacket packet = new HPacket(packetAsByteArray);
|
||||
if (!packet.isCorrupted()) {
|
||||
sendMessage(
|
||||
side == 0 ? HMessage.Direction.TOCLIENT : HMessage.Direction.TOSERVER,
|
||||
packet
|
||||
);
|
||||
}
|
||||
}
|
||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.MANIPULATEDPACKET) {
|
||||
String stringifiedresponse = message.readLongString(6);
|
||||
HMessage responseMessage = new HMessage(stringifiedresponse);
|
||||
sendManipulatedPacket(responseMessage);
|
||||
}
|
||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONCONSOLELOG) {
|
||||
log(message.readString());
|
||||
}
|
||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.PACKETTOSTRING_REQUEST) {
|
||||
HPacket p = new HPacket(new byte[0]);
|
||||
p.constructFromString(message.readLongString());
|
||||
packetToStringRequest(p);
|
||||
}
|
||||
else if (message.headerId() == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.STRINGTOPACKET_REQUEST) {
|
||||
stringToPacketRequest(message.readLongString(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// An extension disconnected, which is OK
|
||||
} finally {
|
||||
synchronized (selff.extensionObservable) {
|
||||
hasClosed();
|
||||
}
|
||||
if (!connection.isClosed()) {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException e) {
|
||||
// e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
public boolean isFireButtonUsed() {
|
||||
return fireEventButtonVisible;
|
||||
}
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
public String getCookie() {
|
||||
return cookie;
|
||||
}
|
||||
public boolean isDeleteButtonVisible() {
|
||||
return deleteButtonVisible;
|
||||
}
|
||||
public boolean isLeaveButtonVisible() {
|
||||
return leaveButtonVisible;
|
||||
}
|
||||
|
||||
public boolean isInstalledExtension() {
|
||||
return isInstalledExtension;
|
||||
}
|
||||
|
||||
|
||||
private boolean sendMessage(HPacket message) {
|
||||
try {
|
||||
synchronized (this) {
|
||||
connection.getOutputStream().write(message.toBytes());
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doubleclick() {
|
||||
sendMessage(new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.ONDOUBLECLICK));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetIntercept(HMessage hMessage) {
|
||||
String stringified = hMessage.stringify();
|
||||
HPacket manipulatePacketRequest = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETINTERCEPT);
|
||||
manipulatePacketRequest.appendLongString(stringified);
|
||||
sendMessage(manipulatePacketRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideFlags(String[] flags) {
|
||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.FLAGSCHECK);
|
||||
packet.appendInt(flags.length);
|
||||
for (String flag : flags) {
|
||||
packet.appendString(flag);
|
||||
}
|
||||
sendMessage(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionStart(String host, int port, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) {
|
||||
HPacket connectionStartPacket = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONSTART)
|
||||
.appendString(host)
|
||||
.appendInt(port)
|
||||
.appendString(hotelVersion)
|
||||
.appendString(clientIdentifier)
|
||||
.appendString(clientType.name());
|
||||
|
||||
packetInfoManager.appendToPacket(connectionStartPacket);
|
||||
sendMessage(connectionStartPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionEnd() {
|
||||
sendMessage(
|
||||
new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.CONNECTIONEND)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(boolean isConnected, HostInfo hostInfo) {
|
||||
HPacket initPacket = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INIT);
|
||||
initPacket.appendBoolean(isConnected);
|
||||
hostInfo.appendToPacket(initPacket);
|
||||
|
||||
sendMessage(initPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
connection.close();
|
||||
} catch (IOException ignored) { }
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHostInfo(HostInfo hostInfo) {
|
||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.UPDATEHOSTINFO);
|
||||
hostInfo.appendToPacket(packet);
|
||||
sendMessage(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetToStringResponse(String string, String expression) {
|
||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.PACKETTOSTRING_RESPONSE);
|
||||
packet.appendLongString(string);
|
||||
packet.appendLongString(expression, StandardCharsets.UTF_8);
|
||||
sendMessage(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stringToPacketResponse(HPacket packetFromString) {
|
||||
HPacket packet = new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.STRINGTOPACKET_RESPONSE);
|
||||
packet.appendLongString(packetFromString.stringify());
|
||||
sendMessage(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionType extensionType() {
|
||||
return ExtensionType.EXTERNAL;
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network.authentication;
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.misc.ConfirmationDialog;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtension;
|
||||
import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
@ -11,62 +10,59 @@ import javafx.scene.control.Label;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 16/10/18.
|
||||
*/
|
||||
public class Authenticator {
|
||||
public final class NetworkExtensionAuthenticator {
|
||||
|
||||
private static Map<String, String> cookies = new HashMap<>();
|
||||
private static Set<String> perma_cookies = new HashSet<>();
|
||||
private static final Map<String, String> COOKIES = new HashMap<>();
|
||||
private static final Set<String> PERSISTENT_COOKIES = new HashSet<>();
|
||||
|
||||
public static String generateCookieForExtension(String filename) {
|
||||
String cookie = getRandomCookie();
|
||||
cookies.put(filename, cookie);
|
||||
return cookie;
|
||||
}
|
||||
public static String generatePermanentCookie() {
|
||||
String cookie = getRandomCookie();
|
||||
perma_cookies.add(cookie);
|
||||
return cookie;
|
||||
}
|
||||
private static volatile boolean rememberOption = false;
|
||||
|
||||
public static boolean evaluate(NetworkExtension extension) {
|
||||
if (extension.getCookie() != null && perma_cookies.contains(extension.getCookie())) {
|
||||
public static boolean evaluate(NetworkExtensionClient extension) {
|
||||
|
||||
final String cookie = extension.getCookie();
|
||||
|
||||
if (cookie != null && PERSISTENT_COOKIES.contains(cookie))
|
||||
return true;
|
||||
}
|
||||
|
||||
if (extension.isInstalledExtension()) {
|
||||
return claimSession(extension.getFileName(), extension.getCookie());
|
||||
}
|
||||
else {
|
||||
return askForPermission(extension);
|
||||
}
|
||||
return extension.isInstalledExtension()
|
||||
? claimSession(extension.getFileName(), cookie)
|
||||
: askForPermission(extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* authenticator: authenticate an extension and remove the cookie
|
||||
* @param filename
|
||||
* @param cookie
|
||||
* @return if the extension is authenticated
|
||||
* Authenticate an extension and remove the cookie
|
||||
*
|
||||
* @return {@code true} if the extension is authenticated.
|
||||
*/
|
||||
private static boolean claimSession(String filename, String cookie) {
|
||||
if (cookies.containsKey(filename) && cookies.get(filename).equals(cookie)) {
|
||||
cookies.remove(filename);
|
||||
if (COOKIES.containsKey(filename) && COOKIES.get(filename).equals(cookie)) {
|
||||
COOKIES.remove(filename);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static volatile boolean rememberOption = false;
|
||||
//for not-installed extensions, popup a dialog
|
||||
private static boolean askForPermission(NetworkExtension extension) {
|
||||
/**
|
||||
* For not yet installed extensions, open a confirmation dialog.
|
||||
*
|
||||
* @param extension the {@link NetworkExtensionClient extension} to ask permission for.
|
||||
*
|
||||
* @return {@code true} if permission is granted, {@code false} if not.
|
||||
*/
|
||||
private static boolean askForPermission(NetworkExtensionClient extension) {
|
||||
boolean[] allowConnection = {true};
|
||||
|
||||
final String connectExtensionKey = "allow_extension_connection";
|
||||
|
||||
if (ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||
boolean[] done = {false};
|
||||
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(0);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
Alert alert = ConfirmationDialog.createAlertWithOptOut(Alert.AlertType.WARNING, connectExtensionKey
|
||||
, LanguageBundle.get("alert.confirmation.windowtitle"), null,
|
||||
@ -84,33 +80,40 @@ public class Authenticator {
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
done[0] = true;
|
||||
countDownLatch.countDown();
|
||||
if (!ConfirmationDialog.showDialog(connectExtensionKey)) {
|
||||
rememberOption = allowConnection[0];
|
||||
}
|
||||
});
|
||||
|
||||
while (!done[0]) {
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
countDownLatch.await();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
return allowConnection[0];
|
||||
}
|
||||
|
||||
return rememberOption;
|
||||
}
|
||||
|
||||
private static String getRandomCookie() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < 40; i++) {
|
||||
builder.append(r.nextInt(40));
|
||||
public static String generateCookieForExtension(String filename) {
|
||||
final String cookie = generateRandomCookie();
|
||||
COOKIES.put(filename, cookie);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public static String generatePermanentCookie() {
|
||||
final String cookie = generateRandomCookie();
|
||||
PERSISTENT_COOKIES.add(cookie);
|
||||
return cookie;
|
||||
}
|
||||
|
||||
private static String generateRandomCookie() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final Random r = new Random();
|
||||
for (int i = 0; i < 40; i++)
|
||||
builder.append(r.nextInt(40));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.misc.HostInfo;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.connection.HClient;
|
||||
import gearth.services.extension_handler.extensions.ExtensionType;
|
||||
import gearth.services.extension_handler.extensions.GEarthExtension;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
import io.netty.channel.Channel;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A client for managing remote extensions.
|
||||
*
|
||||
* @author Dorving
|
||||
*/
|
||||
public final class NetworkExtensionClient extends GEarthExtension {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(NetworkExtensionClient.class);
|
||||
|
||||
private final Channel channel;
|
||||
|
||||
private final String title;
|
||||
private final String author;
|
||||
private final String version;
|
||||
private final String description;
|
||||
private final String fileName;
|
||||
private final String cookie;
|
||||
private final boolean fireEventButtonVisible;
|
||||
private final boolean leaveButtonVisible;
|
||||
private final boolean deleteButtonVisible;
|
||||
private final boolean isInstalledExtension;
|
||||
|
||||
/**
|
||||
* Create a new {@link NetworkExtensionClient} instance.
|
||||
*
|
||||
* @param channel the channel through which to communicate with the remote extension.
|
||||
* @param info the {@link Incoming.ExtensionInfo} detailing the extension.
|
||||
*/
|
||||
public NetworkExtensionClient(Channel channel, Incoming.ExtensionInfo info) {
|
||||
this.channel = channel;
|
||||
title = info.getTitle();
|
||||
author = info.getAuthor();
|
||||
version = info.getVersion();
|
||||
description = info.getDescription();
|
||||
fireEventButtonVisible = info.isOnClickUsed();
|
||||
leaveButtonVisible = info.isCanLeave();
|
||||
deleteButtonVisible = info.isCanDelete();
|
||||
isInstalledExtension = info.getFile() != null;
|
||||
fileName = info.getFile();
|
||||
cookie = info.getCookie();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles {@link Incoming incoming messages}.
|
||||
*
|
||||
* @param incoming the {@link Incoming message} to be handled.
|
||||
*/
|
||||
public void handleIncomingMessage(Incoming incoming) {
|
||||
try {
|
||||
if (incoming instanceof Incoming.RequestFlags)
|
||||
requestFlags();
|
||||
else if (incoming instanceof Incoming.SendMessage) {
|
||||
final Incoming.SendMessage message = ((Incoming.SendMessage) incoming);
|
||||
final HPacket packet = message.getPacket();
|
||||
if (!packet.isCorrupted())
|
||||
sendMessage(message.getDirection(), packet);
|
||||
} else if (incoming instanceof Incoming.ManipulatedPacket) {
|
||||
sendManipulatedPacket(((Incoming.ManipulatedPacket) incoming).gethMessage());
|
||||
} else if (incoming instanceof Incoming.ExtensionConsoleLog) {
|
||||
log(((Incoming.ExtensionConsoleLog) incoming).getContents());
|
||||
} else if (incoming instanceof Incoming.PacketToStringRequest) {
|
||||
final HPacket hPacket = new HPacket(new byte[0]);
|
||||
hPacket.constructFromString(((Incoming.PacketToStringRequest) incoming).getString());
|
||||
packetToStringRequest(hPacket);
|
||||
} else if (incoming instanceof Incoming.StringToPacketRequest) {
|
||||
stringToPacketRequest(((Incoming.StringToPacketRequest) incoming).getString());
|
||||
}
|
||||
} catch (Exception e){
|
||||
LOGGER.error("Failed to handle incoming message {} (channel={})", incoming, channel, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(boolean isConnected, HostInfo hostInfo) {
|
||||
channel.writeAndFlush(new Outgoing.Init(isConnected, hostInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
try {
|
||||
channel.close();
|
||||
} catch (Exception e){
|
||||
LOGGER.error("Failed to close client (channel={})", channel, e);
|
||||
} finally {
|
||||
hasClosed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionStart(String host, int port, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) {
|
||||
channel.writeAndFlush(new Outgoing.ConnectionStart(
|
||||
host,
|
||||
port,
|
||||
hotelVersion,
|
||||
clientIdentifier,
|
||||
clientType,
|
||||
packetInfoManager
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionEnd() {
|
||||
channel.writeAndFlush(new Outgoing.ConnectionEnd());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doubleclick() {
|
||||
channel.writeAndFlush(new Outgoing.OnDoubleClick());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provideFlags(String[] flags) {
|
||||
channel.writeAndFlush(new Outgoing.FlagsCheck(Arrays.asList(flags)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHostInfo(HostInfo hostInfo) {
|
||||
channel.writeAndFlush(new Outgoing.UpdateHostInfo(hostInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetIntercept(HMessage hMessage) {
|
||||
final String messageAsString = hMessage.stringify();
|
||||
channel.writeAndFlush(new Outgoing.PacketIntercept(messageAsString));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packetToStringResponse(String string, String expression) {
|
||||
channel.writeAndFlush(new Outgoing.PacketToStringResponse(string, expression));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stringToPacketResponse(HPacket packet) {
|
||||
channel.writeAndFlush(new Outgoing.StringToPacketResponse(packet.stringify()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionType extensionType() {
|
||||
return ExtensionType.EXTERNAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getCookie() {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFireButtonUsed() {
|
||||
return fireEventButtonVisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeleteButtonVisible() {
|
||||
return deleteButtonVisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLeaveButtonVisible() {
|
||||
return leaveButtonVisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstalledExtension() {
|
||||
return isInstalledExtension;
|
||||
}
|
||||
}
|
@ -0,0 +1,307 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.misc.HostInfo;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.connection.HClient;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Outgoing;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.Incoming;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static gearth.protocol.HMessage.Direction.TOCLIENT;
|
||||
import static gearth.protocol.HMessage.Direction.TOSERVER;
|
||||
|
||||
/**
|
||||
* THE EXTENSION COMMUNICATION PRINCIPLES & PROTOCOL:
|
||||
*
|
||||
* You will be able to write extensions in ANY language you want, but we will only provide an interface
|
||||
* for Java so if you write your own in for example Python, make SURE you do it correctly or it could fuck G-Earth.
|
||||
*
|
||||
* Also, don't let the method where you manipulate the packets block. Similiar as how you must not block things in an UI thread.
|
||||
* Why? Because Habbo relies on the TCP protocol, which ENSURES that packets get received in the right order, so we will not be fucking that up.
|
||||
* That means that all packets following the packet you're manipulating in your extension will be blocked from being sent untill you're done.
|
||||
* TIP: If you're trying to replace a packet in your extension but you know it will take time, just block the packet, end the method, and let something asynchronous send
|
||||
* the edited packet when you're done.
|
||||
*
|
||||
*
|
||||
* You may ignore everything beneath this line if you're extending the abstract Extension class we provide in Java.
|
||||
* -----------------------------------------------------------------------------------------------------------------
|
||||
*
|
||||
* (0. We recommend to use a cross-platform language for your extension)
|
||||
*
|
||||
* 1. An extension will run as a seperate process on your device and has to be called with the flag "-p <PORT>",
|
||||
* where <PORT> is a random port where the G-Earth local extension server will run on. Your extension has to connect with this server.
|
||||
*
|
||||
* 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the exension.
|
||||
* Same story goes for closing the connection between the program and G-Earth, only once (on uninstall or close of G-Earth).
|
||||
*
|
||||
* You may also run your extension completely seperate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth
|
||||
* (but you have to configure the port yourself, which will be displayed in the extension page)
|
||||
*
|
||||
* 3. Once a connection is made, your extension will have to deal with the following incoming & outgoing messages as described (follows the same protocol structure as Habbo communication does):
|
||||
* (if an object is sent; the object will be sent with its String representation from the StringifyAble interface, so the object's class must implement that)
|
||||
*
|
||||
* INCOMING MESSAGES: (marked with * if you're required to correctly respond or take action, ** if it's a response on something you requested)
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | ID | TITLE | BODY & DESCRIPTION |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 1 | ON-DOUBLECLICK | No body, the extension has been double clicked from within G-Earth | ( <- typically for tanji-module-like extensions you will open the UI here)
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 2 | INFO-REQUEST* | Needs response with extension info (name, desc, author, version, ..), |
|
||||
* | | | exact implementation is found in the Java abstract Extension class |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 3 | PACKET-INTERCEPT* | Includes the whole HMessage as body, needs response with the |
|
||||
* | | | manipulated HMessage (OUTGOING id: 2) |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 4 | FLAGS-CHECK** | Body: String with G-Earth's boot flags (args from static gearth method) |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 5 | CONNECTION START | just a note that a new connection has been made, |
|
||||
* | | | you could check this yourself as well (listen to out:4000 packet) |
|
||||
* | | | host/port, hotel version |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 6 | CONNECTION END | Empty body, just a note that a connection has ended |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 7 | INIT | Empty body, a connection with G-Earth has been set up |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 99 | FREE FLOW | extension-specific body |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
*
|
||||
* OUTGOING MESSAGES: (marked with * if that is a response to one of the msgs above)
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | ID | TITLE | BODY & DESCRIPTION |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 1 | EXTENSION-INFO* | Response for INFO-REQUEST |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 2 | MANIPULATED-PACKET*| Response for PACKET-INTERCEPT |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 3 | REQUEST-FLAGS | Request G-Earth's flags, results in incoming FLAGS-CHECK response |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 4 | SEND-MESSAGE | Body: HMessage object. Sends the HPacket wrapped in the HMessage |
|
||||
* | | | to the client/server |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 99 | FREE FLOW | extension-specific body |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
*
|
||||
* 4. Your extension will only appear in the extension list once the EXTENSION-INFO has been received by G-Earth
|
||||
*/
|
||||
public final class NetworkExtensionCodec {
|
||||
|
||||
private final static Map<Class<?>, PacketStructure> outgoingPacketStructures = new HashMap<>();
|
||||
private final static Map<Integer, PacketStructure> incomingPacketStructures = new HashMap<>();
|
||||
|
||||
public static PacketStructure getIncomingStructure(int headerId) {
|
||||
return incomingPacketStructures.get(headerId);
|
||||
}
|
||||
|
||||
public static<T extends NetworkExtensionMessage> PacketStructure getOutgoingStructure(T message) {
|
||||
return outgoingPacketStructures.get(message.getClass());
|
||||
}
|
||||
|
||||
static {
|
||||
registerOutgoingMessages();
|
||||
registerIncomingMessages();
|
||||
}
|
||||
|
||||
private static void registerIncomingMessages() {
|
||||
register(Incoming.ExtensionInfo.HEADER_ID,
|
||||
Incoming.ExtensionInfo.class,
|
||||
(message, hPacket) -> {
|
||||
hPacket.appendString(message.getTitle());
|
||||
hPacket.appendString(message.getAuthor());
|
||||
hPacket.appendString(message.getVersion());
|
||||
hPacket.appendString(message.getDescription());
|
||||
hPacket.appendBoolean(message.isOnClickUsed());
|
||||
hPacket.appendBoolean(message.getFile() != null);
|
||||
hPacket.appendString(Optional.ofNullable(message.getFile()).orElse(""));
|
||||
hPacket.appendString(Optional.ofNullable(message.getCookie()).orElse(""));
|
||||
hPacket.appendBoolean(message.isCanLeave());
|
||||
hPacket.appendBoolean(message.isCanDelete());
|
||||
},
|
||||
(hPacket -> {
|
||||
final String title = hPacket.readString();
|
||||
final String author = hPacket.readString();
|
||||
final String version = hPacket.readString();
|
||||
final String description = hPacket.readString();
|
||||
final boolean isOnClickUsed = hPacket.readBoolean();
|
||||
final boolean hasFile = hPacket.readBoolean();
|
||||
String file = hPacket.readString();
|
||||
if (!hasFile)
|
||||
file = null;
|
||||
String cookie = hPacket.readString();
|
||||
if (cookie.isEmpty())
|
||||
cookie = null;
|
||||
final boolean canLeave = hPacket.readBoolean();
|
||||
final boolean canDelete = hPacket.readBoolean();
|
||||
return new Incoming.ExtensionInfo(title, author, version, description, isOnClickUsed, file, cookie, canLeave, canDelete);
|
||||
}));
|
||||
register(Incoming.ManipulatedPacket.MANIPULATED_PACKET,
|
||||
Incoming.ManipulatedPacket.class,
|
||||
(message, hPacket) -> hPacket.appendLongString(message.gethMessage().stringify()),
|
||||
(hPacket -> {
|
||||
final String packetString = hPacket.readLongString(6);
|
||||
final HMessage hMessage = new HMessage(packetString);
|
||||
return new Incoming.ManipulatedPacket(hMessage);
|
||||
}));
|
||||
register(Incoming.SendMessage.HEADER_ID,
|
||||
Incoming.SendMessage.class,
|
||||
((message, hPacket) -> {
|
||||
hPacket.appendByte((byte) (message.getDirection() == TOCLIENT ? 0 : 1));
|
||||
hPacket.appendInt(message.getPacket().getBytesLength());
|
||||
hPacket.appendBytes(message.getPacket().toBytes());
|
||||
}),
|
||||
(hPacket -> {
|
||||
final byte side = hPacket.readByte();
|
||||
final int length = hPacket.readInteger();
|
||||
final byte[] data = hPacket.readBytes(length);
|
||||
final HPacket packet = new HPacket(data);
|
||||
return new Incoming.SendMessage(packet, side == 0 ? TOCLIENT : TOSERVER);
|
||||
}));
|
||||
register(Incoming.RequestFlags.HEADER_ID,
|
||||
Incoming.RequestFlags.class,
|
||||
(message, hPacket) -> {
|
||||
},
|
||||
(hPacket -> new Incoming.RequestFlags()));
|
||||
register(Incoming.ExtensionConsoleLog.HEADER_ID,
|
||||
Incoming.ExtensionConsoleLog.class,
|
||||
(message, hPacket) -> hPacket.appendString(message.getContents()),
|
||||
(hPacket -> new Incoming.ExtensionConsoleLog(hPacket.readString())));
|
||||
register(Incoming.PacketToStringRequest.HEADER_ID,
|
||||
Incoming.PacketToStringRequest.class,
|
||||
(message, hPacket) -> hPacket.appendLongString(message.getString()),
|
||||
(hPacket -> new Incoming.PacketToStringRequest(hPacket.readLongString())));
|
||||
register(Incoming.StringToPacketRequest.HEADER_ID,
|
||||
Incoming.StringToPacketRequest.class,
|
||||
(message, hPacket) -> hPacket.appendLongString(message.getString(), StandardCharsets.UTF_8),
|
||||
(hPacket -> new Incoming.StringToPacketRequest(hPacket.readLongString(StandardCharsets.UTF_8))));
|
||||
}
|
||||
|
||||
private static void registerOutgoingMessages() {
|
||||
register(Outgoing.InfoRequest.HEADER_ID,
|
||||
Outgoing.InfoRequest.class,
|
||||
(message, hPacket) -> {
|
||||
},
|
||||
(hPacket -> new Outgoing.InfoRequest()));
|
||||
register(Outgoing.ConnectionStart.HEADER_ID,
|
||||
Outgoing.ConnectionStart.class,
|
||||
(message, hPacket) -> {
|
||||
hPacket.appendString(message.getHost());
|
||||
hPacket.appendInt(message.getConnectionPort());
|
||||
hPacket.appendString(message.getHotelVersion());
|
||||
hPacket.appendString(message.getClientIdentifier());
|
||||
hPacket.appendString(message.getClientType().name());
|
||||
message.getPacketInfoManager().appendToPacket(hPacket);
|
||||
},
|
||||
(hPacket -> new Outgoing.ConnectionStart(
|
||||
hPacket.readString(),
|
||||
hPacket.readInteger(),
|
||||
hPacket.readString(),
|
||||
hPacket.readString(),
|
||||
HClient.valueOf(hPacket.readString()),
|
||||
PacketInfoManager.readFromPacket(hPacket)
|
||||
)));
|
||||
register(Outgoing.ConnectionEnd.HEADER_ID,
|
||||
Outgoing.ConnectionEnd.class,
|
||||
(message, hPacket) -> {
|
||||
},
|
||||
(hPacket -> new Outgoing.ConnectionEnd()));
|
||||
register(Outgoing.FlagsCheck.HEADER_ID,
|
||||
Outgoing.FlagsCheck.class,
|
||||
(message, hPacket) -> {
|
||||
hPacket.appendInt(message.getFlags().size());
|
||||
message.getFlags().forEach(hPacket::appendString);
|
||||
},
|
||||
(hPacket -> {
|
||||
final int size = hPacket.readInteger();
|
||||
final List<String> flags = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++)
|
||||
flags.add(hPacket.readString());
|
||||
return new Outgoing.FlagsCheck(flags);
|
||||
}));
|
||||
register(Outgoing.Init.HEADER_ID,
|
||||
Outgoing.Init.class,
|
||||
(message, hPacket) -> {
|
||||
hPacket.appendBoolean(message.isDelayInit());
|
||||
message.getHostInfo().appendToPacket(hPacket);
|
||||
},
|
||||
(hPacket -> new Outgoing.Init(hPacket.readBoolean(), HostInfo.fromPacket(hPacket))));
|
||||
register(Outgoing.OnDoubleClick.HEADER_ID,
|
||||
Outgoing.OnDoubleClick.class,
|
||||
(message, hPacket) -> {
|
||||
},
|
||||
(hPacket -> new Outgoing.OnDoubleClick()));
|
||||
register(Outgoing.PacketIntercept.HEADER_ID,
|
||||
Outgoing.PacketIntercept.class,
|
||||
(message, hPacket) -> hPacket.appendLongString(message.getPacketString()),
|
||||
(hPacket -> new Outgoing.PacketIntercept(hPacket.readLongString())));
|
||||
register(Outgoing.UpdateHostInfo.HEADER_ID,
|
||||
Outgoing.UpdateHostInfo.class,
|
||||
(message, hPacket) -> message.getHostInfo().appendToPacket(hPacket),
|
||||
(hPacket -> new Outgoing.UpdateHostInfo(HostInfo.fromPacket(hPacket))));
|
||||
register(Outgoing.PacketToStringResponse.HEADER_ID,
|
||||
Outgoing.PacketToStringResponse.class,
|
||||
(message, hPacket) -> {
|
||||
hPacket.appendLongString(message.getString());
|
||||
hPacket.appendLongString(message.getExpression(), StandardCharsets.UTF_8);
|
||||
},
|
||||
(hPacket -> new Outgoing.PacketToStringResponse(hPacket.readLongString(), hPacket.readLongString(StandardCharsets.UTF_8)))
|
||||
);
|
||||
register(Outgoing.StringToPacketResponse.HEADER_ID,
|
||||
Outgoing.StringToPacketResponse.class,
|
||||
(message, hPacket) -> hPacket.appendLongString(message.getString()),
|
||||
(hPacket -> new Outgoing.StringToPacketResponse(hPacket.readLongString()))
|
||||
);
|
||||
}
|
||||
|
||||
private static <T extends NetworkExtensionMessage> void register(final int headerId, Class<T> tClass, BiConsumer<T, HPacket> writer, Function<HPacket, T> reader) {
|
||||
final PacketStructure packetStructure = new PacketStructure(headerId, tClass.getSimpleName(), writer, reader);
|
||||
if (tClass.getSuperclass() == Outgoing.class)
|
||||
outgoingPacketStructures.put(tClass, packetStructure);
|
||||
else
|
||||
incomingPacketStructures.put(headerId, packetStructure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the packet structure of a {@link NetworkExtensionMessage}.
|
||||
*
|
||||
* Can be used to {@link PacketStructure#writer write} messages to packets
|
||||
* and {@link PacketStructure#reader read} messages from packets.
|
||||
*
|
||||
* @apiNote At the moment both outgoing and incoming messages have a reader and writer defined,
|
||||
* this is so that in the future the same codec can be used for the Extensions API.
|
||||
*/
|
||||
static class PacketStructure {
|
||||
|
||||
private final int headerId;
|
||||
private final String name;
|
||||
private final BiConsumer<? extends NetworkExtensionMessage, HPacket> writer;
|
||||
private final Function<HPacket, ? extends NetworkExtensionMessage> reader;
|
||||
|
||||
public PacketStructure(int headerId, String name, BiConsumer<? extends NetworkExtensionMessage, HPacket> writer, Function<HPacket, ? extends NetworkExtensionMessage> reader) {
|
||||
this.headerId = headerId;
|
||||
this.name = name;
|
||||
this.writer = writer;
|
||||
this.reader = reader;
|
||||
}
|
||||
|
||||
public int getHeaderId() {
|
||||
return headerId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public BiConsumer<? extends NetworkExtensionMessage, HPacket> getWriter() {
|
||||
return writer;
|
||||
}
|
||||
|
||||
public Function<HPacket, ? extends NetworkExtensionMessage> getReader() {
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
public class NetworkExtensionInfo {
|
||||
|
||||
/**
|
||||
* THE EXTENSION COMMUNCATION PRINCIPLES & PROTOCOL:
|
||||
*
|
||||
* You will be able to write extensions in ANY language you want, but we will only provide an interface
|
||||
* for Java so if you write your own in for example Python, make SURE you do it correctly or it could fuck G-Earth.
|
||||
*
|
||||
* Also, don't let the method where you manipulate the packets block. Similiar as how you must not block things in an UI thread.
|
||||
* Why? Because Habbo relies on the TCP protocol, which ENSURES that packets get received in the right order, so we will not be fucking that up.
|
||||
* That means that all packets following the packet you're manipulating in your extension will be blocked from being sent untill you're done.
|
||||
* TIP: If you're trying to replace a packet in your extension but you know it will take time, just block the packet, end the method, and let something asynchronous send
|
||||
* the editted packet when you're done.
|
||||
*
|
||||
*
|
||||
* You may ignore everything beneath this line if you're extending the abstract Extension class we provide in Java.
|
||||
* -----------------------------------------------------------------------------------------------------------------
|
||||
*
|
||||
* (0. We recommend to use a cross-platform language for your extension)
|
||||
*
|
||||
* 1. An extension will run as a seperate process on your device and has to be called with the flag "-p <PORT>",
|
||||
* where <PORT> is a random port where the G-Earth local extension server will run on. Your extension has to connect with this server.
|
||||
*
|
||||
* 2. G-Earth will open your program only ONCE, that is on the boot of G-Earth or when you install the exension.
|
||||
* Same story goes for closing the connection between the program and G-Earth, only once (on uninstall or close of G-Earth).
|
||||
*
|
||||
* You may also run your extension completely seperate from G-Earth for debugging purpose for example, then it won't be installed in G-Earth
|
||||
* (but you have to configure the port yourself, which will be displayed in the extension page)
|
||||
*
|
||||
* 3. Once a connection is made, your extension will have to deal with the following incoming & outgoing messages as described (follows the same protocol structure as Habbo communication does):
|
||||
* (if an object is sent; the object will be sent with its String representation from the StringifyAble interface, so the object's class must implement that)
|
||||
*
|
||||
* INCOMING MESSAGES: (marked with * if you're required to correctly respond or take action, ** if it's a response on something you requested)
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | ID | TITLE | BODY & DESCRIPTION |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 1 | ON-DOUBLECLICK | No body, the extension has been double clicked from within G-Earth | ( <- typically for tanji-module-like extensions you will open the UI here)
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 2 | INFO-REQUEST* | Needs response with extension info (name, desc, author, version, ..), |
|
||||
* | | | exact implementation is found in the Java abstract Extension class |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 3 | PACKET-INTERCEPT* | Includes the whole HMessage as body, needs response with the |
|
||||
* | | | manipulated HMessage (OUTGOING id: 2) |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 4 | FLAGS-CHECK** | Body: String with G-Earth's boot flags (args from static gearth method) |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 5 | CONNECTION START | just a note that a new connection has been made, |
|
||||
* | | | you could check this yourself as well (listen to out:4000 packet) |
|
||||
* | | | host/port, hotel version |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 6 | CONNECTION END | Empty body, just a note that a connection has ended |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 7 | INIT | Empty body, a connection with G-Earth has been set up |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 99 | FREE FLOW | extension-specific body |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
*
|
||||
* OUTGOING MESSAGES: (marked with * if that is a response to one of the msgs above)
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | ID | TITLE | BODY & DESCRIPTION |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 1 | EXTENSION-INFO* | Response for INFO-REQUEST |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 2 | MANIPULATED-PACKET*| Response for PACKET-INTERCEPT |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 3 | REQUEST-FLAGS | Request G-Earth's flags, results in incoming FLAGS-CHECK response |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 4 | SEND-MESSAGE | Body: HMessage object. Sends the HPacket wrapped in the HMessage |
|
||||
* | | | to the client/server |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
* | 99 | FREE FLOW | extension-specific body |
|
||||
* -----------------------------------------------------------------------------------------------------
|
||||
*
|
||||
* 4. Your extension will only appear in the extension list once the EXTENSION-INFO has been received by G-Earth
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
public static class OUTGOING_MESSAGES_IDS {
|
||||
public static final int ONDOUBLECLICK = 1;
|
||||
public static final int INFOREQUEST = 2;
|
||||
public static final int PACKETINTERCEPT = 3;
|
||||
public static final int FLAGSCHECK = 4;
|
||||
public static final int CONNECTIONSTART = 5;
|
||||
public static final int CONNECTIONEND = 6;
|
||||
public static final int INIT = 7;
|
||||
|
||||
public static final int UPDATEHOSTINFO = 10;
|
||||
|
||||
public static final int PACKETTOSTRING_RESPONSE = 20;
|
||||
public static final int STRINGTOPACKET_RESPONSE = 21;
|
||||
}
|
||||
|
||||
|
||||
public static class INCOMING_MESSAGES_IDS {
|
||||
public static final int EXTENSIONINFO = 1;
|
||||
public static final int MANIPULATEDPACKET = 2;
|
||||
public static final int REQUESTFLAGS = 3;
|
||||
public static final int SENDMESSAGE = 4;
|
||||
|
||||
public static final int PACKETTOSTRING_REQUEST = 20;
|
||||
public static final int STRINGTOPACKET_REQUEST = 21;
|
||||
|
||||
public static final int EXTENSIONCONSOLELOG = 98;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,417 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.misc.HostInfo;
|
||||
import gearth.protocol.HMessage;
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.protocol.connection.HClient;
|
||||
import gearth.services.packet_info.PacketInfoManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a message send or received by G-Earth and a remote extension.
|
||||
*
|
||||
* @see NetworkExtensionCodec the encoding/decoding structures
|
||||
* @see Incoming messages coming from the remote extension to G-Earth
|
||||
* @see Outgoing messages coming from G-Earth to the remote extension
|
||||
*
|
||||
* @author Dorving, Jonas
|
||||
*/
|
||||
public class NetworkExtensionMessage {
|
||||
|
||||
/**
|
||||
* Represents {@link NetworkExtensionMessage messages} coming from the remote extension to G-Earth.
|
||||
*/
|
||||
public static class Incoming extends NetworkExtensionMessage {
|
||||
|
||||
/**
|
||||
* This contains info about the remote extension trying to connect.
|
||||
*
|
||||
* Once this {@link NetworkExtensionMessage message} is received,
|
||||
* a new {@link NetworkExtensionClient} is created to handle the communication.
|
||||
*
|
||||
* @see Outgoing.InfoRequest the request.
|
||||
*/
|
||||
public static class ExtensionInfo extends Incoming {
|
||||
|
||||
public static final int HEADER_ID = 1;
|
||||
|
||||
private final String title;
|
||||
private final String author;
|
||||
private final String version;
|
||||
private final String description;
|
||||
private final boolean onClickUsed;
|
||||
private final String file;
|
||||
private final String cookie;
|
||||
private final boolean canLeave;
|
||||
private final boolean canDelete;
|
||||
|
||||
public ExtensionInfo(String title, String author, String version, String description, boolean onClickUsed, String file, String cookie, boolean canLeave, boolean canDelete) {
|
||||
this.title = title;
|
||||
this.author = author;
|
||||
this.version = version;
|
||||
this.description = description;
|
||||
this.onClickUsed = onClickUsed;
|
||||
this.file = file;
|
||||
this.cookie = cookie;
|
||||
this.canLeave = canLeave;
|
||||
this.canDelete = canDelete;
|
||||
}
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean isOnClickUsed() {
|
||||
return onClickUsed;
|
||||
}
|
||||
|
||||
public String getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public String getCookie() {
|
||||
return cookie;
|
||||
}
|
||||
|
||||
public boolean isCanLeave() {
|
||||
return canLeave;
|
||||
}
|
||||
|
||||
public boolean isCanDelete() {
|
||||
return canDelete;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote extension request G-Earth's flags.
|
||||
*
|
||||
* @see Outgoing.FlagsCheck the response.
|
||||
*/
|
||||
public static class RequestFlags extends Incoming {
|
||||
public static final int HEADER_ID = 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Received a {@link HPacket} from the remote connection
|
||||
* and forward it either to the game {@link HMessage.Direction#TOSERVER server}
|
||||
* or game {@link HMessage.Direction#TOCLIENT client}.
|
||||
*/
|
||||
public static class SendMessage extends Incoming {
|
||||
|
||||
public static final int HEADER_ID = 4;
|
||||
|
||||
private final HPacket packet;
|
||||
private final HMessage.Direction direction;
|
||||
|
||||
public SendMessage(HPacket packet, HMessage.Direction direction) {
|
||||
this.packet = packet;
|
||||
this.direction = direction;
|
||||
}
|
||||
|
||||
public HPacket getPacket() {
|
||||
return packet;
|
||||
}
|
||||
|
||||
public HMessage.Direction getDirection() {
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add documentation.
|
||||
*
|
||||
* @see Outgoing.PacketToStringResponse the response.
|
||||
*/
|
||||
public static class PacketToStringRequest extends Incoming {
|
||||
|
||||
public static final int HEADER_ID = 20;
|
||||
|
||||
private final String string;
|
||||
|
||||
public PacketToStringRequest(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add documentation.
|
||||
*
|
||||
* @see Outgoing.StringToPacketResponse the response.
|
||||
*/
|
||||
public static class StringToPacketRequest extends Incoming {
|
||||
|
||||
public static final int HEADER_ID = 21;
|
||||
|
||||
private final String string;
|
||||
|
||||
public StringToPacketRequest(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add documentation.
|
||||
*/
|
||||
public static class ExtensionConsoleLog extends Incoming {
|
||||
|
||||
public static final int HEADER_ID = 98;
|
||||
|
||||
private final String contents;
|
||||
|
||||
public ExtensionConsoleLog(String contents) {
|
||||
this.contents = contents;
|
||||
}
|
||||
|
||||
public String getContents() {
|
||||
return contents;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a packet modified by the remote extension.
|
||||
*
|
||||
* @see Outgoing.PacketIntercept the ougoing message containing the original packet.
|
||||
*/
|
||||
public static class ManipulatedPacket extends Incoming {
|
||||
public static final int MANIPULATED_PACKET = 2;
|
||||
private final HMessage hMessage;
|
||||
|
||||
public ManipulatedPacket(HMessage hMessage) {
|
||||
this.hMessage = hMessage;
|
||||
}
|
||||
|
||||
public HMessage gethMessage() {
|
||||
return hMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Represents {@link NetworkExtensionMessage messages} coming from G-Earth to the remote extension.
|
||||
*/
|
||||
public static class Outgoing extends NetworkExtensionMessage{
|
||||
|
||||
/**
|
||||
* The extension has been double-clicked from within G-Earth.
|
||||
*/
|
||||
public static class OnDoubleClick extends Outgoing {
|
||||
public static final int HEADER_ID = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request for remote extension to send {@link Incoming.ExtensionInfo}.
|
||||
*
|
||||
* This is the very first message send after a connection is established.
|
||||
*
|
||||
* @see Incoming.ExtensionInfo the response.
|
||||
*/
|
||||
public static class InfoRequest extends Outgoing {
|
||||
public static final int HEADER_ID = 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards a packet intercepted by G-Earth to the remote extension.
|
||||
*
|
||||
* @see Incoming.ManipulatedPacket the response.
|
||||
*/
|
||||
public static class PacketIntercept extends Outgoing {
|
||||
|
||||
public static final int HEADER_ID = 3;
|
||||
|
||||
private final String packetString;
|
||||
|
||||
public PacketIntercept(String packetString) {
|
||||
this.packetString = packetString;
|
||||
}
|
||||
|
||||
public String getPacketString() {
|
||||
return packetString;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains program arguments of G-Earth.
|
||||
*
|
||||
* @see Incoming.RequestFlags the request.
|
||||
*/
|
||||
public static class FlagsCheck extends Outgoing {
|
||||
|
||||
public static final int HEADER_ID = 4;
|
||||
|
||||
private final List<String> flags;
|
||||
|
||||
public FlagsCheck(List<String> flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public List<String> getFlags() {
|
||||
return flags;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies remote extension that a connection to a hotel has been established.
|
||||
*
|
||||
* @apiNote could check this yourself as well (listen to out:4000 packet)
|
||||
*/
|
||||
public static class ConnectionStart extends Outgoing {
|
||||
|
||||
public static final int HEADER_ID = 5;
|
||||
|
||||
private final String host;
|
||||
private final int connectionPort;
|
||||
private final String hotelVersion;
|
||||
private final String clientIdentifier;
|
||||
private final HClient clientType;
|
||||
private final PacketInfoManager packetInfoManager;
|
||||
|
||||
public ConnectionStart(String host, int connectionPort, String hotelVersion, String clientIdentifier, HClient clientType, PacketInfoManager packetInfoManager) {
|
||||
this.host = host;
|
||||
this.connectionPort = connectionPort;
|
||||
this.hotelVersion = hotelVersion;
|
||||
this.clientIdentifier = clientIdentifier;
|
||||
this.clientType = clientType;
|
||||
this.packetInfoManager = packetInfoManager;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
return host;
|
||||
}
|
||||
|
||||
public int getConnectionPort() {
|
||||
return connectionPort;
|
||||
}
|
||||
|
||||
public String getHotelVersion() {
|
||||
return hotelVersion;
|
||||
}
|
||||
|
||||
public String getClientIdentifier() {
|
||||
return clientIdentifier;
|
||||
}
|
||||
|
||||
public HClient getClientType() {
|
||||
return clientType;
|
||||
}
|
||||
|
||||
public PacketInfoManager getPacketInfoManager() {
|
||||
return packetInfoManager;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies a remote extension that the connection to the hotel has been closed.
|
||||
*/
|
||||
public static class ConnectionEnd extends Outgoing {
|
||||
public static final int HEADER_ID = 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies a remote extension that it has been accepted by G-Earth.
|
||||
*/
|
||||
public static class Init extends Outgoing {
|
||||
|
||||
public static final int HEADER_ID = 7;
|
||||
|
||||
private final boolean delayInit;
|
||||
private final HostInfo hostInfo;
|
||||
|
||||
public Init(boolean delayInit, HostInfo hostInfo) {
|
||||
this.delayInit = delayInit;
|
||||
this.hostInfo = hostInfo;
|
||||
}
|
||||
|
||||
public boolean isDelayInit() {
|
||||
return delayInit;
|
||||
}
|
||||
|
||||
public HostInfo getHostInfo() {
|
||||
return hostInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add documentation.
|
||||
*/
|
||||
public static class UpdateHostInfo extends Outgoing {
|
||||
|
||||
public static final int HEADER_ID = 10;
|
||||
|
||||
private final HostInfo hostInfo;
|
||||
|
||||
public UpdateHostInfo(HostInfo hostInfo) {
|
||||
this.hostInfo = hostInfo;
|
||||
}
|
||||
|
||||
public HostInfo getHostInfo() {
|
||||
return hostInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add documentation.
|
||||
*
|
||||
* @see Incoming.PacketToStringRequest the request.
|
||||
*/
|
||||
public static class PacketToStringResponse extends Outgoing {
|
||||
|
||||
public static final int HEADER_ID = 20;
|
||||
|
||||
private final String string;
|
||||
private final String expression;
|
||||
|
||||
public PacketToStringResponse(String string, String expression) {
|
||||
this.string = string;
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public String getExpression() {
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: add documentation.
|
||||
*
|
||||
* @see Incoming.StringToPacketRequest the request.
|
||||
*/
|
||||
public static class StringToPacketResponse extends Outgoing {
|
||||
|
||||
public static final int HEADER_ID = 21;
|
||||
|
||||
private final String string;
|
||||
|
||||
public StringToPacketResponse(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,277 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducer;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerObserver;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionCodec.PacketStructure;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.ServerSocket;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import static gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionMessage.*;
|
||||
|
||||
/**
|
||||
* Represents an {@link ExtensionProducer} that implements a server to which
|
||||
* remotely-ran extensions can connect.
|
||||
*
|
||||
* @see ExtensionProducerFactory#getAll() for instance creation.
|
||||
*
|
||||
* @author Dorving, Jonas
|
||||
*/
|
||||
public final class NetworkExtensionServer implements ExtensionProducer {
|
||||
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(NetworkExtensionServer.class);
|
||||
|
||||
/**
|
||||
* Initial port server tries to listen at, if {@link ServerSocket} creation fails,
|
||||
* it tries next port.
|
||||
*/
|
||||
private static final int PORT_ONSET = 9092;
|
||||
|
||||
/**
|
||||
* The port at which the server is listening.
|
||||
*/
|
||||
private int port = -1;
|
||||
|
||||
@Override
|
||||
public void startProducing(ExtensionProducerObserver observer) {
|
||||
|
||||
final ServerBootstrap bootstrap = new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.childHandler(new Initializer(observer))
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.group(new NioEventLoopGroup());
|
||||
|
||||
port = PORT_ONSET;
|
||||
while (!available(port))
|
||||
port++;
|
||||
LOGGER.debug("Found open port {}, attempting to bind...", port);
|
||||
|
||||
final ChannelFuture channelFuture = bootstrap.bind(port).awaitUninterruptibly();
|
||||
if (!channelFuture.isSuccess())
|
||||
LOGGER.error("Failed to bind to port {}", port);
|
||||
else
|
||||
LOGGER.debug("Successfully bound to port {}", port);
|
||||
}
|
||||
|
||||
/**
|
||||
* The port that the server is bound to.
|
||||
*
|
||||
* @return the port number to which the server is bound or -1 if the socket is not bound (yet).
|
||||
*/
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a specific port is available.
|
||||
*
|
||||
* Taken from <a href="http://svn.apache.org/viewvc/camel/trunk/components/camel-test/src/main/java/org/apache/camel/test/AvailablePortFinder.java?view=markup#l130">http://svn.apache.org/viewvc/camel/trunk/components/camel-test/src/main/java/org/apache/camel/test/AvailablePortFinder.java?view=markup#l130</a>
|
||||
*
|
||||
* @param port the port to check for availability
|
||||
*/
|
||||
private static boolean available(int port) {
|
||||
ServerSocket ss = null;
|
||||
DatagramSocket ds = null;
|
||||
try {
|
||||
ss = new ServerSocket(port);
|
||||
ss.setReuseAddress(true);
|
||||
ds = new DatagramSocket(port);
|
||||
ds.setReuseAddress(true);
|
||||
return true;
|
||||
} catch (IOException ignored) {
|
||||
} finally {
|
||||
if (ds != null) {
|
||||
ds.close();
|
||||
}
|
||||
|
||||
if (ss != null) {
|
||||
try {
|
||||
ss.close();
|
||||
} catch (IOException e) {
|
||||
/* should not be thrown */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static class Initializer extends ChannelInitializer<SocketChannel> {
|
||||
|
||||
private final ExtensionProducerObserver observer;
|
||||
|
||||
public Initializer(ExtensionProducerObserver observer) {
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initChannel(SocketChannel ch) {
|
||||
ch.pipeline()
|
||||
.addLast("decoder", new Decoder())
|
||||
.addLast("encoder", new Encoder())
|
||||
.addLast("handler", new Handler(observer));
|
||||
ch.writeAndFlush(new Outgoing.InfoRequest());
|
||||
}
|
||||
}
|
||||
|
||||
static class Decoder extends ByteToMessageDecoder {
|
||||
|
||||
private final static int HEADER_LENGTH = Integer.BYTES;
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(Decoder.class);
|
||||
|
||||
private volatile Stage stage = Stage.LENGTH;
|
||||
private volatile int payloadLength = 0;
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
switch (stage) {
|
||||
case LENGTH:
|
||||
|
||||
if (in.readableBytes() < HEADER_LENGTH)
|
||||
return;
|
||||
|
||||
payloadLength = in.readInt();
|
||||
stage = Stage.PAYLOAD;
|
||||
break;
|
||||
case PAYLOAD:
|
||||
|
||||
if (in.readableBytes() < payloadLength)
|
||||
return;
|
||||
|
||||
try {
|
||||
|
||||
final byte[] data = new byte[HEADER_LENGTH + payloadLength];
|
||||
in.readBytes(data, HEADER_LENGTH, payloadLength);
|
||||
|
||||
final HPacket hPacket = new HPacket(data);
|
||||
hPacket.fixLength();
|
||||
|
||||
final PacketStructure incomingPacketStructure = NetworkExtensionCodec.getIncomingStructure(hPacket.headerId());
|
||||
if (incomingPacketStructure != null) {
|
||||
final NetworkExtensionMessage message = incomingPacketStructure.getReader().apply(hPacket);
|
||||
out.add(message);
|
||||
} else {
|
||||
LOGGER.error("Did not find decoder for packet {}", hPacket);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to decode message", e);
|
||||
} finally {
|
||||
payloadLength = 0;
|
||||
stage = Stage.LENGTH;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
enum Stage {
|
||||
LENGTH,
|
||||
PAYLOAD
|
||||
}
|
||||
}
|
||||
|
||||
static class Encoder extends MessageToByteEncoder<Outgoing> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, Outgoing msg, ByteBuf out) {
|
||||
final PacketStructure structure = NetworkExtensionCodec.getOutgoingStructure(msg);
|
||||
if (structure == null){
|
||||
LOGGER.error("Structure for Outgoing message not defined (msg={})", msg);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final HPacket hPacket = new HPacket(structure.getHeaderId());
|
||||
final BiConsumer<Outgoing, HPacket> writer = (BiConsumer<Outgoing, HPacket>) structure.getWriter();
|
||||
writer.accept(msg, hPacket);
|
||||
out.writeBytes(hPacket.toBytes());
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Failed to encode Outgoing message as a HPacket (msg={})", msg, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class Handler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
private static final AttributeKey<NetworkExtensionClient> CLIENT = AttributeKey.valueOf("client");
|
||||
|
||||
private final ExtensionProducerObserver observer;
|
||||
|
||||
public Handler(ExtensionProducerObserver observer) {
|
||||
this.observer = observer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
LOGGER.trace("Channel registered (channel={})", ctx.channel());
|
||||
super.handlerAdded(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
||||
LOGGER.trace("Channel unregistered (channel={})", ctx.channel());
|
||||
close(ctx);
|
||||
super.channelUnregistered(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
final Channel channel = ctx.channel();
|
||||
final Attribute<NetworkExtensionClient> clientAttribute = ctx.attr(CLIENT);
|
||||
NetworkExtensionClient client = clientAttribute.get();
|
||||
if (msg instanceof Incoming.ExtensionInfo) {
|
||||
if (client != null)
|
||||
LOGGER.warn("Overriding pre-existing CLIENT for channel (client={}, channel={})", client, channel);
|
||||
client = new NetworkExtensionClient(channel, (Incoming.ExtensionInfo) msg);
|
||||
if (NetworkExtensionAuthenticator.evaluate(client)) {
|
||||
LOGGER.info("Successfully authenticated client {}", client);
|
||||
clientAttribute.set(client);
|
||||
observer.onExtensionProduced(client);
|
||||
} else {
|
||||
LOGGER.warn("Failed to authenticate client {}, closing connection", client);
|
||||
client.close();
|
||||
}
|
||||
}
|
||||
else if (client == null)
|
||||
LOGGER.error("Client was null, could not handle incoming message {}, expected {} first", msg, Incoming.ExtensionInfo.class);
|
||||
else if (msg instanceof Incoming)
|
||||
client.handleIncomingMessage((Incoming) msg);
|
||||
else
|
||||
LOGGER.error("Read invalid message type (message={})", msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
LOGGER.error("Channel exception caught (channel={}), closing channel", ctx.channel(), cause);
|
||||
close(ctx);
|
||||
}
|
||||
|
||||
private void close(ChannelHandlerContext ctx) {
|
||||
final Optional<NetworkExtensionClient> optionalClient = findClient(ctx);
|
||||
if (optionalClient.isPresent())
|
||||
optionalClient.get().close();
|
||||
else
|
||||
ctx.channel().close();
|
||||
}
|
||||
|
||||
private Optional<NetworkExtensionClient> findClient(ChannelHandlerContext ctx) {
|
||||
return Optional.ofNullable(ctx.attr(CLIENT).get());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network;
|
||||
|
||||
import gearth.protocol.HPacket;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducer;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerObserver;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
|
||||
/**
|
||||
* Represents an {@link ExtensionProducer} that implements a server to which
|
||||
* remotely-ran extensions can connect.
|
||||
*
|
||||
* @see ExtensionProducerFactory#getAll() for instance creation.
|
||||
*
|
||||
* Created by Jonas on 21/06/18.
|
||||
*/
|
||||
public final class NetworkExtensionsProducer implements ExtensionProducer {
|
||||
|
||||
/**
|
||||
* Initial port server tries to listen at, if {@link ServerSocket} creation fails,
|
||||
* it tries next port.
|
||||
*/
|
||||
private static final int PORT_ONSET = 9092;
|
||||
|
||||
/**
|
||||
* Represents the number of bytes per boolean encoded in an incoming packet.
|
||||
*/
|
||||
private static final int BOOLEAN_SIZE = 1;
|
||||
|
||||
/**
|
||||
* Represents the maximum number of bytes per string encoded in an incoming packet.
|
||||
*/
|
||||
private static final int MAX_STRING_SIZE = Character.BYTES * 4_000;
|
||||
|
||||
/**
|
||||
* Length is encoded as an {@link Integer} and header id as an {@link Short}.
|
||||
*/
|
||||
private static final int PACKET_HEADER_SIZE = Integer.BYTES + Short.BYTES;
|
||||
|
||||
/**
|
||||
* Represents the maximum number of bytes in the body of an incoming packet.
|
||||
* <p>
|
||||
* Used as a form of validation for packets, prevents other Apps that connect
|
||||
* with the server from sending unexpected data and inexplicably causing huge byte array allocations.
|
||||
* <p>
|
||||
* Since the server only accepts {@link NetworkExtensionInfo.INCOMING_MESSAGES_IDS#EXTENSIONINFO} packets,
|
||||
* this value is calculated based on that packet.
|
||||
*/
|
||||
private static final int MAX_PACKET_BODY_SIZE = (MAX_STRING_SIZE * 6) + (BOOLEAN_SIZE * 4);
|
||||
|
||||
/**
|
||||
* The port at which the {@link #serverSocket} is listening for incoming connections.
|
||||
*/
|
||||
public static int extensionPort = -1;
|
||||
|
||||
private ServerSocket serverSocket;
|
||||
|
||||
@Override
|
||||
public void startProducing(ExtensionProducerObserver observer) {
|
||||
|
||||
/*
|
||||
Initialise the serverSocket at the argued port.
|
||||
*/
|
||||
int port = PORT_ONSET;
|
||||
while (!createServer(port))
|
||||
++port;
|
||||
|
||||
/*
|
||||
Start connection listener thread.
|
||||
*/
|
||||
new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
while (!serverSocket.isClosed()) {
|
||||
|
||||
// accept a new connection
|
||||
final Socket extensionSocket = serverSocket.accept();
|
||||
extensionSocket.setTcpNoDelay(true);
|
||||
|
||||
/*
|
||||
Start client session handler thread.
|
||||
*/
|
||||
new Thread(() -> {
|
||||
|
||||
try {
|
||||
|
||||
// write INFOREQUEST packet to client
|
||||
synchronized (extensionSocket) {
|
||||
extensionSocket.getOutputStream().write((new HPacket(NetworkExtensionInfo.OUTGOING_MESSAGES_IDS.INFOREQUEST)).toBytes());
|
||||
}
|
||||
|
||||
final DataInputStream dIn = new DataInputStream(extensionSocket.getInputStream());
|
||||
|
||||
// listen to incoming data from client
|
||||
while (!extensionSocket.isClosed()) {
|
||||
|
||||
final int bodyLength = dIn.readInt() - Short.BYTES;
|
||||
final short headerId = dIn.readShort();
|
||||
|
||||
if (headerId == NetworkExtensionInfo.INCOMING_MESSAGES_IDS.EXTENSIONINFO) {
|
||||
|
||||
if (bodyLength > MAX_PACKET_BODY_SIZE) {
|
||||
System.err.printf("Incoming packet(h=%d, l=%d) exceeds max packet body size %d.\n", headerId, bodyLength, MAX_PACKET_BODY_SIZE);
|
||||
break;
|
||||
}
|
||||
|
||||
final HPacket packet = readPacket(dIn, bodyLength, headerId);
|
||||
|
||||
final NetworkExtension gEarthExtension = new NetworkExtension(packet, extensionSocket);
|
||||
|
||||
if (Authenticator.evaluate(gEarthExtension))
|
||||
observer.onExtensionProduced(gEarthExtension);
|
||||
else
|
||||
gEarthExtension.close();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private boolean createServer(int port) {
|
||||
try {
|
||||
serverSocket = new ServerSocket(port);
|
||||
extensionPort = port;
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private HPacket readPacket(DataInputStream dIn, int amountToRead, short id) throws IOException {
|
||||
final byte[] headerAndBody = new byte[amountToRead + PACKET_HEADER_SIZE];
|
||||
|
||||
int amountRead = 0;
|
||||
while (amountRead < amountToRead)
|
||||
amountRead += dIn.read(headerAndBody, amountRead + PACKET_HEADER_SIZE, Math.min(dIn.available(), amountToRead - amountRead));
|
||||
|
||||
final HPacket packet = new HPacket(headerAndBody);
|
||||
packet.fixLength();
|
||||
packet.replaceShort(4, id); // add header id
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the {@link ServerSocket#getLocalPort()} of {@link #serverSocket}.
|
||||
*
|
||||
* @return the port number to which {@link #serverSocket} is listening or -1 if the socket is not bound yet.
|
||||
*/
|
||||
public int getPort() {
|
||||
return serverSocket.getLocalPort();
|
||||
}
|
||||
}
|
@ -8,39 +8,39 @@ import java.util.Map;
|
||||
/**
|
||||
* Created by Jonas on 22/09/18.
|
||||
*/
|
||||
public class ExecutionInfo {
|
||||
public final class ExecutionInfo {
|
||||
|
||||
private static Map<String, String[]> extensionTypeToExecutionCommand;
|
||||
public final static List<String> ALLOWEDEXTENSIONTYPES;
|
||||
public final static String EXTENSIONSDIRECTORY = "Extensions";
|
||||
private static final Map<String, String[]> EXTENSION_TYPE_TO_EXECUTION_COMMAND;
|
||||
|
||||
public final static List<String> ALLOWED_EXTENSION_TYPES;
|
||||
public final static String EXTENSIONS_DIRECTORY = "Extensions";
|
||||
|
||||
static {
|
||||
extensionTypeToExecutionCommand = new HashMap<>();
|
||||
extensionTypeToExecutionCommand.put("*.jar", new String[]{"java", "-jar", "{path}"});
|
||||
extensionTypeToExecutionCommand.put("*.py", new String[]{"python", "{path}"});
|
||||
extensionTypeToExecutionCommand.put("*.py3", new String[]{"python3", "{path}"});
|
||||
extensionTypeToExecutionCommand.put("*.sh", new String[]{"{path}"});
|
||||
extensionTypeToExecutionCommand.put("*.exe", new String[]{"{path}"});
|
||||
extensionTypeToExecutionCommand.put("*.js", new String[]{"node", "{path}"});
|
||||
|
||||
String[] extraArgs = {"-p", "{port}", "-f", "{filename}", "-c", "{cookie}"};
|
||||
for(String type : extensionTypeToExecutionCommand.keySet()) {
|
||||
String[] commandShort = extensionTypeToExecutionCommand.get(type);
|
||||
String[] combined = new String[extraArgs.length + commandShort.length];
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND = new HashMap<>();
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.jar", new String[]{"java", "-jar", "{path}"});
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.py", new String[]{"python", "{path}"});
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.py3", new String[]{"python3", "{path}"});
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.sh", new String[]{"{path}"});
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.exe", new String[]{"{path}"});
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put("*.js", new String[]{"node", "{path}"});
|
||||
|
||||
final String[] extraArgs = {"-p", "{port}", "-f", "{filename}", "-c", "{cookie}"};
|
||||
|
||||
for(String type : EXTENSION_TYPE_TO_EXECUTION_COMMAND.keySet()) {
|
||||
|
||||
final String[] commandShort = EXTENSION_TYPE_TO_EXECUTION_COMMAND.get(type);
|
||||
final String[] combined = new String[extraArgs.length + commandShort.length];
|
||||
System.arraycopy(commandShort, 0, combined, 0, commandShort.length);
|
||||
System.arraycopy(extraArgs, 0, combined, commandShort.length, extraArgs.length);
|
||||
|
||||
extensionTypeToExecutionCommand.put(
|
||||
type,
|
||||
combined
|
||||
);
|
||||
EXTENSION_TYPE_TO_EXECUTION_COMMAND.put(type, combined);
|
||||
}
|
||||
|
||||
ALLOWEDEXTENSIONTYPES = new ArrayList<>(extensionTypeToExecutionCommand.keySet());
|
||||
ALLOWED_EXTENSION_TYPES = new ArrayList<>(EXTENSION_TYPE_TO_EXECUTION_COMMAND.keySet());
|
||||
}
|
||||
|
||||
public static String[] getExecutionCommand(String type) {
|
||||
return extensionTypeToExecutionCommand.get(type);
|
||||
return EXTENSION_TYPE_TO_EXECUTION_COMMAND.get(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,9 +3,9 @@ package gearth.services.extension_handler.extensions.implementations.network.exe
|
||||
/**
|
||||
* Created by Jonas on 22/09/18.
|
||||
*/
|
||||
public class ExtensionRunnerFactory {
|
||||
public final class ExtensionRunnerFactory {
|
||||
|
||||
private static ExtensionRunner runner = new NormalExtensionRunner();
|
||||
private static final ExtensionRunner runner = new NormalExtensionRunner();
|
||||
|
||||
public static ExtensionRunner get() {
|
||||
return runner;
|
||||
|
@ -1,207 +1,198 @@
|
||||
package gearth.services.extension_handler.extensions.implementations.network.executer;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator;
|
||||
import gearth.services.internal_extensions.extensionstore.tools.StoreExtensionTools;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.*;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Created by Jonas on 22/09/18.
|
||||
*/
|
||||
public class NormalExtensionRunner implements ExtensionRunner {
|
||||
public final class NormalExtensionRunner implements ExtensionRunner {
|
||||
|
||||
public static final String JARPATH;
|
||||
private final static Logger LOGGER = LoggerFactory.getLogger(NormalExtensionRunner.class);
|
||||
|
||||
public static final String JAR_PATH;
|
||||
|
||||
static {
|
||||
final URL url = getLocation();
|
||||
String value;
|
||||
try {
|
||||
value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getParent();
|
||||
value = new File(url.toURI()).getParent();
|
||||
} catch (URISyntaxException e) {
|
||||
value = new File(GEarth.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getParent();
|
||||
e.printStackTrace();
|
||||
value = new File(url.getPath()).getParent();
|
||||
LOGGER.warn("Failed to load JAR_PATH from url {} as URI, using Path instead", url, e);
|
||||
}
|
||||
JARPATH = value;
|
||||
JAR_PATH = value;
|
||||
LOGGER.debug("Set JAR_PATH as {}", JAR_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAllExtensions(int port) {
|
||||
if (dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)){
|
||||
File folder =
|
||||
new File(JARPATH +
|
||||
FileSystems.getDefault().getSeparator()+
|
||||
ExecutionInfo.EXTENSIONSDIRECTORY);
|
||||
|
||||
File[] childs = folder.listFiles();
|
||||
for (File file : childs) {
|
||||
if (dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY)) {
|
||||
|
||||
final File extensionsDirectory = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY).toFile();
|
||||
final File[] extensionFiles = extensionsDirectory.listFiles();
|
||||
|
||||
if (extensionFiles == null) {
|
||||
LOGGER.error("Provided extensionsDirectory does not exist (extensionsDirectory={})", extensionsDirectory);
|
||||
return;
|
||||
}
|
||||
|
||||
for (File file : extensionFiles)
|
||||
tryRunExtension(file.getPath(), port);
|
||||
}
|
||||
}
|
||||
} else
|
||||
LOGGER.warn("Did not run extensions because extensions directory does not exist at {}", ExecutionInfo.EXTENSIONS_DIRECTORY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void installAndRunExtension(String path, int port) {
|
||||
if (!dirExists(ExecutionInfo.EXTENSIONSDIRECTORY)) {
|
||||
createDirectory(ExecutionInfo.EXTENSIONSDIRECTORY);
|
||||
}
|
||||
public void installAndRunExtension(String stringPath, int port) {
|
||||
|
||||
if (!dirExists(ExecutionInfo.EXTENSIONS_DIRECTORY))
|
||||
tryCreateDirectory(ExecutionInfo.EXTENSIONS_DIRECTORY);
|
||||
|
||||
String name = Paths.get(path).getFileName().toString();
|
||||
String[] split = name.split("\\.");
|
||||
String ext = "*." + split[split.length - 1];
|
||||
final Path path = Paths.get(stringPath);
|
||||
final String name = path.getFileName().toString();
|
||||
final String[] split = name.split("\\.");
|
||||
final String ext = "*." + split[split.length - 1];
|
||||
|
||||
String realname = String.join(".",Arrays.copyOf(split, split.length-1));
|
||||
String newname = realname + "-" + getRandomString() + ext.substring(1);
|
||||
final String realName = String.join(".", Arrays.copyOf(split, split.length - 1));
|
||||
final String newName = realName + "-" + getRandomString() + ext.substring(1);
|
||||
|
||||
Path originalPath = Paths.get(path);
|
||||
Path newPath = Paths.get(
|
||||
JARPATH,
|
||||
ExecutionInfo.EXTENSIONSDIRECTORY,
|
||||
newname
|
||||
);
|
||||
final Path newPath = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, newName);
|
||||
|
||||
try {
|
||||
Files.copy(
|
||||
originalPath,
|
||||
newPath
|
||||
);
|
||||
|
||||
// addExecPermission(newPath.toString());
|
||||
Files.copy(path, newPath);
|
||||
|
||||
tryRunExtension(newPath.toString(), port);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void tryRunExtension(String path, int port) {
|
||||
try {
|
||||
|
||||
if (new File(path).isDirectory()) {
|
||||
// this extension is installed from the extension store and requires
|
||||
// different behavior
|
||||
// this extension is installed from the extension store and requires different behavior
|
||||
StoreExtensionTools.executeExtension(path, port);
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = Paths.get(path).getFileName().toString();
|
||||
|
||||
String[] execCommand = ExecutionInfo.getExecutionCommand(getFileExtension(path));
|
||||
execCommand = Arrays.copyOf(execCommand, execCommand.length);
|
||||
String cookie = Authenticator.generateCookieForExtension(filename);
|
||||
final String filename = Paths.get(path).getFileName().toString();
|
||||
final String[] execCommand = ExecutionInfo
|
||||
.getExecutionCommand(getFileExtension(path))
|
||||
.clone();
|
||||
final String cookie = NetworkExtensionAuthenticator.generateCookieForExtension(filename);
|
||||
for (int i = 0; i < execCommand.length; i++) {
|
||||
execCommand[i] = execCommand[i]
|
||||
.replace("{path}", path)
|
||||
.replace("{port}", port+"")
|
||||
.replace("{port}", port + "")
|
||||
.replace("{filename}", filename)
|
||||
.replace("{cookie}", cookie);
|
||||
}
|
||||
ProcessBuilder pb = new ProcessBuilder(execCommand);
|
||||
// Process proc = Runtime.getRuntime().exec(execCommand);
|
||||
Process proc = pb.start();
|
||||
|
||||
maybeLogExtension(path, proc);
|
||||
final ProcessBuilder processBuilder = new ProcessBuilder(execCommand);
|
||||
final Process process = processBuilder.start();
|
||||
|
||||
maybeLogExtension(path, process);
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to run extension at path {} using port {}", path, port, e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void maybeLogExtension(String path, Process proc) {
|
||||
|
||||
public static void maybeLogExtension(String path, Process process) {
|
||||
if (GEarth.hasFlag(ExtensionRunner.SHOW_EXTENSIONS_LOG)) {
|
||||
String sep = "" + System.lineSeparator();
|
||||
synchronized (System.out) {
|
||||
System.out.println(path + sep + "Launching" + sep + "----------" + sep);
|
||||
}
|
||||
|
||||
BufferedReader stdInput = new BufferedReader(new
|
||||
InputStreamReader(proc.getInputStream()));
|
||||
final Logger logger = LoggerFactory.getLogger(path);
|
||||
|
||||
logger.info("Launching...");
|
||||
|
||||
final BufferedReader processInputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String line;
|
||||
while((line = stdInput.readLine()) != null) {
|
||||
synchronized (System.out) {
|
||||
System.out.println(path + sep + "Output" + sep + line + sep + "----------" + sep);
|
||||
}
|
||||
}
|
||||
while ((line = processInputReader.readLine()) != null)
|
||||
logger.info(line);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to read input line from process {}", process, e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
BufferedReader stdError = new BufferedReader(new
|
||||
InputStreamReader(proc.getErrorStream()));
|
||||
}, path+"-input").start();
|
||||
|
||||
final BufferedReader processErrorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String line;
|
||||
while((line = stdError.readLine()) != null) {
|
||||
synchronized (System.out) {
|
||||
System.out.println(path + sep + "Error" + sep + line + sep + "----------" + sep);
|
||||
}
|
||||
}
|
||||
while ((line = processErrorReader.readLine()) != null)
|
||||
logger.error(line);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to read error line from process {}", process, e);
|
||||
}
|
||||
}).start();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void uninstallExtension(String filename) {
|
||||
try {
|
||||
Path path = Paths.get(JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY, filename);
|
||||
final Path path = Paths.get(JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, filename);
|
||||
if (new File(path.toString()).isDirectory()) {
|
||||
// is installed through extension store
|
||||
StoreExtensionTools.removeExtension(path.toString());
|
||||
}
|
||||
else {
|
||||
} else
|
||||
Files.delete(path);
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
LOGGER.error("Failed to uninstall extension at {}", filename, e);
|
||||
}
|
||||
}
|
||||
|
||||
// private void addExecPermission(String path) {
|
||||
// //not needed at first sight
|
||||
// }
|
||||
private static void tryCreateDirectory(String path) {
|
||||
if (!dirExists(path)) {
|
||||
try {
|
||||
Files.createDirectories(Paths.get(JAR_PATH, path));
|
||||
} catch (IOException e) {
|
||||
LOGGER.error("Failed to create directory at {}", path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getFileExtension(String path) {
|
||||
String name = Paths.get(path).getFileName().toString();
|
||||
String[] split = name.split("\\.");
|
||||
private static boolean dirExists(String dir) {
|
||||
return Files.isDirectory(Paths.get(JAR_PATH, dir));
|
||||
}
|
||||
|
||||
private static URL getLocation() {
|
||||
return GEarth.class.getProtectionDomain().getCodeSource().getLocation();
|
||||
}
|
||||
|
||||
private static String getFileExtension(String path) {
|
||||
final String name = Paths.get(path).getFileName().toString();
|
||||
final String[] split = name.split("\\.");
|
||||
return "*." + split[split.length - 1];
|
||||
}
|
||||
|
||||
private boolean dirExists(String dir) {
|
||||
return Files.isDirectory(Paths.get(JARPATH, dir));
|
||||
}
|
||||
private void createDirectory(String dir) {
|
||||
if (!dirExists(dir)) {
|
||||
try {
|
||||
Files.createDirectories(Paths.get(JARPATH, dir));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
private String getRandomString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < 12; i++) {
|
||||
builder.append(r.nextInt(10));
|
||||
}
|
||||
|
||||
private static String getRandomString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
final Random random = new Random();
|
||||
for (int i = 0; i < 12; i++)
|
||||
builder.append(random.nextInt(10));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package gearth.services.internal_extensions.extensionstore.application.entities.extensiondetails;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner;
|
||||
import gearth.services.extension_handler.extensions.extensionproducers.ExtensionProducerFactory;
|
||||
import gearth.services.internal_extensions.extensionstore.GExtensionStore;
|
||||
import gearth.services.internal_extensions.extensionstore.application.GExtensionStoreController;
|
||||
import gearth.services.internal_extensions.extensionstore.application.WebUtils;
|
||||
@ -16,13 +14,11 @@ import gearth.ui.titlebar.TitleBarController;
|
||||
import gearth.ui.translations.LanguageBundle;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ButtonType;
|
||||
import org.apache.maven.artifact.versioning.ComparableVersion;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@ -137,7 +133,7 @@ public class StoreExtensionDetailsOverview extends HOverview {
|
||||
@Override
|
||||
public void success(String installationFolder) {
|
||||
Platform.runLater(() -> successPopup(modeString));
|
||||
StoreExtensionTools.executeExtension(installationFolder, NetworkExtensionsProducer.extensionPort);
|
||||
StoreExtensionTools.executeExtension(installationFolder, ExtensionProducerFactory.getExtensionServer().getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -2,7 +2,7 @@ package gearth.services.internal_extensions.extensionstore.tools;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.misc.OSValidator;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.NormalExtensionRunner;
|
||||
import gearth.services.internal_extensions.extensionstore.repository.StoreFetch;
|
||||
@ -34,7 +34,7 @@ public class StoreExtensionTools {
|
||||
|
||||
}
|
||||
|
||||
public final static String EXTENSIONS_PATH = Paths.get(NormalExtensionRunner.JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY).toString();
|
||||
public final static String EXTENSIONS_PATH = Paths.get(NormalExtensionRunner.JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY).toString();
|
||||
|
||||
|
||||
public static void executeExtension(String extensionPath, int port) {
|
||||
@ -42,7 +42,7 @@ public class StoreExtensionTools {
|
||||
String installedExtensionId = Paths.get(extensionPath).getFileName().toString();
|
||||
|
||||
String commandPath = Paths.get(extensionPath, "command.txt").toString();
|
||||
String cookie = Authenticator.generateCookieForExtension(installedExtensionId);
|
||||
String cookie = NetworkExtensionAuthenticator.generateCookieForExtension(installedExtensionId);
|
||||
List<String> command = new JSONArray(FileUtils.readFileToString(new File(commandPath), "UTF-8"))
|
||||
.toList().stream().map(o -> (String)o).map(s -> s
|
||||
.replace("{port}", port+"")
|
||||
|
@ -100,7 +100,7 @@ public class ExtensionItemContainer extends GridPane {
|
||||
reloadButton.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
reloadButton.setVisible(false);
|
||||
ExtensionRunner runner = ExtensionRunnerFactory.get();
|
||||
runner.tryRunExtension(Paths.get(NormalExtensionRunner.JARPATH, ExecutionInfo.EXTENSIONSDIRECTORY, item.getFileName()).toString(), port);
|
||||
runner.tryRunExtension(Paths.get(NormalExtensionRunner.JAR_PATH, ExecutionInfo.EXTENSIONS_DIRECTORY, item.getFileName()).toString(), port);
|
||||
});
|
||||
|
||||
DeleteButton deleteButton = new DeleteButton();
|
||||
|
@ -1,10 +1,9 @@
|
||||
package gearth.ui.subforms.extensions;
|
||||
|
||||
import gearth.GEarth;
|
||||
import gearth.services.extension_handler.ExtensionHandler;
|
||||
import gearth.services.extension_handler.extensions.ExtensionListener;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionsProducer;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.authentication.Authenticator;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionServer;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.NetworkExtensionAuthenticator;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExecutionInfo;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunner;
|
||||
import gearth.services.extension_handler.extensions.implementations.network.executer.ExtensionRunnerFactory;
|
||||
@ -39,7 +38,7 @@ public class ExtensionsController extends SubForm {
|
||||
|
||||
private ExtensionRunner extensionRunner = null;
|
||||
private ExtensionHandler extensionHandler;
|
||||
private NetworkExtensionsProducer networkExtensionsProducer; // needed for port
|
||||
private NetworkExtensionServer networkExtensionsProducer; // needed for port
|
||||
private ExtensionLogger extensionLogger = null;
|
||||
|
||||
public Label lbl_tableTitle, lbl_tableDesc, lbl_tableAuthor, lbl_tableVersion, lbl_tableEdit, lbl_port;
|
||||
@ -61,8 +60,8 @@ public class ExtensionsController extends SubForm {
|
||||
|
||||
//noinspection OptionalGetWithoutIsPresent
|
||||
networkExtensionsProducer
|
||||
= (NetworkExtensionsProducer) extensionHandler.getExtensionProducers().stream()
|
||||
.filter(producer1 -> producer1 instanceof NetworkExtensionsProducer)
|
||||
= (NetworkExtensionServer) extensionHandler.getExtensionProducers().stream()
|
||||
.filter(producer1 -> producer1 instanceof NetworkExtensionServer)
|
||||
.findFirst().get();
|
||||
|
||||
|
||||
@ -87,7 +86,7 @@ public class ExtensionsController extends SubForm {
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
fileChooser.setTitle(LanguageBundle.get("tab.extensions.button.install.windowtitle"));
|
||||
fileChooser.getExtensionFilters().addAll(
|
||||
new FileChooser.ExtensionFilter(LanguageBundle.get("tab.extensions.button.install.filetype"), ExecutionInfo.ALLOWEDEXTENSIONTYPES));
|
||||
new FileChooser.ExtensionFilter(LanguageBundle.get("tab.extensions.button.install.filetype"), ExecutionInfo.ALLOWED_EXTENSION_TYPES));
|
||||
File selectedFile = fileChooser.showOpenDialog(parentController.getStage());
|
||||
if (selectedFile != null) {
|
||||
extensionRunner.installAndRunExtension(selectedFile.getPath(), networkExtensionsProducer.getPort());
|
||||
@ -123,7 +122,7 @@ public class ExtensionsController extends SubForm {
|
||||
GPythonShell shell = new GPythonShell(
|
||||
String.format("%s %d", LanguageBundle.get("tab.extensions.button.pythonshell.windowtitle"),gpytonShellCounter++),
|
||||
networkExtensionsProducer.getPort(),
|
||||
Authenticator.generatePermanentCookie()
|
||||
NetworkExtensionAuthenticator.generatePermanentCookie()
|
||||
);
|
||||
shell.launch((b) -> {
|
||||
pythonShellLaunching = false;
|
||||
|
Loading…
Reference in New Issue
Block a user