Skip to content

Package: IMAPInputStream

IMAPInputStream

nameinstructionbranchcomplexitylinemethod
IMAPInputStream(IMAPMessage, String, int, boolean)
M: 22 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
available()
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
checkSeen()
M: 27 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
fill()
M: 222 C: 0
0%
M: 36 C: 0
0%
M: 19 C: 0
0%
M: 47 C: 0
0%
M: 1 C: 0
0%
forceCheckExpunged()
M: 52 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
read()
M: 27 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
read(byte[])
M: 7 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
read(byte[], int, int)
M: 40 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.imap;
18:
19: import jakarta.mail.Flags;
20: import jakarta.mail.Folder;
21: import jakarta.mail.FolderClosedException;
22: import jakarta.mail.MessagingException;
23: import org.eclipse.angus.mail.iap.ByteArray;
24: import org.eclipse.angus.mail.iap.ConnectionException;
25: import org.eclipse.angus.mail.iap.ProtocolException;
26: import org.eclipse.angus.mail.imap.protocol.BODY;
27: import org.eclipse.angus.mail.imap.protocol.IMAPProtocol;
28:
29: import java.io.IOException;
30: import java.io.InputStream;
31:
32: /**
33: * This class implements an IMAP data stream.
34: *
35: * @author John Mani
36: */
37:
38: public class IMAPInputStream extends InputStream {
39: private IMAPMessage msg; // this message
40: private String section; // section-id
41: private int pos; // track the position within the IMAP datastream
42: private int blksize; // number of bytes to read in each FETCH request
43: private int max; // the total number of bytes in this section.
44: // -1 indicates unknown
45: private byte[] buf; // the buffer obtained from fetchBODY()
46: private int bufcount; // The index one greater than the index of the
47: // last valid byte in 'buf'
48: private int bufpos; // The current position within 'buf'
49: private boolean lastBuffer; // is this the last buffer of data?
50: private boolean peek; // peek instead of fetch?
51: private ByteArray readbuf; // reuse for each read
52:
53: // Allocate this much extra space in the read buffer to allow
54: // space for the FETCH response overhead
55: private static final int slop = 64;
56:
57:
58: /**
59: * Create an IMAPInputStream.
60: *
61: * @param msg the IMAPMessage the data will come from
62: * @param section the IMAP section/part identifier for the data
63: * @param max the number of bytes in this section
64: * @param peek peek instead of fetch?
65: */
66: public IMAPInputStream(IMAPMessage msg, String section, int max,
67: boolean peek) {
68: this.msg = msg;
69: this.section = section;
70: this.max = max;
71: this.peek = peek;
72: pos = 0;
73: blksize = msg.getFetchBlockSize();
74: }
75:
76: /**
77: * Do a NOOP to force any untagged EXPUNGE responses
78: * and then check if this message is expunged.
79: */
80: private void forceCheckExpunged() throws IOException {
81: synchronized (msg.getMessageCacheLock()) {
82: try {
83: msg.getProtocol().noop();
84: } catch (ConnectionException cex) {
85: throw new IOException(new FolderClosedException(msg.getFolder(), cex.getMessage()));
86: } catch (FolderClosedException fex) {
87: throw new IOException(new FolderClosedException(fex.getFolder(), fex.getMessage()));
88: } catch (ProtocolException pex) {
89: // ignore it
90: }
91: }
92:• if (msg.isExpunged())
93: throw new IOException(new MessagingException());
94: }
95:
96: /**
97: * Fetch more data from the server. This method assumes that all
98: * data has already been read in, hence bufpos > bufcount.
99: */
100: private void fill() throws IOException {
101: /*
102: * If we've read the last buffer, there's no more to read.
103: * If we know the total number of bytes available from this
104: * section, let's check if we have consumed that many bytes.
105: */
106:• if (lastBuffer || max != -1 && pos >= max) {
107:• if (pos == 0)
108: checkSeen();
109: readbuf = null; // XXX - return to pool?
110: return; // the caller of fill() will return -1.
111: }
112:
113: BODY b = null;
114:• if (readbuf == null)
115: readbuf = new ByteArray(blksize + slop);
116:
117: ByteArray ba;
118: int cnt;
119: // Acquire MessageCacheLock, to freeze seqnum.
120: synchronized (msg.getMessageCacheLock()) {
121: try {
122: IMAPProtocol p = msg.getProtocol();
123:
124: // Check whether this message is expunged
125:• if (msg.isExpunged())
126: throw new IOException(new MessagingException("No content for expunged message"));
127:
128: int seqnum = msg.getSequenceNumber();
129: cnt = blksize;
130:• if (max != -1 && pos + blksize > max)
131: cnt = max - pos;
132:• if (peek)
133: b = p.peekBody(seqnum, section, pos, cnt, readbuf);
134: else
135: b = p.fetchBody(seqnum, section, pos, cnt, readbuf);
136: } catch (ProtocolException pex) {
137: forceCheckExpunged();
138: throw new IOException(pex.getMessage());
139: } catch (FolderClosedException fex) {
140: throw new IOException(new FolderClosedException(fex.getFolder(), fex.getMessage()));
141: }
142:
143:• if (b == null || ((ba = b.getByteArray()) == null)) {
144: forceCheckExpunged();
145: // nope, the server doesn't think it's expunged.
146: // can't tell the difference between the server returning NIL
147: // and some other error that caused null to be returned above,
148: // so we'll just assume it was empty content.
149: ba = new ByteArray(0);
150: }
151: }
152:
153: // make sure the SEEN flag is set after reading the first chunk
154:• if (pos == 0)
155: checkSeen();
156:
157: // setup new values ..
158: buf = ba.getBytes();
159: bufpos = ba.getStart();
160: int n = ba.getCount(); // will be zero, if all data has been
161: // consumed from the server.
162:
163:• int origin = b != null ? b.getOrigin() : pos;
164:• if (origin < 0) {
165: /*
166: * Some versions of Exchange will return the entire message
167: * body even though we only ask for a chunk, and the returned
168: * data won't specify an "origin". If this happens, and we
169: * get more data than we asked for, assume it's the entire
170: * message body.
171: */
172:• if (pos == 0) {
173: /*
174: * If we got more or less than we asked for,
175: * this is the last buffer of data.
176: */
177:• lastBuffer = n != cnt;
178: } else {
179: /*
180: * We asked for data NOT starting at the beginning,
181: * but we got back data starting at the beginning.
182: * Possibly we could extract the needed data from
183: * some part of the data we got back, but really this
184: * should never happen so we just assume something is
185: * broken and terminate the data here.
186: */
187: n = 0;
188: lastBuffer = true;
189: }
190:• } else if (origin == pos) {
191: /*
192: * If we got less than we asked for,
193: * this is the last buffer of data.
194: */
195:• lastBuffer = n < cnt;
196: } else {
197: /*
198: * We got back data that doesn't match the request.
199: * Just terminate the data here.
200: */
201: n = 0;
202: lastBuffer = true;
203: }
204:
205: bufcount = bufpos + n;
206: pos += n;
207:
208: }
209:
210: /**
211: * Reads the next byte of data from this buffered input stream.
212: * If no byte is available, the value <code>-1</code> is returned.
213: */
214: @Override
215: public synchronized int read() throws IOException {
216:• if (bufpos >= bufcount) {
217: fill();
218:• if (bufpos >= bufcount)
219: return -1; // EOF
220: }
221: return buf[bufpos++] & 0xff;
222: }
223:
224: /**
225: * Reads up to <code>len</code> bytes of data from this
226: * input stream into the given buffer. <p>
227: *
228: * Returns the total number of bytes read into the buffer,
229: * or <code>-1</code> if there is no more data. <p>
230: *
231: * Note that this method mimics the "weird !" semantics of
232: * BufferedInputStream in that the number of bytes actually
233: * returned may be less that the requested value. So callers
234: * of this routine should be aware of this and must check
235: * the return value to insure that they have obtained the
236: * requisite number of bytes.
237: */
238: @Override
239: public synchronized int read(byte[] b, int off, int len)
240: throws IOException {
241:
242: int avail = bufcount - bufpos;
243:• if (avail <= 0) {
244: fill();
245: avail = bufcount - bufpos;
246:• if (avail <= 0)
247: return -1; // EOF
248: }
249: int cnt = Math.min(avail, len);
250: System.arraycopy(buf, bufpos, b, off, cnt);
251: bufpos += cnt;
252: return cnt;
253: }
254:
255: /**
256: * Reads up to <code>b.length</code> bytes of data from this input
257: * stream into an array of bytes. <p>
258: *
259: * Returns the total number of bytes read into the buffer, or
260: * <code>-1</code> is there is no more data. <p>
261: *
262: * Note that this method mimics the "weird !" semantics of
263: * BufferedInputStream in that the number of bytes actually
264: * returned may be less that the requested value. So callers
265: * of this routine should be aware of this and must check
266: * the return value to insure that they have obtained the
267: * requisite number of bytes.
268: */
269: @Override
270: public int read(byte[] b) throws IOException {
271: return read(b, 0, b.length);
272: }
273:
274: /**
275: * Returns the number of bytes that can be read from this input
276: * stream without blocking.
277: */
278: @Override
279: public synchronized int available() throws IOException {
280: return (bufcount - bufpos);
281: }
282:
283: /**
284: * Normally the SEEN flag will have been set by now, but if not,
285: * force it to be set (as long as the folder isn't open read-only
286: * and we're not peeking).
287: * And of course, if there's no folder (e.g., a nested message)
288: * don't do anything.
289: */
290: private void checkSeen() {
291:• if (peek) // if we're peeking, don't set the SEEN flag
292: return;
293: try {
294: Folder f = msg.getFolder();
295:• if (f != null && f.getMode() != Folder.READ_ONLY &&
296:• !msg.isSet(Flags.Flag.SEEN))
297: msg.setFlag(Flags.Flag.SEEN, true);
298: } catch (MessagingException ex) {
299: // ignore it
300: }
301: }
302: }