001package ezvcard.io.scribe; 002 003import java.net.URI; 004import java.net.URISyntaxException; 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.List; 008import java.util.regex.Matcher; 009import java.util.regex.Pattern; 010 011import com.github.mangstadt.vinnie.io.VObjectPropertyValues; 012 013import ezvcard.VCard; 014import ezvcard.VCardDataType; 015import ezvcard.VCardVersion; 016import ezvcard.io.CannotParseException; 017import ezvcard.io.ParseContext; 018import ezvcard.io.html.HCardElement; 019import ezvcard.io.json.JCardValue; 020import ezvcard.io.text.WriteContext; 021import ezvcard.io.xml.XCardElement; 022import ezvcard.parameter.VCardParameters; 023import ezvcard.property.Impp; 024 025/* 026 Copyright (c) 2012-2023, Michael Angstadt 027 All rights reserved. 028 029 Redistribution and use in source and binary forms, with or without 030 modification, are permitted provided that the following conditions are met: 031 032 1. Redistributions of source code must retain the above copyright notice, this 033 list of conditions and the following disclaimer. 034 2. Redistributions in binary form must reproduce the above copyright notice, 035 this list of conditions and the following disclaimer in the documentation 036 and/or other materials provided with the distribution. 037 038 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 039 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 040 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 041 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 042 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 043 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 044 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 045 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 046 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 047 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 048 */ 049 050/** 051 * Marshals {@link Impp} properties. 052 * @author Michael Angstadt 053 */ 054public class ImppScribe extends VCardPropertyScribe<Impp> { 055 public static final String AIM = "aim"; 056 public static final String ICQ = "icq"; 057 public static final String IRC = "irc"; 058 public static final String MSN = "msnim"; 059 public static final String SIP = "sip"; 060 public static final String SKYPE = "skype"; 061 public static final String XMPP = "xmpp"; 062 public static final String YAHOO = "ymsgr"; 063 064 public ImppScribe() { 065 super(Impp.class, "IMPP"); 066 } 067 068 @Override 069 protected void _prepareParameters(Impp property, VCardParameters copy, VCardVersion version, VCard vcard) { 070 handlePrefParam(property, copy, version, vcard); 071 } 072 073 @Override 074 protected VCardDataType _defaultDataType(VCardVersion version) { 075 return VCardDataType.URI; 076 } 077 078 @Override 079 protected String _writeText(Impp property, WriteContext context) { 080 return write(property); 081 } 082 083 @Override 084 protected Impp _parseText(String value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 085 value = VObjectPropertyValues.unescape(value); 086 return parse(value); 087 } 088 089 @Override 090 protected void _writeXml(Impp property, XCardElement parent) { 091 parent.append(VCardDataType.URI, write(property)); 092 } 093 094 @Override 095 protected Impp _parseXml(XCardElement element, VCardParameters parameters, ParseContext context) { 096 String value = element.first(VCardDataType.URI); 097 if (value != null) { 098 return parse(value); 099 } 100 101 throw missingXmlElements(VCardDataType.URI); 102 } 103 104 @Override 105 protected Impp _parseHtml(HCardElement element, ParseContext context) { 106 String href = element.attr("href"); 107 if (href.isEmpty()) { 108 href = element.value(); 109 } 110 111 try { 112 URI uri = parseHtmlLink(href); 113 if (uri == null) { 114 throw new IllegalArgumentException(); 115 } 116 return new Impp(uri); 117 } catch (IllegalArgumentException e) { 118 throw new CannotParseException(14, href); 119 } 120 } 121 122 @Override 123 protected JCardValue _writeJson(Impp property) { 124 return JCardValue.single(write(property)); 125 } 126 127 @Override 128 protected Impp _parseJson(JCardValue value, VCardDataType dataType, VCardParameters parameters, ParseContext context) { 129 return parse(value.asSingle()); 130 } 131 132 private String write(Impp property) { 133 URI uri = property.getUri(); 134 return (uri == null) ? "" : uri.toASCIIString(); 135 } 136 137 private Impp parse(String value) { 138 if (value == null || value.isEmpty()) { 139 return new Impp((URI) null); 140 } 141 142 try { 143 return new Impp(value); 144 } catch (IllegalArgumentException e) { 145 throw new CannotParseException(15, value, e.getMessage()); 146 } 147 } 148 149 /** 150 * List of recognized IM protocols that can be parsed from an HTML link 151 * (hCard). 152 */ 153 private static final List<HtmlLinkFormat> htmlLinkFormats; 154 static { 155 List<HtmlLinkFormat> list = new ArrayList<>(); 156 157 //http://en.wikipedia.org/wiki/AOL_Instant_Messenger#URI_scheme 158 list.add(new HtmlLinkFormat(AIM, "(goim|addbuddy)\\?.*?\\bscreenname=(.*?)(&|$)", 2, "goim?screenname=%s")); 159 160 //http://en.wikipedia.org/wiki/Yahoo!_Messenger#URI_scheme 161 list.add(new HtmlLinkFormat(YAHOO, "(sendim|addfriend|sendfile|call)\\?(.*)", 2, "sendim?%s")); 162 163 //http://developer.skype.com/skype-uri/skype-uri-ref-api 164 list.add(new HtmlLinkFormat(SKYPE, "(.*?)(\\?|$)", 1, "%s")); 165 166 //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/ 167 list.add(new HtmlLinkFormat(MSN, "(chat|add|voice|video)\\?contact=(.*?)(&|$)", 2, "chat?contact=%s")); 168 169 //http://www.tech-recipes.com/rx/1157/msn-messenger-msnim-hyperlink-command-codes/ 170 list.add(new HtmlLinkFormat(XMPP, "(.*?)(\\?|$)", 1, "%s?message")); 171 172 //http://forums.miranda-im.org/showthread.php?26589-Add-support-to-quot-icq-message-uin-12345-quot-web-links 173 list.add(new HtmlLinkFormat(ICQ, "message\\?uin=(\\d+)", 1, "message?uin=%s")); 174 175 //SIP: http://en.wikipedia.org/wiki/Session_Initiation_Protocol 176 //leave as-is 177 list.add(new HtmlLinkFormat(SIP)); 178 179 //IRC: http://stackoverflow.com/questions/11970897/how-do-i-open-a-query-window-using-the-irc-uri-scheme 180 //IRC handles are not globally unique, so leave as-is 181 list.add(new HtmlLinkFormat(IRC)); 182 183 htmlLinkFormats = Collections.unmodifiableList(list); 184 } 185 186 /** 187 * Parses an IM URI from an HTML link. 188 * @param linkUri the HTML link (e.g. "aim:goim?screenname=theuser") 189 * @return the IM URI or null if not recognized 190 */ 191 public URI parseHtmlLink(String linkUri) { 192 for (HtmlLinkFormat format : htmlLinkFormats) { 193 String handle = format.parseHandle(linkUri); 194 if (handle == null) { 195 continue; 196 } 197 198 try { 199 return new URI(format.getProtocol(), handle, null); 200 } catch (URISyntaxException e) { 201 throw new IllegalArgumentException(e); 202 } 203 } 204 return null; 205 } 206 207 /** 208 * Builds a URI suitable for use as a link on a webpage. 209 * @param property the property 210 * @return the link URI (e.g. "aim:goim?screenname=theuser") or null if the 211 * property has no URI 212 */ 213 public String writeHtmlLink(Impp property) { 214 URI uri = property.getUri(); 215 if (uri == null) { 216 return null; 217 } 218 219 String protocol = uri.getScheme(); 220 String handle = uri.getSchemeSpecificPart(); 221 222 for (HtmlLinkFormat format : htmlLinkFormats) { 223 if (protocol.equals(format.getProtocol())) { 224 return format.buildLink(handle); 225 } 226 } 227 return uri.toASCIIString(); 228 } 229 230 private static class HtmlLinkFormat { 231 private final Pattern parseRegex; 232 private final String protocol; 233 private final int handleGroup; 234 private final String linkFormat; 235 236 /** 237 * @param protocol the IM protocol (e.g. "aim") 238 */ 239 public HtmlLinkFormat(String protocol) { 240 this(protocol, "(.*)", 1, "%s"); 241 } 242 243 /** 244 * @param protocol the IM protocol (e.g. "aim") 245 * @param linkRegex the regular expression used to parse a link 246 * @param handleGroup the group number from the regular expression that 247 * contains the IM handle 248 * @param linkFormat the format string for building a link 249 */ 250 public HtmlLinkFormat(String protocol, String linkRegex, int handleGroup, String linkFormat) { 251 this.parseRegex = Pattern.compile('^' + protocol + ':' + linkRegex, Pattern.CASE_INSENSITIVE); 252 this.protocol = protocol; 253 this.handleGroup = handleGroup; 254 this.linkFormat = protocol + ':' + linkFormat; 255 } 256 257 /** 258 * Parses the IM handle out of a link. 259 * @param linkUri the link 260 * @return the IM handle or null if it can't be found 261 */ 262 public String parseHandle(String linkUri) { 263 Matcher m = parseRegex.matcher(linkUri); 264 return m.find() ? m.group(handleGroup) : null; 265 } 266 267 /** 268 * Builds a link for inclusion in a webpage. 269 * @param handle the IM handle 270 * @return the link 271 */ 272 public String buildLink(String handle) { 273 return String.format(linkFormat, handle); 274 } 275 276 /** 277 * Gets the protocol. 278 * @return the protocol (e.g. "aim") 279 */ 280 public String getProtocol() { 281 return protocol; 282 } 283 } 284}