/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.camel.component.irc; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.apache.camel.RuntimeCamelException; import org.apache.camel.spi.Metadata; import org.apache.camel.spi.UriParam; import org.apache.camel.spi.UriParams; import org.apache.camel.spi.UriPath; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.URISupport; import org.apache.camel.util.UnsafeUriCharactersEncoder; import org.apache.camel.util.jsse.SSLContextParameters; import org.schwering.irc.lib.ssl.SSLDefaultTrustManager; import org.schwering.irc.lib.ssl.SSLTrustManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @UriParams public class IrcConfiguration implements Cloneable { private static final Logger LOG = LoggerFactory.getLogger(IrcConfiguration.class); private boolean usingSSL; private List<IrcChannel> channels = new ArrayList<IrcChannel>(); @UriPath @Metadata(required = "true") private String hostname; @UriPath private int port; private int[] ports = {6667, 6668, 6669}; @UriParam(label = "security", secret = true) private String password; @UriParam private String nickname; @UriParam private String realname; @UriParam(label = "security", secret = true) private String username; @UriParam(label = "security") private SSLTrustManager trustManager = new SSLDefaultTrustManager(); @UriParam(defaultValue = "true") @Deprecated private boolean persistent = true; @UriParam(defaultValue = "true", label = "advanced") private boolean colors = true; @UriParam(defaultValue = "true", label = "filter") private boolean onNick = true; @UriParam(defaultValue = "true", label = "filter") private boolean onQuit = true; @UriParam(defaultValue = "true", label = "filter") private boolean onJoin = true; @UriParam(defaultValue = "true", label = "filter") private boolean onKick = true; @UriParam(defaultValue = "true", label = "filter") private boolean onMode = true; @UriParam(defaultValue = "true", label = "filter") private boolean onPart = true; @UriParam(label = "filter") private boolean onReply; @UriParam(defaultValue = "true", label = "filter") private boolean onTopic = true; @UriParam(defaultValue = "true", label = "filter") private boolean onPrivmsg = true; @UriParam(defaultValue = "true") private boolean autoRejoin = true; @UriParam private boolean namesOnJoin; @UriParam(label = "security") private SSLContextParameters sslContextParameters; @UriParam(label = "security", secret = true) private String nickPassword; public IrcConfiguration() { } public IrcConfiguration(String hostname, String nickname, String displayname, List<IrcChannel> channels) { this(hostname, null, null, nickname, displayname, channels); } public IrcConfiguration(String hostname, String username, String password, String nickname, String displayname, List<IrcChannel> channels) { this.channels = channels; this.hostname = hostname; this.username = username; this.password = password; this.nickname = nickname; this.realname = displayname; } public IrcConfiguration copy() { try { return (IrcConfiguration) clone(); } catch (CloneNotSupportedException e) { throw new RuntimeCamelException(e); } } public String getCacheKey() { return hostname + ":" + nickname; } /* * Return space separated list of channel names without pwd */ public String getListOfChannels() { String retval = ""; for (IrcChannel channel : channels) { retval += (retval.isEmpty() ? "" : " ") + channel.getName(); } return retval; } public void configure(String uriStr) throws URISyntaxException, UnsupportedEncodingException { // fix provided URI and handle that we can use # to indicate the IRC room if (uriStr.startsWith("ircs")) { setUsingSSL(true); if (!uriStr.startsWith("ircs://")) { uriStr = uriStr.replace("ircs:", "ircs://"); } } else if (!uriStr.startsWith("irc://")) { uriStr = uriStr.replace("irc:", "irc://"); } if (uriStr.contains("?")) { uriStr = ObjectHelper.before(uriStr, "?"); } URI uri = new URI(uriStr); // Because we can get a "sanitized" URI, we need to deal with the situation where the // user info includes the username and password together or else we get a mangled username // that includes the user's secret being sent to the server. String userInfo = uri.getUserInfo(); String username = null; String password = null; if (userInfo != null) { int colonIndex = userInfo.indexOf(":"); if (colonIndex != -1) { username = userInfo.substring(0, colonIndex); password = userInfo.substring(colonIndex + 1); } else { username = userInfo; } } if (uri.getPort() != -1) { setPorts(new int[] {uri.getPort()}); setPort(uri.getPort()); } setNickname(username); setUsername(username); setRealname(username); setPassword(password); setHostname(uri.getHost()); String path = uri.getPath(); if (path != null && !path.isEmpty()) { LOG.warn("Channel {} should not be specified in the URI path. Use an @channel query parameter instead.", path); } } public void setChannel(String channel) { channels.add(createChannel(channel)); } public void setChannel(List<String> channels) { for (String ci : channels) { this.channels.add(createChannel(ci)); } } public List<IrcChannel> getChannels() { return channels; } public IrcChannel findChannel(String name) { for (IrcChannel channel : channels) { if (channel.getName().equals(name)) { return channel; } } return null; } /** * The trust manager used to verify the SSL server's certificate. */ public void setTrustManager(SSLTrustManager trustManager) { this.trustManager = trustManager; } public SSLTrustManager getTrustManager() { return trustManager; } public boolean getUsingSSL() { return usingSSL; } private void setUsingSSL(boolean usingSSL) { this.usingSSL = usingSSL; } public String getHostname() { return hostname; } /** * Hostname for the IRC chat server */ public void setHostname(String hostname) { this.hostname = hostname; } public String getPassword() { return password; } /** * The IRC server password. */ public void setPassword(String password) { this.password = password; } public String getNickname() { return nickname; } /** * The nickname used in chat. */ public void setNickname(String nickname) { this.nickname = nickname; } public String getRealname() { return realname; } /** * The IRC user's actual name. */ public void setRealname(String realname) { this.realname = realname; } public String getUsername() { return username; } /** * The IRC server user name. */ public void setUsername(String username) { this.username = username; } public int[] getPorts() { return ports; } /** * Port numbers for the IRC chat server */ public void setPorts(int[] ports) { this.ports = ports; } public int getPort() { return port; } /** * Port number for the IRC chat server. If no port is configured then a default port of either 6667, 6668 or 6669 is used. */ public void setPort(int port) { this.port = port; } public boolean isPersistent() { return persistent; } /** * Use persistent messages. * @deprecated not in use */ @Deprecated public void setPersistent(boolean persistent) { this.persistent = persistent; } public boolean isColors() { return colors; } /** * Whether or not the server supports color codes. */ public void setColors(boolean colors) { this.colors = colors; } public boolean isOnNick() { return onNick; } /** * Handle nickname change events. */ public void setOnNick(boolean onNick) { this.onNick = onNick; } public boolean isOnQuit() { return onQuit; } /** * Handle user quit events. */ public void setOnQuit(boolean onQuit) { this.onQuit = onQuit; } public boolean isOnJoin() { return onJoin; } /** * Handle user join events. */ public void setOnJoin(boolean onJoin) { this.onJoin = onJoin; } public boolean isOnKick() { return onKick; } /** * Handle kick events. */ public void setOnKick(boolean onKick) { this.onKick = onKick; } public boolean isOnMode() { return onMode; } /** * Handle mode change events. */ public void setOnMode(boolean onMode) { this.onMode = onMode; } public boolean isOnPart() { return onPart; } /** * Handle user part events. */ public void setOnPart(boolean onPart) { this.onPart = onPart; } public boolean isOnReply() { return onReply; } /** * Whether or not to handle general responses to commands or informational messages. */ public void setOnReply(boolean onReply) { this.onReply = onReply; } public boolean isOnTopic() { return onTopic; } /** * Handle topic change events. */ public void setOnTopic(boolean onTopic) { this.onTopic = onTopic; } public boolean isOnPrivmsg() { return onPrivmsg; } /** * Handle private message events. */ public void setOnPrivmsg(boolean onPrivmsg) { this.onPrivmsg = onPrivmsg; } public boolean isAutoRejoin() { return autoRejoin; } /** * Whether to auto re-join when being kicked */ public void setAutoRejoin(boolean autoRejoin) { this.autoRejoin = autoRejoin; } public SSLContextParameters getSslContextParameters() { return sslContextParameters; } /** * Used for configuring security using SSL. * Reference to a org.apache.camel.util.jsse.SSLContextParameters in the Registry. * This reference overrides any configured SSLContextParameters at the component level. * Note that this setting overrides the trustManager option. */ public void setSslContextParameters(SSLContextParameters sslContextParameters) { this.sslContextParameters = sslContextParameters; } /** * Your IRC server nickname password. */ public String getNickPassword() { return nickPassword; } public void setNickPassword(String nickPassword) { this.nickPassword = nickPassword; } public boolean isNamesOnJoin() { return namesOnJoin; } /** * Sends <code>NAMES</code> command to channel after joining it.<br> * {@link #onReply} has to be <code>true</code> in order to process the * result which will have the header value <code>irc.num = '353'</code>. */ public void setNamesOnJoin(boolean namesOnJoin) { this.namesOnJoin = namesOnJoin; } public String toString() { return "IrcConfiguration[hostname: " + hostname + ", ports=" + Arrays.toString(ports) + ", username=" + username + "]"; } private static IrcChannel createChannel(String channelInfo) { String[] pair = channelInfo.split("!"); return new IrcChannel(pair[0], pair.length > 1 ? pair[1] : null); } @Deprecated public static String sanitize(String uri) { // may be removed in camel-3.0.0 // make sure it's an URL first int colon = uri.indexOf(':'); if (colon != -1 && uri.indexOf("://") != colon) { uri = uri.substring(0, colon) + "://" + uri.substring(colon + 1); } try { URI u = new URI(UnsafeUriCharactersEncoder.encode(uri)); String[] userInfo = u.getUserInfo() != null ? u.getUserInfo().split(":") : null; String username = userInfo != null ? userInfo[0] : null; String password = userInfo != null && userInfo.length > 1 ? userInfo[1] : null; String path = URLDecoder.decode(u.getPath() != null ? u.getPath() : "", "UTF-8"); if (path.startsWith("/")) { path = path.substring(1); } if (path.startsWith("#") && !path.startsWith("##")) { path = path.substring(1); } Map<String, Object> parameters = URISupport.parseParameters(u); String user = (String)parameters.get("username"); String nick = (String)parameters.get("nickname"); // not specified in authority if (user != null) { if (username == null) { username = user; } else if (!username.equals(user)) { LOG.warn("Username specified twice in endpoint URI with different values. " + "The userInfo value ('{}') will be used, paramter ('{}') ignored", username, user); } parameters.remove("username"); } if (nick != null) { if (username == null) { username = nick; } if (username.equals(nick)) { parameters.remove("nickname"); // redundant } } if (username == null) { throw new RuntimeCamelException("IrcEndpoint URI with no user/nick specified is invalid"); } String pwd = (String)parameters.get("password"); if (pwd != null) { password = pwd; parameters.remove("password"); } // Remove unneeded '#' channel prefixes per convention // and replace ',' separators and merge channel and key using convention "channel!key" List<String> cl = new ArrayList<String>(); String channels = (String)parameters.get("channels"); String keys = (String)parameters.get("keys"); keys = keys == null ? keys : keys + " "; // if @keys ends with a ',' it will miss the last empty key after split(",") if (channels != null) { String[] chs = channels.split(","); String[] ks = keys != null ? keys.split(",") : null; parameters.remove("channels"); int count = chs.length; if (ks != null) { parameters.remove("keys"); if (!path.isEmpty()) { LOG.warn("Specifying a channel '{}' in the URI path is ambiguous" + " when @channels and @keys are provided and will be ignored", path); path = ""; } if (ks.length != chs.length) { count = count < ks.length ? count : ks.length; LOG.warn("Different count of @channels and @keys. Only the first {} are used.", count); } } for (int i = 0; i < count; i++) { String channel = chs[i].trim(); String key = ks != null ? ks[i].trim() : null; if (channel.startsWith("#") && !channel.startsWith("##")) { channel = channel.substring(1); } if (key != null && !key.isEmpty()) { channel += "!" + key; } cl.add(channel); } } else { if (path.isEmpty()) { LOG.warn("No channel specified for the irc endpoint"); } cl.add(path); } parameters.put("channel", cl); StringBuilder sb = new StringBuilder(); sb.append(u.getScheme()); sb.append("://"); sb.append(username); sb.append(password == null ? "" : ":" + password); sb.append("@"); sb.append(u.getHost()); sb.append(u.getPort() == -1 ? "" : ":" + u.getPort()); // ignore the path we have it as a @channel now String query = formatQuery(parameters); if (!query.isEmpty()) { sb.append("?"); sb.append(query); } // make things a bit more predictable return sb.toString(); } catch (Exception e) { throw new RuntimeCamelException(e); } } private static String formatQuery(Map<String, Object> params) { if (params == null || params.size() == 0) { return ""; } StringBuilder result = new StringBuilder(); for (Map.Entry<String, Object> pair : params.entrySet()) { Object value = pair.getValue(); // the value may be a list since the same key has multiple values if (value instanceof List) { List<?> list = (List<?>)value; for (Object s : list) { addQueryParameter(result, pair.getKey(), s); } } else { addQueryParameter(result, pair.getKey(), value); } } return result.toString(); } private static void addQueryParameter(StringBuilder sb, String key, Object value) { sb.append(sb.length() == 0 ? "" : "&"); sb.append(key); if (value != null) { String s = value.toString(); sb.append(s.isEmpty() ? "" : "=" + UnsafeUriCharactersEncoder.encode(s)); } } }