/*
* Copyright 2012 The Stanford MobiSocial Laboratory
*
* Licensed 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.
*/
package org.mobisocial.corral;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Date;
import java.util.List;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import mobisocial.musubi.App;
import mobisocial.musubi.model.MIdentity;
import mobisocial.musubi.model.helpers.IdentitiesManager;
import mobisocial.socialkit.musubi.DbIdentity;
import mobisocial.socialkit.musubi.DbObj;
import org.json.JSONException;
import org.json.JSONObject;
import org.mobisocial.corral.CorralDownloadHandler.CorralDownloadFuture;
import org.mobisocial.corral.CorralHelper.DownloadProgressCallback.DownloadChannel;
import org.mobisocial.corral.CorralHelper.DownloadProgressCallback.DownloadState;
import android.content.Context;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
import android.os.Environment;
import android.util.Log;
public class CorralHelper {
private static String TAG = "CorralHelper";
public static final long CANCEL_SAMPLE_WINDOW = 2500;
private static MIdentity[] getBuddies(Context context, Uri feedUri) {
SQLiteOpenHelper helper = App.getDatabaseSource(context);
IdentitiesManager identitiesManager = new IdentitiesManager(helper);
List<DbIdentity> dbis = App.getMusubi(context).getFeed(feedUri).getMembers();
MIdentity[] buddies = new MIdentity[dbis.size()];
int i = 0;
for(DbIdentity dbi:dbis){
buddies[i++] = identitiesManager.getIdentityForId(dbi.getLocalId());
}
return buddies;
}
static Uri downloadContent(Context context, File cachefile, DbObj obj,
CorralDownloadFuture future, DownloadProgressCallback callback) throws IOException {
try {
DownloadChannel server = DownloadChannel.SERVER;
callback.onProgress(DownloadState.PREPARING_CONNECTION, server, 0);
JSONObject json = obj.getJson();
String mykey = json.getString(CorralDownloadClient.OBJ_PRESHARED_KEY);
String objName = obj.getUniversalHashString();
CorralTicketProvider ctp = new CorralTicketProvider(context);
String ticket = ctp.getDownloadTicket(objName);
String datestr = ctp.getDatestr();
if(ticket==null){
throw new IOException("failed to get ticket for download");
}
if (future.isCancelled()) {
throw new IOException("User cancelled download");
}
CorralS3Connector s3cn = new CorralS3Connector(context);
try {
s3cn.downloadAndDecrypt(ticket, datestr, objName, cachefile, mykey, future, callback);
} catch (IOException e) {
callback.onProgress(DownloadState.TRANSFER_COMPLETE, server, DownloadProgressCallback.FAILURE);
throw e;
} catch (GeneralSecurityException e) {
callback.onProgress(DownloadState.TRANSFER_COMPLETE, server, DownloadProgressCallback.FAILURE);
throw new IOException("Failed to decrypt");
}
Uri result = Uri.fromFile(cachefile);
future.setResult(result);
callback.onProgress(DownloadState.TRANSFER_COMPLETE, server, DownloadProgressCallback.SUCCESS);
Log.d(TAG, "-----END-----"+(String.valueOf(System.currentTimeMillis())));
return Uri.fromFile(cachefile);
} catch (JSONException e) {
throw new IOException(e);
}
}
public interface UploadProgressCallback {
enum UploadState { PREPARING_UPLOAD, TRANSFER_IN_PROGRESS, FINISHING_UP };
public void onProgress(UploadState state, int progress);
public boolean isCancelled();
}
public interface DownloadProgressCallback {
enum DownloadState { DOWNLOAD_PENDING, PREPARING_CONNECTION, TRANSFER_IN_PROGRESS,
TRANSFER_COMPLETE };
enum DownloadChannel { NONE, LAN, BLUETOOTH, SERVER };
public static final int SUCCESS = 1;
public static final int FAILURE = 2;
public void onProgress(DownloadState state, DownloadChannel channel, int progress);
}
public static boolean uploadContent(Context context, DbObj obj, UploadProgressCallback callback) {
MIdentity[] buddies = getBuddies(context, obj.getContainingFeed().getUri());
try {
Uri dataUri = null;
String mykey = null;
String mime_type = null;
if (obj.getJson() == null) {
throw new JSONException("null json");
}
JSONObject jso = obj.getJson();
if (jso == null || !(jso.has(CorralDownloadClient.OBJ_LOCAL_URI)
&& jso.has(CorralDownloadClient.OBJ_MIME_TYPE))) {
return false;
}
dataUri = Uri.parse(jso.getString(CorralDownloadClient.OBJ_LOCAL_URI));
mykey = jso.getString(CorralDownloadClient.OBJ_PRESHARED_KEY);
mime_type = jso.getString(CorralDownloadClient.OBJ_MIME_TYPE);
EncRslt rslt = encryptData(dataUri, context, mykey, callback);
if(rslt == null){
Log.e(TAG, "failed to encrypt");
return false;
}
String objName = obj.getUniversalHashString();
// NOTE
// We cannot use URLEncode and Base64 because they generate "/" and "%2f"
// which are not allowed to use inside URLs for Tomcat Server...
// So, hex encoded value of the strings will be sent.
CorralTicketProvider ctp = new CorralTicketProvider(context);
String ticket = ctp.getUploadTicket(
objName,
bin2hex(mime_type.getBytes()),
String.valueOf(rslt.length),
bin2hex(rslt.md5.getBytes()));
String datestr = ctp.getDatestr();
if(ticket==null){
Log.e(TAG, "failed to get ticket for upload");
return false;
}
CorralS3Connector s3cn = new CorralS3Connector(context);
s3cn.uploadToServer(ticket, rslt, datestr, objName, callback);
ctp.putACL(objName, buddies);
return true;
} catch (IOException e1) {
e1.printStackTrace();
return false;
} catch (JSONException e) {
e.printStackTrace();
return false;
}
}
private static EncRslt encryptData(Uri uri, Context context, String mykey,
UploadProgressCallback callback) {
try {
EncRslt rslt = new EncRslt();
CryptUtil cu = new CryptUtil(mykey);
cu.InitCiphers();
rslt.key = cu.getKey();
InputStream is = context.getContentResolver().openInputStream(uri);
File dst = getFileForCrypt(String.valueOf(rslt.key.hashCode()), context);
FileOutputStream fos = new FileOutputStream(dst);
cu.encrypt(is, fos, callback);
rslt.length = cu.getLength();
rslt.md5 = cu.getMd5();
rslt.uri = Uri.fromFile(dst);
is.close();
fos.close();
return rslt;
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
} catch (ShortBufferException e) {
e.printStackTrace();
return null;
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
return null;
} catch (BadPaddingException e) {
e.printStackTrace();
return null;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (InvalidKeyException e) {
e.printStackTrace();
return null;
} catch (NoSuchProviderException e) {
e.printStackTrace();
return null;
} catch (NoSuchPaddingException e) {
e.printStackTrace();
return null;
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
return null;
}
}
public static File getFileForCrypt(String hash, Context context){
File externalCacheDir = new File(new File(new File(new File(Environment.getExternalStorageDirectory(), "Android"), "data"), context.getPackageName()), "cypher");
externalCacheDir.mkdirs();
// clear old items
String[] files = externalCacheDir.list();
long th = new Date().getTime() - 24*60*60*1000; // expire one day
for(int i=0; i<files.length;i++){
File tmp = new File(externalCacheDir, files[i]);
if(tmp.lastModified()<th){
tmp.delete();
}
}
// add new file
File dst = new File(externalCacheDir, hash+".tmp");
if(dst.exists()){
dst.delete();
}
// dst.deleteOnExit();
return dst;
}
public static class EncRslt {
public Uri uri;
public String key;
public long length;
public String md5;
}
public static String bin2hex(byte[] data) {
return String.format("%0" + (data.length*2) + "X", new BigInteger(1, data));
}
public byte[] hex2bin(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}