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}