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 ****************************************************************/
019
020package org.apache.james.mime4j.field.address;
021
022import java.util.ArrayList;
023import java.util.BitSet;
024import java.util.Collections;
025import java.util.List;
026
027import org.apache.james.mime4j.codec.DecodeMonitor;
028import org.apache.james.mime4j.codec.DecoderUtil;
029import org.apache.james.mime4j.dom.address.Address;
030import org.apache.james.mime4j.dom.address.AddressList;
031import org.apache.james.mime4j.dom.address.DomainList;
032import org.apache.james.mime4j.dom.address.Group;
033import org.apache.james.mime4j.dom.address.Mailbox;
034import org.apache.james.mime4j.stream.ParserCursor;
035import org.apache.james.mime4j.stream.RawFieldParser;
036import org.apache.james.mime4j.util.ByteSequence;
037import org.apache.james.mime4j.util.CharsetUtil;
038import org.apache.james.mime4j.util.ContentUtil;
039
040/**
041 * Lenient (tolerant to non-critical format violations) builder for {@link Address}
042 * and its subclasses.
043 */
044public class LenientAddressBuilder {
045
046    private static final int AT                = '@';
047    private static final int OPENING_BRACKET   = '<';
048    private static final int CLOSING_BRACKET   = '>';
049    private static final int COMMA             = ',';
050    private static final int COLON             = ':';
051    private static final int SEMICOLON         = ';';
052
053    private static final BitSet AT_AND_CLOSING_BRACKET = RawFieldParser.INIT_BITSET(AT, CLOSING_BRACKET);
054    private static final BitSet CLOSING_BRACKET_ONLY   = RawFieldParser.INIT_BITSET(CLOSING_BRACKET);
055    private static final BitSet COMMA_ONLY             = RawFieldParser.INIT_BITSET(COMMA);
056    private static final BitSet COLON_ONLY             = RawFieldParser.INIT_BITSET(COLON);
057    private static final BitSet SEMICOLON_ONLY         = RawFieldParser.INIT_BITSET(SEMICOLON);
058
059    public static final LenientAddressBuilder DEFAULT = new LenientAddressBuilder(DecodeMonitor.SILENT);
060
061    private final DecodeMonitor monitor;
062    private final RawFieldParser parser;
063
064    protected LenientAddressBuilder(final DecodeMonitor monitor) {
065        super();
066        this.monitor = monitor;
067        this.parser = new RawFieldParser();
068    }
069
070    String parseDomain(final ByteSequence buf, final ParserCursor cursor, final BitSet delimiters) {
071        StringBuilder dst = new StringBuilder();
072        while (!cursor.atEnd()) {
073            char current = (char) (buf.byteAt(cursor.getPos()) & 0xff);
074            if (delimiters != null && delimiters.get(current)) {
075                break;
076            } else if (CharsetUtil.isWhitespace(current)) {
077                this.parser.skipWhiteSpace(buf, cursor);
078            } else if (current == '(') {
079                this.parser.skipComment(buf, cursor);
080            } else {
081                this.parser.copyContent(buf, cursor, delimiters, dst);
082            }
083        }
084        return dst.toString();
085    }
086
087    DomainList parseRoute(final ByteSequence buf, final ParserCursor cursor, final BitSet delimiters) {
088        BitSet bitset = RawFieldParser.INIT_BITSET(COMMA, COLON);
089        if (delimiters != null) {
090            bitset.or(delimiters);
091        }
092        List<String> domains = null;
093        for (;;) {
094            this.parser.skipAllWhiteSpace(buf, cursor);
095            if (cursor.atEnd()) {
096                break;
097            }
098            int pos = cursor.getPos();
099            int current = (char) (buf.byteAt(pos) & 0xff);
100            if (current == AT) {
101                cursor.updatePos(pos + 1);
102            } else {
103                break;
104            }
105            String s = parseDomain(buf, cursor, bitset);
106            if (s != null && s.length() > 0) {
107                if (domains == null) {
108                    domains = new ArrayList<String>();
109                }
110                domains.add(s);
111            }
112            if (cursor.atEnd()) {
113                break;
114            }
115            pos = cursor.getPos();
116            current = (char) (buf.byteAt(pos) & 0xff);
117            if (current == COMMA) {
118                cursor.updatePos(pos + 1);
119                continue;
120            } else if (current == COLON) {
121                cursor.updatePos(pos + 1);
122                break;
123            } else {
124                break;
125            }
126        }
127        return domains != null ? new DomainList(domains, true) : null;
128    }
129
130    private Mailbox createMailbox(
131            final String name, final DomainList route, final String localPart, final String domain) {
132        return new Mailbox(
133                name != null ? DecoderUtil.decodeEncodedWords(name, this.monitor) : null, 
134                        route, localPart, domain);
135    }
136    
137    Mailbox parseMailboxAddress(
138            final String openingText, final ByteSequence buf, final ParserCursor cursor) {
139        if (cursor.atEnd()) {
140            return createMailbox(null, null, openingText, null);
141        }
142        int pos = cursor.getPos();
143        char current = (char) (buf.byteAt(pos) & 0xff);
144        if (current == OPENING_BRACKET) {
145            cursor.updatePos(pos + 1);
146        } else {
147            return createMailbox(null, null, openingText, null);
148        }
149        DomainList domainList = parseRoute(buf, cursor, CLOSING_BRACKET_ONLY);
150        String localPart = this.parser.parseValue(buf, cursor, AT_AND_CLOSING_BRACKET);
151        if (cursor.atEnd()) {
152            return createMailbox(openingText, domainList, localPart, null);
153        }
154        pos = cursor.getPos();
155        current = (char) (buf.byteAt(pos) & 0xff);
156        if (current == AT) {
157            cursor.updatePos(pos + 1);
158        } else {
159            return createMailbox(openingText, domainList, localPart, null);
160        }
161        String domain = parseDomain(buf, cursor, CLOSING_BRACKET_ONLY);
162        if (cursor.atEnd()) {
163            return createMailbox(openingText, domainList, localPart, domain);
164        }
165        pos = cursor.getPos();
166        current = (char) (buf.byteAt(pos) & 0xff);
167        if (current == CLOSING_BRACKET) {
168            cursor.updatePos(pos + 1);
169        } else {
170            return createMailbox(openingText, domainList, localPart, domain);
171        }
172        while (!cursor.atEnd()) {
173            pos = cursor.getPos();
174            current = (char) (buf.byteAt(pos) & 0xff);
175            if (CharsetUtil.isWhitespace(current)) {
176                this.parser.skipWhiteSpace(buf, cursor);
177            } else if (current == '(') {
178                this.parser.skipComment(buf, cursor);
179            } else {
180                break;
181            }
182        }
183        return createMailbox(openingText, domainList, localPart, domain);
184    }
185
186    private Mailbox createMailbox(final String localPart) {
187        if (localPart != null && localPart.length() > 0) {
188            return new Mailbox(null, null, localPart, null);
189        } else {
190            return null;
191        }
192    }
193
194    public Mailbox parseMailbox(
195            final ByteSequence buf, final ParserCursor cursor, final BitSet delimiters) {
196        BitSet bitset = RawFieldParser.INIT_BITSET(AT, OPENING_BRACKET);
197        if (delimiters != null) {
198            bitset.or(delimiters);
199        }
200        String openingText = this.parser.parseValue(buf, cursor, bitset);
201        if (cursor.atEnd()) {
202            return createMailbox(openingText);
203        }
204        int pos = cursor.getPos();
205        char current = (char) (buf.byteAt(pos) & 0xff);
206        if (current == OPENING_BRACKET) {
207            // name <localPart @ domain> form
208            return parseMailboxAddress(openingText, buf, cursor);
209        } else if (current == AT) {
210            // localPart @ domain form
211            cursor.updatePos(pos + 1);
212            String localPart = openingText;
213            String domain = parseDomain(buf, cursor, delimiters);
214            return new Mailbox(null, null, localPart, domain);
215        } else {
216            return createMailbox(openingText);
217        }
218    }
219
220    public Mailbox parseMailbox(final String text) {
221        ByteSequence raw = ContentUtil.encode(text);
222        ParserCursor cursor = new ParserCursor(0, text.length());
223        return parseMailbox(raw, cursor, null);
224    }
225
226    List<Mailbox> parseMailboxes(
227            final ByteSequence buf, final ParserCursor cursor, final BitSet delimiters) {
228        BitSet bitset = RawFieldParser.INIT_BITSET(COMMA);
229        if (delimiters != null) {
230            bitset.or(delimiters);
231        }
232        List<Mailbox> mboxes = new ArrayList<Mailbox>();
233        while (!cursor.atEnd()) {
234            int pos = cursor.getPos();
235            int current = (char) (buf.byteAt(pos) & 0xff);
236            if (delimiters != null && delimiters.get(current)) {
237                break;
238            } else if (current == COMMA) {
239                cursor.updatePos(pos + 1);
240            } else {
241                Mailbox mbox = parseMailbox(buf, cursor, bitset);
242                if (mbox != null) {
243                    mboxes.add(mbox);
244                }
245            }
246        }
247        return mboxes;
248    }
249
250    public Group parseGroup(final ByteSequence buf, final ParserCursor cursor) {
251        String name = this.parser.parseToken(buf, cursor, COLON_ONLY);
252        if (cursor.atEnd()) {
253            return new Group(name, Collections.<Mailbox>emptyList());
254        }
255        int pos = cursor.getPos();
256        int current = (char) (buf.byteAt(pos) & 0xff);
257        if (current == COLON) {
258            cursor.updatePos(pos + 1);
259        }
260        List<Mailbox> mboxes = parseMailboxes(buf, cursor, SEMICOLON_ONLY);
261        return new Group(name, mboxes);
262    }
263
264    public Group parseGroup(final String text) {
265        ByteSequence raw = ContentUtil.encode(text);
266        ParserCursor cursor = new ParserCursor(0, text.length());
267        return parseGroup(raw, cursor);
268    }
269
270    public Address parseAddress(
271            final ByteSequence buf, final ParserCursor cursor, final BitSet delimiters) {
272        BitSet bitset = RawFieldParser.INIT_BITSET(COLON, AT, OPENING_BRACKET);
273        if (delimiters != null) {
274            bitset.or(delimiters);
275        }
276        String openingText = this.parser.parseValue(buf, cursor, bitset);
277        if (cursor.atEnd()) {
278            return createMailbox(openingText);
279        }
280        int pos = cursor.getPos();
281        char current = (char) (buf.byteAt(pos) & 0xff);
282        if (current == OPENING_BRACKET) {
283            // name <localPart @ domain> form
284            return parseMailboxAddress(openingText, buf, cursor);
285        } else if (current == AT) {
286            // localPart @ domain form
287            cursor.updatePos(pos + 1);
288            String localPart = openingText;
289            String domain = parseDomain(buf, cursor, delimiters);
290            return new Mailbox(null, null, localPart, domain);
291        } else if (current == COLON) {
292            // group-name: localPart @ domain, name <localPart @ domain>; form
293            cursor.updatePos(pos + 1);
294            String name = openingText;
295            List<Mailbox> mboxes = parseMailboxes(buf, cursor, SEMICOLON_ONLY);
296            if (!cursor.atEnd()) {
297                pos = cursor.getPos();
298                current = (char) (buf.byteAt(pos) & 0xff);
299                if (current == SEMICOLON) {
300                    cursor.updatePos(pos + 1);
301                }
302            }
303            return new Group(name, mboxes);
304        } else {
305            return createMailbox(openingText);
306        }
307    }
308
309    public Address parseAddress(final String text) {
310        ByteSequence raw = ContentUtil.encode(text);
311        ParserCursor cursor = new ParserCursor(0, text.length());
312        return parseAddress(raw, cursor, null);
313    }
314
315    public AddressList parseAddressList(final ByteSequence buf, final ParserCursor cursor) {
316        List<Address> addresses = new ArrayList<Address>();
317        while (!cursor.atEnd()) {
318            int pos = cursor.getPos();
319            int current = (char) (buf.byteAt(pos) & 0xff);
320            if (current == COMMA) {
321                cursor.updatePos(pos + 1);
322            } else {
323                Address address = parseAddress(buf, cursor, COMMA_ONLY);
324                if (address != null) {
325                    addresses.add(address);
326                }
327            }
328        }
329        return new AddressList(addresses, false);
330    }
331
332    public AddressList parseAddressList(final String text) {
333        ByteSequence raw = ContentUtil.encode(text);
334        ParserCursor cursor = new ParserCursor(0, text.length());
335        return parseAddressList(raw, cursor);
336    }
337
338}