package net.glowstone.net;
import net.glowstone.entity.meta.profile.PlayerProfile;
import net.glowstone.entity.meta.profile.PlayerProperty;
import net.glowstone.util.UuidUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* Container for proxy (e.g. BungeeCord) player data spoofing.
*/
public final class ProxyData {
private String securityKey; // Lilypad security key - not used by us
private String hostname;
private InetSocketAddress address;
private String name;
private UUID uuid;
private List<PlayerProperty> properties;
public ProxyData(String securityKey, String hostname, InetSocketAddress address, String name, UUID uuid, List<PlayerProperty> properties) {
this.securityKey = securityKey;
this.hostname = hostname;
this.address = address;
this.name = name;
this.uuid = uuid;
this.properties = properties;
}
/**
* Create a proxy data structure for a session from the given source text.
*
* @param session The session to create the data for.
* @param sourceText Contents of the hostname field of the handshake.
* @throws Exception if an error occurs parsing the source text.
*/
public ProxyData(GlowSession session, String sourceText) throws Exception {
// Attempt to parse the sourceText as JSON (LilyPad) first
try {
// This throws a ParseException if parsing failed (ie: not LilyPad)
JSONObject payload = (JSONObject) new JSONParser().parse(sourceText);
// LilyPad-only values
securityKey = (String) payload.get("s"); // Not used by us anywhere
name = (String) payload.get("n");
// Spoof hostname, address, and UUID
// LilyPad also spoofs the port, unlike Bungee
hostname = (String) payload.get("h");
uuid = UuidUtils.fromFlatString((String) payload.get("u"));
address = new InetSocketAddress((String) payload.get("rIp"), ((Long) payload.get("rP")).intValue());
// Extract properties, if available
if (payload.containsKey("p")) {
JSONArray props = (JSONArray) payload.get("p");
properties = new ArrayList<>(props.size());
for (Object obj : props) {
JSONObject prop = (JSONObject) obj;
String propName = (String) prop.get("n");
String value = (String) prop.get("v");
String signature = (String) prop.get("s");
properties.add(new PlayerProperty(propName, value, signature));
}
} else {
properties = new ArrayList<>(0);
}
return; // We've processed the data, don't re-parse it as Bungee data
} catch (ParseException ignored) {
// Swallow JSON parse exception and process sourceText as Bungee data
}
// Likely Bungee data at this point. If not, then a friendly exception will be thrown.
String[] parts = sourceText.split("\0");
if (parts.length != 3 && parts.length != 4) {
throw new IllegalArgumentException("parts length was " + parts.length + ", should be 3 or 4");
}
// Set values that aren't supported or present to null
name = null;
securityKey = null;
// Spoof hostname, address, and UUID
hostname = parts[0];
address = new InetSocketAddress(parts[1], session.getAddress().getPort());
uuid = UuidUtils.fromFlatString(parts[2]);
if (parts.length == 4) {
// Spoof properties
JSONArray jsonProperties = (JSONArray) new JSONParser().parse(parts[3]);
properties = new ArrayList<>(jsonProperties.size());
for (Object obj : jsonProperties) {
JSONObject propJson = (JSONObject) obj;
String propName = (String) propJson.get("name");
String value = (String) propJson.get("value");
String signature = (String) propJson.get("signature");
properties.add(new PlayerProperty(propName, value, signature));
}
} else {
properties = new ArrayList<>(0);
}
}
/**
* Gets the security key sent by the proxy, if any.
*
* @return The security key, or null if not present.
*/
public String getSecurityKey() {
return securityKey;
}
/**
* Get the spoofed hostname to use instead of the actual one.
*
* @return The spoofed hostname.
*/
public String getHostname() {
return hostname;
}
/**
* Get the spoofed address to use instead of the actual one.
*
* @return The spoofed address.
*/
public InetSocketAddress getAddress() {
return address;
}
/**
* Get a spoofed profile to use with the given name.
*
* @param name The player name.
* @return The spoofed profile.
*/
public PlayerProfile getProfile(String name) {
return new PlayerProfile(name, uuid, properties);
}
/**
* Get a spoofed profile to use. Returns null if the proxy did not send a
* username as part of the payload.
*
* @return The spoofed profile.
*/
public PlayerProfile getProfile() {
if (name == null) return null;
return new PlayerProfile(name, uuid, properties);
}
}