diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..947ef88 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c024495..27bdb36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +## [0.14.0] - 2021-01-05 +### Added +- Parameter to disable host validation in server. + ## [0.13.0] - 2020-09-23 ### Changed - Update author and URLs to indicate new project home. @@ -37,7 +41,8 @@ ### Changed - Property, Action, and Event description now use `links` rather than `href`. - [Spec PR](https://github.com/WebThingsIO/wot/pull/119) -[Unreleased]: https://github.com/WebThingsIO/webthing-java/compare/v0.13.0...HEAD +[Unreleased]: https://github.com/WebThingsIO/webthing-java/compare/v0.14.0...HEAD +[0.14.0]: https://github.com/WebThingsIO/webthing-java/compare/v0.13.0...v0.14.0 [0.13.0]: https://github.com/WebThingsIO/webthing-java/compare/v0.12.3...v0.13.0 [0.12.3]: https://github.com/WebThingsIO/webthing-java/compare/v0.12.2...v0.12.3 [0.12.2]: https://github.com/WebThingsIO/webthing-java/compare/v0.12.1...v0.12.2 diff --git a/README.md b/README.md index 86b5d09..10b70f7 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Add the following dependency to your project: io.webthings webthing - 0.13.0 + 0.14.0 ``` @@ -29,7 +29,7 @@ Add the following dependency to your project: ```gradle dependencies { runtime( - [group: 'io.webthings', name: 'webthing', version: '0.13.0'], + [group: 'io.webthings', name: 'webthing', version: '0.14.0'], ) } ``` diff --git a/pom.xml b/pom.xml index d4305f5..a6d3ad4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.webthings webthing - 0.13.0 + 0.14.0 WebThing Implementation of an HTTP Web Thing. diff --git a/src/main/java/io/webthings/webthing/Action.java b/src/main/java/io/webthings/webthing/Action.java index 927cc52..c9fff13 100644 --- a/src/main/java/io/webthings/webthing/Action.java +++ b/src/main/java/io/webthings/webthing/Action.java @@ -10,14 +10,14 @@ * An Action represents an individual action on a thing. */ public class Action { - private String id; - private Thing thing; - private String name; - private JSONObject input; + private final String id; + private final Thing thing; + private final String name; + private final JSONObject input; private String hrefPrefix; - private String href; + private final String href; private String status; - private String timeRequested; + private final String timeRequested; private String timeCompleted; /** diff --git a/src/main/java/io/webthings/webthing/Event.java b/src/main/java/io/webthings/webthing/Event.java index 52331ea..5c596fe 100644 --- a/src/main/java/io/webthings/webthing/Event.java +++ b/src/main/java/io/webthings/webthing/Event.java @@ -12,10 +12,10 @@ * @param The type of the event data. */ public class Event { - private Thing thing; - private String name; - private T data; - private String time; + private final Thing thing; + private final String name; + private final T data; + private final String time; /** * Initialize the object. diff --git a/src/main/java/io/webthings/webthing/Property.java b/src/main/java/io/webthings/webthing/Property.java index 30fa205..17883b3 100644 --- a/src/main/java/io/webthings/webthing/Property.java +++ b/src/main/java/io/webthings/webthing/Property.java @@ -8,6 +8,7 @@ import org.everit.json.schema.loader.SchemaLoader; import org.json.JSONArray; import org.json.JSONObject; + import io.webthings.webthing.errors.PropertyError; /** @@ -16,12 +17,12 @@ * @param The type of the property value. */ public class Property { - private Thing thing; - private String name; + private final Thing thing; + private final String name; private String hrefPrefix; - private String href; - private JSONObject metadata; - private Value value; + private final String href; + private final JSONObject metadata; + private final Value value; /** * Initialize the object. diff --git a/src/main/java/io/webthings/webthing/Thing.java b/src/main/java/io/webthings/webthing/Thing.java index acf7d24..40661c4 100644 --- a/src/main/java/io/webthings/webthing/Thing.java +++ b/src/main/java/io/webthings/webthing/Thing.java @@ -9,7 +9,6 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import io.webthings.webthing.errors.PropertyError; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; @@ -20,21 +19,23 @@ import java.util.Map; import java.util.Set; +import io.webthings.webthing.errors.PropertyError; + /** * A Web Thing. */ public class Thing { - private String id; - private String context; - private JSONArray type; - private String title; - private String description; - private Map properties; - private Map availableActions; - private Map availableEvents; - private Map> actions; - private List events; - private Set subscribers; + private final String id; + private final String context; + private final JSONArray type; + private final String title; + private final String description; + private final Map properties; + private final Map availableActions; + private final Map availableEvents; + private final Map> actions; + private final List events; + private final Set subscribers; private String hrefPrefix; private String uiHref; @@ -200,15 +201,10 @@ public void setUiHref(String href) { public void setHrefPrefix(String prefix) { this.hrefPrefix = prefix; - this.properties.forEach((name, value) -> { - value.setHrefPrefix(prefix); - }); + this.properties.forEach((name, value) -> value.setHrefPrefix(prefix)); - this.actions.forEach((actionName, list) -> { - list.forEach((action) -> { - action.setHrefPrefix(prefix); - }); - }); + this.actions.forEach((actionName, list) -> list.forEach((action) -> action + .setHrefPrefix(prefix))); } /** @@ -268,6 +264,7 @@ public JSONObject getPropertyDescriptions() { try { obj.put(name, value.asPropertyDescription()); } catch (JSONException e) { + // pass } }); @@ -284,15 +281,11 @@ public JSONArray getActionDescriptions(String actionName) { JSONArray array = new JSONArray(); if (actionName == null) { - this.actions.forEach((name, list) -> { - list.forEach((action) -> { - array.put(action.asActionDescription()); - }); - }); + this.actions.forEach((name, list) -> list.forEach((action) -> array.put( + action.asActionDescription()))); } else if (this.actions.containsKey(actionName)) { - this.actions.get(actionName).forEach((action) -> { - array.put(action.asActionDescription()); - }); + this.actions.get(actionName) + .forEach((action) -> array.put(action.asActionDescription())); } return array; @@ -308,9 +301,7 @@ public JSONArray getEventDescriptions(String eventName) { JSONArray array = new JSONArray(); if (eventName == null) { - this.events.forEach((event) -> { - array.put(event.asEventDescription()); - }); + this.events.forEach((event) -> array.put(event.asEventDescription())); } else { this.events.forEach((event) -> { if (event.getName().equals(eventName)) { @@ -338,9 +329,7 @@ public void addProperty(Property property) { * @param property Property to remove. */ public void removeProperty(Property property) { - if (this.properties.containsKey(property.getName())) { - this.properties.remove(property.getName()); - } + this.properties.remove(property.getName()); } /** @@ -380,9 +369,8 @@ public T getProperty(String propertyName) { */ public JSONObject getProperties() { JSONObject properties = new JSONObject(); - this.properties.forEach((name, property) -> { - properties.put(name, property.getValue()); - }); + this.properties.forEach((name, property) -> properties.put(name, + property.getValue())); return properties; } @@ -427,8 +415,7 @@ public Action getAction(String actionName, String actionId) { } List actions = this.actions.get(actionName); - for (int i = 0; i < actions.size(); ++i) { - Action action = actions.get(i); + for (Action action : actions) { if (actionId.equals(action.getId())) { return action; } @@ -547,13 +534,11 @@ public void addSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) { * @param ws The websocket */ public void removeSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) { - if (this.subscribers.contains(ws)) { - this.subscribers.remove(ws); - } + this.subscribers.remove(ws); - this.availableEvents.forEach((name, value) -> { - this.removeEventSubscriber(name, ws); - }); + this.availableEvents.forEach((name, value) -> this.removeEventSubscriber( + name, + ws)); } /** @@ -597,9 +582,7 @@ public void propertyNotify(Property property) { String message = json.toString(); - this.subscribers.forEach((subscriber) -> { - subscriber.sendMessage(message); - }); + this.subscribers.forEach((subscriber) -> subscriber.sendMessage(message)); } /** @@ -615,9 +598,7 @@ public void actionNotify(Action action) { String message = json.toString(); - this.subscribers.forEach((subscriber) -> { - subscriber.sendMessage(message); - }); + this.subscribers.forEach((subscriber) -> subscriber.sendMessage(message)); } /** @@ -640,17 +621,17 @@ public void eventNotify(Event event) { this.availableEvents.get(eventName) .getSubscribers() - .forEach((subscriber) -> { - subscriber.sendMessage(message); - }); + .forEach((subscriber) -> subscriber.sendMessage( + message)); } /** * Class to describe an event available for subscription. */ - private class AvailableEvent { - private JSONObject metadata; - private Set subscribers; + private static class AvailableEvent { + private final JSONObject metadata; + private final Set + subscribers; /** * Initialize the object. @@ -686,9 +667,7 @@ public void addSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) { * @param ws The websocket */ public void removeSubscriber(WebThingServer.ThingHandler.ThingWebSocket ws) { - if (this.subscribers.contains(ws)) { - this.subscribers.remove(ws); - } + this.subscribers.remove(ws); } /** @@ -704,10 +683,10 @@ public Set getSubscribers() { /** * Class to describe an action available to be taken. */ - private class AvailableAction { - private JSONObject metadata; - private Class cls; - private Schema schema; + private static class AvailableAction { + private final JSONObject metadata; + private final Class cls; + private final Schema schema; /** * Initialize the object. diff --git a/src/main/java/io/webthings/webthing/WebThingServer.java b/src/main/java/io/webthings/webthing/WebThingServer.java index 3e93fb9..5b15710 100644 --- a/src/main/java/io/webthings/webthing/WebThingServer.java +++ b/src/main/java/io/webthings/webthing/WebThingServer.java @@ -6,7 +6,6 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import io.webthings.webthing.errors.PropertyError; import java.io.IOException; import java.net.InetAddress; @@ -25,6 +24,7 @@ import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoWSD; import fi.iki.elonen.router.RouterNanoHTTPD; +import io.webthings.webthing.errors.PropertyError; /** * Server to represent a Web Thing over HTTP. @@ -32,13 +32,14 @@ public class WebThingServer extends RouterNanoHTTPD { private static final int SOCKET_READ_TIMEOUT = 30 * 1000; private static final int WEBSOCKET_PING_INTERVAL = 20 * 1000; - private int port; - private ThingsType things; - private String name; + private final int port; + private final ThingsType things; + private final String name; private String hostname; - private String basePath; - private List hosts; - private boolean isTls; + private final boolean disableHostValidation; + private final String basePath; + private final List hosts; + private final boolean isTls; private JmDNS jmdns; /** @@ -50,7 +51,7 @@ public class WebThingServer extends RouterNanoHTTPD { */ public WebThingServer(ThingsType things) throws IOException, NullPointerException { - this(things, 80, null, null, null, "/"); + this(things, 80, null, null, null, "/", false); } /** @@ -63,7 +64,7 @@ public WebThingServer(ThingsType things) */ public WebThingServer(ThingsType things, int port) throws IOException, NullPointerException { - this(things, port, null, null, null, "/"); + this(things, port, null, null, null, "/", false); } /** @@ -77,7 +78,7 @@ public WebThingServer(ThingsType things, int port) */ public WebThingServer(ThingsType things, int port, String hostname) throws IOException, NullPointerException { - this(things, port, hostname, null, null, "/"); + this(things, port, hostname, null, null, "/", false); } /** @@ -95,7 +96,7 @@ public WebThingServer(ThingsType things, String hostname, SSLOptions sslOptions) throws IOException, NullPointerException { - this(things, port, hostname, sslOptions, null, "/"); + this(things, port, hostname, sslOptions, null, "/", false); } /** @@ -115,7 +116,7 @@ public WebThingServer(ThingsType things, SSLOptions sslOptions, List additionalRoutes) throws IOException, NullPointerException { - this(things, port, hostname, sslOptions, additionalRoutes, "/"); + this(things, port, hostname, sslOptions, additionalRoutes, "/", false); } /** @@ -137,6 +138,39 @@ public WebThingServer(ThingsType things, List additionalRoutes, String basePath) throws IOException, NullPointerException { + this(things, + port, + hostname, + sslOptions, + additionalRoutes, + basePath, + false); + } + + /** + * Initialize the WebThingServer. + * + * @param things List of Things managed by this server + * @param port Port to listen on + * @param hostname Host name, i.e. mything.com + * @param sslOptions SSL options to pass to the NanoHTTPD server + * @param additionalRoutes List of additional routes to add to the + * server + * @param basePath Base URL path to use, rather than '/' + * @param disableHostValidation Whether or not to disable host validation -- + * note that this can lead to DNS rebinding + * attacks + * @throws IOException If server fails to bind. + * @throws NullPointerException If something bad happened. + */ + public WebThingServer(ThingsType things, + int port, + String hostname, + SSLOptions sslOptions, + List additionalRoutes, + String basePath, + boolean disableHostValidation) + throws IOException, NullPointerException { super(port); this.port = port; this.things = things; @@ -144,6 +178,7 @@ public WebThingServer(ThingsType things, this.isTls = sslOptions != null; this.hostname = hostname; this.basePath = basePath.replaceAll("/$", ""); + this.disableHostValidation = disableHostValidation; this.hosts = new ArrayList<>(); this.hosts.add("localhost"); @@ -185,47 +220,56 @@ public WebThingServer(ThingsType things, PropertyHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/:thingId/properties", PropertiesHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/:thingId/actions/:actionName/:actionId", ActionIDHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/:thingId/actions/:actionName", ActionHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/:thingId/actions", ActionsHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/:thingId/events/:eventName", EventHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/:thingId/events", EventsHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/:thingId", ThingHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/", ThingsHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); } else { things.getThing(0).setHrefPrefix(this.basePath); @@ -234,42 +278,50 @@ public WebThingServer(ThingsType things, PropertyHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/properties", PropertiesHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/actions/:actionName/:actionId", ActionIDHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/actions/:actionName", ActionHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/actions", ActionsHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/events/:eventName", EventHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/events", EventsHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); addRoute(this.basePath + "/", ThingHandler.class, this.things, this.hosts, - this.isTls); + this.isTls, + this.disableHostValidation); } setNotFoundHandler(Error404UriHandler.class); @@ -310,7 +362,7 @@ public void start(boolean daemon) throws IOException { txt); this.jmdns.registerService(serviceInfo); - super.start(this.SOCKET_READ_TIMEOUT, daemon); + super.start(WebThingServer.SOCKET_READ_TIMEOUT, daemon); } /** @@ -349,7 +401,7 @@ interface ThingsType { * Thread to perform an action. */ private static class ActionRunner extends Thread { - private Action action; + private final Action action; /** * Initialize the object. @@ -372,9 +424,9 @@ public void run() { * Class to hold options required by SSL server. */ public static class SSLOptions { - private String path; - private String password; - private String[] protocols; + private final String path; + private final String password; + private final String[] protocols; /** * Initialize the object. @@ -554,13 +606,12 @@ public String getUriParam(String uri, int index) { * @return The parsed JSON body as a JSONObject, or null on error. */ public JSONObject parseBody(IHTTPSession session) { - Integer contentLength = Integer.parseInt(session.getHeaders() - .get("content-length")); + int contentLength = Integer.parseInt(session.getHeaders() + .get("content-length")); byte[] buffer = new byte[contentLength]; try { session.getInputStream().read(buffer, 0, contentLength); - JSONObject obj = new JSONObject(new String(buffer)); - return obj; + return new JSONObject(new String(buffer)); } catch (IOException e) { return null; } @@ -596,14 +647,17 @@ public Thing getThing(UriResource uriResource, IHTTPSession session) { */ public boolean validateHost(UriResource uriResource, IHTTPSession session) { - List hosts = uriResource.initParameter(1, List.class); + boolean disableHostValidation = + uriResource.initParameter(3, Boolean.class); - String host = session.getHeaders().get("host"); - if (host != null && hosts.contains(host.toLowerCase())) { + if (disableHostValidation) { return true; } - return false; + List hosts = uriResource.initParameter(1, List.class); + + String host = session.getHeaders().get("host"); + return (host != null && hosts.contains(host.toLowerCase())); } /** @@ -987,6 +1041,7 @@ public void sendMessage(String message) { try { this.send(message); } catch (IOException e) { + // pass } } } @@ -1666,7 +1721,7 @@ public Response get(UriResource uriResource, * A container for a single thing. */ public static class SingleThing implements ThingsType { - private Thing thing; + private final Thing thing; /** * Initialize the container. @@ -1711,8 +1766,8 @@ public String getName() { * A container for multiple things. */ public static class MultipleThings implements ThingsType { - private List things; - private String name; + private final List things; + private final String name; /** * Initialize the container. diff --git a/src/main/java/io/webthings/webthing/example/MultipleThings.java b/src/main/java/io/webthings/webthing/example/MultipleThings.java index 8ccb291..74f7324 100644 --- a/src/main/java/io/webthings/webthing/example/MultipleThings.java +++ b/src/main/java/io/webthings/webthing/example/MultipleThings.java @@ -2,6 +2,13 @@ import org.json.JSONArray; import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; + import io.webthings.webthing.Action; import io.webthings.webthing.Event; import io.webthings.webthing.Property; @@ -10,12 +17,6 @@ import io.webthings.webthing.WebThingServer; import io.webthings.webthing.errors.PropertyError; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; - public class MultipleThings { public static void main(String[] args) { // Create a thing that represents a dimmable light @@ -36,15 +37,11 @@ public static void main(String[] args) { "LightAndTempDevice"), 8888); - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - server.stop(); - } - }); + Runtime.getRuntime().addShutdownHook(new Thread(server::stop)); server.start(false); } catch (IOException e) { - System.out.println(e); + System.out.println(e.toString()); System.exit(1); } } @@ -148,12 +145,14 @@ public void performAction() { try { Thread.sleep(input.getInt("duration")); } catch (InterruptedException e) { + // pass } try { thing.setProperty("brightness", input.getInt("brightness")); thing.addEvent(new OverheatedEvent(thing, 102)); } catch (PropertyError e) { + // pass } } } diff --git a/src/main/java/io/webthings/webthing/example/SingleThing.java b/src/main/java/io/webthings/webthing/example/SingleThing.java index 763c43f..c2b0fa9 100644 --- a/src/main/java/io/webthings/webthing/example/SingleThing.java +++ b/src/main/java/io/webthings/webthing/example/SingleThing.java @@ -2,6 +2,11 @@ import org.json.JSONArray; import org.json.JSONObject; + +import java.io.IOException; +import java.util.Arrays; +import java.util.UUID; + import io.webthings.webthing.Action; import io.webthings.webthing.Event; import io.webthings.webthing.Property; @@ -10,10 +15,6 @@ import io.webthings.webthing.WebThingServer; import io.webthings.webthing.errors.PropertyError; -import java.io.IOException; -import java.util.Arrays; -import java.util.UUID; - public class SingleThing { public static Thing makeThing() { Thing thing = new Thing("urn:dev:ops:my-lamp-1234", @@ -89,15 +90,11 @@ public static void main(String[] args) { server = new WebThingServer(new WebThingServer.SingleThing(thing), 8888); - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - server.stop(); - } - }); + Runtime.getRuntime().addShutdownHook(new Thread(server::stop)); server.start(false); } catch (IOException e) { - System.out.println(e); + System.out.println(e.toString()); System.exit(1); } } @@ -120,12 +117,14 @@ public void performAction() { try { Thread.sleep(input.getInt("duration")); } catch (InterruptedException e) { + // pass } try { thing.setProperty("brightness", input.getInt("brightness")); thing.addEvent(new OverheatedEvent(thing, 102)); } catch (PropertyError e) { + // pass } } }