001/****************************************************************
002 * Licensed to the Apache Software Foundation (ASF) under one   *
003 * or more contributor license agreements.  See the NOTICE file *
004 * distributed with this work for additional information        *
005 * regarding copyright ownership.  The ASF licenses this file   *
006 * to you under the Apache License, Version 2.0 (the            *
007 * "License"); you may not use this file except in compliance   *
008 * with the License.  You may obtain a copy of the License at   *
009 *                                                              *
010 *   http://www.apache.org/licenses/LICENSE-2.0                 *
011 *                                                              *
012 * Unless required by applicable law or agreed to in writing,   *
013 * software distributed under the License is distributed on an  *
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015 * KIND, either express or implied.  See the License for the    *
016 * specific language governing permissions and limitations      *
017 * under the License.                                           *
018 ****************************************************************/
019package org.apache.james.rrt.lib;
020
021import java.util.HashMap;
022import java.util.Locale;
023import java.util.Map;
024import java.util.StringTokenizer;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027import java.util.regex.PatternSyntaxException;
028
029import org.apache.james.rrt.api.RecipientRewriteTable;
030import org.apache.mailet.MailAddress;
031
032/**
033 * This helper class contains methods for the RecipientRewriteTable implementations
034 */
035public class RecipientRewriteTableUtil {
036
037    private RecipientRewriteTableUtil() {
038    }
039
040    // @deprecated QUERY is deprecated - SQL queries are now located in
041    // sqlResources.xml
042    public static final String QUERY = "select RecipientRewriteTable.target_address from RecipientRewriteTable, RecipientRewriteTable as VUTDomains where (RecipientRewriteTable.user like ? or RecipientRewriteTable.user like '\\%') and (RecipientRewriteTable.domain like ? or (RecipientRewriteTable.domain like '%*%' and VUTDomains.domain like ?)) order by concat(RecipientRewriteTable.user,'@',RecipientRewriteTable.domain) desc limit 1";
043
044    /**
045     * Processes regex virtual user mapping
046     * 
047     * If a mapped target string begins with the prefix regex:, it must be
048     * formatted as regex:<regular-expression>:<parameterized-string>, e.g.,
049     * regex:(.*)@(.*):${1}@tld
050     * 
051     * @param address
052     *            the MailAddress to be mapped
053     * @param targetString
054     *            a String specifying the mapping
055     * @throws MalformedPatternException
056     */
057    public static String regexMap(MailAddress address, String targetString) {
058        String result = null;
059        int identifierLength = RecipientRewriteTable.REGEX_PREFIX.length();
060
061        int msgPos = targetString.indexOf(':', identifierLength + 1);
062
063        // Throw exception on invalid format
064        if (msgPos < identifierLength + 1)
065            throw new PatternSyntaxException("Regex should be formatted as regex:<regular-expression>:<parameterized-string>", targetString, 0);
066
067        // log("regex: targetString = " + targetString);
068        // log("regex: msgPos = " + msgPos);
069        // log("regex: compile " + targetString.substring("regex:".length(),
070        // msgPos));
071        // log("regex: address = " + address.toString());
072        // log("regex: replace = " + targetString.substring(msgPos + 1));
073
074        Pattern pattern = Pattern.compile(targetString.substring(identifierLength, msgPos));
075        Matcher match = pattern.matcher(address.toString());
076
077        if (match.matches()) {
078            Map<String, String> parameters = new HashMap<String, String>(match.groupCount());
079            for (int i = 1; i < match.groupCount(); i++) {
080                parameters.put(Integer.toString(i), match.group(i));
081            }
082            result = replaceParameters(targetString.substring(msgPos + 1), parameters);
083        }
084        return result;
085    }
086
087    /**
088     * Returns a named string, replacing parameters with the values set.
089     * 
090     * @param str
091     *            the name of the String resource required.
092     * @param parameters
093     *            a map of parameters (name-value string pairs) which are
094     *            replaced where found in the input strings
095     * @return the requested resource
096     */
097    static public String replaceParameters(String str, Map<String, String> parameters) {
098        if (str != null && parameters != null) {
099            // Do parameter replacements for this string resource.
100            StringBuilder replaceBuffer = new StringBuilder(64);
101            for (Map.Entry<String, String> entry : parameters.entrySet()) {
102                replaceBuffer.setLength(0);
103                replaceBuffer.append("${").append(entry.getKey()).append("}");
104                str = substituteSubString(str, replaceBuffer.toString(), entry.getValue());
105            }
106        }
107
108        return str;
109    }
110
111    /**
112     * Replace substrings of one string with another string and return altered
113     * string.
114     * 
115     * @param input
116     *            input string
117     * @param find
118     *            the string to replace
119     * @param replace
120     *            the string to replace with
121     * @return the substituted string
122     */
123    static private String substituteSubString(String input, String find, String replace) {
124        int find_length = find.length();
125        int replace_length = replace.length();
126
127        StringBuilder output = new StringBuilder(input);
128        int index = input.indexOf(find);
129        int outputOffset = 0;
130
131        while (index > -1) {
132            output.replace(index + outputOffset, index + outputOffset + find_length, replace);
133            outputOffset = outputOffset + (replace_length - find_length);
134
135            index = input.indexOf(find, index + find_length);
136        }
137
138        String result = output.toString();
139        return result;
140    }
141
142    /**
143     * Returns the real recipient given a virtual username and domain.
144     * 
145     * @param user
146     *            the virtual user
147     * @param domain
148     *            the virtual domain
149     * @return the real recipient address, or <code>null</code> if no mapping
150     *         exists
151     */
152    public static String getTargetString(String user, String domain, Map<String, String> mappings) {
153        StringBuffer buf;
154        String target;
155
156        // Look for exact (user@domain) match
157        buf = new StringBuffer().append(user).append("@").append(domain);
158        target = mappings.get(buf.toString());
159        if (target != null) {
160            return target;
161        }
162
163        // Look for user@* match
164        buf = new StringBuffer().append(user).append("@*");
165        target = mappings.get(buf.toString());
166        if (target != null) {
167            return target;
168        }
169
170        // Look for *@domain match
171        buf = new StringBuffer().append("*@").append(domain);
172        target = mappings.get(buf.toString());
173        if (target != null) {
174            return target;
175        }
176
177        return null;
178    }
179
180    /**
181     * Returns the character used to delineate multiple addresses.
182     * 
183     * @param targetString
184     *            the string to parse
185     * @return the character to tokenize on
186     */
187    public static String getSeparator(String targetString) {
188        return (targetString.indexOf(',') > -1 ? "," : (targetString.indexOf(';') > -1 ? ";" : ((targetString.contains(RecipientRewriteTable.ERROR_PREFIX) || targetString.contains(RecipientRewriteTable.REGEX_PREFIX) || targetString.contains(RecipientRewriteTable.ALIASDOMAIN_PREFIX)) ? "" : ":")));
189    }
190
191    /**
192     * Returns a Map which contains the mappings
193     * 
194     * @param mapping
195     *            A String which contains a list of mappings
196     * @return Map which contains the mappings
197     */
198    public static Map<String, String> getXMLMappings(String mapping) {
199        Map<String, String> mappings = new HashMap<String, String>();
200        StringTokenizer tokenizer = new StringTokenizer(mapping, ",");
201        while (tokenizer.hasMoreTokens()) {
202            String mappingItem = tokenizer.nextToken();
203            int index = mappingItem.indexOf('=');
204            String virtual = mappingItem.substring(0, index).trim().toLowerCase(Locale.US);
205            String real = mappingItem.substring(index + 1).trim().toLowerCase(Locale.US);
206            mappings.put(virtual, real);
207        }
208        return mappings;
209    }
210
211}