Bytestream.java

  1. /**
  2.  *
  3.  * Copyright the original author or authors
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.bytestreams.socks5.packet;

  18. import java.net.InetAddress;
  19. import java.util.ArrayList;
  20. import java.util.Collections;
  21. import java.util.List;

  22. import javax.xml.namespace.QName;

  23. import org.jivesoftware.smack.packet.ExtensionElement;
  24. import org.jivesoftware.smack.packet.IQ;
  25. import org.jivesoftware.smack.util.InternetAddress;
  26. import org.jivesoftware.smack.util.Objects;
  27. import org.jivesoftware.smack.util.XmlStringBuilder;

  28. import org.jxmpp.jid.Jid;

  29. /**
  30.  * A stanza representing part of a SOCKS5 Bytestream negotiation.
  31.  *
  32.  * @author Alexander Wenckus
  33.  */
  34. public class Bytestream extends IQ {

  35.     public static final String ELEMENT = QUERY_ELEMENT;

  36.     /**
  37.      * The XMPP namespace of the SOCKS5 Bytestream.
  38.      */
  39.     public static final String NAMESPACE = "http://jabber.org/protocol/bytestreams";

  40.     private String sessionID;

  41.     private Mode mode = Mode.tcp;

  42.     private final List<StreamHost> streamHosts = new ArrayList<>();

  43.     private StreamHostUsed usedHost;

  44.     private Activate toActivate;

  45.     /**
  46.      * The default constructor.
  47.      */
  48.     public Bytestream() {
  49.         super(ELEMENT, NAMESPACE);
  50.     }

  51.     /**
  52.      * A constructor where the session ID can be specified.
  53.      *
  54.      * @param SID The session ID related to the negotiation.
  55.      * @see #setSessionID(String)
  56.      */
  57.     public Bytestream(final String SID) {
  58.         this();
  59.         setSessionID(SID);
  60.     }

  61.     /**
  62.      * Set the session ID related to the bytestream. The session ID is a unique identifier used to
  63.      * differentiate between stream negotiations.
  64.      *
  65.      * @param sessionID the unique session ID that identifies the transfer.
  66.      */
  67.     public void setSessionID(final String sessionID) {
  68.         this.sessionID = sessionID;
  69.     }

  70.     /**
  71.      * Returns the session ID related to the bytestream negotiation.
  72.      *
  73.      * @return Returns the session ID related to the bytestream negotiation.
  74.      * @see #setSessionID(String)
  75.      */
  76.     public String getSessionID() {
  77.         return sessionID;
  78.     }

  79.     /**
  80.      * Set the transport mode. This should be put in the initiation of the interaction.
  81.      *
  82.      * @param mode the transport mode, either UDP or TCP
  83.      * @see Mode
  84.      */
  85.     public void setMode(final Mode mode) {
  86.         this.mode = mode;
  87.     }

  88.     /**
  89.      * Returns the transport mode.
  90.      *
  91.      * @return Returns the transport mode.
  92.      * @see #setMode(Mode)
  93.      */
  94.     public Mode getMode() {
  95.         return mode;
  96.     }

  97.     /**
  98.      * Adds a potential stream host that the remote user can connect to to receive the file.
  99.      *
  100.      * @param JID The JID of the stream host.
  101.      * @param address The internet address of the stream host.
  102.      * @return The added stream host.
  103.      */
  104.     public StreamHost addStreamHost(final Jid JID, String address) {
  105.         return addStreamHost(JID, address, 0);
  106.     }

  107.     /**
  108.      * Adds a potential stream host that the remote user can connect to to receive the file.
  109.      *
  110.      * @param JID The JID of the stream host.
  111.      * @param address The internet address of the stream host.
  112.      * @param port The port on which the remote host is seeking connections.
  113.      * @return The added stream host.
  114.      */
  115.     public StreamHost addStreamHost(final Jid JID, String address, final int port) {
  116.         StreamHost host = new StreamHost(JID, address, port);
  117.         addStreamHost(host);

  118.         return host;
  119.     }

  120.     /**
  121.      * Adds a potential stream host that the remote user can transfer the file through.
  122.      *
  123.      * @param host The potential stream host.
  124.      */
  125.     public void addStreamHost(final StreamHost host) {
  126.         streamHosts.add(host);
  127.     }

  128.     /**
  129.      * Returns the list of stream hosts contained in the packet.
  130.      *
  131.      * @return Returns the list of stream hosts contained in the packet.
  132.      */
  133.     public List<StreamHost> getStreamHosts() {
  134.         return Collections.unmodifiableList(streamHosts);
  135.     }

  136.     /**
  137.      * Returns the stream host related to the given JID, or null if there is none.
  138.      *
  139.      * @param JID The JID of the desired stream host.
  140.      * @return Returns the stream host related to the given JID, or null if there is none.
  141.      */
  142.     public StreamHost getStreamHost(final Jid JID) {
  143.         if (JID == null) {
  144.             return null;
  145.         }
  146.         for (StreamHost host : streamHosts) {
  147.             if (host.getJID().equals(JID)) {
  148.                 return host;
  149.             }
  150.         }

  151.         return null;
  152.     }

  153.     /**
  154.      * Returns the count of stream hosts contained in this packet.
  155.      *
  156.      * @return Returns the count of stream hosts contained in this packet.
  157.      */
  158.     public int countStreamHosts() {
  159.         return streamHosts.size();
  160.     }

  161.     /**
  162.      * Upon connecting to the stream host the target of the stream replies to the initiator with the
  163.      * JID of the SOCKS5 host that they used.
  164.      *
  165.      * @param JID The JID of the used host.
  166.      */
  167.     public void setUsedHost(final Jid JID) {
  168.         this.usedHost = new StreamHostUsed(JID);
  169.     }

  170.     /**
  171.      * Returns the SOCKS5 host connected to by the remote user.
  172.      *
  173.      * @return Returns the SOCKS5 host connected to by the remote user.
  174.      */
  175.     public StreamHostUsed getUsedHost() {
  176.         return usedHost;
  177.     }

  178.     /**
  179.      * Returns the activate element of the stanza sent to the proxy host to verify the identity of
  180.      * the initiator and match them to the appropriate stream.
  181.      *
  182.      * @return Returns the activate element of the stanza sent to the proxy host to verify the
  183.      *         identity of the initiator and match them to the appropriate stream.
  184.      */
  185.     public Activate getToActivate() {
  186.         return toActivate;
  187.     }

  188.     /**
  189.      * Upon the response from the target of the used host the activate stanza is sent to the SOCKS5
  190.      * proxy. The proxy will activate the stream or return an error after verifying the identity of
  191.      * the initiator, using the activate packet.
  192.      *
  193.      * @param targetID The JID of the target of the file transfer.
  194.      */
  195.     public void setToActivate(final Jid targetID) {
  196.         this.toActivate = new Activate(targetID);
  197.     }

  198.     @Override
  199.     protected IQChildElementXmlStringBuilder getIQChildElementBuilder(IQChildElementXmlStringBuilder xml) {
  200.         switch (getType()) {
  201.         case set:
  202.             xml.optAttribute("sid", getSessionID());
  203.             xml.optAttribute("mode", getMode());
  204.             xml.rightAngleBracket();
  205.             if (getToActivate() == null) {
  206.                 for (StreamHost streamHost : getStreamHosts()) {
  207.                     xml.append(streamHost.toXML());
  208.                 }
  209.             }
  210.             else {
  211.                 xml.append(getToActivate().toXML());
  212.             }
  213.             break;
  214.         case result:
  215.             xml.rightAngleBracket();
  216.             xml.optAppend(getUsedHost());
  217.             // TODO Bytestream can include either used host *or* streamHosts. Never both. This should be ensured by the
  218.             // constructions mechanisms of Bytestream
  219.             // A result from the server can also contain stream hosts
  220.             for (StreamHost host : streamHosts) {
  221.                 xml.append(host.toXML());
  222.             }
  223.             break;
  224.         case get:
  225.             xml.setEmptyElement();
  226.             break;
  227.         default:
  228.             throw new IllegalStateException();
  229.         }

  230.         return xml;
  231.     }

  232.     private abstract static class BytestreamExtensionElement implements ExtensionElement {
  233.         @Override
  234.         public final String getNamespace() {
  235.             return NAMESPACE;
  236.         }
  237.     }

  238.     /**
  239.      * Stanza extension that represents a potential SOCKS5 proxy for the file transfer. Stream hosts
  240.      * are forwarded to the target of the file transfer who then chooses and connects to one.
  241.      *
  242.      * @author Alexander Wenckus
  243.      */
  244.     public static class StreamHost extends BytestreamExtensionElement {

  245.         public static final String ELEMENT = "streamhost";
  246.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  247.         private final Jid jid;

  248.         private final InternetAddress address;

  249.         private final int port;

  250.         public StreamHost(Jid jid, String address) {
  251.             this(jid, address, 0);
  252.         }

  253.         /**
  254.          * Default constructor.
  255.          *
  256.          * @param jid The JID of the stream host.
  257.          * @param address The internet address of the stream host.
  258.          * @param port port of the stream host.
  259.          */
  260.         public StreamHost(final Jid jid, final String address, int port) {
  261.             this(jid, InternetAddress.fromIgnoringZoneId(address), port);
  262.         }

  263.         public StreamHost(Jid jid, InetAddress address, int port) {
  264.             this(jid, InternetAddress.from(address), port);
  265.         }

  266.         /**
  267.          * Stream Host constructor.
  268.          *
  269.          * @param jid The JID of the stream host.
  270.          * @param address The internet address of the stream host.
  271.          * @param port port of the stream host.
  272.          */
  273.         public StreamHost(Jid jid, InternetAddress address, int port) {
  274.             this.jid = Objects.requireNonNull(jid, "StreamHost JID must not be null");
  275.             this.address = Objects.requireNonNull(address);
  276.             this.port = port;
  277.         }

  278.         /**
  279.          * Returns the JID of the stream host.
  280.          *
  281.          * @return Returns the JID of the stream host.
  282.          */
  283.         public Jid getJID() {
  284.             return jid;
  285.         }

  286.         /**
  287.          * Returns the internet address of the stream host.
  288.          *
  289.          * @return Returns the internet address of the stream host.
  290.          */
  291.         public InternetAddress getAddress() {
  292.             return address;
  293.         }

  294.         /**
  295.          * Returns the port on which the potential stream host would accept the connection.
  296.          *
  297.          * @return Returns the port on which the potential stream host would accept the connection.
  298.          */
  299.         public int getPort() {
  300.             return port;
  301.         }

  302.         @Override
  303.         public String getElementName() {
  304.             return QNAME.getLocalPart();
  305.         }

  306.         @Override
  307.         public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
  308.             XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
  309.             xml.attribute("jid", getJID());
  310.             xml.attribute("host", address);
  311.             if (getPort() != 0) {
  312.                 xml.attribute("port", Integer.toString(getPort()));
  313.             } else {
  314.                 xml.attribute("zeroconf", "_jabber.bytestreams");
  315.             }
  316.             xml.closeEmptyElement();
  317.             return xml;
  318.         }

  319.         @Override
  320.         public String toString() {
  321.             return "SOCKS5 Stream Host: " + jid + "[" + address + ":" + port + "]";
  322.         }
  323.     }

  324.     /**
  325.      * After selected a SOCKS5 stream host and successfully connecting, the target of the file
  326.      * transfer returns a byte stream stanza with the stream host used extension.
  327.      *
  328.      * @author Alexander Wenckus
  329.      */
  330.     public static class StreamHostUsed extends BytestreamExtensionElement {

  331.         public static final String ELEMENT = "streamhost-used";
  332.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  333.         private final Jid jid;

  334.         /**
  335.          * Default constructor.
  336.          *
  337.          * @param jid The JID of the selected stream host.
  338.          */
  339.         public StreamHostUsed(final Jid jid) {
  340.             this.jid = jid;
  341.         }

  342.         /**
  343.          * Returns the JID of the selected stream host.
  344.          *
  345.          * @return Returns the JID of the selected stream host.
  346.          */
  347.         public Jid getJID() {
  348.             return jid;
  349.         }

  350.         @Override
  351.         public String getElementName() {
  352.             return QNAME.getLocalPart();
  353.         }

  354.         @Override
  355.         public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
  356.             XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
  357.             xml.attribute("jid", getJID());
  358.             xml.closeEmptyElement();
  359.             return xml;
  360.         }
  361.     }

  362.     /**
  363.      * The stanza sent by the stream initiator to the stream proxy to activate the connection.
  364.      *
  365.      * @author Alexander Wenckus
  366.      */
  367.     public static class Activate extends BytestreamExtensionElement {

  368.         public static final String ELEMENT = "activate";
  369.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  370.         private final Jid target;

  371.         /**
  372.          * Default constructor specifying the target of the stream.
  373.          *
  374.          * @param target The target of the stream.
  375.          */
  376.         public Activate(final Jid target) {
  377.             this.target = target;
  378.         }

  379.         /**
  380.          * Returns the target of the activation.
  381.          *
  382.          * @return Returns the target of the activation.
  383.          */
  384.         public Jid getTarget() {
  385.             return target;
  386.         }

  387.         @Override
  388.         public String getElementName() {
  389.             return QNAME.getLocalPart();
  390.         }

  391.         @Override
  392.         public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
  393.             XmlStringBuilder xml = new XmlStringBuilder(this, enclosingNamespace);
  394.             xml.rightAngleBracket();
  395.             xml.escape(getTarget());
  396.             xml.closeElement(this);
  397.             return xml;
  398.         }

  399.     }

  400.     /**
  401.      * The stream can be either a TCP stream or a UDP stream.
  402.      *
  403.      * @author Alexander Wenckus
  404.      */
  405.     public enum Mode {

  406.         /**
  407.          * A TCP based stream.
  408.          */
  409.         tcp,

  410.         /**
  411.          * A UDP based stream.
  412.          */
  413.         udp;

  414.         public static Mode fromName(String name) {
  415.             Mode mode;
  416.             try {
  417.                 mode = Mode.valueOf(name);
  418.             }
  419.             catch (Exception ex) {
  420.                 mode = tcp;
  421.             }

  422.             return mode;
  423.         }
  424.     }
  425. }