Source for gnu.inet.imap.UTF7imap

   1: /*
   2:  * UTF7imap.java
   3:  * Copyright (C) 2003 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20:  *
  21:  * Linking this library statically or dynamically with other modules is
  22:  * making a combined work based on this library.  Thus, the terms and
  23:  * conditions of the GNU General Public License cover the whole
  24:  * combination.
  25:  *
  26:  * As a special exception, the copyright holders of this library give you
  27:  * permission to link this library with independent modules to produce an
  28:  * executable, regardless of the license terms of these independent
  29:  * modules, and to copy and distribute the resulting executable under
  30:  * terms of your choice, provided that you also meet, for each linked
  31:  * independent module, the terms and conditions of the license of that
  32:  * module.  An independent module is a module which is not derived from
  33:  * or based on this library.  If you modify this library, you may extend
  34:  * this exception to your version of the library, but you are not
  35:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.imap;
  40: 
  41: import java.io.ByteArrayOutputStream;
  42: import java.io.IOException;
  43: 
  44: /**
  45:  * Encodes and decodes text according to the IMAP4rev1 mailbox name
  46:  * encoding scheme.
  47:  *
  48:  * @author <a href="mailto:dog@gnu.org">Chris Burdess</a>
  49:  */
  50: public final class UTF7imap
  51: {
  52: 
  53:   private static final String US_ASCII = "US-ASCII";
  54: 
  55:   private static final byte[] src = {
  56:     0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
  57:     0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x54,
  58:     0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64,
  59:     0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
  60:     0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
  61:     0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
  62:     0x38, 0x39, 0x2b, 0x2c
  63:   };
  64: 
  65:   private static final byte[] dst;
  66:   static
  67:   {
  68:     dst = new byte[0x100];
  69:     for (int i = 0; i < 0xff; i++)
  70:       {
  71:         dst[i] = -1;
  72:       }
  73:     for (int i = 0; i < src.length; i++)
  74:       {
  75:         dst[src[i]] = (byte) i;
  76:       }
  77: 
  78:   }
  79: 
  80:   private UTF7imap()
  81:   {
  82:   }
  83: 
  84:   /**
  85:    * Encode the specified byte array using the modified BASE64 algorithm
  86:    * specified by UTF-7, with further IMAP4rev1 modifications.
  87:    *
  88:    * @param bs the source byte array
  89:    */
  90:   static byte[] encode(byte[] bs)
  91:   {
  92:     int si = 0, ti = 0;         // source/target array indices
  93:     byte[] bt = new byte[(((bs.length + 2) / 3) * 4) -1];// target byte array
  94:     for (; si < bs.length; si += 3)
  95:       {
  96:         int buflen = bs.length - si;
  97:         if (buflen == 1)
  98:           {
  99:             byte b = bs[si];
 100:             int i = 0;
 101:             boolean flag = false;
 102:             bt[ti++] = src[b >>> 2 & 0x3f];
 103:             bt[ti++] = src[(b << 4 & 0x30) + (i >>> 4 & 0xf)];
 104:           }
 105:         else if (buflen == 2)
 106:           {
 107:             byte b1 = bs[si], b2 = bs[si + 1];
 108:             int i = 0;
 109:             bt[ti++] = src[b1 >>> 2 & 0x3f];
 110:             bt[ti++] = src[(b1 << 4 & 0x30) + (b2 >>> 4 & 0xf)];
 111:             bt[ti++] = src[(b2 << 2 & 0x3c) + (i >>> 6 & 0x3)];
 112:           }
 113:         else if (buflen == 3)
 114:           {
 115:             byte b1 = bs[si], b2 = bs[si + 1], b3 = bs[si + 2];
 116:             bt[ti++] = src[b1 >>> 2 & 0x3f];
 117:             bt[ti++] = src[(b1 << 4 & 0x30) + (b2 >>> 4 & 0xf)];
 118:             bt[ti++] = src[(b2 << 2 & 0x3c) + (b3 >>> 6 & 0x3)];
 119:             bt[ti++] = src[b3 & 0x3f];
 120:           }
 121:       }
 122:     return bt;
 123:   }
 124: 
 125:   /**
 126:    * Decode the specified byte array using the modified BASE64 algorithm
 127:    * specified by UTF-7, with further IMAP4rev1 modifications.
 128:    *
 129:    * @param bs the source byte array
 130:    */
 131:   static int[] decode(byte[] bs)
 132:   {
 133:     int[] buffer = new int[bs.length];
 134:     int buflen = 0;
 135:     int si = 0;
 136:     int len = bs.length - si;
 137:     while (len > 0)
 138:       {
 139:         byte b0 = dst[bs[si++] & 0xff];
 140:         byte b2 = dst[bs[si++] & 0xff];
 141:         buffer[buflen++] = (b0 << 2 & 0xfc | b2 >>> 4 & 0x3);
 142:         if (len > 2)
 143:           {
 144:             b0 = b2;
 145:             b2 = dst[bs[si++] & 0xff];
 146:             buffer[buflen++] = (b0 << 4 & 0xf0 | b2 >>> 2 & 0xf);
 147:             if (len > 3)
 148:               {
 149:                 b0 = b2;
 150:                 b2 = dst[bs[si++] & 0xff];
 151:                 buffer[buflen++] = (b0 << 6 & 0xc0 | b2 & 0x3f);
 152:               }
 153:           }
 154:         len = bs.length - si;
 155:       }
 156:     int[] bt = new int[buflen];
 157:     System.arraycopy(buffer, 0, bt, 0, buflen);
 158:     return bt;
 159:   }
 160: 
 161:   /**
 162:    * Encodes the specified name using the UTF-7.imap encoding.
 163:    * See IMAP4rev1 spec, section 5.1.3
 164:    */
 165:   public static String encode(String name)
 166:   {
 167:     try
 168:       {
 169:         StringBuffer buffer = null;
 170:         ByteArrayOutputStream encoderSink = null;
 171:         char[] chars = name.toCharArray();
 172:         boolean encoding = false;
 173:         for (int i = 0; i < chars.length; i++)
 174:           {
 175:             char c = chars[i];
 176:             if (c == '&')
 177:               {
 178:                 if (buffer == null)
 179:                   {
 180:                     buffer = new StringBuffer();
 181:                     for (int j = 0; j < i; j++)
 182:                       {
 183:                         buffer.append(chars[j]);
 184:                       }
 185:                   }
 186:                 buffer.append('&');
 187:                 buffer.append('-');
 188:               }
 189:             if (c < 0x1f || c > 0x7f)
 190:               {
 191:                 // needs encoding
 192:                 if (buffer == null)
 193:                   {
 194:                     buffer = new StringBuffer();
 195:                     for (int j = 0; j < i; j++)
 196:                       {
 197:                         buffer.append(chars[j]);
 198:                       }
 199:                     encoderSink = new ByteArrayOutputStream();
 200:                   }
 201:                 if (!encoding)
 202:                   {
 203:                     encoderSink.reset();
 204:                     buffer.append('&');
 205:                     encoding = true;
 206:                   }
 207:                 encoderSink.write(((int) c) / 0x100);
 208:                 encoderSink.write(((int) c) % 0x100);
 209:               }
 210:             else if (encoding)
 211:               {
 212:                 encoderSink.flush();
 213:                 byte[] encoded = encode(encoderSink.toByteArray());
 214:                 buffer.append(new String(encoded, US_ASCII));
 215:                 buffer.append('-');
 216:                 encoding = false;
 217:                 if (c != '-')
 218:                   {
 219:                     buffer.append(c);
 220:                   }
 221:               }
 222:             else if (buffer != null)
 223:               {
 224:                 buffer.append(c);
 225:               }
 226:           }
 227:         if (encoding)
 228:           {
 229:             encoderSink.flush();
 230:             byte[] encoded = encode(encoderSink.toByteArray());
 231:             buffer.append(new String(encoded, US_ASCII));
 232:             buffer.append('-');
 233:           }
 234:         if (buffer != null)
 235:           {
 236:             return buffer.toString();
 237:           }
 238:       }
 239:     catch (IOException e)
 240:       {
 241:         // This should never happen
 242:         throw new RuntimeException(e.getMessage());
 243:       }
 244:     return name;
 245:   }
 246:   
 247:   /**
 248:    * Decodes the specified name using the UTF-7.imap decoding.
 249:    * See IMAP4rev1 spec, section 5.1.3
 250:    */
 251:   public static String decode(String name)
 252:   {
 253:     StringBuffer buffer = null;
 254:     ByteArrayOutputStream decoderSink = null;
 255:     char[] chars = name.toCharArray();
 256:     boolean encoded = false;
 257:     for (int i = 0; i < chars.length; i++)
 258:       {
 259:         char c = chars[i];
 260:         if (c == '&')
 261:           {
 262:             if (buffer == null)
 263:               {
 264:                 buffer = new StringBuffer();
 265:                 decoderSink = new ByteArrayOutputStream();
 266:                 for (int j = 0; j < i; j++)
 267:                   {
 268:                     buffer.append(chars[j]);
 269:                   }
 270:               }
 271:             decoderSink.reset();
 272:             encoded = true;
 273:           }
 274:         else if (c == '-' && encoded)
 275:           {
 276:             if (decoderSink.size() == 0)
 277:               {
 278:                 buffer.append('&');
 279:               }
 280:             else
 281:               {
 282:                 int[] decoded = decode(decoderSink.toByteArray());
 283:                 for (int j = 0; j < decoded.length - 1; j += 2)
 284:                   {
 285:                     int hibyte = decoded[j];
 286:                     int lobyte = decoded[j + 1];
 287:                     int d = (hibyte * 0x100) | lobyte;
 288:                     buffer.append((char) d);
 289:                   }
 290:               }
 291:             encoded = false;
 292:           }
 293:         else if (encoded)
 294:           {
 295:             decoderSink.write((byte) c);
 296:           }
 297:         else if (buffer != null)
 298:           {
 299:             buffer.append(c);
 300:           }
 301:       }
 302:     if (buffer != null)
 303:       {
 304:         return buffer.toString();
 305:       }
 306:     return name;
 307:     }
 308:   
 309:   public static void main(String[] args)
 310:   {
 311:     boolean decode = false;
 312:     for (int i = 0; i < args.length; i++)
 313:       {
 314:         if (args[i].equals("-d"))
 315:           {
 316:             decode = true;
 317:           }
 318:         else
 319:           {
 320:             String ret = decode ? decode(args[i]) : encode(args[i]);
 321:             StringBuffer buf = new StringBuffer(args[i]);
 322:             buf.append(" = \"");
 323:             buf.append(ret);
 324:             buf.append("\"(");
 325:             for (int j = 0; j < ret.length(); j++)
 326:               {
 327:                 if (j > 0)
 328:                   {
 329:                     buf.append(' ');
 330:                   }
 331:                 int c = (int) ret.charAt(j);
 332:                 buf.append(Integer.toString(c, 16));
 333:               }
 334:             buf.append(")");
 335:             System.out.println(buf.toString());
 336:           }
 337:       }
 338:   }
 339:   
 340: }