source: contrib/MailArchiver/sources/vendor/mime4j/custom/core/src/main/java/org/apache/james/mime4j/io/LineNumberInputStream.java @ 6785

Revision 6785, 13.3 KB checked in by rafaelraymundo, 12 years ago (diff)

Ticket #2946 - Liberado codigo do MailArchiver?. Documentação na subpasta DOCS.

Line 
1/****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one   *
3 * or more contributor license agreements.  See the NOTICE file *
4 * distributed with this work for additional information        *
5 * regarding copyright ownership.  The ASF licenses this file   *
6 * to you under the Apache License, Version 2.0 (the            *
7 * "License"); you may not use this file except in compliance   *
8 * with the License.  You may obtain a copy of the License at   *
9 *                                                              *
10 *   http://www.apache.org/licenses/LICENSE-2.0                 *
11 *                                                              *
12 * Unless required by applicable law or agreed to in writing,   *
13 * software distributed under the License is distributed on an  *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15 * KIND, either express or implied.  See the License for the    *
16 * specific language governing permissions and limitations      *
17 * under the License.                                           *
18 ****************************************************************/
19
20package org.apache.james.mime4j.io;
21
22import java.io.FilterInputStream;
23import java.io.IOException;
24import java.io.InputStream;
25
26import java.util.List;
27import java.util.ArrayList;
28import java.util.Collections;
29import java.util.regex.Pattern;
30import java.util.regex.Matcher;
31
32/**
33 * <code>InputStream</code> used by the parser to wrap the original user
34 * supplied stream. This stream keeps track of the current line number.
35 */
36public class LineNumberInputStream extends FilterInputStream implements LineNumberSource {
37
38    /**
39     * Creates a new <code>LineNumberInputStream</code>.
40     *
41     * @param is the stream to read from.
42     */
43    public LineNumberInputStream(InputStream is) {
44        super(is);
45
46        currentEntity = new Entity();
47        currentEntity.startLine = 1;
48        rootEntity = currentEntity;
49    }
50
51    @Override
52    public int read() throws IOException {
53        int b = in.read();
54        if(b >= 0) {
55            process(b);
56        }
57        return b;
58    }
59
60    @Override
61    public int read(byte[] b, int off, int len) throws IOException {
62        int n = in.read(b, off, len);
63        if(n >= 0) {
64            for (int i = off; i < off + n; i++) {
65                process(b[i]);
66            }
67        }
68        return n;
69    }
70
71
72    //=========================================================================
73    private static final int CR = 13;
74    private static final int LF = 10;
75
76    private int lineNumber = 1;
77    private int lineOffset = 0;
78    private int offset = 0;
79
80    private boolean pendingCR = false;
81    private boolean insideBody = false;
82
83    private StringBuilder lineBuilder = new StringBuilder(4096);
84    private StringBuilder fieldBuilder = new StringBuilder(8192);
85
86    private Entity currentEntity;
87
88    private final Entity rootEntity;
89
90    public Entity getRootEntity() {
91        return rootEntity;
92    }
93
94    public int getLineNumber() {
95        return lineNumber;
96    }
97
98   
99    private void process(int b) {
100        if(pendingCR) {
101            if(b == LF) {
102                ++offset;
103                processLine();
104            }
105            else {
106                processLine();
107                process(b);
108            }
109        }
110        else {
111            ++offset;
112            if(b == CR) {
113                pendingCR = true;
114            }
115            else if(b == LF) {
116                processLine();
117            }
118            else {
119                lineBuilder.append((char)b);
120            }
121        }
122    }
123
124
125    private void processLine() {
126        if(insideBody) {
127            processBodyLine();
128        }
129        else {
130            processHeaderLine();
131        }
132        pendingCR = false;
133        lineBuilder.setLength(0);
134        lineOffset = offset;
135        ++lineNumber;
136    }
137
138
139    private void processHeaderLine() {
140
141        String line = lineBuilder.toString();
142        if(line.trim().isEmpty()) {
143
144            //reach the separator line between header and body
145
146            processField();
147
148            currentEntity.separatorLine = lineNumber;
149            currentEntity.bodyOffset = offset;
150
151            if(currentEntity.isMessageMimeType()) {
152
153                if(currentEntity.isBase64Encoded()) {
154                    insideBody = true;
155                }
156
157                currentEntity = currentEntity.getEmbeddedMessage();
158                currentEntity.startLine = lineNumber + 1;
159            }
160            else {
161                insideBody = true;
162            }
163        }
164        else if(Character.isWhitespace(line.charAt(0))) {
165            fieldBuilder.append(line);
166        }
167        else {
168            processField();
169            fieldBuilder.append(line);
170        }
171    }
172
173
174    private static final Pattern base64EncodedPattern = Pattern.compile("^content-transfer-encoding(\\s*):(\\s*)(base64)", Pattern.CASE_INSENSITIVE);
175    private static final Pattern compositeTypePattern = Pattern.compile("^content-type(\\s*):(\\s*)((message)|(multipart))/([\\w-]+)", Pattern.CASE_INSENSITIVE);
176    private static final Pattern boundaryPattern = Pattern.compile(";(\\s*)boundary(\\s*)=(\\s*)([^\"\\s;]+|\"[^\"\\\\\\r\\n]*(?:\\\\.[^\"\\\\\\r\\n]*)*\")", Pattern.CASE_INSENSITIVE);
177   
178    private void processField() {
179
180        if(fieldBuilder.length() == 0) {
181            return;
182        }
183
184        Matcher base64Matcher = base64EncodedPattern.matcher(fieldBuilder);
185
186        if(base64Matcher.lookingAt()) {
187            currentEntity.base64Encoded = true;
188        }
189
190        Matcher compositeMatcher = compositeTypePattern.matcher(fieldBuilder);
191
192        if(compositeMatcher.lookingAt()) {
193
194            if(compositeMatcher.group(4) != null) {
195                currentEntity.messageMimeType = true;
196            }
197            else {
198                currentEntity.multipartMimeType = true;
199
200                Matcher boundaryMatcher = boundaryPattern.matcher(fieldBuilder.subSequence(compositeMatcher.end(6), fieldBuilder.length()));
201
202                if(boundaryMatcher.lookingAt()) {
203                    String boundary = boundaryMatcher.group(4);
204                    if(boundary.charAt(0) == '\"') {
205                        boundary = boundary.substring(1, boundary.length() - 1);
206                    }
207                    currentEntity.boundary = boundary;
208                }
209                else {
210                    throw new RuntimeException("boundary parameter not found");
211                }
212            }
213        }
214        fieldBuilder.setLength(0);
215    }
216
217    private void processBodyLine() {
218
219        if((lineBuilder.length() < 2) || (lineBuilder.charAt(0) != '-') || (lineBuilder.charAt(1) != '-')) {
220            return;
221        }
222
223        Entity ancestorEntity;
224
225        if(currentEntity.isMultipartMimeType() && (currentEntity.parts == null)) {
226            ancestorEntity = currentEntity;
227        }
228        else {
229            ancestorEntity = currentEntity.parent;
230
231            while(ancestorEntity != null) {
232                if(ancestorEntity.isMultipartMimeType()) {
233                    break;
234                }
235                ancestorEntity = ancestorEntity.parent;
236            }
237        }
238
239        if((ancestorEntity == null) || ((ancestorEntity.boundary.length() + 2) > lineBuilder.length())) {
240            return;
241        }
242
243        for(int i = 0, j = 2; i < ancestorEntity.boundary.length(); i++, j++) {
244            if(ancestorEntity.boundary.charAt(i) != lineBuilder.charAt(j)) {
245                return;
246            }
247        }
248
249        int k = 0;
250
251        for(int i = 0, j = ancestorEntity.boundary.length() + 2; j < lineBuilder.length(); i++, j++) {
252            if((i == 0) && (lineBuilder.charAt(j) == '-')) {
253                k = 1;
254                continue;
255            }
256            else if((i == 1) && (k == 1)) {
257                if(lineBuilder.charAt(j) == '-') {
258                    k = 2;
259                    continue;
260                }
261                return;
262            }
263            else if(Character.isWhitespace(lineBuilder.charAt(j))){
264                continue;
265            }
266            return;
267        }
268
269        if(k == 1) {
270            return;
271        }
272
273        //it's a boundary line !
274
275        if(ancestorEntity.parts != null) {
276            do {
277                currentEntity.endLine = lineNumber - 1;
278                currentEntity.bodyLength = lineOffset - currentEntity.bodyOffset;
279       
280                currentEntity = currentEntity.parent;
281            }
282            while(currentEntity != ancestorEntity);
283        }
284
285        if(k == 0) {
286            currentEntity = currentEntity.getLocalNextPart();
287            currentEntity.startLine = lineNumber + 1;
288            insideBody = false;
289        }
290    }
291
292    public void endOfStream() {
293        if(lineBuilder.length() > 0) {
294            if(insideBody) {
295                processBodyLine();
296            }
297            else {
298                processHeaderLine();
299            }
300        }
301
302        for(;;) {
303            currentEntity.endLine = lineNumber - 1;
304            currentEntity.bodyLength = lineOffset - currentEntity.bodyOffset;
305
306            if(currentEntity.parent == null) {
307                break;
308            }
309
310            currentEntity = currentEntity.parent;
311        }
312    }
313
314
315    public class Entity {
316
317        private int startLine = -1;
318        private int separatorLine = -1;
319        private int endLine = -1;
320
321        private int bodyOffset = -1;
322        private int bodyLength = -1;
323
324        public int getStartLine()     { return startLine; }
325        public int getSeparatorLine() { return separatorLine; }
326        public int getEndLine()       { return endLine; }
327
328        public int getBodyOffset() { return bodyOffset; }
329        public int getBodyLength() { return bodyLength; }
330
331        //-----------------------------
332        private boolean base64Encoded;
333
334        public boolean isBase64Encoded() {
335            return base64Encoded;
336        }
337
338        //-----------------------------
339        private Entity parent;
340
341        public Entity getParent() {
342            return parent;
343        }
344
345        //-----------------------------
346        private boolean multipartMimeType;
347        private String boundary;
348        private List<Entity> parts;
349        private int nextPart = 0;
350        private int localNextPart = 0;
351
352        public boolean isMultipartMimeType() {
353            return multipartMimeType;
354        }
355
356        public String getBoundary() {
357            return boundary;
358        }
359
360        public synchronized Entity getNextPart() {
361            if(parts == null) {
362                parts = new ArrayList<Entity>();
363                multipartMimeType = true;
364            }
365            while(parts.size() <= nextPart) {
366                Entity bodyPart = new Entity();
367                bodyPart.parent = this;
368                parts.add(bodyPart);
369            }
370            return parts.get(nextPart++);
371        }
372
373        private synchronized Entity getLocalNextPart() {
374            if(parts == null) {
375                parts = new ArrayList<Entity>();
376                multipartMimeType = true;
377            }
378            while(parts.size() <= localNextPart) {
379                Entity bodyPart = new Entity();
380                bodyPart.parent = this;
381                parts.add(bodyPart);
382            }
383            return parts.get(localNextPart++);
384        }
385
386        //-----------------------------
387        private boolean messageMimeType;
388        private Entity embeddedMessage;
389
390        public boolean isMessageMimeType() {
391            return messageMimeType;
392        }
393
394        public synchronized Entity getEmbeddedMessage() {
395            if(embeddedMessage == null) {
396                embeddedMessage = new Entity();
397                embeddedMessage.parent = this;
398                messageMimeType = true;
399            }
400            return embeddedMessage;
401        }
402
403        //-----------------------------
404        private void dump(String label, String indent, StringBuilder sb) {
405            sb.append(indent).append(label);
406
407            if(this == currentEntity) {
408                sb.append(" #{").append(hashCode()).append("}#");
409            }
410            else {
411                sb.append(" {").append(hashCode()).append("}");
412            }
413            sb.append(" (startLine:").append(startLine)
414                .append(", separatorLine:").append(separatorLine)
415                .append(", endLine:").append(endLine)
416                .append(", bodyOffset:").append(bodyOffset)
417                .append(", bodyLength:").append(bodyLength);
418            if(boundary != null) {
419                sb.append(", boundary:").append(boundary);
420            }
421            sb.append(")\n");
422            if(isMessageMimeType()) {
423                if(embeddedMessage == null) {
424                    sb.append(indent + "    Embedded: null");
425                }
426                else {
427                    embeddedMessage.dump("Embedded", indent + "    ", sb);
428                }
429            }
430            else if(isMultipartMimeType()) {
431                if(parts == null) {
432                    sb.append(indent + "    Parts: null");
433                }
434                else {
435                    for(int i = 0; i < parts.size(); i++) {
436                        parts.get(i).dump("Part[" + (i+1) + "]", indent + "    ", sb);
437                    }
438                }
439            }
440        }
441
442        public String dump(String label) {
443            StringBuilder sb = new StringBuilder();
444            dump(label, "", sb);
445            return sb.toString();
446        }
447    }
448}
Note: See TracBrowser for help on using the repository browser.