001/**
002 *
003 * Copyright 2020 Aditya Borikar.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smack.altconnections;
018
019import java.io.FileNotFoundException;
020import java.io.IOException;
021import java.io.InputStream;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.net.URLConnection;
026import java.util.ArrayList;
027import java.util.List;
028
029import org.jivesoftware.smack.util.PacketParserUtils;
030import org.jivesoftware.smack.util.ParserUtils;
031import org.jivesoftware.smack.xml.XmlPullParser;
032import org.jivesoftware.smack.xml.XmlPullParserException;
033
034import org.jxmpp.jid.DomainBareJid;
035
036/**
037 * The HTTP lookup method uses web host metadata to list the URIs of alternative connection methods.
038 *
039 * <p>In order to obtain host-meta XRD element from the host in the form of an <i>InputStream</i>,
040 * use {@link #getXrdStream(DomainBareJid)} method. To obtain endpoints for Bosh or Websocket
041 * connection endpoints from host, use {@link LinkRelation#BOSH} and {@link LinkRelation#WEBSOCKET}
042 * respectively with the {@link #lookup(DomainBareJid, LinkRelation)} method. In case one is looking
043 * for endpoints described by other than BOSH or Websocket LinkRelation, use the more flexible
044 * {@link #lookup(DomainBareJid, String)} method.</p>
045 * Example:<br>
046 * <pre>
047 * {@code
048 * DomainBareJid xmppServerAddress = JidCreate.domainBareFrom("example.org");
049 * List<URI> endpoints = HttpLookupMethod.lookup(xmppServiceAddress, LinkRelation.WEBSOCKET);
050 * }
051 * </pre>
052 * @see <a href="https://xmpp.org/extensions/xep-0156.html#http">
053 *     HTTP Lookup Method from XEP-0156.
054 *     </a>
055 */
056public final class HttpLookupMethod {
057    private static final String XRD_NAMESPACE = "http://docs.oasis-open.org/ns/xri/xrd-1.0";
058
059    /**
060     * Specifies a link relation for the selected type of connection.
061     */
062    public enum LinkRelation {
063        /**
064         * Selects link relation attribute as "urn:xmpp:alt-connections:xbosh".
065         */
066        BOSH("urn:xmpp:alt-connections:xbosh"),
067        /**
068         * Selects link relation attribute as "urn:xmpp:alt-connections:websocket".
069         */
070        WEBSOCKET("urn:xmpp:alt-connections:websocket");
071        private final String attribute;
072        LinkRelation(String relAttribute) {
073            this.attribute = relAttribute;
074        }
075    }
076
077    /**
078     * Get remote endpoints for the given LinkRelation from host.
079     *
080     * @param xmppServiceAddress address of host
081     * @param relation LinkRelation as a string specifying type of connection
082     * @return list of endpoints
083     * @throws IOException exception due to input/output operations
084     * @throws XmlPullParserException exception encountered during XML parsing
085     * @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
086     */
087    public static List<URI> lookup(DomainBareJid xmppServiceAddress, String relation) throws IOException, XmlPullParserException, URISyntaxException {
088        try (InputStream inputStream = getXrdStream(xmppServiceAddress)) {
089            XmlPullParser xmlPullParser = PacketParserUtils.getParserFor(inputStream);
090            List<URI> endpoints = parseXrdLinkReferencesFor(xmlPullParser, relation);
091            return endpoints;
092        }
093    }
094
095    /**
096     * Get remote endpoints for the given LinkRelation from host.
097     *
098     * @param xmppServiceAddress address of host
099     * @param relation {@link LinkRelation} specifying type of connection
100     * @return list of endpoints
101     * @throws IOException exception due to input/output operations
102     * @throws XmlPullParserException exception encountered during XML parsing
103     * @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
104     */
105    public static List<URI> lookup(DomainBareJid xmppServiceAddress, LinkRelation relation) throws IOException, XmlPullParserException, URISyntaxException {
106        return lookup(xmppServiceAddress, relation.attribute);
107    }
108
109    /**
110     * Constructs a HTTP connection with the host specified by the DomainBareJid
111     * and retrieves XRD element in the form of an InputStream. The method will
112     * throw a {@link FileNotFoundException} if host-meta isn't published.
113     *
114     * @param xmppServiceAddress address of host
115     * @return InputStream containing XRD element
116     * @throws IOException exception due to input/output operations
117     */
118    public static InputStream getXrdStream(DomainBareJid xmppServiceAddress) throws IOException {
119        final String metadataUrl = "https://" + xmppServiceAddress + "/.well-known/host-meta";
120        final URL putUrl = new URL(metadataUrl);
121        final URLConnection urlConnection = putUrl.openConnection();
122        return  urlConnection.getInputStream();
123    }
124
125    /**
126     * Get remote endpoints for the provided LinkRelation from provided XmlPullParser.
127     *
128     * @param parser XmlPullParser that contains LinkRelations
129     * @param relation type of endpoints specified by the given LinkRelation
130     * @return list of endpoints
131     * @throws IOException exception due to input/output operations
132     * @throws XmlPullParserException exception encountered during XML parsing
133     * @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
134     */
135    public static List<URI> parseXrdLinkReferencesFor(XmlPullParser parser, String relation) throws IOException, XmlPullParserException, URISyntaxException {
136        ParserUtils.forwardToStartElement(parser);
137        List<URI> uriList = new ArrayList<>();
138        int initialDepth = parser.getDepth();
139
140        loop: while (true) {
141            XmlPullParser.TagEvent tag = parser.nextTag();
142            switch (tag) {
143            case START_ELEMENT:
144                String name = parser.getName();
145                String namespace = parser.getNamespace();
146                String rel = parser.getAttributeValue("rel");
147
148                if (!namespace.equals(XRD_NAMESPACE) || !name.equals("Link") || !rel.equals(relation)) {
149                    continue loop;
150                }
151                String endpointUri = parser.getAttributeValue("href");
152                URI uri = new URI(endpointUri);
153                uriList.add(uri);
154                break;
155            case END_ELEMENT:
156                if (parser.getDepth() == initialDepth) {
157                    break loop;
158                }
159                break;
160            }
161        }
162        return uriList;
163    }
164
165    /**
166     * Get remote endpoints for the provided LinkRelation from provided XmlPullParser.
167     *
168     * @param parser XmlPullParser that contains LinkRelations
169     * @param relation type of endpoints specified by the given LinkRelation
170     * @return list of endpoints
171     * @throws IOException exception due to input/output operations
172     * @throws XmlPullParserException exception encountered during XML parsing
173     * @throws URISyntaxException exception to indicate that a string could not be parsed as a URI reference
174     */
175    public static List<URI> parseXrdLinkReferencesFor(XmlPullParser parser, LinkRelation relation) throws IOException, XmlPullParserException, URISyntaxException {
176        return parseXrdLinkReferencesFor(parser, relation.attribute);
177    }
178}