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