Home | History | Annotate | Download | only in adapter
      1 /* Copyright (C) 2011 The Android Open Source Project.
      2  *
      3  * Licensed under the Apache License, Version 2.0 (the "License");
      4  * you may not use this file except in compliance with the License.
      5  * You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software
     10  * distributed under the License is distributed on an "AS IS" BASIS,
     11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12  * See the License for the specific language governing permissions and
     13  * limitations under the License.
     14  */
     15 
     16 package com.android.exchange.adapter;
     17 
     18 import android.content.Context;
     19 
     20 import com.android.emailcommon.provider.EmailContent.Attachment;
     21 import com.android.emailcommon.provider.EmailContent.Message;
     22 import com.android.emailcommon.service.EmailServiceStatus;
     23 import com.android.emailcommon.utility.AttachmentUtilities;
     24 import com.android.emailsync.PartRequest;
     25 import com.android.exchange.Eas;
     26 import com.android.exchange.EasResponse;
     27 import com.android.exchange.EasSyncService;
     28 import com.android.exchange.utility.UriCodec;
     29 import com.google.common.annotations.VisibleForTesting;
     30 
     31 import org.apache.http.HttpStatus;
     32 
     33 import java.io.Closeable;
     34 import java.io.File;
     35 import java.io.FileInputStream;
     36 import java.io.FileNotFoundException;
     37 import java.io.FileOutputStream;
     38 import java.io.IOException;
     39 import java.io.InputStream;
     40 import java.io.OutputStream;
     41 
     42 /**
     43  * Handle EAS attachment loading, regardless of protocol version
     44  */
     45 public class AttachmentLoader {
     46     private final EasSyncService mService;
     47     private final Context mContext;
     48     private final Attachment mAttachment;
     49     private final long mAttachmentId;
     50     private final int mAttachmentSize;
     51     private final long mMessageId;
     52     private final Message mMessage;
     53 
     54     public AttachmentLoader(EasSyncService service, PartRequest req) {
     55         mService = service;
     56         mContext = service.mContext;
     57         mAttachment = req.mAttachment;
     58         mAttachmentId = mAttachment.mId;
     59         mAttachmentSize = (int)mAttachment.mSize;
     60         mMessageId = mAttachment.mMessageKey;
     61         mMessage = Message.restoreMessageWithId(mContext, mMessageId);
     62     }
     63 
     64     private void doStatusCallback(int status) {
     65 
     66     }
     67 
     68     private void doProgressCallback(int progress) {
     69 
     70     }
     71 
     72     @VisibleForTesting
     73     static String encodeForExchange2003(String str) {
     74         AttachmentNameEncoder enc = new AttachmentNameEncoder();
     75         StringBuilder sb = new StringBuilder(str.length() + 16);
     76         enc.appendPartiallyEncoded(sb, str);
     77         return sb.toString();
     78     }
     79 
     80     /**
     81      * Encoder for Exchange 2003 attachment names.  They come from the server partially encoded,
     82      * but there are still possible characters that need to be encoded (Why, MSFT, why?)
     83      */
     84     private static class AttachmentNameEncoder extends UriCodec {
     85         @Override protected boolean isRetained(char c) {
     86             // These four characters are commonly received in EAS 2.5 attachment names and are
     87             // valid (verified by testing); we won't encode them
     88             return c == '_' || c == ':' || c == '/' || c == '.';
     89         }
     90     }
     91 
     92     /**
     93      * Close, ignoring errors (as during cleanup)
     94      * @param c a Closeable
     95      */
     96     private static void close(Closeable c) {
     97         try {
     98             c.close();
     99         } catch (IOException e) {
    100         }
    101     }
    102 
    103     /**
    104      * Save away the contentUri for this Attachment and notify listeners
    105      * @throws IOException
    106      */
    107     private void finishLoadAttachment(File file) throws IOException {
    108         InputStream in = null;
    109         try {
    110             in = new FileInputStream(file);
    111             AttachmentUtilities.saveAttachment(mContext, in, mAttachment);
    112             doStatusCallback(EmailServiceStatus.SUCCESS);
    113         } catch (FileNotFoundException e) {
    114             // Not bloody likely, as we just created it successfully
    115             throw new IOException("Attachment file not found?");
    116         } finally {
    117             close(in);
    118         }
    119     }
    120 
    121     /**
    122      * Loads an attachment, based on the PartRequest passed in the constructor
    123      * @throws IOException
    124      */
    125     public void loadAttachment() throws IOException {
    126         if (mMessage == null) {
    127             doStatusCallback(EmailServiceStatus.MESSAGE_NOT_FOUND);
    128             return;
    129         }
    130         // Say we've started loading the attachment
    131         doProgressCallback(0);
    132 
    133         EasResponse resp = null;
    134         // The method of attachment loading is different in EAS 14.0 than in earlier versions
    135         boolean eas14 = mService.mProtocolVersionDouble >= Eas.SUPPORTED_PROTOCOL_EX2010_DOUBLE;
    136         try {
    137             if (eas14) {
    138                 Serializer s = new Serializer();
    139                 s.start(Tags.ITEMS_ITEMS).start(Tags.ITEMS_FETCH);
    140                 s.data(Tags.ITEMS_STORE, "Mailbox");
    141                 s.data(Tags.BASE_FILE_REFERENCE, mAttachment.mLocation);
    142                 s.end().end().done(); // ITEMS_FETCH, ITEMS_ITEMS
    143                 resp = mService.sendHttpClientPost("ItemOperations", s.toByteArray());
    144             } else {
    145                 String location = mAttachment.mLocation;
    146                 // For Exchange 2003 (EAS 2.5), we have to look for illegal chars in the file name
    147                 // that EAS sent to us!
    148                 if (mService.mProtocolVersionDouble < Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
    149                     location = encodeForExchange2003(location);
    150                 }
    151                 String cmd = "GetAttachment&AttachmentName=" + location;
    152                 resp = mService.sendHttpClientPost(cmd, null, EasSyncService.COMMAND_TIMEOUT);
    153             }
    154 
    155             int status = resp.getStatus();
    156             if (status == HttpStatus.SC_OK) {
    157                 if (!resp.isEmpty()) {
    158                     InputStream is = resp.getInputStream();
    159                     OutputStream os = null;
    160                     File tmpFile = null;
    161                     try {
    162                         tmpFile = File.createTempFile("eas_", "tmp", mContext.getCacheDir());
    163                         os = new FileOutputStream(tmpFile);
    164                         if (eas14) {
    165                             ItemOperationsParser p = new ItemOperationsParser(is, os,
    166                                     mAttachmentSize, null);
    167                             p.parse();
    168                             if (p.getStatusCode() == 1 /* Success */) {
    169                                 finishLoadAttachment(tmpFile);
    170                                 return;
    171                             }
    172                         } else {
    173                             int len = resp.getLength();
    174                             if (len != 0) {
    175                                 // len > 0 means that Content-Length was set in the headers
    176                                 // len < 0 means "chunked" transfer-encoding
    177                                 ItemOperationsParser.readChunked(is, os,
    178                                         (len < 0) ? mAttachmentSize : len, null);
    179                                 finishLoadAttachment(tmpFile);
    180                                 return;
    181                             }
    182                         }
    183                     } catch (FileNotFoundException e) {
    184                         mService.errorLog("Can't get attachment; write file not found?");
    185                         doStatusCallback(EmailServiceStatus.ATTACHMENT_NOT_FOUND);
    186                     } finally {
    187                         close(is);
    188                         close(os);
    189                         if (tmpFile != null) {
    190                             tmpFile.delete();
    191                         }
    192                     }
    193                 }
    194             }
    195         } catch (IOException e) {
    196             // Report the error, but also report back to the service
    197             doStatusCallback(EmailServiceStatus.CONNECTION_ERROR);
    198             throw e;
    199         } finally {
    200             if (resp != null) {
    201                 resp.close();
    202             }
    203         }
    204     }
    205 }
    206