/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.mail.iap; import java.util.List; import java.util.ArrayList; import java.io.*; import java.nio.charset.Charset; import com.sun.mail.util.*; /** * @author John Mani * @author Bill Shannon */ public class Argument { protected List items; /** * Constructor */ public Argument() { items = new ArrayList<>(1); } /** * Append the given Argument to this Argument. All items * from the source argument are copied into this destination * argument. * * @param arg the Argument to append * @return this */ public Argument append(Argument arg) { items.addAll(arg.items); return this; } /** * Write out given string as an ASTRING, depending on the type * of the characters inside the string. The string should * contain only ASCII characters.

* * XXX: Hmm .. this should really be called writeASCII() * * @param s String to write out * @return this */ public Argument writeString(String s) { items.add(new AString(ASCIIUtility.getBytes(s))); return this; } /** * Convert the given string into bytes in the specified * charset, and write the bytes out as an ASTRING * * @param s String to write out * @param charset the charset * @return this * @exception UnsupportedEncodingException for bad charset */ public Argument writeString(String s, String charset) throws UnsupportedEncodingException { if (charset == null) // convenience writeString(s); else items.add(new AString(s.getBytes(charset))); return this; } /** * Convert the given string into bytes in the specified * charset, and write the bytes out as an ASTRING * * @param s String to write out * @param charset the charset * @return this * @since JavaMail 1.6.0 */ public Argument writeString(String s, Charset charset) { if (charset == null) // convenience writeString(s); else items.add(new AString(s.getBytes(charset))); return this; } /** * Write out given string as an NSTRING, depending on the type * of the characters inside the string. The string should * contain only ASCII characters.

* * @param s String to write out * @return this * @since JavaMail 1.5.1 */ public Argument writeNString(String s) { if (s == null) items.add(new NString(null)); else items.add(new NString(ASCIIUtility.getBytes(s))); return this; } /** * Convert the given string into bytes in the specified * charset, and write the bytes out as an NSTRING * * @param s String to write out * @param charset the charset * @return this * @exception UnsupportedEncodingException for bad charset * @since JavaMail 1.5.1 */ public Argument writeNString(String s, String charset) throws UnsupportedEncodingException { if (s == null) items.add(new NString(null)); else if (charset == null) // convenience writeString(s); else items.add(new NString(s.getBytes(charset))); return this; } /** * Convert the given string into bytes in the specified * charset, and write the bytes out as an NSTRING * * @param s String to write out * @param charset the charset * @return this * @since JavaMail 1.6.0 */ public Argument writeNString(String s, Charset charset) { if (s == null) items.add(new NString(null)); else if (charset == null) // convenience writeString(s); else items.add(new NString(s.getBytes(charset))); return this; } /** * Write out given byte[] as a Literal. * @param b byte[] to write out * @return this */ public Argument writeBytes(byte[] b) { items.add(b); return this; } /** * Write out given ByteArrayOutputStream as a Literal. * @param b ByteArrayOutputStream to be written out. * @return this */ public Argument writeBytes(ByteArrayOutputStream b) { items.add(b); return this; } /** * Write out given data as a literal. * @param b Literal representing data to be written out. * @return this */ public Argument writeBytes(Literal b) { items.add(b); return this; } /** * Write out given string as an Atom. Note that an Atom can contain only * certain US-ASCII characters. No validation is done on the characters * in the string. * @param s String * @return this */ public Argument writeAtom(String s) { items.add(new Atom(s)); return this; } /** * Write out number. * @param i number * @return this */ public Argument writeNumber(int i) { items.add(Integer.valueOf(i)); return this; } /** * Write out number. * @param i number * @return this */ public Argument writeNumber(long i) { items.add(Long.valueOf(i)); return this; } /** * Write out as parenthesised list. * * @param c the Argument * @return this */ public Argument writeArgument(Argument c) { items.add(c); return this; } /* * Write out all the buffered items into the output stream. */ public void write(Protocol protocol) throws IOException, ProtocolException { int size = items != null ? items.size() : 0; DataOutputStream os = (DataOutputStream)protocol.getOutputStream(); for (int i=0; i < size; i++) { if (i > 0) // write delimiter if not the first item os.write(' '); Object o = items.get(i); if (o instanceof Atom) { os.writeBytes(((Atom)o).string); } else if (o instanceof Number) { os.writeBytes(((Number)o).toString()); } else if (o instanceof AString) { astring(((AString)o).bytes, protocol); } else if (o instanceof NString) { nstring(((NString)o).bytes, protocol); } else if (o instanceof byte[]) { literal((byte[])o, protocol); } else if (o instanceof ByteArrayOutputStream) { literal((ByteArrayOutputStream)o, protocol); } else if (o instanceof Literal) { literal((Literal)o, protocol); } else if (o instanceof Argument) { os.write('('); // open parans ((Argument)o).write(protocol); os.write(')'); // close parans } } } /** * Write out given String as either an Atom, QuotedString or Literal */ private void astring(byte[] bytes, Protocol protocol) throws IOException, ProtocolException { nastring(bytes, protocol, false); } /** * Write out given String as either NIL, QuotedString, or Literal. */ private void nstring(byte[] bytes, Protocol protocol) throws IOException, ProtocolException { if (bytes == null) { DataOutputStream os = (DataOutputStream)protocol.getOutputStream(); os.writeBytes("NIL"); } else nastring(bytes, protocol, true); } private void nastring(byte[] bytes, Protocol protocol, boolean doQuote) throws IOException, ProtocolException { DataOutputStream os = (DataOutputStream)protocol.getOutputStream(); int len = bytes.length; // If length is greater than 1024 bytes, send as literal if (len > 1024) { literal(bytes, protocol); return; } // if 0 length, send as quoted-string boolean quote = len == 0 ? true : doQuote; boolean escape = false; boolean utf8 = protocol.supportsUtf8(); byte b; for (int i = 0; i < len; i++) { b = bytes[i]; if (b == '\0' || b == '\r' || b == '\n' || (!utf8 && ((b & 0xff) > 0177))) { // NUL, CR or LF means the bytes need to be sent as literals literal(bytes, protocol); return; } if (b == '*' || b == '%' || b == '(' || b == ')' || b == '{' || b == '"' || b == '\\' || ((b & 0xff) <= ' ') || ((b & 0xff) > 0177)) { quote = true; if (b == '"' || b == '\\') // need to escape these characters escape = true; } } /* * Make sure the (case-independent) string "NIL" is always quoted, * so as not to be confused with a real NIL (handled above in nstring). * This is more than is necessary, but it's rare to begin with and * this makes it safer than doing the test in nstring above in case * some code calls writeString when it should call writeNString. */ if (!quote && bytes.length == 3 && (bytes[0] == 'N' || bytes[0] == 'n') && (bytes[1] == 'I' || bytes[1] == 'i') && (bytes[2] == 'L' || bytes[2] == 'l')) quote = true; if (quote) // start quote os.write('"'); if (escape) { // already quoted for (int i = 0; i < len; i++) { b = bytes[i]; if (b == '"' || b == '\\') os.write('\\'); os.write(b); } } else os.write(bytes); if (quote) // end quote os.write('"'); } /** * Write out given byte[] as a literal */ private void literal(byte[] b, Protocol protocol) throws IOException, ProtocolException { startLiteral(protocol, b.length).write(b); } /** * Write out given ByteArrayOutputStream as a literal. */ private void literal(ByteArrayOutputStream b, Protocol protocol) throws IOException, ProtocolException { b.writeTo(startLiteral(protocol, b.size())); } /** * Write out given Literal as a literal. */ private void literal(Literal b, Protocol protocol) throws IOException, ProtocolException { b.writeTo(startLiteral(protocol, b.size())); } private OutputStream startLiteral(Protocol protocol, int size) throws IOException, ProtocolException { DataOutputStream os = (DataOutputStream)protocol.getOutputStream(); boolean nonSync = protocol.supportsNonSyncLiterals(); os.write('{'); os.writeBytes(Integer.toString(size)); if (nonSync) // server supports non-sync literals os.writeBytes("+}\r\n"); else os.writeBytes("}\r\n"); os.flush(); // If we are using synchronized literals, wait for the server's // continuation signal if (!nonSync) { for (; ;) { Response r = protocol.readResponse(); if (r.isContinuation()) break; if (r.isTagged()) throw new LiteralException(r); // XXX - throw away untagged responses; // violates IMAP spec, hope no servers do this } } return os; } } class Atom { String string; Atom(String s) { string = s; } } class AString { byte[] bytes; AString(byte[] b) { bytes = b; } } class NString { byte[] bytes; NString(byte[] b) { bytes = b; } }