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