edu.ncsu.asbransc.mouflon.recorder.UploadFile.java Source code

Java tutorial

Introduction

Here is the source code for edu.ncsu.asbransc.mouflon.recorder.UploadFile.java

Source

/*Mouflon: an Android app for collecting and reporting application usage.
Copyright (C) 2013 Andrew Branscomb
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
*/
package edu.ncsu.asbransc.mouflon.recorder;

import java.io.BufferedInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.os.Binder;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.util.Base64;
import android.util.Base64OutputStream;

public class UploadFile extends Service {

    private boolean mManual = false;
    private IBinder mBinder = new UploadBinder();

    public class UploadBinder extends Binder {
        UploadBinder getService() {
            return UploadBinder.this;
        }
    }

    @Override
    public void onCreate() {

        super.onCreate();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        mManual = intent.getBooleanExtra("edu.ncsu.asbransc.mouflon.recorder.ManualUpload", false);
        upload();
        return START_STICKY;
    }

    protected void upload() {
        Thread t = new Thread(new Runnable() {

            @Override
            public void run() {
                UploadFile.this.doUpload();
            }
        });
        t.start();

    }

    /*@Override
    protected Dialog onCreateDialog(int id) {
       Dialog dialog;
       switch(id) {
       case DIALOG_CLEAR_ID:
     AlertDialog.Builder builder = new AlertDialog.Builder(this);
     builder.setMessage("Would you like to clear the log database?")
           .setCancelable(false)
           .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
                  
              @Override
              public void onClick(DialogInterface dialog, int which) {
                 DbAdapter dba = new DbAdapter(UploadFile.this);
                 dba.open();
                 dba.clearDB();
                 dba.close();
              }
           })
           .setNegativeButton("No", new DialogInterface.OnClickListener() {
                  
              @Override
              public void onClick(DialogInterface dialog, int which) {
                 dialog.cancel();
                     
              }
           });
     Looper.prepare();
     dialog = builder.create();
     break;
       default:
     dialog = null;
       }
       return dialog;
           
    }*/

    protected void doUpload() {
        DbAdapter dba = new DbAdapter(this);
        dba.open();
        Cursor allLogs = dba.fetchAll();
        StringBuilder sb = new StringBuilder();
        allLogs.moveToFirst();
        sb.append("DateTime");
        sb.append(",");
        sb.append("Process");
        sb.append(",");
        sb.append("Type");
        sb.append(",");
        sb.append("Component");
        sb.append(",");
        sb.append("ActionString");
        sb.append(",");
        sb.append("Category");
        sb.append("\n");
        while (!allLogs.isAfterLast()) {
            sb.append(allLogs.getString(allLogs.getColumnIndex(DbAdapter.KEY_TIME)));
            sb.append(",");
            sb.append(allLogs.getString(allLogs.getColumnIndex(DbAdapter.KEY_PROCESSTAG)));
            sb.append(",");
            sb.append(allLogs.getString(allLogs.getColumnIndex(DbAdapter.KEY_EXTRA_1)));
            sb.append(",");
            sb.append(allLogs.getString(allLogs.getColumnIndex(DbAdapter.KEY_EXTRA_2)));
            sb.append(",");
            sb.append(allLogs.getString(allLogs.getColumnIndex(DbAdapter.KEY_EXTRA_3)));
            sb.append(",");
            sb.append(allLogs.getString(allLogs.getColumnIndex(DbAdapter.KEY_EXTRA_4)));
            sb.append("\n");
            allLogs.moveToNext();
        }
        dba.close();
        File appDir = getDir("toUpload", MODE_PRIVATE);
        UUID uuid;
        uuid = MainScreen.getOrCreateUUID(this);
        long time = System.currentTimeMillis();
        String basename = uuid.toString() + "_AT_" + time;
        String filename = basename + ".zip.enc";
        File file = new File(appDir, filename);
        FileOutputStream out = null;
        ZipOutputStream outzip = null;
        CipherOutputStream outcipher = null;
        Cipher datac = null;

        File keyfile = new File(appDir, basename + ".key.enc");
        //Log.i("sb length", Integer.toString(sb.length()));
        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
        String email = prefs.getString(MainScreen.EMAIL_KEY, "");
        String emailFilename = "email.txt";
        String csvFilename = "mouflon_log_" + time + ".csv";
        try {
            SecretKey aeskey = generateAESKey();
            //Log.i("secret key", bytearrToString(aeskey.getEncoded()));
            encryptAndWriteAESKey(aeskey, keyfile);
            datac = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
            byte[] ivbytes = genIV();
            IvParameterSpec iv = new IvParameterSpec(ivbytes);
            datac.init(Cipher.ENCRYPT_MODE, aeskey, iv);
            out = new FileOutputStream(file);
            out.write(ivbytes);
            //Log.i("iv bytes", bytearrToString(ivbytes));
            outcipher = new CipherOutputStream(out, datac);
            outzip = new ZipOutputStream(outcipher);
            outzip.setMethod(ZipOutputStream.DEFLATED);
            //write the first file (e-mail address)
            String androidVersion = android.os.Build.VERSION.RELEASE;
            String deviceName = android.os.Build.MODEL;
            ZipEntry infoEntry = new ZipEntry("info.txt");
            outzip.putNextEntry(infoEntry);
            outzip.write((androidVersion + "\n" + deviceName).getBytes());
            outzip.closeEntry();
            ZipEntry emailEntry = new ZipEntry(emailFilename);
            outzip.putNextEntry(emailEntry);
            outzip.write(email.getBytes());
            outzip.closeEntry();
            ZipEntry csvEntry = new ZipEntry(csvFilename);
            outzip.putNextEntry(csvEntry);
            outzip.write(sb.toString().getBytes());
            outzip.closeEntry();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                outzip.close();
                outcipher.close();
                out.close();
            } catch (IOException e) {
                //ignore
            } catch (NullPointerException ne) {
                //ignore
            }
        }
        //here we actually upload the files 
        String containerFilename = basename + "_container.zip";
        File containerFile = new File(appDir, containerFilename);
        zipUp(containerFile, new File[] { file, keyfile });
        boolean success = uploadFile(containerFile);
        containerFile.delete();
        file.delete();
        keyfile.delete();
        if (success && prefs.getBoolean(MainScreen.DELETE_KEY, true)) {
            DbAdapter dba2 = new DbAdapter(this);
            dba2.open();
            dba2.clearDB();
            dba2.close();
        }
        if (!success && prefs.getBoolean(MainScreen.UPLOAD_KEY, false)) {
            Editor e = prefs.edit();
            e.putInt(MainScreen.DAY_KEY, 6); //reset it to run tomorrow if it fails
            e.commit();
        }
        String s = success ? "Upload complete. Thanks!" : "Upload Failed";
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(UploadFile.this)
                .setSmallIcon(R.drawable.ic_launcher_bw).setContentTitle("Mouflon Recorder").setContentText(s)
                .setAutoCancel(true).setOngoing(false);

        if (mManual) { //only show a notification if we manually upload the file.
            Intent toLaunch = new Intent(UploadFile.this, MainScreen.class);
            //The notification has to go somewhere.
            PendingIntent pi = PendingIntent.getActivity(UploadFile.this, 0, toLaunch,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            mBuilder.setContentIntent(pi);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            manager.notify(1, mBuilder.build());
        }
        stopSelf();
    }

    private void zipUp(File out, File[] in) {
        FileOutputStream fout = null;
        ZipOutputStream zout = null;
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        try {
            fout = new FileOutputStream(out);
            zout = new ZipOutputStream(fout);
            zout.setMethod(ZipOutputStream.DEFLATED);

            for (File currFile : in) {
                FileInputStream fin = new FileInputStream(currFile);
                ZipEntry currEntry = new ZipEntry(currFile.getName());
                zout.putNextEntry(currEntry);
                while ((bytesRead = fin.read(buffer)) > 0) {
                    zout.write(buffer, 0, bytesRead);
                }
                zout.closeEntry();
                fin.close();
            }
        } catch (FileNotFoundException e) {

            e.printStackTrace();
        } catch (IOException e) {

            e.printStackTrace();
        } finally {
            try {
                zout.close();
                fout.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException e) {
                e.printStackTrace();
            }

        }

    }

    private byte[] genIV() {
        SecureRandom r = new SecureRandom();
        byte[] iv = new byte[16];
        r.nextBytes(iv);
        return iv;
    }

    private void encryptAndWriteAESKey(SecretKey aeskey, File dest)
            throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
            InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
        Cipher keyc;
        AssetManager am = getAssets();
        InputStream in = am.open("mouflon_key.pub");
        byte[] readFromFile = new byte[in.available()];
        //TODO check that this is 294 bytes and replace with a constant. in.available is not guaranteed to return a useful value
        in.read(readFromFile);
        keyc = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
        //ECB and CBC etc don't make sense for RSA, but the way this API is designed you have to specify something.
        KeyFactory kf = KeyFactory.getInstance("RSA");
        KeySpec ks = new X509EncodedKeySpec(readFromFile);
        RSAPublicKey key = (RSAPublicKey) kf.generatePublic(ks);
        keyc.init(Cipher.ENCRYPT_MODE, key);
        //byte[] encrpytedKey = keyc.doFinal(aeskey.getEncoded());
        FileOutputStream out = new FileOutputStream(dest);
        CipherOutputStream outcipher = new CipherOutputStream(out, keyc);
        outcipher.write(aeskey.getEncoded());
        outcipher.close();
        out.close();
    }

    private SecretKey generateAESKey() throws NoSuchAlgorithmException {
        KeyGenerator aeskeygen = KeyGenerator.getInstance("AES");
        SecretKey aeskey = aeskeygen.generateKey();
        return aeskey;
    }

    /*private String bytearrToString(byte[] arr) {
       StringBuilder sb = new StringBuilder();
       //byte[] arr = sk.getEncoded();
       sb.append('[');
       sb.append(Integer.toHexString(arr[0] < 0 ? arr[0]+256:arr[0]));
       for(int i = 1; i < arr.length; i++){
     sb.append(',');
     sb.append(Integer.toHexString(arr[i] < 0 ? arr[i]+256:arr[i]));
       }
       sb.append(']');
       return sb.toString();
    }*/

    protected boolean uploadFile(File fileToUpload) {
        String lineEnding = "\r\n";
        String twoHyphens = "--";
        String boundary = "*****";
        boolean success = true;
        HttpURLConnection connection = null;
        try {
            URL dest = new URL("http://mouflon.csc.ncsu.edu/cgi-bin/upload.cgi");
            connection = (HttpURLConnection) dest.openConnection();

            connection.setDoInput(true);
            connection.setDoOutput(true);
            connection.setUseCaches(false);
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Connection", "Keep-Alive");
            connection.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);
            connection.setChunkedStreamingMode(0);
            DataOutputStream out = new DataOutputStream(connection.getOutputStream());
            out.writeBytes(twoHyphens + boundary + lineEnding);
            //Log.i("uploadFile", fileToUpload.getName());
            out.writeBytes("Content-Disposition: form-data; name=\"uploadedfile\"; filename=\""
                    + fileToUpload.getName() + "\"" + lineEnding);
            out.writeBytes("Content-Type: application/octet-stream" + lineEnding);
            out.writeBytes("Content-Transfer-Encoding: base64" + lineEnding);
            out.writeBytes(lineEnding);
            encodeFileBase64(fileToUpload, out); //TODO this works fine for small files but for some file size between 256k and 2M it begins failing.

            out.writeBytes(lineEnding);
            out.writeBytes(twoHyphens + boundary + twoHyphens + lineEnding);
            //Log.i("UploadTest", "File uploaded");
            out.flush();
            out.close();

        } catch (Exception e) {
            e.printStackTrace();
            success = false;
        }
        try {
            BufferedInputStream in = new BufferedInputStream(connection.getInputStream());
            byte[] resp = new byte[80];
            int read = 0;
            if ((read = in.read(resp)) > 0) {
                String responseString = new String(resp, 0, read);
                //Log.i("uploadFile", responseString);
                if (!responseString.equals(fileToUpload.getName())) {
                    //Log.e("Upload", "File upload failed");
                }
            }
            //else 
            //Log.e("Upload", "No response received from server");

        } catch (Exception e) {
            e.printStackTrace();
            success = false;
        }
        return success;
    }

    private void encodeFileBase64(File file, DataOutputStream out) {
        byte[] buffer = new byte[4096];
        int bytesRead = 0;
        FileInputStream in = null;
        try {
            in = new FileInputStream(file);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
            return;
        }
        Base64OutputStream out64 = new Base64OutputStream(out, Base64.NO_CLOSE);
        try {
            while ((bytesRead = in.read(buffer)) > 0) { //this loop grabs more of the file and uploads it 4KB  at a time
                out64.write(buffer, 0, bytesRead);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out64.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (NullPointerException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public IBinder onBind(Intent intent) {

        return mBinder;
    }

}