Skip to contentPackage: Atom
Atom
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.iap;
18:
19: import java.util.List;
20:
21: import org.eclipse.angus.mail.util.ASCIIUtility;
22:
23: import java.util.ArrayList;
24: import java.io.*;
25: import java.nio.charset.Charset;
26:
27: /**
28: * @author John Mani
29: * @author Bill Shannon
30: */
31:
32: public class Argument {
33: protected List<Object> items;
34:
35: /**
36: * Constructor
37: */
38: public Argument() {
39:         items = new ArrayList<>(1);
40: }
41:
42: /**
43: * Append the given Argument to this Argument. All items
44: * from the source argument are copied into this destination
45: * argument.
46: *
47: * @param        arg        the Argument to append
48: * @return                this
49: */
50: public Argument append(Argument arg) {
51:         items.addAll(arg.items);
52:         return this;
53: }
54:
55: /**
56: * Write out given string as an ASTRING, depending on the type
57: * of the characters inside the string. The string should
58: * contain only ASCII characters. <p>
59: *
60: * XXX: Hmm .. this should really be called writeASCII()
61: *
62: * @param        s        String to write out
63: * @return                this
64: */
65: public Argument writeString(String s) {
66:         items.add(new AString(ASCIIUtility.getBytes(s)));
67:         return this;
68: }
69:
70: /**
71: * Convert the given string into bytes in the specified
72: * charset, and write the bytes out as an ASTRING
73: *
74: * @param        s        String to write out
75: * @param        charset        the charset
76: * @return                this
77: * @exception        UnsupportedEncodingException        for bad charset
78: */
79: public Argument writeString(String s, String charset)
80:                 throws UnsupportedEncodingException {
81:         if (charset == null) // convenience
82:          writeString(s);
83:         else
84:          items.add(new AString(s.getBytes(charset)));
85:         return this;
86: }
87:
88: /**
89: * Convert the given string into bytes in the specified
90: * charset, and write the bytes out as an ASTRING
91: *
92: * @param        s        String to write out
93: * @param        charset        the charset
94: * @return                this
95: * @since        JavaMail 1.6.0
96: */
97: public Argument writeString(String s, Charset charset) {
98:         if (charset == null) // convenience
99:          writeString(s);
100:         else
101:          items.add(new AString(s.getBytes(charset)));
102:         return this;
103: }
104:
105: /**
106: * Write out given string as an NSTRING, depending on the type
107: * of the characters inside the string. The string should
108: * contain only ASCII characters. <p>
109: *
110: * @param        s        String to write out
111: * @return                this
112: * @since        JavaMail 1.5.1
113: */
114: public Argument writeNString(String s) {
115:         if (s == null)
116:          items.add(new NString(null));
117:         else
118:          items.add(new NString(ASCIIUtility.getBytes(s)));
119:         return this;
120: }
121:
122: /**
123: * Convert the given string into bytes in the specified
124: * charset, and write the bytes out as an NSTRING
125: *
126: * @param        s        String to write out
127: * @param        charset        the charset
128: * @return                this
129: * @exception        UnsupportedEncodingException        for bad charset
130: * @since        JavaMail 1.5.1
131: */
132: public Argument writeNString(String s, String charset)
133:                 throws UnsupportedEncodingException {
134:         if (s == null)
135:          items.add(new NString(null));
136:         else if (charset == null) // convenience
137:          writeString(s);
138:         else
139:          items.add(new NString(s.getBytes(charset)));
140:         return this;
141: }
142:
143: /**
144: * Convert the given string into bytes in the specified
145: * charset, and write the bytes out as an NSTRING
146: *
147: * @param        s        String to write out
148: * @param        charset        the charset
149: * @return                this
150: * @since        JavaMail 1.6.0
151: */
152: public Argument writeNString(String s, Charset charset) {
153:         if (s == null)
154:          items.add(new NString(null));
155:         else if (charset == null) // convenience
156:          writeString(s);
157:         else
158:          items.add(new NString(s.getBytes(charset)));
159:         return this;
160: }
161:
162: /**
163: * Write out given byte[] as a Literal.
164: * @param b byte[] to write out
165: * @return        this
166: */
167: public Argument writeBytes(byte[] b) {
168:         items.add(b);
169:         return this;
170: }
171:
172: /**
173: * Write out given ByteArrayOutputStream as a Literal.
174: * @param b ByteArrayOutputStream to be written out.
175: * @return        this
176: */
177: public Argument writeBytes(ByteArrayOutputStream b) {
178:         items.add(b);
179:         return this;
180: }
181:
182: /**
183: * Write out given data as a literal.
184: * @param b Literal representing data to be written out.
185: * @return        this
186: */
187: public Argument writeBytes(Literal b) {
188:         items.add(b);
189:         return this;
190: }
191:
192: /**
193: * Write out given string as an Atom. Note that an Atom can contain only
194: * certain US-ASCII characters. No validation is done on the characters
195: * in the string.
196: * @param s String
197: * @return        this
198: */
199: public Argument writeAtom(String s) {
200:         items.add(new Atom(s));
201:         return this;
202: }
203:
204: /**
205: * Write out number.
206: * @param i number
207: * @return        this
208: */
209: public Argument writeNumber(int i) {
210:         items.add(Integer.valueOf(i));
211:         return this;
212: }
213:
214: /**
215: * Write out number.
216: * @param i number
217: * @return        this
218: */
219: public Argument writeNumber(long i) {
220:         items.add(Long.valueOf(i));
221:         return this;
222: }
223:
224: /**
225: * Write out as parenthesised list.
226: *
227: * @param        c        the Argument
228: * @return        this
229: */
230: public Argument writeArgument(Argument c) {
231:         items.add(c);
232:         return this;
233: }
234:
235: /*
236: * Write out all the buffered items into the output stream.
237: */
238: public void write(Protocol protocol)
239:                 throws IOException, ProtocolException {
240:         int size = items != null ? items.size() : 0;
241:         DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
242:
243:         for (int i=0; i < size; i++) {
244:          if (i > 0)        // write delimiter if not the first item
245:                 os.write(' ');
246:
247:          Object o = items.get(i);
248:          if (o instanceof Atom) {
249:                 os.writeBytes(((Atom)o).string);
250:          } else if (o instanceof Number) {
251:                 os.writeBytes(((Number)o).toString());
252:          } else if (o instanceof AString) {
253:                 astring(((AString)o).bytes, protocol);
254:          } else if (o instanceof NString) {
255:                 nstring(((NString)o).bytes, protocol);
256:          } else if (o instanceof byte[]) {
257:                 literal((byte[])o, protocol);
258:          } else if (o instanceof ByteArrayOutputStream) {
259:                 literal((ByteArrayOutputStream)o, protocol);
260:          } else if (o instanceof Literal) {
261:                 literal((Literal)o, protocol);
262:          } else if (o instanceof Argument) {
263:                 os.write('('); // open parans
264:                 ((Argument)o).write(protocol);
265:                 os.write(')'); // close parans
266:          }
267:         }
268: }
269:
270: /**
271: * Write out given String as either an Atom, QuotedString or Literal
272: */
273: private void astring(byte[] bytes, Protocol protocol)
274:                         throws IOException, ProtocolException {
275:         nastring(bytes, protocol, false);
276: }
277:
278: /**
279: * Write out given String as either NIL, QuotedString, or Literal.
280: */
281: private void nstring(byte[] bytes, Protocol protocol)
282:                         throws IOException, ProtocolException {
283:         if (bytes == null) {
284:          DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
285:          os.writeBytes("NIL");
286:         } else
287:          nastring(bytes, protocol, true);
288: }
289:
290: private void nastring(byte[] bytes, Protocol protocol, boolean doQuote)
291:                         throws IOException, ProtocolException {
292:         DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
293:         int len = bytes.length;
294:
295:         // If length is greater than 1024 bytes, send as literal
296:         if (len > 1024) {
297:          literal(bytes, protocol);
298:          return;
299:         }
300:
301: // if 0 length, send as quoted-string
302: boolean quote = len == 0 ? true : doQuote;
303:         boolean escape = false;
304:         boolean utf8 = protocol.supportsUtf8();
305:
306:         byte b;
307:         for (int i = 0; i < len; i++) {
308:          b = bytes[i];
309:          if (b == '\0' || b == '\r' || b == '\n' ||
310:                  (!utf8 && ((b & 0xff) > 0177))) {
311:                 // NUL, CR or LF means the bytes need to be sent as literals
312:                 literal(bytes, protocol);
313:                 return;
314:          }
315:          if (b == '*' || b == '%' || b == '(' || b == ')' || b == '{' ||
316:                  b == '"' || b == '\\' ||
317:                  ((b & 0xff) <= ' ') || ((b & 0xff) > 0177)) {
318:                 quote = true;
319:                 if (b == '"' || b == '\\') // need to escape these characters
320:                  escape = true;
321:          }
322:         }
323:
324:         /*
325:          * Make sure the (case-independent) string "NIL" is always quoted,
326:          * so as not to be confused with a real NIL (handled above in nstring).
327:          * This is more than is necessary, but it's rare to begin with and
328:          * this makes it safer than doing the test in nstring above in case
329:          * some code calls writeString when it should call writeNString.
330:          */
331:         if (!quote && bytes.length == 3 &&
332:                 (bytes[0] == 'N' || bytes[0] == 'n') &&
333:                 (bytes[1] == 'I' || bytes[1] == 'i') &&
334:                 (bytes[2] == 'L' || bytes[2] == 'l'))
335:          quote = true;
336:
337:         if (quote) // start quote
338:          os.write('"');
339:
340: if (escape) {
341: // already quoted
342: for (int i = 0; i < len; i++) {
343: b = bytes[i];
344: if (b == '"' || b == '\\')
345: os.write('\\');
346: os.write(b);
347: }
348: } else
349: os.write(bytes);
350:
351:
352:         if (quote) // end quote
353:          os.write('"');
354: }
355:
356: /**
357: * Write out given byte[] as a literal
358: */
359: private void literal(byte[] b, Protocol protocol)
360:                         throws IOException, ProtocolException {
361:         startLiteral(protocol, b.length).write(b);
362: }
363:
364: /**
365: * Write out given ByteArrayOutputStream as a literal.
366: */
367: private void literal(ByteArrayOutputStream b, Protocol protocol)
368:                         throws IOException, ProtocolException {
369:         b.writeTo(startLiteral(protocol, b.size()));
370: }
371:
372: /**
373: * Write out given Literal as a literal.
374: */
375: private void literal(Literal b, Protocol protocol)
376:                         throws IOException, ProtocolException {
377:         b.writeTo(startLiteral(protocol, b.size()));
378: }
379:
380: private OutputStream startLiteral(Protocol protocol, int size)
381:                         throws IOException, ProtocolException {
382:         DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
383:         boolean nonSync = protocol.supportsNonSyncLiterals();
384:
385:         os.write('{');
386:         os.writeBytes(Integer.toString(size));
387:         if (nonSync) // server supports non-sync literals
388:          os.writeBytes("+}\r\n");
389:         else
390:          os.writeBytes("}\r\n");
391:         os.flush();
392:
393:         // If we are using synchronized literals, wait for the server's
394:         // continuation signal
395:         if (!nonSync) {
396:          for (; ;) {
397:                 Response r = protocol.readResponse();
398:                 if (r.isContinuation())
399:                  break;
400:                 if (r.isTagged())
401:                  throw new LiteralException(r);
402:                 // XXX - throw away untagged responses;
403:                 //         violates IMAP spec, hope no servers do this
404:          }
405:         }
406:         return os;
407: }
408: }
409:
410: class Atom {
411: String string;
412:
413: Atom(String s) {
414:         string = s;
415: }
416: }
417:
418: class AString {
419: byte[] bytes;
420:
421: AString(byte[] b) {
422:         bytes = b;
423: }
424: }
425:
426: class NString {
427: byte[] bytes;
428:
429: NString(byte[] b) {
430:         bytes = b;
431: }
432: }