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}