package org.dynmap; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URI; import java.net.URL; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.dynmap.common.DynmapCommandSender; import org.dynmap.common.DynmapListenerManager; import org.dynmap.common.DynmapListenerManager.EventType; import org.dynmap.common.DynmapPlayer; import org.dynmap.common.DynmapServerInterface; import org.dynmap.debug.Debug; import org.dynmap.debug.Debugger; import org.dynmap.exporter.DynmapExpCommands; import org.dynmap.hdmap.HDBlockModels; import org.dynmap.hdmap.HDBlockStateTextureMap; import org.dynmap.hdmap.TexturePack; import org.dynmap.markers.MarkerAPI; import org.dynmap.markers.impl.MarkerAPIImpl; import org.dynmap.modsupport.ModSupportImpl; import org.dynmap.renderer.DynmapBlockState; import org.dynmap.servlet.*; import org.dynmap.storage.MapStorage; import org.dynmap.storage.filetree.FileTreeMapStorage; import org.dynmap.storage.mysql.MySQLMapStorage; import org.dynmap.storage.mariadb.MariaDBMapStorage; import org.dynmap.storage.sqllte.SQLiteMapStorage; import org.dynmap.storage.postgresql.PostgreSQLMapStorage; import org.dynmap.utils.BlockStep; import org.dynmap.utils.ImageIOManager; import org.dynmap.web.BanIPFilter; import org.dynmap.web.CustomHeaderFilter; import org.dynmap.web.FilterHandler; import org.dynmap.web.HandlerRouter; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.NetworkTrafficServerConnector; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AllowSymLinkAliasChecker; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.session.DefaultSessionIdManager; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.util.resource.FileResource; import org.eclipse.jetty.util.thread.ExecutorThreadPool; import org.yaml.snakeyaml.Yaml; import javax.servlet.*; import javax.servlet.http.HttpServlet; public class DynmapCore implements DynmapCommonAPI { /** * Callbacks for core initialization - subclassed by platform plugins */ public static abstract class EnableCoreCallbacks { /** * Called during enableCore to report that configuration.txt is loaded */ public abstract void configurationLoaded(); } private File jarfile; private DynmapServerInterface server; private String version; private String platform = null; private String platformVersion = null; private Server webServer = null; private String webhostname = null; private int webport = 0; private HandlerRouter router = null; public MapManager mapManager = null; public PlayerList playerList; public ConfigurationNode configuration; public ConfigurationNode world_config; public ComponentManager componentManager = new ComponentManager(); public DynmapListenerManager listenerManager = new DynmapListenerManager(this); public PlayerFaces playerfacemgr; public Events events = new Events(); public String deftemplatesuffix = ""; private DynmapMapCommands dmapcmds = new DynmapMapCommands(); private DynmapExpCommands dynmapexpcmds = new DynmapExpCommands(); boolean bettergrass = false; boolean smoothlighting = false; private boolean ctmsupport = false; private boolean customcolorssupport = false; private String def_image_format = "png"; private HashSet enabledTriggers = new HashSet(); public boolean disable_chat_to_web = false; private WebAuthManager authmgr; public boolean player_info_protected; private boolean transparentLeaves = true; private List sortPermissionNodes; private int perTickLimit = 50; // 50 ms private boolean dumpMissing = false; private static boolean migrate_chunks = false; private int config_hashcode; /* Used to signal need to reload web configuration (world changes, config update, etc) */ private int fullrenderplayerlimit; /* Number of online players that will cause fullrender processing to pause */ private int updateplayerlimit; /* Number of online players that will cause update processing to pause */ private boolean didfullpause; private boolean didupdatepause; private Map> ids_by_ip = new HashMap>(); private boolean persist_ids_by_ip = false; private int snapshotcachesize; private boolean snapshotsoftref; private String[] biomenames = new String[0]; private Map blockmap = null; private Map itemmap = null; private boolean loginRequired; /* Flag to let code know that we're doing reload - make sure we don't double-register event handlers */ public boolean is_reload = false; public static boolean ignore_chunk_loads = false; /* Flag keep us from processing our own chunk loads */ private MarkerAPIImpl markerapi; private File dataDirectory; private File tilesDirectory; private File exportDirectory; private String plugin_ver; private MapStorage defaultStorage; private String[] deftriggers = { }; /* Constructor for core */ public DynmapCore() { } /* Cleanup method */ public void cleanup() { server = null; markerapi = null; } public void restartMarkerSaveJob(){ this.markerapi.scheduleWriteJob(); } // Set plugin jar file public void setPluginJarFile(File f) { jarfile = f; } // Get plugin jar file public File getPluginJarFile() { return jarfile; } /* Dependencies - need to be supplied by plugin wrapper */ public void setPluginVersion(String pluginver, String platform) { this.plugin_ver = pluginver; this.platform = platform; } /* Default platform to forge... */ public void setPluginVersion(String pluginver) { setPluginVersion(pluginver, "Forge"); } public void setDataFolder(File dir) { dataDirectory = dir; } public final File getDataFolder() { return dataDirectory; } public final File getTilesFolder() { return tilesDirectory; } public final File getExportFolder() { return exportDirectory; } public void setMinecraftVersion(String mcver) { this.platformVersion = mcver; } public void setServer(DynmapServerInterface srv) { server = srv; } public final DynmapServerInterface getServer() { return server; } public final void setBiomeNames(String[] names) { biomenames = names; } public static final boolean migrateChunks() { return migrate_chunks; } public final String getBiomeName(int biomeid) { String n = null; if ((biomeid >= 0) && (biomeid < biomenames.length)) { n = biomenames[biomeid]; } if(n == null) n = "biome" + biomeid; return n; } public final String[] getBiomeNames() { return biomenames; } public final MapManager getMapManager() { return mapManager; } public final void setTriggerDefault(String[] triggers) { deftriggers = triggers; } public final void setLeafTransparency(boolean trans) { transparentLeaves = trans; } public final boolean getLeafTransparency() { return transparentLeaves; } /* Add/Replace branches in configuration tree with contribution from a separate file */ private void mergeConfigurationBranch(ConfigurationNode cfgnode, String branch, boolean replace_existing, boolean islist) { Object srcbranch = cfgnode.getObject(branch); if(srcbranch == null) return; /* See if top branch is in configuration - if not, just add whole thing */ Object destbranch = configuration.getObject(branch); if(destbranch == null) { /* Not found */ configuration.put(branch, srcbranch); /* Add new tree to configuration */ return; } /* If list, merge by "name" attribute */ if(islist) { List dest = configuration.getNodes(branch); List src = cfgnode.getNodes(branch); /* Go through new records : see what to do with each */ for(ConfigurationNode node : src) { String name = node.getString("name", null); if(name == null) continue; /* Walk destination - see if match */ boolean matched = false; for(ConfigurationNode dnode : dest) { String dname = dnode.getString("name", null); if(dname == null) continue; if(dname.equals(name)) { /* Match? */ if(replace_existing) { dnode.clear(); dnode.putAll(node); } matched = true; break; } } /* If no match, add to end */ if(!matched) { dest.add(node); } } configuration.put(branch,dest); } /* If configuration node, merge by key */ else { ConfigurationNode src = cfgnode.getNode(branch); ConfigurationNode dest = configuration.getNode(branch); for(String key : src.keySet()) { /* Check each contribution */ if(dest.containsKey(key)) { /* Exists? */ if(replace_existing) { /* If replacing, do so */ dest.put(key, src.getObject(key)); } } else { /* Else, always add if not there */ dest.put(key, src.getObject(key)); } } } } /* Table of default templates - all are resources in dynmap.jar unnder templates/, and go in templates directory when needed */ private static final String[] stdtemplates = { "normal.txt", "nether.txt", "normal-lowres.txt", "nether-lowres.txt", "normal-hires.txt", "nether-hires.txt", "normal-vlowres.txt", "nether-vlowres.txt", "the_end.txt", "the_end-vlowres.txt", "the_end-lowres.txt", "the_end-hires.txt", "normal-low_boost_hi.txt", "normal-hi_boost_vhi.txt", "normal-hi_boost_xhi.txt", "nether-low_boost_hi.txt", "nether-hi_boost_vhi.txt", "nether-hi_boost_xhi.txt", "the_end-low_boost_hi.txt", "the_end-hi_boost_vhi.txt", "the_end-hi_boost_xhi.txt" }; private static final String CUSTOM_PREFIX = "custom-"; /* Load templates from template folder */ private void loadTemplates() { File templatedir = new File(dataDirectory, "templates"); templatedir.mkdirs(); /* First, prime the templates directory with default standard templates, if needed */ for(String stdtemplate : stdtemplates) { File f = new File(templatedir, stdtemplate); updateVersionUsingDefaultResource("/templates/" + stdtemplate, f); } /* Now process files */ String[] templates = templatedir.list(); /* Go through list - process all ones not starting with 'custom' first */ for(String tname: templates) { /* If matches naming convention */ if(tname.endsWith(".txt") && (!tname.startsWith(CUSTOM_PREFIX))) { File tf = new File(templatedir, tname); ConfigurationNode cn = new ConfigurationNode(tf); cn.load(); /* Supplement existing values (don't replace), since configuration.txt is more custom than these */ mergeConfigurationBranch(cn, "templates", false, false); } } /* Go through list again - this time do custom- ones */ for(String tname: templates) { /* If matches naming convention */ if(tname.endsWith(".txt") && tname.startsWith(CUSTOM_PREFIX)) { File tf = new File(templatedir, tname); ConfigurationNode cn = new ConfigurationNode(tf); cn.load(); /* This are overrides - replace even configuration.txt content */ mergeConfigurationBranch(cn, "templates", true, false); } } } public boolean enableCore() { boolean rslt = initConfiguration(null); if (rslt) rslt = enableCore(null); return rslt; } public boolean initConfiguration(EnableCoreCallbacks cb) { /* Start with clean events */ events = new Events(); /* Default to being unprotected - set to protected by update components */ player_info_protected = false; /* Load plugin version info */ loadVersion(); /* Initialize confguration.txt if needed */ File f = new File(dataDirectory, "configuration.txt"); if(!createDefaultFileFromResource("/configuration.txt", f)) { return false; } /* Load configuration.txt */ configuration = new ConfigurationNode(f); configuration.load(); /* Prime the tiles directory */ tilesDirectory = getFile(configuration.getString("tilespath", "web/tiles")); if (!tilesDirectory.isDirectory() && !tilesDirectory.mkdirs()) { Log.warning("Could not create directory for tiles ('" + tilesDirectory + "')."); } // Prime the exports directory exportDirectory = getFile(configuration.getString("exportpath", "export")); if (!exportDirectory.isDirectory() && !exportDirectory.mkdirs()) { Log.warning("Could not create directory for exports ('" + exportDirectory + "')."); } // Create default storage handler String storetype = configuration.getString("storage/type", "filetree"); if (storetype.equals("filetree")) { defaultStorage = new FileTreeMapStorage(); } else if (storetype.equals("sqlite")) { defaultStorage = new SQLiteMapStorage(); } else if (storetype.equals("mysql")) { defaultStorage = new MySQLMapStorage(); } else if (storetype.equals("mariadb")) { defaultStorage = new MariaDBMapStorage(); } else if (storetype.equals("postgres") || storetype.equals("postgresql")) { defaultStorage = new PostgreSQLMapStorage(); } else { Log.severe("Invalid storage type for map data: " + storetype); return false; } if (!defaultStorage.init(this)) { Log.severe("Map storage initialization failure"); return false; } /* Register API with plugin, if needed */ if(!markerAPIInitialized()) { MarkerAPIImpl api = MarkerAPIImpl.initializeMarkerAPI(this); this.registerMarkerAPI(api); } /* Call back to plugin to report that configuration is available */ if(cb != null) cb.configurationLoaded(); return true; } public boolean enableCore(EnableCoreCallbacks cb) { /* Update extracted files, if needed */ updateExtractedFiles(); /* Initialize authorization manager */ if(configuration.getBoolean("login-enabled", false)) { authmgr = new WebAuthManager(this); defaultStorage.setLoginEnabled(this); } /* Load control for leaf transparency (spout lighting bug workaround) */ transparentLeaves = configuration.getBoolean("transparent-leaves", true); /* Get default image format */ def_image_format = configuration.getString("image-format", "png"); MapType.ImageFormat fmt = MapType.ImageFormat.fromID(def_image_format); if(fmt == null) { Log.severe("Invalid image-format: " + def_image_format); def_image_format = "png"; } DynmapWorld.doInitialScan(configuration.getBoolean("initial-zoomout-validate", true)); smoothlighting = configuration.getBoolean("smooth-lighting", false); ctmsupport = configuration.getBoolean("ctm-support", true); customcolorssupport = configuration.getBoolean("custom-colors-support", true); Log.verbose = configuration.getBoolean("verbose", true); deftemplatesuffix = configuration.getString("deftemplatesuffix", ""); /* Get snapshot cache size */ snapshotcachesize = configuration.getInteger("snapshotcachesize", 500); /* Get soft ref flag for cache (weak=false, soft=true) */ snapshotsoftref = configuration.getBoolean("soft-ref-cache", true); /* Default better-grass */ bettergrass = configuration.getBoolean("better-grass", false); /* Load full render processing player limit */ fullrenderplayerlimit = configuration.getInteger("fullrenderplayerlimit", 0); /* Load update render processing player limit */ updateplayerlimit = configuration.getInteger("updateplayerlimit", 0); /* Load sort permission nodes */ sortPermissionNodes = configuration.getStrings("player-sort-permission-nodes", null); perTickLimit = configuration.getInteger("per-tick-time-limit", 50); if (perTickLimit < 5) perTickLimit = 5; dumpMissing = configuration.getBoolean("dump-missing-blocks", false); migrate_chunks = configuration.getBoolean("migrate-chunks", false); if (migrate_chunks) Log.info("EXPERIMENTAL: chunk migration enabled"); /* Load preupdate/postupdate commands */ ImageIOManager.preUpdateCommand = configuration.getString("custom-commands/image-updates/preupdatecommand", ""); ImageIOManager.postUpdateCommand = configuration.getString("custom-commands/image-updates/postupdatecommand", ""); /* Get block and item maps */ blockmap = server.getBlockUniqueIDMap(); itemmap = server.getItemUniqueIDMap(); /* Process mod support */ ModSupportImpl.complete(this.dataDirectory); /* Load block models */ Log.verboseinfo("Loading models..."); HDBlockModels.loadModels(this, configuration); /* Load texture mappings */ Log.verboseinfo("Loading texture mappings..."); TexturePack.loadTextureMapping(this, configuration); /* Now, process worlds.txt - merge it in as an override of existing values (since it is only user supplied values) */ File f = new File(dataDirectory, "worlds.txt"); if(!createDefaultFileFromResource("/worlds.txt", f)) { return false; } world_config = new ConfigurationNode(f); world_config.load(); /* Now, process templates */ Log.verboseinfo("Loading templates..."); loadTemplates(); /* If we're persisting ids-by-ip, load it */ persist_ids_by_ip = configuration.getBoolean("persist-ids-by-ip", true); if(persist_ids_by_ip) { Log.verboseinfo("Loading userid-by-IP data..."); loadIDsByIP(); } loadDebuggers(); playerList = new PlayerList(getServer(), getFile("hiddenplayers.txt"), configuration); playerList.load(); mapManager = new MapManager(this, configuration); mapManager.startRendering(); playerfacemgr = new PlayerFaces(this); updateConfigHashcode(); /* Initialize/update config hashcode */ loginRequired = configuration.getBoolean("login-required", false); loadWebserver(); enabledTriggers.clear(); List triggers = configuration.getStrings("render-triggers", new ArrayList()); if ((triggers != null) && (triggers.size() > 0)) { for (Object trigger : triggers) { enabledTriggers.add((String) trigger); } } else { for (String def : deftriggers) { enabledTriggers.add(def); } } // Load components. for(Component component : configuration.createInstances("components", new Class[] { DynmapCore.class }, new Object[] { this })) { componentManager.add(component); } Log.verboseinfo("Loaded " + componentManager.components.size() + " components."); if (!configuration.getBoolean("disable-webserver", false)) { startWebserver(); } /* Add login/logoff listeners */ listenerManager.addListener(EventType.PLAYER_JOIN, new DynmapListenerManager.PlayerEventListener() { @Override public void playerEvent(DynmapPlayer p) { playerJoined(p); } }); listenerManager.addListener(EventType.PLAYER_QUIT, new DynmapListenerManager.PlayerEventListener() { @Override public void playerEvent(DynmapPlayer p) { playerQuit(p); } }); /* Print version info */ Log.info("version " + plugin_ver + " is enabled - core version " + version ); Log.info("For support, visit https://forums.dynmap.us"); Log.info("To report or track bugs, visit https://github.com/webbukkit/dynmap/issues"); events.trigger("initialized", null); //dumpColorMap("standard.txt", "standard"); //dumpColorMap("dokudark.txt", "dokudark.zip"); //dumpColorMap("dokulight.txt", "dokulight.zip"); //dumpColorMap("dokuhigh.txt", "dokuhigh.zip"); //dumpColorMap("misa.txt", "misa.zip"); //dumpColorMap("sphax.txt", "sphax.zip"); return true; } void dumpColorMap(String id, String name) { int[] sides = new int[] { BlockStep.Y_MINUS.ordinal(), BlockStep.X_PLUS.ordinal(), BlockStep.Z_PLUS.ordinal(), BlockStep.Y_PLUS.ordinal(), BlockStep.X_MINUS.ordinal(), BlockStep.Z_MINUS.ordinal() }; FileWriter fw = null; try { fw = new FileWriter(id); TexturePack tp = TexturePack.getTexturePack(this, name); if (tp == null) return; tp = tp.resampleTexturePack(1); if (tp == null) return; Color c = new Color(); for (int gidx = 0; gidx < DynmapBlockState.getGlobalIndexMax(); gidx++) { DynmapBlockState blk = DynmapBlockState.getStateByGlobalIndex(gidx); if (blk.isAir()) continue; int meta0color = 0; HDBlockStateTextureMap map = HDBlockStateTextureMap.getByBlockState(blk); boolean done = false; for (int i = 0; (!done) && (i < sides.length); i++) { int idx = map.getIndexForFace(sides[i]); if (idx < 0) continue; int rgb[] = tp.getTileARGB(idx % 1000000); if (rgb == null) continue; if (rgb[0] == 0) continue; c.setARGB(rgb[0]); idx = (idx / 1000000); switch(idx) { case 1: // grass case 18: // grass System.out.println("Used grass for " + blk); c.blendColor(tp.getTrivialGrassMultiplier() | 0xFF000000); break; case 2: // foliage case 19: // foliage case 22: // foliage System.out.println("Used foliage for " + blk); c.blendColor(tp.getTrivialFoliageMultiplier() | 0xFF000000); break; case 13: // pine c.blendColor(0x619961 | 0xFF000000); break; case 14: // birch c.blendColor(0x80a755 | 0xFF000000); break; case 15: // lily c.blendColor(0x208030 | 0xFF000000); break; case 3: // water case 20: // water System.out.println("Used water for " + blk); c.blendColor(tp.getTrivialWaterMultiplier() | 0xFF000000); break; case 12: // clear inside if (blk.isWater()) { // special case for water System.out.println("Used water for " + blk); c.blendColor(tp.getTrivialWaterMultiplier() | 0xFF000000); } break; } int custmult = tp.getCustomBlockMultiplier(blk); if (custmult != 0xFFFFFF) { System.out.println(String.format("Custom color: %06x for %s", custmult, blk)); if ((custmult & 0xFF000000) == 0) { custmult |= 0xFF000000; } c.blendColor(custmult); } String ln = ""; if (blk.stateIndex == 0) { meta0color = c.getARGB(); ln = blk.blockName + " "; } else { ln = blk + " "; } if ((blk.stateIndex == 0) || (meta0color != c.getARGB())) { ln += c.getRed() + " " + c.getGreen() + " " + c.getBlue() + " " + c.getAlpha(); ln += " " + (c.getRed()*4/5) + " " + (c.getGreen()*4/5) + " " + (c.getBlue()*4/5) + " " + c.getAlpha(); ln += " " + (c.getRed()/2) + " " + (c.getGreen()/2) + " " + (c.getBlue()/2) + " " + c.getAlpha(); ln += " " + (c.getRed()*2/5) + " " + (c.getGreen()*2/5) + " " + (c.getBlue()*2/5) + " " + c.getAlpha() + "\n"; fw.write(ln); } done = true; } } } catch (IOException iox) { } finally { if (fw != null) { try { fw.close(); } catch (IOException x) {} } } } private void playerJoined(DynmapPlayer p) { playerList.updateOnlinePlayers(null); if((fullrenderplayerlimit > 0) || (updateplayerlimit > 0)) { int pcnt = getServer().getOnlinePlayers().length; if ((fullrenderplayerlimit > 0) && (pcnt == fullrenderplayerlimit)) { if(getPauseFullRadiusRenders() == false) { /* If not paused, pause it */ setPauseFullRadiusRenders(true); Log.info("Pause full/radius renders - player limit reached"); didfullpause = true; } else { didfullpause = false; } } if ((updateplayerlimit > 0) && (pcnt == updateplayerlimit)) { if(getPauseUpdateRenders() == false) { /* If not paused, pause it */ setPauseUpdateRenders(true); Log.info("Pause tile update renders - player limit reached"); didupdatepause = true; } else { didupdatepause = false; } } } /* Add player info to IP-to-ID table */ InetSocketAddress addr = p.getAddress(); if(addr != null) { String ip = addr.getAddress().getHostAddress(); LinkedList ids = ids_by_ip.get(ip); if(ids == null) { ids = new LinkedList(); ids_by_ip.put(ip, ids); } String pid = p.getName(); if(ids.indexOf(pid) != 0) { ids.remove(pid); /* Remove from list */ ids.addFirst(pid); /* Put us first on list */ } } /* Check sort weight permissions list */ if ((sortPermissionNodes != null) && (sortPermissionNodes.size() > 0)) { int ord; for (ord = 0; ord < sortPermissionNodes.size(); ord++) { if (p.hasPermissionNode(sortPermissionNodes.get(ord))) { break; } } p.setSortWeight(ord); } else { p.setSortWeight(0); // Initialize to zero } /* And re-attach to active jobs */ if(mapManager != null) mapManager.connectTasksToPlayer(p); } /* Called by plugin each time a player quits the server */ private void playerQuit(DynmapPlayer p) { playerList.updateOnlinePlayers(p.getName()); if ((fullrenderplayerlimit > 0) || (updateplayerlimit > 0)) { /* Quitting player is still online at this moment, so discount count by 1 */ int pcnt = getServer().getOnlinePlayers().length - 1; if ((fullrenderplayerlimit > 0) && (pcnt == (fullrenderplayerlimit - 1))) { if(didfullpause && getPauseFullRadiusRenders()) { /* Only unpause if we did the pause */ setPauseFullRadiusRenders(false); Log.info("Resume full/radius renders - below player limit"); } didfullpause = false; } if ((updateplayerlimit > 0) && (pcnt == (updateplayerlimit - 1))) { if(didupdatepause && getPauseUpdateRenders()) { /* Only unpause if we did the pause */ setPauseUpdateRenders(false); Log.info("Resume tile update renders - below player limit"); } didupdatepause = false; } } } public void updateConfigHashcode() { config_hashcode = (int)System.currentTimeMillis(); } public int getConfigHashcode() { return config_hashcode; } private FileResource createFileResource(String path) { try { File f = new File(path); URI uri = f.toURI(); URL url = uri.toURL(); return new FileResource(url); } catch(Exception e) { Log.info("Could not create file resource"); return null; } } public void loadWebserver() { org.eclipse.jetty.util.log.Log.setLog(new JettyNullLogger()); String ip = server.getServerIP(); if ((ip == null) || (ip.trim().length() == 0)) { ip = "0.0.0.0"; } webhostname = configuration.getString("webserver-bindaddress", ip); webport = configuration.getInteger("webserver-port", 8123); int maxconnections = configuration.getInteger("max-sessions", 30); if(maxconnections < 2) maxconnections = 2; LinkedBlockingQueue queue = new LinkedBlockingQueue(maxconnections); ExecutorThreadPool pool = new ExecutorThreadPool(maxconnections, 2, queue); webServer = new Server(pool); webServer.setSessionIdManager(new DefaultSessionIdManager(webServer)); NetworkTrafficServerConnector connector = new NetworkTrafficServerConnector(webServer); connector.setIdleTimeout(5000); connector.setAcceptQueueSize(50); if(webhostname.equals("0.0.0.0") == false) connector.setHost(webhostname); connector.setPort(webport); webServer.setConnectors(new Connector[]{connector}); webServer.setStopAtShutdown(true); //webServer.setGracefulShutdown(1000); final boolean allow_symlinks = configuration.getBoolean("allow-symlinks", false); router = new HandlerRouter() {{ FileResourceHandler fileResourceHandler = new FileResourceHandler() {{ this.setWelcomeFiles(new String[] { "index.html" }); this.setRedirectWelcome(false); this.setDirectoriesListed(true); this.setBaseResource(createFileResource(getFile(getWebPath()).getAbsolutePath())); }}; try { fileResourceHandler.doStart(); }catch (Exception ex){ ex.printStackTrace(); Log.severe("Failed to start resource handler: "+ex.getMessage()); } ContextHandler fileResourceContext = new ContextHandler(); fileResourceContext.setHandler(fileResourceHandler); fileResourceContext.clearAliasChecks(); if (allow_symlinks){ fileResourceContext.addAliasCheck(new ContextHandler.ApproveAliases()); fileResourceContext.addAliasCheck(new ContextHandler.ApproveNonExistentDirectoryAliases()); fileResourceContext.addAliasCheck(new AllowSymLinkAliasChecker()); } try { Class handlerClass = fileResourceHandler.getClass().getSuperclass().getSuperclass(); Field field = handlerClass.getDeclaredField("_context"); field.setAccessible(true); field.set(fileResourceHandler,fileResourceContext); }catch (Exception e){ Log.severe("Failed to initialize resource handler: "+e.getMessage()); } this.addHandler("/", fileResourceHandler); this.addHandler("/tiles/*", new MapStorageResourceHandler() {{ this.setCore(DynmapCore.this); }}); }}; if(allow_symlinks) Log.verboseinfo("Web server is permitting symbolic links"); else Log.verboseinfo("Web server is not permitting symbolic links"); List filters = new LinkedList(); /* Check for banned IPs */ boolean checkbannedips = configuration.getBoolean("check-banned-ips", true); if (checkbannedips) { filters.add(new BanIPFilter(this)); } // filters.add(new LoginFilter(this)); /* Load customized response headers, if any */ filters.add(new CustomHeaderFilter(configuration.getNode("http-response-headers"))); FilterHandler fh = new FilterHandler(router, filters); ContextHandler contextHandler = new ContextHandler(); contextHandler.setContextPath("/"); contextHandler.setHandler(fh); HandlerList hlist = new HandlerList(); hlist.setHandlers(new org.eclipse.jetty.server.Handler[] { new SessionHandler(), contextHandler }); webServer.setHandler(hlist); addServlet("/up/configuration", new ClientConfigurationServlet(this)); addServlet("/standalone/config.js", new ConfigJSServlet(this)); if(authmgr != null) { LoginServlet login = new LoginServlet(this); addServlet("/up/login", login); addServlet("/up/register", login); } } public boolean isLoginSupportEnabled() { return (authmgr != null); } public boolean isLoginRequired() { return loginRequired; } public boolean isCTMSupportEnabled() { return ctmsupport; } public boolean isCustomColorsSupportEnabled() { return customcolorssupport; } public Set getIPBans() { return getServer().getIPBans(); } public void addServlet(String path, HttpServlet servlet) { new ServletHolder(servlet); router.addServlet(path, servlet); } public void startWebserver() { try { if(webServer != null) { webServer.start(); Log.info("Web server started on address " + webhostname + ":" + webport); } } catch (Exception e) { Log.severe("Failed to start WebServer on address " + webhostname + ":" + webport + " : " + e.getMessage()); } } public void disableCore() { if(persist_ids_by_ip) saveIDsByIP(); if (webServer != null) { try { webServer.stop(); for(int i = 0; i < 100; i++) { /* Limit wait to 10 seconds */ if(webServer.isStopping()) Thread.sleep(100); } if(webServer.isStopping()) { Log.warning("Graceful shutdown timed out - continuing to terminate"); } } catch (Exception e) { Log.severe("Failed to stop WebServer!", e); } webServer = null; } if (componentManager != null) { int componentCount = componentManager.components.size(); for(Component component : componentManager.components) { component.dispose(); } componentManager.clear(); Log.info("Unloaded " + componentCount + " components."); } if (mapManager != null) { mapManager.stopRendering(); mapManager = null; } playerfacemgr = null; /* Clean up registered listeners */ listenerManager.cleanup(); /* Don't clean up markerAPI - other plugins may still be accessing it */ authmgr = null; Debug.clearDebuggers(); } private static File combinePaths(File parent, String path) { return combinePaths(parent, new File(path)); } private static File combinePaths(File parent, File path) { if (path.isAbsolute()) return path; return new File(parent, path.getPath()); } public File getFile(String path) { return combinePaths(getDataFolder(), path); } protected void loadDebuggers() { List debuggersConfiguration = configuration.getNodes("debuggers"); Debug.clearDebuggers(); for (ConfigurationNode debuggerConfiguration : debuggersConfiguration) { try { Class debuggerClass = Class.forName((String) debuggerConfiguration.getString("class")); Constructor constructor = debuggerClass.getConstructor(DynmapCore.class, ConfigurationNode.class); Debugger debugger = (Debugger) constructor.newInstance(this, debuggerConfiguration); Debug.addDebugger(debugger); } catch (Exception e) { Log.severe("Error loading debugger: " + e); e.printStackTrace(); continue; } } } /* Parse argument strings : handle quoted strings */ public static String[] parseArgs(String[] args, DynmapCommandSender snd) { ArrayList rslt = new ArrayList(); /* Build command line, so we can parse our way - make sure there is trailing space */ String cmdline = ""; for(int i = 0; i < args.length; i++) { cmdline += args[i] + " "; } boolean inquote = false; StringBuilder sb = new StringBuilder(); for(int i = 0; i < cmdline.length(); i++) { char c = cmdline.charAt(i); if(inquote) { /* If in quote, accumulate until end or another quote */ if(c == '\"') { /* End quote */ inquote = false; } else { sb.append(c); } } else if(c == '\"') { /* Start of quote? */ inquote = true; } else if(c == ' ') { /* Ending space? */ rslt.add(sb.toString()); sb.setLength(0); } else { sb.append(c); } } if(inquote) { /* If still in quote, syntax error */ snd.sendMessage("Error: unclosed doublequote"); return null; } return rslt.toArray(new String[rslt.size()]); } private static final Set commands = new HashSet(Arrays.asList(new String[] { "render", "hide", "show", "version", "fullrender", "cancelrender", "radiusrender", "updaterender", "reload", "stats", "triggerstats", "resetstats", "sendtoweb", "pause", "purgequeue", "purgemap", "purgeworld", "quiet", "ids-for-ip", "ips-for-id", "add-id-for-ip", "del-id-for-ip", "webregister", "help"})); private static class CommandInfo { final String cmd; final String subcmd; final String args; final String helptext; public CommandInfo(String cmd, String subcmd, String helptxt) { this.cmd = cmd; this.subcmd = subcmd; this.helptext = helptxt; this.args = ""; } public CommandInfo(String cmd, String subcmd, String args, String helptxt) { this.cmd = cmd; this.subcmd = subcmd; this.args = args; this.helptext = helptxt; } public boolean matches(String c, String sc) { return (cmd.equals(c) && subcmd.equals(sc)); } public boolean matches(String c) { return cmd.equals(c); } }; private static final CommandInfo[] commandinfo = { new CommandInfo("dynmap", "", "Control execution of dynmap."), new CommandInfo("dynmap", "hide", "Hides the current player from the map."), new CommandInfo("dynmap", "hide", "", "Hides on the map."), new CommandInfo("dynmap", "show", "Shows the current player on the map."), new CommandInfo("dynmap", "show", "", "Shows on the map."), new CommandInfo("dynmap", "render", "Renders the tile at your location."), new CommandInfo("dynmap", "fullrender", "Render all maps for entire world from your location."), new CommandInfo("dynmap", "fullrender", "", "Render all maps for world ."), new CommandInfo("dynmap", "fullrender", ":", "Render map of world ."), new CommandInfo("dynmap", "fullrender", "resume ", "Resume render of all maps for world . Skip already rendered tiles."), new CommandInfo("dynmap", "fullrender", "resume :", "Resume render of map of world . Skip already rendered tiles."), new CommandInfo("dynmap", "radiusrender", "", "Render at least block radius from your location on all maps."), new CommandInfo("dynmap", "radiusrender", " ", "Render at least block radius from your location on map ."), new CommandInfo("dynmap", "radiusrender", " ", "Render at least block radius from location , on world ."), new CommandInfo("dynmap", "radiusrender", " ", "Render at least block radius from location , on world on map ."), new CommandInfo("dynmap", "updaterender", "Render updates starting at your location on all maps."), new CommandInfo("dynmap", "updaterender", "", "Render updates starting at your location on map ."), new CommandInfo("dynmap", "updaterender", " ", "Render updates starting at location , on world for map ."), new CommandInfo("dynmap", "cancelrender", "Cancels any active renders on current world."), new CommandInfo("dynmap", "cancelrender", "", "Cancels any active renders of world ."), new CommandInfo("dynmap", "stats", "Show render statistics."), new CommandInfo("dynmap", "triggerstats", "Show render update trigger statistics."), new CommandInfo("dynmap", "resetstats", "Reset render statistics."), new CommandInfo("dynmap", "sendtoweb", "", "Send message to web users."), new CommandInfo("dynmap", "purgequeue", "Empty all pending tile updates from update queue."), new CommandInfo("dynmap", "purgequeue", "", "Empty all pending tile updates from update queue for world ."), new CommandInfo("dynmap", "purgemap", " ", "Delete all existing tiles for map on world ."), new CommandInfo("dynmap", "purgeworld", "", "Delete all existing directories for world ."), new CommandInfo("dynmap", "pause", "Show render pause state."), new CommandInfo("dynmap", "pause", "", "Set render pause state."), new CommandInfo("dynmap", "quiet", "Stop output from active jobs."), new CommandInfo("dynmap", "ids-for-ip", "", "Show player IDs that have logged in from address ."), new CommandInfo("dynmap", "ips-for-id", "", "Show IP addresses that have been used for player ."), new CommandInfo("dynmap", "add-id-for-ip", " ", "Associate player with IP address ."), new CommandInfo("dynmap", "del-id-for-ip", " ", "Disassociate player from IP address ."), new CommandInfo("dynmap", "webregister", "Start registration process for creating web login account"), new CommandInfo("dynmap", "webregister", "", "Start registration process for creating web login account for player "), new CommandInfo("dynmap", "version", "Return version information"), new CommandInfo("dmarker", "", "Manipulate map markers."), new CommandInfo("dmarker", "add", "