You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by do...@apache.org on 2013/08/22 17:29:58 UTC

svn commit: r1516486 [3/4] - in /james/hupa/trunk: ./ client/ client/src/main/java/org/apache/hupa/ client/src/main/java/org/apache/hupa/client/ client/src/main/java/org/apache/hupa/client/activity/ client/src/main/java/org/apache/hupa/client/ioc/ clie...

Added: james/hupa/trunk/mock/src/main/resources/mime/12.msg
URL: http://svn.apache.org/viewvc/james/hupa/trunk/mock/src/main/resources/mime/12.msg?rev=1516486&view=auto
==============================================================================
--- james/hupa/trunk/mock/src/main/resources/mime/12.msg (added)
+++ james/hupa/trunk/mock/src/main/resources/mime/12.msg Thu Aug 22 15:29:57 2013
@@ -0,0 +1,57 @@
+Delivered-To: mcdodot@gmail.com
+Received: by 10.60.131.212 with SMTP id oo20csp73659oeb;
+        Mon, 10 Jun 2013 08:49:12 -0700 (PDT)
+X-Received: by 10.205.22.196 with SMTP id qx4mr1610488bkb.57.1370879351744;
+        Mon, 10 Jun 2013 08:49:11 -0700 (PDT)
+Return-Path: <ec...@gmail.com>
+Received: from mail-bk0-x22a.google.com (mail-bk0-x22a.google.com [2a00:1450:4008:c01::22a])
+        by mx.google.com with ESMTPS id t2si950481bkh.191.2013.06.10.08.49.11
+        for <mc...@gmail.com>
+        (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128);
+        Mon, 10 Jun 2013 08:49:11 -0700 (PDT)
+Received-SPF: pass (google.com: domain of echowdx@gmail.com designates 2a00:1450:4008:c01::22a as permitted sender) client-ip=2a00:1450:4008:c01::22a;
+Authentication-Results: mx.google.com;
+       spf=pass (google.com: domain of echowdx@gmail.com designates 2a00:1450:4008:c01::22a as permitted sender) smtp.mail=echowdx@gmail.com;
+       dkim=pass header.i=@gmail.com
+Received: by mail-bk0-x22a.google.com with SMTP id jk13so3448363bkc.1
+        for <mc...@gmail.com>; Mon, 10 Jun 2013 08:49:11 -0700 (PDT)
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
+        d=gmail.com; s=20120113;
+        h=message-id:date:from:user-agent:mime-version:to:subject
+         :x-chiaramail-content-pointer:x-chiaramail-content-key2
+         :x-chiaramail-content-server-name:x-chiaramail-content-server-port
+         :content-type:content-transfer-encoding;
+        bh=40fimkYYpXaLiVIKuTv2tLgUPaqUGA1DOkoRGdypt/8=;
+        b=vvOKuDgFhW7lhslsd+p1zFzCgGsB1q+Nz/VX94yvOzYNgzK+ztjWuRXqhxInvKB/6A
+         KULfTqxc6eCGaOwQe2jfzcCnE9NqbqNyqWdrKGtcYo/kC3cmSM+vPiVVwin4Ocib+tZk
+         gLRThq7wglPAniM9+y0MEtXWU2C0PRWCKM/keEZloAJrL9x3RR/5vQ3Cn8oVyI5yg328
+         sFIUYa2UvKJ+3rb3I/XCoqaDu26+A2wDiemzPMXUP3p3rTawvt/Bmg8SsxuzCo5zi8vf
+         oUsbFJWM9wPxCW53BBKQXNJ8yIPh+MD76ymg6bGuKHkSilqfTQk1NCr8QbFdO/FaeAba
+         Ad5g==
+X-Received: by 10.204.74.138 with SMTP id u10mr1617410bkj.113.1370879351096;
+        Mon, 10 Jun 2013 08:49:11 -0700 (PDT)
+Return-Path: <ec...@gmail.com>
+Received: from [10.20.0.2] ([69.85.95.7])
+        by mx.google.com with ESMTPSA id jm15sm4132474bkb.13.2013.06.10.08.49.08
+        for <multiple recipients>
+        (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128);
+        Mon, 10 Jun 2013 08:49:10 -0700 (PDT)
+Message-ID: <51...@gmail.com>
+Date: Mon, 10 Jun 2013 23:49:05 +0800
+From: dongxu <ec...@gmail.com>
+User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:17.0) Gecko/20130509 Thunderbird/17.0.6
+MIME-Version: 1.0
+To: Manuel Carrasco <mc...@gmail.com>
+Subject: Test with bcc without content inside
+X-ChiaraMail-Content-Pointer: 72
+X-ChiaraMail-Content-Key2: 98333074730306711837946622793808
+X-ChiaraMail-Content-Server-Name: www.chiaramail.com
+X-ChiaraMail-Content-Server-Port: 443
+Content-Type: text/plain; charset=ISO-8859-1; format=flowed
+Content-Transfer-Encoding: 7bit
+
+This message was sent using ECS technology 
+(http://en.wikipedia.org/wiki/Envelope-content_splitting), but your mail 
+client does not support ECS. Please download and install the appropriate 
+ECS-enabling software for your mail client 
+(http://www.chiaramail.com/download_extension.html) to read this message.

Added: james/hupa/trunk/mock/src/main/resources/mime/13.msg
URL: http://svn.apache.org/viewvc/james/hupa/trunk/mock/src/main/resources/mime/13.msg?rev=1516486&view=auto
==============================================================================
--- james/hupa/trunk/mock/src/main/resources/mime/13.msg (added)
+++ james/hupa/trunk/mock/src/main/resources/mime/13.msg Thu Aug 22 15:29:57 2013
@@ -0,0 +1,43 @@
+Return-Path: <mc...@gmail.com>
+Received: from [192.168.1.35] (239.Red-79-148-67.dynamicIP.rima-tde.net. [79.148.67.239])
+        by mx.google.com with ESMTPSA id l7sm2877195wiw.4.1969.12.31.16.00.00
+        (version=TLSv1 cipher=ECDHE-RSA-RC4-SHA bits=128/128);
+        Thu, 15 Aug 2013 06:08:16 -0700 (PDT)
+Message-ID: <52...@gmail.com>
+Date: Thu, 15 Aug 2013 15:08:07 +0200
+From: Manuel Carrasco <mc...@gmail.com>
+User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20130803 Thunderbird/17.0.8
+MIME-Version: 1.0
+To: mcdodot@gmail.com
+Subject: ECS unencripted attachment
+X-ChiaraMail-Content-Pointer: 16 24
+X-ChiaraMail-Content-Server-Name: www.chiaramail.com
+X-ChiaraMail-Content-Server-Port: 443
+Content-Type: multipart/mixed;
+ boundary="------------020706090302030909010608"
+
+This is a multi-part message in MIME format.
+--------------020706090302030909010608
+Content-Type: text/plain; charset=ISO-8859-1; format=flowed
+Content-Transfer-Encoding: 7bit
+
+This message was sent using ECS technology 
+(http://en.wikipedia.org/wiki/Envelope-content_splitting), but your mail 
+client does not support ECS. Please download and install the appropriate 
+ECS-enabling software for your mail client 
+(http://www.chiaramail.com/download_extension.html) to read this message.
+
+--------------020706090302030909010608
+Content-Type: application/pdf;
+ name="TRATAMIENTOS TERMAEUROPA 2013.pdf"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment;
+ filename="TRATAMIENTOS TERMAEUROPA 2013.pdf"
+
+VGhpcyBtZXNzYWdlIHdhcyBzZW50IHVzaW5nIEVDUyB0ZWNobm9sb2d5IChodHRwOi8vZW4u
+d2lraXBlZGlhLm9yZy93aWtpL0VudmVsb3BlLWNvbnRlbnRfc3BsaXR0aW5nKSwgYnV0IHlv
+dXIgbWFpbCBjbGllbnQgZG9lcyBub3Qgc3VwcG9ydCBFQ1MuIFBsZWFzZSBkb3dubG9hZCBh
+bmQgaW5zdGFsbCB0aGUgYXBwcm9wcmlhdGUgRUNTLWVuYWJsaW5nIHNvZnR3YXJlIGZvciB5
+b3VyIG1haWwgY2xpZW50IChodHRwOi8vd3d3LmNoaWFyYW1haWwuY29tL2Rvd25sb2FkX2V4
+dGVuc2lvbi5odG1sKSB0byByZWFkIHRoaXMgbWVzc2FnZS4=
+--------------020706090302030909010608--

Modified: james/hupa/trunk/pom.xml
URL: http://svn.apache.org/viewvc/james/hupa/trunk/pom.xml?rev=1516486&r1=1516485&r2=1516486&view=diff
==============================================================================
--- james/hupa/trunk/pom.xml (original)
+++ james/hupa/trunk/pom.xml Thu Aug 22 15:29:57 2013
@@ -27,7 +27,7 @@
     </parent>
     <groupId>org.apache.james.hupa</groupId>
     <artifactId>hupa-parent</artifactId>
-    <version>0.0.5-SNAPSHOT</version>
+    <version>0.0.3-SNAPSHOT</version>
     <packaging>pom</packaging>
     <name>Apache James Hupa Parent</name>
     <description>Hupa is a GWT based Webmail</description>

Modified: james/hupa/trunk/server/pom.xml
URL: http://svn.apache.org/viewvc/james/hupa/trunk/server/pom.xml?rev=1516486&r1=1516485&r2=1516486&view=diff
==============================================================================
--- james/hupa/trunk/server/pom.xml (original)
+++ james/hupa/trunk/server/pom.xml Thu Aug 22 15:29:57 2013
@@ -1,135 +1,145 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
-<!--
-    Licensed to the Apache Software Foundation (ASF) under one
-    or more contributor license agreements. See the NOTICE file
-    distributed with this work for additional information
-    regarding copyright ownership. The ASF licenses this file
-    to you under the Apache License, Version 2.0 (the
-    "License"); you may not use this file except in compliance
-    with the License. You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing,
-    software distributed under the License is distributed on an
-    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-    KIND, either express or implied. See the License for the
-    specific language governing permissions and limitations
-    under the License.
--->
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
-    <modelVersion>4.0.0</modelVersion>
-    <parent>
-        <groupId>org.apache.james.hupa</groupId>
-        <artifactId>hupa-parent</artifactId>
-        <version>0.0.5-SNAPSHOT</version>
-        <relativePath>../pom.xml</relativePath>
-    </parent>
-    <artifactId>hupa-server</artifactId>
-    <packaging>jar</packaging>
-    <name>Apache James Hupa Server</name>
-    <description>Servercode implementation to access IMAP Mailbox</description>
-    <dependencies>
-        <dependency>
-            <groupId>${project.groupId}</groupId>
-            <artifactId>hupa-mock</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>${project.groupId}</groupId>
-            <artifactId>hupa-shared</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>javax.inject</groupId>
-            <artifactId>javax.inject</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>aopalliance</groupId>
-            <artifactId>aopalliance</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>commons-logging</groupId>
-            <artifactId>commons-logging</artifactId>
-            <exclusions>
-                <exclusion>
-                    <groupId>logkit</groupId>
-                    <artifactId>logkit</artifactId>
-                </exclusion>
-                <exclusion>
-                    <groupId>javax.servlet</groupId>
-                    <artifactId>servlet-api</artifactId>
-                </exclusion>
-            </exclusions>
-        </dependency>
-        <dependency>
-            <groupId>log4j</groupId>
-            <artifactId>log4j</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>javax.mail</groupId>
-            <artifactId>mail</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.googlecode.gwtupload</groupId>
-            <artifactId>gwtupload</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>commons-fileupload</groupId>
-            <artifactId>commons-fileupload</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>commons-io</groupId>
-            <artifactId>commons-io</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.google.code.guice</groupId>
-            <artifactId>guice</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.google.code.guice</groupId>
-            <artifactId>guice-assistedinject</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.google.code.guice</groupId>
-            <artifactId>guice-servlet</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.google.gwt</groupId>
-            <artifactId>gwt-user</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.google.gwt</groupId>
-            <artifactId>gwt-servlet</artifactId>
-            <scope>runtime</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.gwt</groupId>
-            <artifactId>gwt-dev</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
-    <build>
-        <outputDirectory>war/WEB-INF/classes</outputDirectory>
-        <plugins>
-            <plugin>
-                <groupId>org.apache.maven.plugins</groupId>
-                <artifactId>maven-war-plugin</artifactId>
-                <configuration>
-                    <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
-                    <webResources>
-                        <resource>
-                            <directory>war</directory>
-                            <excludes>
-                                <exclude>**/hupa/*</exclude>
-                            </excludes>
-                        </resource>
-                    </webResources>
-                    <warName>${project.name}</warName>
-                </configuration>
-            </plugin>
-        </plugins>
-    </build>
-</project>
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.james.hupa</groupId>
+        <artifactId>hupa-parent</artifactId>
+        <version>0.0.3-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <artifactId>hupa-server</artifactId>
+    <packaging>jar</packaging>
+    <name>Apache James Hupa Server</name>
+    <description>Servercode implementation to access IMAP Mailbox</description>
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>hupa-mock</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>hupa-shared</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>aopalliance</groupId>
+            <artifactId>aopalliance</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-logging</groupId>
+            <artifactId>commons-logging</artifactId>
+            <exclusions>
+                <exclusion>
+                    <groupId>logkit</groupId>
+                    <artifactId>logkit</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>javax.servlet</groupId>
+                    <artifactId>servlet-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.mail</groupId>
+            <artifactId>mail</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.googlecode.gwtupload</groupId>
+            <artifactId>gwtupload</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-fileupload</groupId>
+            <artifactId>commons-fileupload</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.guice</groupId>
+            <artifactId>guice</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.guice</groupId>
+            <artifactId>guice-assistedinject</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.guice</groupId>
+            <artifactId>guice-servlet</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.gwt</groupId>
+            <artifactId>gwt-user</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.google.gwt</groupId>
+            <artifactId>gwt-servlet</artifactId>
+            <scope>runtime</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.gwt</groupId>
+            <artifactId>gwt-dev</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <!-- ECS specific stuff
+            ECS encryption needs oracle crypto extension to work
+                http://www.oracle.com/technetwork/es/java/javase/downloads/jce-6-download-429243.html
+                http://www.oracle.com/technetwork/es/java/javase/downloads/jce-7-download-432124.html
+         -->
+        <dependency>
+             <groupId>org.bouncycastle</groupId>
+             <artifactId>bcprov-ext-jdk14</artifactId>
+             <version>1.49</version>
+        </dependency>
+    </dependencies>
+    <build>
+        <outputDirectory>war/WEB-INF/classes</outputDirectory>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <configuration>
+                    <webXml>src/main/webapp/WEB-INF/web.xml</webXml>
+                    <webResources>
+                        <resource>
+                            <directory>war</directory>
+                            <excludes>
+                                <exclude>**/hupa/*</exclude>
+                            </excludes>
+                        </resource>
+                    </webResources>
+                    <warName>${project.name}</warName>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

Added: james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Account.java
URL: http://svn.apache.org/viewvc/james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Account.java?rev=1516486&view=auto
==============================================================================
--- james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Account.java (added)
+++ james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Account.java Thu Aug 22 15:29:57 2013
@@ -0,0 +1,47 @@
+package com.chiaramail.hupa.helper;
+
+import java.util.Date;
+
+public class Account {
+    
+    public static final int LICENSE_UNKNOWN = 0;
+    public static final int LICENSE_ACTIVE = 1;
+    public static final int LICENSE_EXPIRED = 2;
+    
+    private int license = LICENSE_UNKNOWN;
+    private long time = System.currentTimeMillis() + (24*60*60*1000);
+    public String email;
+    public String password;
+    public String name;
+    public String serverName = "www.chiaramail.com";
+    public String serverPort = "443";
+
+    public String getEmail() {
+        return email;
+    }
+    public String getName() {
+        return name;
+    }
+    public void setLicenseStatus(int licenseActive) {
+        license = licenseActive;
+    }
+    public int getLicenseStatus() {
+        return license;
+    }
+    public void setLicenseCheckDate(long time) {
+        this.time = time;
+    }
+    public int getLicenseCheckDate() {
+        return (int)time;
+    }
+    public String getPassword() {
+        return password;
+    }
+    public String getContentServerName() {
+        return serverName;
+    }
+    public String getContentServerPort() {
+        return serverPort;
+    }
+
+}

Added: james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Utility.java
URL: http://svn.apache.org/viewvc/james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Utility.java?rev=1516486&view=auto
==============================================================================
--- james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Utility.java (added)
+++ james/hupa/trunk/server/src/main/java/com/chiaramail/hupa/helper/Utility.java Thu Aug 22 15:29:57 2013
@@ -0,0 +1,1476 @@
+package com.chiaramail.hupa.helper;
+
+ import static com.chiaramail.hupa.helper.Account.LICENSE_ACTIVE;
+import static com.chiaramail.hupa.helper.Account.LICENSE_EXPIRED;
+import static com.chiaramail.hupa.helper.Account.LICENSE_UNKNOWN;
+
+import java.awt.Cursor;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.security.Security;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.StringTokenizer;
+import java.util.Vector;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.crypto.Cipher;
+import javax.crypto.spec.SecretKeySpec;
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.net.ssl.HttpsURLConnection;
+
+import org.apache.commons.codec.binary.Base64;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+
+public class Utility {
+    /**
+     * Regular expression that represents characters we won't allow in file
+     * names.
+     * 
+     * <p>
+     * Allowed are:
+     * <ul>
+     * <li>word characters (letters, digits, and underscores): {@code \w}</li>
+     * <li>spaces: {@code " "}</li>
+     * <li>special characters: {@code !}, {@code #}, {@code $}, {@code %},
+     * {@code &}, {@code '}, {@code (}, {@code )}, {@code -}, {@code @},
+     * {@code ^}, {@code `}, <code>&#123;</code>, <code>&#125;</code>, {@code ~}, {@code .}, {@code ,}</li>
+     * </ul>
+     * </p>
+     * 
+     * @see #sanitizeFilename(String)
+     */
+    private static final String INVALID_CHARACTERS = "[^\\w !#$%&'()\\-@\\^`{}~.,]+";
+
+    /**
+     * Invalid characters in a file name are replaced by this character.
+     * 
+     * @see #sanitizeFilename(String)
+     */
+    private static final String REPLACEMENT_CHARACTER = "_";
+
+    // \u00A0 (non-breaking space) happens to be used by French MUA
+
+    // Note: no longer using the ^ beginning character combined with (...)+
+    // repetition matching as we might want to strip ML tags. Ex:
+    // Re: [foo] Re: RE : [foo] blah blah blah
+    private static final Pattern RESPONSE_PATTERN = Pattern.compile(
+            "((Re|Fw|Fwd|Aw|R\\u00E9f\\.)(\\[\\d+\\])?[\\u00A0 ]?: *)+",
+            Pattern.CASE_INSENSITIVE);
+
+    /**
+     * Mailing-list tag pattern to match strings like "[foobar] "
+     */
+    private static final Pattern TAG_PATTERN = Pattern.compile(
+            "\\[[-_a-z0-9]+\\] ", Pattern.CASE_INSENSITIVE);
+    public static final String CONTENT_SERVER_APP = "/DynamicContentServer/ContentServer";
+    public static final String RECEIVE_CONTENT = "RECEIVE CONTENT ";
+    public static final String UPDATE_CONTENT = "UPDATE CONTENT ";
+    public static final String FETCH_CONTENT = "FETCH CONTENT ";
+    public static final String DELETE_CONTENT = "DELETE CONTENT ";
+    public static final String DELETE_DATA = "DELETE DATA ";
+    public static final String REMOVE_RECIPIENT = "REMOVE RECIPIENT ";
+    public static final String GET_DATA = "GET DATA ";
+    public static final String USER_REGISTERED = "USER REGISTERED ";
+    public static final String SERVER_LICENSED = "SERVER LICENSED ";
+    // private static final int MAX_SIZE = 32768;
+    // private static final int MAX_SIZE = 7844;
+    // private static final int MAX_SIZE = 128;
+    private static final int MAX_SIZE = 76;
+    private static final int DECODED_SIZE = 57;
+    private static final int NEXT_DAY = 24 * 60 * 60 * 1000; // Number of msec
+                                                             // in a day
+
+    public static final String BLANK = " ";
+    public static final String CONTENT_SERVER_NAME = "X-ChiaraMail-Content-Server-Name";
+    public static final String CONTENT_SERVER_PORT = "X-ChiaraMail-Content-Server-Port";
+    public static final String CONTENT_POINTER = "X-ChiaraMail-Content-Pointer";
+    public static final String ENCRYPTION_KEY = "X-ChiaraMail-Content-Key2";
+    public static final String CONTENT_DURATION = "X-ChiaraMail-Content-Duration";
+    public static final String DEFAULT_CONTENT_SERVER_NAME = "www.chiaramail.com";
+    public static final String DEFAULT_CONTENT_SERVER_PORT = "443";
+
+    public static final String GREEN = "#00a000";
+    public static final String RED = "#ff0000";
+    public static final String BLACK = "#000000";
+
+    public static Vector ValidECSMessages = new Vector();
+    public static Vector BogusECSMessages = new Vector();
+
+    public static boolean arrayContains(Object[] a, Object o) {
+        for (Object element : a) {
+            if (element.equals(o)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean arrayContainsAny(Object[] a, Object... o) {
+        for (Object element : a) {
+            if (arrayContains(o, element)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Combines the given array of Objects into a single String using each
+     * Object's toString() method and the separator character between each part.
+     * 
+     * @param parts
+     * @param separator
+     * @return new String
+     */
+    public static String combine(Object[] parts, char separator) {
+        if (parts == null) {
+            return null;
+        } else if (parts.length == 0) {
+            return "";
+        } else if (parts.length == 1) {
+            return parts[0].toString();
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append(parts[0]);
+        for (int i = 1; i < parts.length; ++i) {
+            sb.append(separator);
+            sb.append(parts[i]);
+        }
+        return sb.toString();
+    }
+
+    public static String base64Decode(String encoded) {
+        if (encoded == null) {
+            return null;
+        }
+        byte[] decoded = new Base64().decode(encoded.getBytes());
+        return new String(decoded);
+    }
+
+    public static byte[] base64DecodeToBytes(String encoded) {
+        if (encoded == null) {
+            return null;
+        }
+        byte[] decoded = new Base64().decode(encoded.getBytes());
+        return decoded;
+    }
+
+    public static String base64Encode(String s) {
+        if (s == null) {
+            return s;
+        }
+        byte[] encoded = new Base64().encode(s.getBytes());
+        return new String(encoded);
+    }
+
+    public static boolean domainFieldValid(String s) {
+        if (s != null) {
+            if (s.matches("^([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)*[a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?$")
+                    && s.length() <= 253) {
+                return true;
+            }
+            if (s.matches("^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$")) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static final Pattern ATOM = Pattern
+            .compile("^(?:[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]|\\s)+$");
+
+    /**
+     * Quote a string, if necessary, based upon the definition of an "atom," as
+     * defined by RFC2822 (http://tools.ietf.org/html/rfc2822#section-3.2.4).
+     * Strings that consist purely of atoms are left unquoted; anything else is
+     * returned as a quoted string.
+     * 
+     * @param text
+     *            String to quote.
+     * @return Possibly quoted string.
+     */
+    public static String quoteAtoms(final String text) {
+        if (ATOM.matcher(text).matches()) {
+            return text;
+        } else {
+            return quoteString(text);
+        }
+    }
+
+    /**
+     * Ensures that the given string starts and ends with the double quote
+     * character. The string is not modified in any way except to add the double
+     * quote character to start and end if it's not already there. sample ->
+     * "sample" "sample" -> "sample" ""sample"" -> "sample"
+     * "sample"" -> "sample" sa"mp"le -> "sa"mp"le" "sa"mp"le" -> "sa"mp"le"
+     * (empty string) -> "" " -> ""
+     * 
+     * @param s
+     * @return
+     */
+    public static String quoteString(String s) {
+        if (s == null) {
+            return null;
+        }
+        if (!s.matches("^\".*\"$")) {
+            return "\"" + s + "\"";
+        } else {
+            return s;
+        }
+    }
+
+    /**
+     * A fast version of URLDecoder.decode() that works only with UTF-8 and does
+     * only two allocations. This version is around 3x as fast as the standard
+     * one and I'm using it hundreds of times in places that slow down the UI,
+     * so it helps.
+     */
+    public static String fastUrlDecode(String s) {
+        try {
+            byte[] bytes = s.getBytes("UTF-8");
+            byte ch;
+            int length = 0;
+            for (int i = 0, count = bytes.length; i < count; i++) {
+                ch = bytes[i];
+                if (ch == '%') {
+                    int h = (bytes[i + 1] - '0');
+                    int l = (bytes[i + 2] - '0');
+                    if (h > 9) {
+                        h -= 7;
+                    }
+                    if (l > 9) {
+                        l -= 7;
+                    }
+                    bytes[length] = (byte) ((h << 4) | l);
+                    i += 2;
+                } else if (ch == '+') {
+                    bytes[length] = ' ';
+                } else {
+                    bytes[length] = bytes[i];
+                }
+                length++;
+            }
+            return new String(bytes, 0, length, "UTF-8");
+        } catch (UnsupportedEncodingException uee) {
+            return null;
+        }
+    }
+
+    /**
+     * <p>
+     * Wraps a multiline string of text, identifying words by <code>' '</code>.
+     * </p>
+     * 
+     * <p>
+     * New lines will be separated by the system property line separator. Very
+     * long words, such as URLs will <i>not</i> be wrapped.
+     * </p>
+     * 
+     * <p>
+     * Leading spaces on a new line are stripped. Trailing spaces are not
+     * stripped.
+     * </p>
+     * 
+     * <pre>
+     * WordUtils.wrap(null, *) = null
+     * WordUtils.wrap("", *) = ""
+     * </pre>
+     * 
+     * Adapted from the Apache Commons Lang library.
+     * http://svn.apache.org/viewvc/commons/proper/lang
+     * /trunk/src/main/java/org/apache/commons/lang3/text/WordUtils.java SVN
+     * Revision 925967, Mon Mar 22 06:16:49 2010 UTC
+     * 
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements. See the NOTICE file distributed with this
+     * work for additional information regarding copyright ownership. The ASF
+     * licenses this file to You under the Apache License, Version 2.0 (the
+     * "License"); you may not use this file except in compliance with the
+     * License. You may obtain a copy of the License at
+     * 
+     * http://www.apache.org/licenses/LICENSE-2.0
+     * 
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+     * License for the specific language governing permissions and limitations
+     * under the License.
+     * 
+     * @param str
+     *            the String to be word wrapped, may be null
+     * @param wrapLength
+     *            the column to wrap the words at, less than 1 is treated as 1
+     * @return a line with newlines inserted, <code>null</code> if null input
+     */
+    private static final String NEWLINE_REGEX = "(?:\\r?\\n)";
+
+    public static String wrap(String str, int wrapLength) {
+        StringBuilder result = new StringBuilder();
+        for (String piece : str.split(NEWLINE_REGEX)) {
+            result.append(wrap(piece, wrapLength, null, false));
+            result.append("\n");
+        }
+        return result.toString();
+    }
+
+    /**
+     * <p>
+     * Wraps a single line of text, identifying words by <code>' '</code>.
+     * </p>
+     * 
+     * <p>
+     * Leading spaces on a new line are stripped. Trailing spaces are not
+     * stripped.
+     * </p>
+     * 
+     * <pre>
+     * WordUtils.wrap(null, *, *, *) = null
+     * WordUtils.wrap("", *, *, *) = ""
+     * </pre>
+     * 
+     * This is from the Apache Commons Lang library.
+     * http://svn.apache.org/viewvc/commons/proper/lang
+     * /trunk/src/main/java/org/apache/commons/lang3/text/WordUtils.java SVN
+     * Revision 925967, Mon Mar 22 06:16:49 2010 UTC
+     * 
+     * Licensed to the Apache Software Foundation (ASF) under one or more
+     * contributor license agreements. See the NOTICE file distributed with this
+     * work for additional information regarding copyright ownership. The ASF
+     * licenses this file to You under the Apache License, Version 2.0 (the
+     * "License"); you may not use this file except in compliance with the
+     * License. You may obtain a copy of the License at
+     * 
+     * http://www.apache.org/licenses/LICENSE-2.0
+     * 
+     * Unless required by applicable law or agreed to in writing, software
+     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+     * License for the specific language governing permissions and limitations
+     * under the License.
+     * 
+     * @param str
+     *            the String to be word wrapped, may be null
+     * @param wrapLength
+     *            the column to wrap the words at, less than 1 is treated as 1
+     * @param newLineStr
+     *            the string to insert for a new line, <code>null</code> uses
+     *            the system property line separator
+     * @param wrapLongWords
+     *            true if long words (such as URLs) should be wrapped
+     * @return a line with newlines inserted, <code>null</code> if null input
+     */
+    public static String wrap(String str, int wrapLength, String newLineStr,
+            boolean wrapLongWords) {
+        if (str == null) {
+            return null;
+        }
+        if (newLineStr == null) {
+            newLineStr = "\n";
+        }
+        if (wrapLength < 1) {
+            wrapLength = 1;
+        }
+        int inputLineLength = str.length();
+        int offset = 0;
+        StringBuilder wrappedLine = new StringBuilder(inputLineLength + 32);
+
+        while ((inputLineLength - offset) > wrapLength) {
+            if (str.charAt(offset) == ' ') {
+                offset++;
+                continue;
+            }
+            int spaceToWrapAt = str.lastIndexOf(' ', wrapLength + offset);
+
+            if (spaceToWrapAt >= offset) {
+                // normal case
+                wrappedLine.append(str.substring(offset, spaceToWrapAt));
+                wrappedLine.append(newLineStr);
+                offset = spaceToWrapAt + 1;
+            } else {
+                // really long word or URL
+                if (wrapLongWords) {
+                    // wrap really long word one line at a time
+                    wrappedLine.append(str.substring(offset, wrapLength
+                            + offset));
+                    wrappedLine.append(newLineStr);
+                    offset += wrapLength;
+                } else {
+                    // do not wrap really long word, just extend beyond limit
+                    spaceToWrapAt = str.indexOf(' ', wrapLength + offset);
+                    if (spaceToWrapAt >= 0) {
+                        wrappedLine
+                                .append(str.substring(offset, spaceToWrapAt));
+                        wrappedLine.append(newLineStr);
+                        offset = spaceToWrapAt + 1;
+                    } else {
+                        wrappedLine.append(str.substring(offset));
+                        offset = inputLineLength;
+                    }
+                }
+            }
+        }
+
+        // Whatever is left in line is short enough to just pass through
+        wrappedLine.append(str.substring(offset));
+
+        return wrappedLine.toString();
+    }
+
+    /**
+     * Extract the 'original' subject value, by ignoring leading
+     * response/forward marker and '[XX]' formatted tags (as many mailing-list
+     * softwares do).
+     * 
+     * <p>
+     * Result is also trimmed.
+     * </p>
+     * 
+     * @param subject
+     *            Never <code>null</code>.
+     * @return Never <code>null</code>.
+     */
+    public static String stripSubject(final String subject) {
+        int lastPrefix = 0;
+
+        final Matcher tagMatcher = TAG_PATTERN.matcher(subject);
+        String tag = null;
+        // whether tag stripping logic should be active
+        boolean tagPresent = false;
+        // whether the last action stripped a tag
+        boolean tagStripped = false;
+        if (tagMatcher.find(0)) {
+            tagPresent = true;
+            if (tagMatcher.start() == 0) {
+                // found at beginning of subject, considering it an actual tag
+                tag = tagMatcher.group();
+
+                // now need to find response marker after that tag
+                lastPrefix = tagMatcher.end();
+                tagStripped = true;
+            }
+        }
+
+        final Matcher matcher = RESPONSE_PATTERN.matcher(subject);
+
+        // while:
+        // - lastPrefix is within the bounds
+        // - response marker found at lastPrefix position
+        // (to make sure we don't catch response markers that are part of
+        // the actual subject)
+
+        while (lastPrefix < subject.length() - 1
+                && matcher.find(lastPrefix)
+                && matcher.start() == lastPrefix
+                && (!tagPresent || tag == null || subject.regionMatches(
+                        matcher.end(), tag, 0, tag.length()))) {
+            lastPrefix = matcher.end();
+
+            if (tagPresent) {
+                tagStripped = false;
+                if (tag == null) {
+                    // attempt to find tag
+                    if (tagMatcher.start() == lastPrefix) {
+                        tag = tagMatcher.group();
+                        lastPrefix += tag.length();
+                        tagStripped = true;
+                    }
+                } else if (lastPrefix < subject.length() - 1
+                        && subject.startsWith(tag, lastPrefix)) {
+                    // Re: [foo] Re: [foo] blah blah blah
+                    // ^ ^
+                    // ^ ^
+                    // ^ new position
+                    // ^
+                    // initial position
+                    lastPrefix += tag.length();
+                    tagStripped = true;
+                }
+            }
+        }
+        // Null pointer check is to make the static analysis component of
+        // Eclipse happy.
+        if (tagStripped && (tag != null)) {
+            // restore the last tag
+            lastPrefix -= tag.length();
+        }
+        if (lastPrefix > -1 && lastPrefix < subject.length() - 1) {
+            return subject.substring(lastPrefix).trim();
+        } else {
+            return subject.trim();
+        }
+    }
+
+    /**
+     * @param parentDir
+     * @param name
+     *            Never <code>null</code>.
+     */
+    public static void touchFile(final File parentDir, final String name) {
+        final File file = new File(parentDir, name);
+        try {
+            if (!file.exists()) {
+                file.createNewFile();
+            } else {
+                file.setLastModified(System.currentTimeMillis());
+            }
+        } catch (Exception e) {
+            System.out.println("Unable to touch file: "
+                    + file.getAbsolutePath() + " " + e.getMessage());
+        }
+    }
+
+    /**
+     * Creates a unique file in the given directory by appending a hyphen and a
+     * number to the given filename.
+     * 
+     * @param directory
+     * @param filename
+     * @return
+     */
+    public static File createUniqueFile(File directory, String filename) {
+        File file = new File(directory, filename);
+        if (!file.exists()) {
+            return file;
+        }
+        // Get the extension of the file, if any.
+        int index = filename.lastIndexOf('.');
+        String format;
+        if (index != -1) {
+            String name = filename.substring(0, index);
+            String extension = filename.substring(index);
+            format = name + "-%d" + extension;
+        } else {
+            format = filename + "-%d";
+        }
+        for (int i = 2; i < Integer.MAX_VALUE; i++) {
+            file = new File(directory, String.format(format, i));
+            if (!file.exists()) {
+                return file;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @param from
+     * @param to
+     * @return
+     */
+    public static boolean move(final File from, final File to) {
+        if (to.exists()) {
+            to.delete();
+        }
+        to.getParentFile().mkdirs();
+
+        try {
+            FileInputStream in = new FileInputStream(from);
+            try {
+                FileOutputStream out = new FileOutputStream(to);
+                try {
+                    byte[] buffer = new byte[1024];
+                    int count = -1;
+                    while ((count = in.read(buffer)) > 0) {
+                        out.write(buffer, 0, count);
+                    }
+                } finally {
+                    out.close();
+                }
+            } finally {
+                try {
+                    in.close();
+                } catch (Throwable ignore) {
+                }
+            }
+            from.delete();
+            return true;
+        } catch (Exception e) {
+            System.out.println("cannot move " + from.getAbsolutePath() + " to "
+                    + to.getAbsolutePath() + " " + e.getMessage());
+            return false;
+        }
+
+    }
+
+    /**
+     * @param fromDir
+     * @param toDir
+     */
+    public static void moveRecursive(final File fromDir, final File toDir) {
+        if (!fromDir.exists()) {
+            return;
+        }
+        if (!fromDir.isDirectory()) {
+            if (toDir.exists()) {
+                if (!toDir.delete()) {
+                    System.out
+                            .println("cannot delete already existing file/directory "
+                                    + toDir.getAbsolutePath());
+                }
+            }
+            if (!fromDir.renameTo(toDir)) {
+                System.out.println("cannot rename " + fromDir.getAbsolutePath()
+                        + " to " + toDir.getAbsolutePath()
+                        + " - moving instead");
+                move(fromDir, toDir);
+            }
+            return;
+        }
+        if (!toDir.exists() || !toDir.isDirectory()) {
+            if (toDir.exists()) {
+                toDir.delete();
+            }
+            if (!toDir.mkdirs()) {
+                System.out.println("cannot create directory "
+                        + toDir.getAbsolutePath());
+            }
+        }
+        File[] files = fromDir.listFiles();
+        for (File file : files) {
+            if (file.isDirectory()) {
+                moveRecursive(file, new File(toDir, file.getName()));
+                file.delete();
+            } else {
+                File target = new File(toDir, file.getName());
+                if (!file.renameTo(target)) {
+                    System.out.println("cannot rename "
+                            + file.getAbsolutePath() + " to "
+                            + target.getAbsolutePath() + " - moving instead");
+                    move(file, target);
+                }
+            }
+        }
+        if (!fromDir.delete()) {
+            System.out.println("cannot delete " + fromDir.getAbsolutePath());
+        }
+    }
+
+    private static final String IMG_SRC_REGEX = "(?is:<img[^>]+src\\s*=\\s*['\"]?([a-z]+)\\:)";
+    private static final Pattern IMG_PATTERN = Pattern.compile(IMG_SRC_REGEX);
+
+    /**
+     * Figure out if this part has images. TODO: should only return true if
+     * we're an html part
+     * 
+     * @param message
+     *            Content to evaluate
+     * @return True if it has external images; false otherwise.
+     */
+    public static boolean hasExternalImages(final String message) {
+        Matcher imgMatches = IMG_PATTERN.matcher(message);
+        while (imgMatches.find()) {
+            if (!imgMatches.group(1).equals("content")) {
+                System.out.println("External images found");
+                return true;
+            }
+        }
+        System.out.println("No external images.");
+        return false;
+    }
+
+    /**
+     * Unconditionally close a Cursor. Equivalent to {@link Cursor#close()}, if
+     * cursor is non-null. This is typically used in finally blocks.
+     * 
+     * @param cursor
+     *            cursor to close
+     */
+    public static void closeQuietly(final Cursor cursor) {
+        if (cursor != null) {
+            // cursor.close();
+        }
+    }
+
+    private static class BlockData {
+        int blockSize;
+        int bytesRead;
+        String msgBlock;
+        String msgChunk;
+    }
+
+    private static BlockData getNextBlock(BufferedReader rdr, char[] buf)
+            throws IOException {
+        BlockData blockData = new BlockData();
+        String msgChunk = "";
+        String msgBlock = "";
+        int blockSize = 0, bytesRead = 0;
+
+        while (blockSize < MAX_SIZE) {
+            bytesRead = rdr.read(buf, 0, MAX_SIZE - bytesRead);
+            if (bytesRead == -1)
+                return null;
+            msgChunk = new String(buf, 0, bytesRead);
+            msgBlock += msgChunk;
+            blockSize += bytesRead;
+        }
+        blockData.blockSize = blockSize;
+        blockData.bytesRead = bytesRead;
+        blockData.msgBlock = msgBlock;
+        blockData.msgChunk = msgChunk;
+        return blockData;
+    }
+
+    public static String fetchBodyContent(Message message, Account account,
+            String[] contentPointers, String contentServerName,
+            String contentServerPort, String encryptionKey) {
+        try {
+            String from = message.getFrom()[0].toString().replaceFirst("^.*<(.*)>.*$", "$1");
+            String[] reply = doFetchContent(account,
+                    from + BLANK
+                            + contentPointers[0], "https://"
+                            + contentServerName + ":" + contentServerPort,
+                    account.getEmail(),
+                    base64Encode(account.getPassword()),
+                    false, null,
+                    null);
+            if (reply[0].equals("3")) {
+                // Indicate to MessageList to set message Subject field color to
+                // green in the message list.
+                if (!ValidECSMessages
+                        .contains(message.getHeader("Message-ID")[0]))
+                    ValidECSMessages
+                            .addElement(message.getHeader("Message-ID")[0]);
+                String[] encryptionHeader = message.getHeader(ENCRYPTION_KEY);
+                if (encryptionHeader != null) {
+                    return new String(decrypt(encryptionKey.getBytes(),
+                            base64DecodeToBytes(reply[1].substring(reply[1]
+                                    .toUpperCase().indexOf("CONTENT = ")
+                                    + "CONTENT = ".length()))));
+                }
+                return base64Decode(reply[1].substring(reply[1].toUpperCase()
+                        .indexOf("CONTENT = ") + "CONTENT = ".length()));
+            } else {
+                // Indicate to MessageList to set message Subject field color to
+                // red in the message list; the fetch had problems, so this
+                // message may be bogus. Better safe than sorry.
+                if (!BogusECSMessages
+                        .contains(message.getHeader("Message-ID")[0]))
+                    BogusECSMessages
+                            .addElement(message.getHeader("Message-ID")[0]);
+                // Toast.makeText(getContext(),
+                // getContext().getString(R.string.message_read_error_fetching_content)
+                // + reply[1], Toast.LENGTH_LONG).show();
+                return null;
+            }
+        } catch (Exception e) {
+            System.out.println("Exception when fetching message content: "
+                    + " " + e.getMessage());
+            e.printStackTrace();
+            // Toast.makeText(this.getContext(),
+            // getContext().getString(R.string.message_fetch_exception) + e,
+            // Toast.LENGTH_LONG).show();
+            return null;
+        }
+    }
+
+    /****************************************************************************/
+    /* The askServer() method sends an HTTP request to a server. */
+    /****************************************************************************/
+    public static String[] askServer(String url_str, String command,
+            String email_addr, String password, boolean isAttachment,
+            String filename, String key) throws Exception {
+        
+//        System.out.println("AskServer: " + url_str + " cmd:" + command + " email:" + email_addr + " pass:" + password + " att:" + isAttachment + " file:" + filename + " key:" + key);
+        String tmp = "", parms;
+
+        HttpURLConnection connection;
+
+        InputStream is;
+
+        BufferedWriter writer;
+
+        BufferedReader rdr;
+
+        String[] srvr_rsp = null;
+
+        URL url;
+
+        int index;
+
+        srvr_rsp = new String[2];
+
+        try {
+            url = new URL(url_str);
+            if (command.startsWith(SERVER_LICENSED)) {
+                // Since the request isn't going to the ChiaraMail content
+                // server, make sure the private server has an active license.
+                connection = (HttpsURLConnection) new URL("https://"
+                        + DEFAULT_CONTENT_SERVER_NAME + ":"
+                        + DEFAULT_CONTENT_SERVER_PORT + CONTENT_SERVER_APP)
+                        .openConnection();
+                connection.setDoOutput(true);
+                writer = new BufferedWriter(new OutputStreamWriter(
+                        connection.getOutputStream()));
+                writer.write("email_addr="
+                        + URLEncoder.encode(email_addr, "UTF-8")
+                        + "&"
+                        + "passwd="
+                        + URLEncoder.encode(password, "UTF-8")
+                        + "&"
+                        + "cmd="
+                        + SERVER_LICENSED
+                        + "&"
+                        + "parms="
+                        + URLEncoder.encode(
+                                url_str.substring(0,
+                                        url_str.indexOf(CONTENT_SERVER_APP)),
+                                "UTF-8"));
+                writer.close();
+                is = connection.getInputStream();
+
+                rdr = new BufferedReader(new InputStreamReader(is));
+                tmp = rdr.readLine();
+                if ((index = tmp.indexOf(BLANK)) != -1) {
+                    srvr_rsp[0] = tmp.substring(0, tmp.indexOf(BLANK));
+                    srvr_rsp[1] = tmp.substring(tmp.indexOf(BLANK) + 1);
+                } else {
+                    srvr_rsp[0] = tmp;
+                    srvr_rsp[1] = "";
+                }
+                rdr.close();
+                is.close();
+                return srvr_rsp;
+                // if (!srvr_rsp[0].equals("10")) return srvr_rsp;
+            }
+
+            if (url_str.startsWith("https")) {
+                connection = (HttpsURLConnection) url.openConnection();
+            } else {
+                connection = (HttpURLConnection) url.openConnection();
+            }
+            connection.setDoOutput(true);
+
+            index = command.indexOf(BLANK);
+            tmp = "";
+            tmp += command.substring(0, index + 1);
+            command = command.substring(index).trim();
+            index = command.indexOf(BLANK);
+            if (index == -1) {
+                tmp += command;
+                parms = "";
+            } else {
+                tmp += command.substring(0, index);
+                parms = command.substring(index).trim();
+            }
+            writer = new BufferedWriter(new OutputStreamWriter(
+                    connection.getOutputStream()));
+            writer.write("email_addr=" + URLEncoder.encode(email_addr, "UTF-8")
+                    + "&" + "passwd=" + URLEncoder.encode(password, "UTF-8")
+                    + "&" + "cmd=" + tmp + "&" + "parms="
+                    + URLEncoder.encode(parms, "UTF-8"));
+            writer.close();
+
+            is = connection.getInputStream();
+            rdr = new BufferedReader(new InputStreamReader(is));
+
+            if (isAttachment) {
+                char[] buf = new char[MAX_SIZE + 29]; // Include room for
+                                                      // response from content
+                                                      // server plus 76 bytes of
+                                                      // Base64 encoded content
+                byte[] blockArray = new byte[16 * DECODED_SIZE];
+                byte[] decodedChunk;
+                int bytesRead = 0;
+                int totalBlockLen = 0;
+                String msgBlock = "";
+
+                File file = new File(System.getProperty("java.io.tmpdir") + File.separatorChar + filename);
+                if (file.exists())
+                    file.delete();
+
+                System.out.println("Creating attachment file: " + file.getAbsolutePath());
+                
+                RandomAccessFile attachmentFile = new RandomAccessFile(file,
+                        "rw");
+                bytesRead = rdr.read(buf);
+                /**
+                 * if (bytesRead < MAX_SIZE + 29) { // Should never happen
+                 * attachmentFile.close(); rdr.close(); is.close(); return null;
+                 * }
+                 **/
+                String tmp2 = new String(buf, 0, 29);
+                if (!tmp2.startsWith("3 ")) {
+                    tmp = new String(buf, 0, buf.length);
+                    if ((index = tmp.indexOf(BLANK)) != -1) {
+                        srvr_rsp[0] = tmp.substring(0, tmp.indexOf(BLANK));
+                        srvr_rsp[1] = tmp.substring(tmp.indexOf(BLANK) + 1);
+                    } else {
+                        srvr_rsp[0] = tmp;
+                        srvr_rsp[1] = "";
+                    }
+                    return srvr_rsp;
+                }
+                int startContentIndex = tmp2.toUpperCase()
+                        .indexOf("CONTENT = ") + "CONTENT = ".length();
+                tmp = tmp2.substring(0, startContentIndex);
+                if (tmp.startsWith("3 ")) {
+                    msgBlock = new String(buf).substring(startContentIndex);
+                    decodedChunk = base64DecodeToBytes(msgBlock);
+                    if (key != null) {
+                        for (int i = 0; i < decodedChunk.length; i++) {
+                            blockArray[i] = decodedChunk[i];
+                        }
+                        totalBlockLen = decodedChunk.length;
+                    } else {
+                        attachmentFile.write(decodedChunk);
+                    }
+                    buf = new char[MAX_SIZE];
+                    bytesRead = 0;
+                    while (bytesRead >= 0) {
+                        if (key != null) {
+                            /*
+                             * The following code reads 76-byte data blocks from
+                             * the content server, decodes them and concatenates
+                             * them into a block whose lengths is divisible by
+                             * 16, which is needed for decryption. Then result
+                             * is decrypted prior to saving to disk.
+                             */
+                            do {
+                                BlockData blockData = getNextBlock(rdr, buf);
+                                if (blockData != null) {
+                                    bytesRead = blockData.bytesRead;
+                                    decodedChunk = base64DecodeToBytes(blockData.msgBlock);
+                                    for (int i = 0; i < decodedChunk.length; i++) {
+                                        blockArray[i + totalBlockLen] = decodedChunk[i];
+                                    }
+                                    totalBlockLen += decodedChunk.length;
+                                    if (totalBlockLen % 16 == 0)
+                                        break; // Read the next block from the
+                                               // server
+                                } else { // EOF, bytesRead == -1
+                                    bytesRead = -1;
+                                    break;
+                                }
+                            } while (totalBlockLen % 16 != 0);
+                            if (totalBlockLen == 0)
+                                break;
+                            byte[] tmpArray = decrypt(key.getBytes(),
+                                    blockArray);
+                            blockArray = Arrays.copyOf(tmpArray, totalBlockLen);
+
+                            attachmentFile.write(blockArray);
+                            blockArray = new byte[16 * DECODED_SIZE];
+                            totalBlockLen = 0;
+                        } else {
+                            BlockData blockData = getNextBlock(rdr, buf);
+                            if (blockData == null)
+                                break;
+                            bytesRead = blockData.bytesRead;
+                            decodedChunk = base64DecodeToBytes(blockData.msgBlock);
+                            attachmentFile.write(decodedChunk);
+                        }
+                    }
+                }
+                attachmentFile.close();
+            } else {
+                tmp = rdr.readLine();
+            }
+
+            rdr.close();
+            is.close();
+
+            if ((index = tmp.indexOf(BLANK)) != -1) {
+                srvr_rsp[0] = tmp.substring(0, tmp.indexOf(BLANK));
+                srvr_rsp[1] = tmp.substring(tmp.indexOf(BLANK) + 1);
+            } else {
+                srvr_rsp[0] = tmp;
+                srvr_rsp[1] = "";
+            }
+        } catch (MalformedURLException e) {
+            srvr_rsp[0] = "-1";
+            srvr_rsp[1] = e.getMessage();
+            System.out
+                    .println("MalformedURLException when sending content to server: "
+                            + " " + e.getMessage());
+            e.printStackTrace();
+        } catch (IOException e) {
+            srvr_rsp[0] = "-1";
+            srvr_rsp[1] = e.getMessage();
+            System.out.println("IOException when sending content to server: "
+                    + " " + e.getMessage());
+            e.printStackTrace();
+        }
+        return srvr_rsp;
+    }
+
+    /****************************************************************************/
+    /* The registerServlet() method sends an HTTP request to the Register */
+    /* servlet. */
+    /****************************************************************************/
+    public static void registerServlet(Account account, String addr)
+            throws Exception {
+        HttpURLConnection connection;
+
+        BufferedWriter writer;
+
+        URL url;
+
+        try {
+            url = new URL("https://www.chiaramail.com/Register");
+            connection = (HttpsURLConnection) url.openConnection();
+            connection.setDoOutput(true);
+
+            writer = new BufferedWriter(new OutputStreamWriter(
+                    connection.getOutputStream()));
+            writer.write("email_addr="
+                    + URLEncoder.encode(account.getEmail(), "UTF-8") + "&addr="
+                    + URLEncoder.encode("!FGw38;_&hIoPDC6887", "UTF-8")
+                    + "&username="
+                    + URLEncoder.encode(account.getName(), "UTF-8") + "&city="
+                    + "&zipcode=00000" + "&country="
+                    + URLEncoder.encode("United States of America", "UTF-8"));
+            writer.close();
+
+            connection.getResponseCode();
+        } catch (MalformedURLException e) {
+            System.out
+                    .println("MalformedURLException when sending registration request to server: "
+                            + " " + e.getMessage());
+        } catch (IOException e) {
+            System.out
+                    .println("IOException when sending registration request to server: "
+                            + " " + e.getMessage());
+        }
+
+        return;
+    }
+
+    /****************************************************************************/
+    /* doUpdateContent() sends updated content to the ContentServer. */
+    /****************************************************************************/
+    public static String[] doUpdateContent(String parms, String url_str,
+            String email_addr, String password) throws Exception {
+        String[] rsp;
+
+        rsp = askServer(url_str + CONTENT_SERVER_APP, UPDATE_CONTENT + parms,
+                email_addr, password, false, null, null);
+
+        return rsp;
+    }
+
+    /****************************************************************************/
+    /* doFetchContent() fetches content from the ContentServer. */
+    /****************************************************************************/
+    public static String[] doFetchContent(Account account, String parms,
+            String url_str, String email_addr, String password,
+            boolean isAttachment, String filename, String encryption_key)
+            throws Exception {
+        String[] rsp;
+
+        if (!url_str.toLowerCase().startsWith(
+                "https://" + DEFAULT_CONTENT_SERVER_NAME + ":"
+                        + DEFAULT_CONTENT_SERVER_PORT)
+                && (account.getLicenseStatus() == LICENSE_UNKNOWN || account
+                        .getLicenseCheckDate() + NEXT_DAY < getCurrentDate())) {
+            rsp = askServer(url_str + CONTENT_SERVER_APP, SERVER_LICENSED
+                    + parms, email_addr, password, false, null, null);
+            if (rsp[0].equals("10")) {
+                account.setLicenseStatus(LICENSE_ACTIVE);
+                account.setLicenseCheckDate(new Date().getTime());
+                rsp = askServer(url_str + CONTENT_SERVER_APP, FETCH_CONTENT
+                        + parms, email_addr, password, isAttachment, filename,
+                        encryption_key);
+            } else {
+                account.setLicenseStatus(LICENSE_EXPIRED);
+            }
+        } else {
+            rsp = askServer(url_str + CONTENT_SERVER_APP,
+                    FETCH_CONTENT + parms, email_addr, password, isAttachment,
+                    filename, encryption_key);
+        }
+
+        return rsp;
+    }
+
+    /****************************************************************************/
+    /* doReceiveContent() requests the ContentServer to store content. */
+    /****************************************************************************/
+    public static String[] doReceiveContent(Account account, String parms,
+            String url_str, String email_addr, String password)
+            throws Exception {
+        String[] rsp;
+
+        if (!url_str.toLowerCase().startsWith(
+                "https://" + DEFAULT_CONTENT_SERVER_NAME + ":"
+                        + DEFAULT_CONTENT_SERVER_PORT)
+                && (account.getLicenseStatus() == LICENSE_UNKNOWN || account
+                        .getLicenseCheckDate() + NEXT_DAY < getCurrentDate())) {
+            rsp = askServer(url_str + CONTENT_SERVER_APP, SERVER_LICENSED
+                    + parms, email_addr, password, false, null, null);
+            if (rsp[0].equals("10")) {
+                account.setLicenseStatus(LICENSE_ACTIVE);
+                account.setLicenseCheckDate(new Date().getTime());
+                rsp = askServer(url_str + CONTENT_SERVER_APP, RECEIVE_CONTENT
+                        + parms, email_addr, password, false, null, null);
+            } else {
+                account.setLicenseStatus(LICENSE_EXPIRED);
+            }
+        } else {
+            rsp = askServer(url_str + CONTENT_SERVER_APP, RECEIVE_CONTENT
+                    + parms, email_addr, password, false, null, null);
+        }
+
+        return rsp;
+    }
+
+    /****************************************************************************/
+    /* doRemoveRecipient() requests the ContentServer to remove the recipient */
+    /* from the access list of the named message. */
+    /****************************************************************************/
+    public static String[] doRemoveRecipient(String parms, String url_str,
+            String email_addr, String password) throws Exception {
+        String[] rsp;
+
+        rsp = askServer(url_str + CONTENT_SERVER_APP, REMOVE_RECIPIENT + parms,
+                email_addr, password, false, null, null);
+
+        return rsp;
+    }
+
+    /****************************************************************************/
+    /* doDeleteContent() requests the ContentServer to delete content and */
+    /* content pointer entries in the content_indexes database table. */
+    /****************************************************************************/
+    public static String[] doDeleteContent(String parms, String url_str,
+            String email_addr, String password) throws Exception {
+        String[] rsp;
+
+        rsp = askServer(url_str + CONTENT_SERVER_APP, DELETE_CONTENT + parms,
+                email_addr, password, false, null, null);
+
+        return rsp;
+    }
+
+    /****************************************************************************/
+    /* doDeleteData() requests the ContentServer to delete content only. */
+    /****************************************************************************/
+    public static String[] doDeleteData(String parms, String url_str,
+            String email_addr, String password) throws Exception {
+        String[] rsp;
+
+        rsp = askServer(url_str + CONTENT_SERVER_APP, DELETE_DATA + parms,
+                email_addr, password, false, null, null);
+
+        return rsp;
+    }
+
+    /****************************************************************************/
+    /* doGetData() requests the ContentServer to return the amount of space */
+    /* the user has left. */
+    /****************************************************************************/
+    public static String[] doGetData(String url_str, String email_addr,
+            String password) throws Exception {
+        String[] rsp;
+
+        rsp = askServer(url_str + CONTENT_SERVER_APP, GET_DATA, email_addr,
+                password, false, null, null);
+
+        return rsp;
+    }
+
+    /****************************************************************************/
+    /* isUserRegistered() requests the ContentServer to report if the given */
+    /* account exists. */
+    /****************************************************************************/
+    public static String isUserRegistered(Account account, String address) {
+        try {
+            String[] reply = askServer(
+                    "https://" + account.getContentServerName() + ":"
+                            + account.getContentServerPort()
+                            + CONTENT_SERVER_APP, USER_REGISTERED + address,
+                    account.getEmail(),
+                    base64Encode(account.getPassword()), false,
+                    null, null);
+            if (reply[0].equals("7")) {
+                String rsp = reply[1].substring(reply[1].lastIndexOf("= ") + 2);
+                return rsp.substring(0, rsp.length() - 1);
+            } else {
+                System.out.println(reply[1]);
+                return "";
+            }
+        } catch (Exception e) {
+            System.out
+                    .println("Exception when fetching content server response: "
+                            + " " + e.getMessage());
+            return "";
+        }
+    }
+
+    /****************************************************************************/
+    /* doRegisterUser() registers the user for content service. */
+    /****************************************************************************/
+    public static void doRegisterUser(Account account, String addr)
+            throws Exception {
+        String[] rsp;
+
+        registerServlet(account, addr);
+
+        return;
+    }
+
+    /****************************************************************************/
+    /* getCurrentDate() fetches the current date and is used when validating */
+    /* licenses. */
+    /****************************************************************************/
+    private static long getCurrentDate() {
+        Date date = new Date();
+        return date.getTime();
+    }
+
+    /**
+     * Replace characters we don't allow in file names with a replacement
+     * character.
+     * 
+     * @param filename
+     *            The original file name.
+     * 
+     * @return The sanitized file name containing only allowed characters.
+     */
+    public static String sanitizeFilename(String filename) {
+        return filename.replaceAll(INVALID_CHARACTERS, REPLACEMENT_CHARACTER);
+    }
+
+    /**
+     * Check to see if we have network connectivity.
+     * 
+     * @param app
+     *            Current application (Hint: see if your base class has a
+     *            getApplication() method.)
+     * @return true if we have connectivity, false otherwise.
+     */
+    public static boolean hasConnectivity(final Object app) {
+        return true;
+    }
+
+    private static final Pattern MESSAGE_ID = Pattern.compile("<" + "(?:"
+            + "[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+"
+            + "(?:\\.[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+)*" + "|"
+            + "\"(?:[^\\\\\"]|\\\\.)*\"" + ")" + "@" + "(?:"
+            + "[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+"
+            + "(?:\\.[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~]+)*" + "|"
+            + "\\[(?:[^\\\\\\]]|\\\\.)*\\]" + ")" + ">");
+
+    public static List<String> extractMessageIds(final String text) {
+        List<String> messageIds = new ArrayList<String>();
+        Matcher matcher = MESSAGE_ID.matcher(text);
+
+        int start = 0;
+        while (matcher.find(start)) {
+            String messageId = text.substring(matcher.start(), matcher.end());
+            messageIds.add(messageId);
+            start = matcher.end();
+        }
+
+        return messageIds;
+    }
+
+    public static String extractMessageId(final String text) {
+        Matcher matcher = MESSAGE_ID.matcher(text);
+
+        if (matcher.find()) {
+            return text.substring(matcher.start(), matcher.end());
+        }
+
+        return null;
+    }
+
+    public static String[] copyOf(String[] original, int newLength) {
+        return Arrays.copyOf(original, newLength);
+    }
+
+    public static String extractAddresses(Address[] toAddrs, Address[] ccAddrs,
+            Address[] bccAddrs) {
+        String tmp = "", tmp1 = "", tmp2 = "", tmp3 = "";
+
+        StringTokenizer st;
+
+        for (int i = 0; i < toAddrs.length; i++) {
+            st = new StringTokenizer(toAddrs[i].toString(), " ,");
+            for (int j = 0; st.hasMoreTokens(); j++) {
+                tmp1 += st.nextToken() + ",";
+            }
+        }
+
+        for (int i = 0; i < ccAddrs.length; i++) {
+            st = new StringTokenizer(ccAddrs[i].toString(), " ,");
+
+            for (int j = 0; st.hasMoreTokens(); j++) {
+                tmp2 += st.nextToken() + ",";
+            }
+        }
+
+        for (int i = 0; i < bccAddrs.length; i++) {
+            st = new StringTokenizer(bccAddrs[i].toString(), " ,");
+
+            while (st.hasMoreTokens()) {
+                tmp3 += st.nextToken() + ",";
+            }
+        }
+        if (tmp1.length() > 0) {
+            tmp += tmp1;
+            if (tmp2.length() > 0) {
+                tmp += "," + tmp2;
+                if (tmp3.length() > 0) {
+                    tmp += "," + tmp3;
+                    return tmp;
+                }
+                return tmp;
+            } else {
+                if (tmp3.length() > 0) {
+                    tmp += "," + tmp3;
+                    return tmp;
+                }
+                return tmp;
+            }
+        } else {
+            if (tmp2.length() > 0) {
+                tmp += tmp2;
+                if (tmp3.length() > 0) {
+                    tmp += "," + tmp3;
+                    return tmp;
+                }
+                return tmp;
+            } else {
+                if (tmp3.length() > 0) {
+                    return tmp3;
+                }
+            }
+        }
+        return tmp;
+    }
+
+    public static String generateEncryptionKey() {
+        Random random = new Random();
+        return (String.valueOf(random.nextLong()) + "01234567890123456789012345678901")
+                .substring(0, 32); // Pad out to 32 bytes
+    }
+    
+    public static byte[] doFinal(int mode, byte[] key, byte[] data) throws Exception {
+        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
+        SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");
+        Cipher cipher = Cipher.getInstance("AES/ECB/ZeroBytePadding");
+        try {
+            cipher.init(mode, skeySpec);
+        } catch (Exception e) {
+            System.err.println("Error initializing Cipher: " + e.getMessage());
+            System.err.println("Your java version is : " + System.getProperty("java.version") + " installed in: " + System.getProperty("java.home"));
+            System.err.println("If you are using sun/oracle version, be sure you have installed 'Java Cryptography Extension (JCE)'");
+            throw e;
+        }
+        return cipher.doFinal(data);
+    }
+
+    public static String encrypt(byte[] raw, byte[] clear) throws Exception {
+        return new String(Base64.encodeBase64(doFinal(Cipher.ENCRYPT_MODE, raw, clear)));
+    }
+
+    public static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
+        return doFinal(Cipher.DECRYPT_MODE, raw, encrypted);
+    }
+
+    // MCM
+    public static int getSpinnerIndex() {
+        return -1;
+    }
+
+    public static boolean validateHeaders(Message message) {
+        String contentServerName[] = null;
+        String contentServerPort[] = null;
+        String contentPointers[] = null;
+
+        int port = 0, pointer = 0;
+
+        try {
+            if ((contentServerName = message.getHeader(CONTENT_SERVER_NAME)) == null
+                    || (contentServerPort = message
+                            .getHeader(CONTENT_SERVER_PORT)) == null
+                    || (contentPointers = message.getHeader(CONTENT_POINTER)) == null)
+                return false;
+
+            if (contentServerName.length == 0) {
+                // Toast.makeText(this.getContext(),
+                // this.getContext().getString(R.string.message_compose_error_missing_content_server_name),
+                // Toast.LENGTH_LONG).show();
+                return false;
+            }
+
+            if (contentServerPort.length == 0) {
+                // Toast.makeText(this.getContext(),
+                // this.getContext().getString(R.string.message_compose_error_missing_content_server_port),
+                // Toast.LENGTH_LONG).show();
+                return false;
+            } else {
+                try {
+                    port = Integer.parseInt(contentServerPort[0]);
+                } catch (Exception e) {
+                    // Toast.makeText(this.getContext(),
+                    // this.getContext().getString(R.string.message_compose_error_bogus_content_server_port)
+                    // + port, Toast.LENGTH_LONG).show();
+                    return false;
+                }
+                if (port < 0) {
+                    // Toast.makeText(this.getContext(),
+                    // this.getContext().getString(R.string.message_compose_error_rangerr_content_server_port)
+                    // + contentServerPort, Toast.LENGTH_LONG).show();
+                    return false;
+                }
+            }
+
+            if (contentPointers.length == 0) {
+                // Toast.makeText(this.getContext(),
+                // this.getContext().getString(R.string.message_compose_error_missing_content_pointers),
+                // Toast.LENGTH_LONG).show();
+                return false;
+            } else {
+                StringTokenizer st = new StringTokenizer(contentPointers[0]);
+                for (; st.hasMoreTokens();) {
+                    try {
+                        pointer = Integer.parseInt(st.nextToken());
+                    } catch (Exception e) {
+                        // Toast.makeText(this.getContext(),
+                        // this.getContext().getString(R.string.message_compose_error_bogus_content_pointer)
+                        // + pointer, Toast.LENGTH_LONG).show();
+                        return false;
+                    }
+                    if (pointer < 0 || pointer % 8 != 0) {
+                        // Toast.makeText(this.getContext(),
+                        // this.getContext().getString(R.string.message_compose_error_bogus_content_pointer)
+                        // + pointer, Toast.LENGTH_LONG).show();
+                        return false;
+                    }
+                }
+            }
+        } catch (MessagingException e) {
+            return false;
+        }
+        return true;
+    }
+
+    public static long getCurrentFreeMemoryBytes() {
+        long heapSize = Runtime.getRuntime().totalMemory();
+        long heapRemaining = Runtime.getRuntime().freeMemory();
+        long nativeUsage = 0; // Debug.getNativeHeapAllocatedSize();
+
+        return Runtime.getRuntime().maxMemory() - (heapSize - heapRemaining)
+                - nativeUsage;
+    }
+}
\ No newline at end of file

Modified: james/hupa/trunk/server/src/main/java/org/apache/hupa/server/service/GetMessageDetailsServiceImpl.java
URL: http://svn.apache.org/viewvc/james/hupa/trunk/server/src/main/java/org/apache/hupa/server/service/GetMessageDetailsServiceImpl.java?rev=1516486&r1=1516485&r2=1516486&view=diff
==============================================================================
--- james/hupa/trunk/server/src/main/java/org/apache/hupa/server/service/GetMessageDetailsServiceImpl.java (original)
+++ james/hupa/trunk/server/src/main/java/org/apache/hupa/server/service/GetMessageDetailsServiceImpl.java Thu Aug 22 15:29:57 2013
@@ -54,11 +54,14 @@ import javax.mail.Flags.Flag;
 import javax.mail.Header;
 import javax.mail.Message;
 import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Part;
 import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeUtility;
 
-import org.apache.hupa.server.utils.MessageUtils;
 import org.apache.hupa.shared.data.GetMessageDetailsResultImpl;
 import org.apache.hupa.shared.data.MailHeaderImpl;
+import org.apache.hupa.shared.data.MessageAttachmentImpl;
 import org.apache.hupa.shared.data.MessageDetailsImpl;
 import org.apache.hupa.shared.domain.GetMessageDetailsAction;
 import org.apache.hupa.shared.domain.GetMessageDetailsResult;
@@ -115,12 +118,12 @@ public class GetMessageDetailsServiceImp
 	        MessagingException, UnsupportedEncodingException {
 		MessageDetails mDetails = new MessageDetailsImpl();
 
-		Object content = message.getContent();
+		Object con = message.getContent();
 
 		StringBuffer sbPlain = new StringBuffer();
 		ArrayList<MessageAttachment> attachmentList = new ArrayList<MessageAttachment>();
 
-		boolean isHTML = MessageUtils.handleParts(message, content, sbPlain, attachmentList);
+		boolean isHTML = handleParts(message, con, sbPlain, attachmentList);
 
 		if (isHTML) {
 			mDetails.setText(filterHtmlDocument(sbPlain.toString(), folderName, uid));
@@ -130,14 +133,115 @@ public class GetMessageDetailsServiceImp
 
 		mDetails.setMessageAttachments(attachmentList);
 
-		for (@SuppressWarnings("unchecked") Enumeration<Header> en = message.getAllHeaders(); en.hasMoreElements();) {
+		for (@SuppressWarnings("unchecked")
+		Enumeration<Header> en = message.getAllHeaders(); en.hasMoreElements();) {
 			Header header = en.nextElement();
 			mDetails.setMailHeader(new MailHeaderImpl(header.getName(), header.getValue()));
+//			mDetails.addHeader(header.getName(), header.getValue());
 		}
 
 		return mDetails;
 	}
 
+	/**
+	 * Handle the parts of the given message. The method will call itself
+	 * recursively to handle all nested parts
+	 * 
+	 * @param message the MimeMessage
+	 * @param con the current processing Content
+	 * @param sbPlain the StringBuffer to fill with text
+	 * @param attachmentList ArrayList with attachments
+	 * @throws UnsupportedEncodingException
+	 * @throws MessagingException
+	 * @throws IOException
+	 */
+	protected boolean handleParts(MimeMessage message, Object con, StringBuffer sbPlain,
+	        ArrayList<MessageAttachment> attachmentList) throws UnsupportedEncodingException, MessagingException,
+	        IOException {
+		boolean isHTML = false;
+		if (con instanceof String) {
+			if (message.getContentType().toLowerCase().startsWith("text/html")) {
+				isHTML = true;
+			} else {
+				isHTML = false;
+			}
+			sbPlain.append((String) con);
+
+		} else if (con instanceof Multipart) {
+
+			Multipart mp = (Multipart) con;
+			String multipartContentType = mp.getContentType().toLowerCase();
+
+			String text = null;
+
+			if (multipartContentType.startsWith("multipart/alternative")) {
+				isHTML = handleMultiPartAlternative(mp, sbPlain);
+			} else {
+				for (int i = 0; i < mp.getCount(); i++) {
+					Part part = mp.getBodyPart(i);
+
+					String contentType = part.getContentType().toLowerCase();
+
+					Boolean bodyRead = sbPlain.length() > 0;
+
+					if (!bodyRead && contentType.startsWith("text/plain")) {
+						isHTML = false;
+						text = (String) part.getContent();
+					} else if (!bodyRead && contentType.startsWith("text/html")) {
+						isHTML = true;
+						text = (String) part.getContent();
+					} else if (!bodyRead && contentType.startsWith("message/rfc822")) {
+						// Extract the message and pass it
+						MimeMessage msg = (MimeMessage) part.getDataHandler().getContent();
+						isHTML = handleParts(msg, msg.getContent(), sbPlain, attachmentList);
+					} else {
+						if (part.getFileName() != null) {
+							// Inline images are not added to the attachment
+							// list
+							// TODO: improve the in-line images detection
+							if (part.getHeader("Content-ID") == null) {
+								MessageAttachment attachment = new MessageAttachmentImpl();
+								attachment.setName(MimeUtility.decodeText(part.getFileName()));
+								attachment.setContentType(part.getContentType());
+								attachment.setSize(part.getSize());
+								attachmentList.add(attachment);
+							}
+						} else {
+							isHTML = handleParts(message, part.getContent(), sbPlain, attachmentList);
+						}
+					}
+
+				}
+				if (text != null)
+					sbPlain.append(text);
+			}
+
+		}
+		return isHTML;
+	}
+
+	private boolean handleMultiPartAlternative(Multipart mp, StringBuffer sbPlain) throws MessagingException,
+	        IOException {
+		String text = null;
+		boolean isHTML = false;
+		for (int i = 0; i < mp.getCount(); i++) {
+			Part part = mp.getBodyPart(i);
+
+			String contentType = part.getContentType().toLowerCase();
+
+			// we prefer html
+			if (text == null && contentType.startsWith("text/plain")) {
+				isHTML = false;
+				text = (String) part.getContent();
+			} else if (contentType.startsWith("text/html")) {
+				isHTML = true;
+				text = (String) part.getContent();
+			}
+		}
+		sbPlain.append(text);
+		return isHTML;
+	}
+
 	protected String txtDocumentToHtml(String txt, String folderName, long uuid) {
 
 		if (txt == null || txt.length() == 0)



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org