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    
020    package org.apache.james.mime4j.field;
021    
022    import java.util.regex.Matcher;
023    import java.util.regex.Pattern;
024    
025    import org.apache.james.mime4j.MimeException;
026    import org.apache.james.mime4j.util.ByteSequence;
027    import org.apache.james.mime4j.util.ContentUtil;
028    import org.apache.james.mime4j.util.MimeUtil;
029    
030    /**
031     * The base class of all field classes.
032     */
033    public abstract class AbstractField implements ParsedField {
034    
035        private static final Pattern FIELD_NAME_PATTERN = Pattern
036                .compile("^([\\x21-\\x39\\x3b-\\x7e]+):");
037    
038        private static final DefaultFieldParser parser = new DefaultFieldParser();
039        
040        private final String name;
041        private final String body;
042        private final ByteSequence raw;
043        
044        protected AbstractField(final String name, final String body, final ByteSequence raw) {
045            this.name = name;
046            this.body = body;
047            this.raw = raw;
048        }
049    
050        /**
051         * Parses the given byte sequence and returns an instance of the
052         * <code>Field</code> class. The type of the class returned depends on the
053         * field name; see {@link #parse(String)} for a table of field names and
054         * their corresponding classes.
055         * 
056         * @param raw the bytes to parse.
057         * @return a <code>ParsedField</code> instance.
058         * @throws MimeException if the raw string cannot be split into field name and body.
059         * @see #isValidField()
060         */
061        public static ParsedField parse(final ByteSequence raw) throws MimeException {
062            String rawStr = ContentUtil.decode(raw);
063            return parse(raw, rawStr);
064        }
065    
066        /**
067         * Parses the given string and returns an instance of the 
068         * <code>Field</code> class. The type of the class returned depends on
069         * the field name:
070         * <p>
071         * <table>
072         *   <tr><th>Class returned</th><th>Field names</th></tr>
073         *   <tr><td>{@link ContentTypeField}</td><td>Content-Type</td></tr>
074         *   <tr><td>{@link ContentTransferEncodingField}</td><td>Content-Transfer-Encoding</td></tr>
075         *   <tr><td>{@link ContentDispositionField}</td><td>Content-Disposition</td></tr>
076         *   <tr><td>{@link DateTimeField}</td><td>Date, Resent-Date</td></tr>
077         *   <tr><td>{@link MailboxField}</td><td>Sender, Resent-Sender</td></tr>
078         *   <tr><td>{@link MailboxListField}</td><td>From, Resent-From</td></tr>
079         *   <tr><td>{@link AddressListField}</td><td>To, Cc, Bcc, Reply-To, Resent-To, Resent-Cc, Resent-Bcc</td></tr>
080         *   <tr><td>{@link UnstructuredField}</td><td>Subject and others</td></tr>
081         * </table>
082         * 
083         * @param rawStr the string to parse.
084         * @return a <code>ParsedField</code> instance.
085         * @throws MimeException if the raw string cannot be split into field name and body.
086         * @see #isValidField()
087         */
088        public static ParsedField parse(final String rawStr) throws MimeException {
089            ByteSequence raw = ContentUtil.encode(rawStr);
090            return parse(raw, rawStr);
091        }
092    
093        /**
094         * Gets the default parser used to parse fields.
095         * 
096         * @return the default field parser
097         */
098        public static DefaultFieldParser getParser() {
099            return parser;
100        }
101        
102        /**
103         * Gets the name of the field (<code>Subject</code>, 
104         * <code>From</code>, etc).
105         * 
106         * @return the field name.
107         */
108        public String getName() {
109            return name;
110        }
111        
112        /**
113         * Gets the original raw field string.
114         * 
115         * @return the original raw field string.
116         */
117        public ByteSequence getRaw() {
118            return raw;
119        }
120        
121        /**
122         * Gets the unfolded, unparsed and possibly encoded (see RFC 2047) field 
123         * body string.
124         * 
125         * @return the unfolded unparsed field body string.
126         */
127        public String getBody() {
128            return body;
129        }
130    
131        /**
132         * @see ParsedField#isValidField() 
133         */
134        public boolean isValidField() {
135            return getParseException() == null;
136        }
137    
138        /**
139         * @see ParsedField#getParseException() 
140         */
141        public ParseException getParseException() {
142            return null;
143        }
144    
145        @Override
146        public String toString() {
147            return name + ": " + body;
148        }
149    
150        private static ParsedField parse(final ByteSequence raw, final String rawStr)
151                throws MimeException {
152            /*
153             * Unfold the field.
154             */
155            final String unfolded = MimeUtil.unfold(rawStr);
156    
157            /*
158             * Split into name and value.
159             */
160            final Matcher fieldMatcher = FIELD_NAME_PATTERN.matcher(unfolded);
161            if (!fieldMatcher.find()) {
162                throw new MimeException("Invalid field in string");
163            }
164            final String name = fieldMatcher.group(1);
165    
166            String body = unfolded.substring(fieldMatcher.end());
167            if (body.length() > 0 && body.charAt(0) == ' ') {
168                body = body.substring(1);
169            }
170    
171            return parser.parse(name, body, raw);
172        }
173    
174    }