diff --git a/app/src/main/java/protect/card_locker/ImportExportActivity.java b/app/src/main/java/protect/card_locker/ImportExportActivity.java index eb44bba87..40f565010 100644 --- a/app/src/main/java/protect/card_locker/ImportExportActivity.java +++ b/app/src/main/java/protect/card_locker/ImportExportActivity.java @@ -12,13 +12,14 @@ import android.text.InputType; import android.util.Log; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; +import android.widget.FrameLayout; import android.widget.Toast; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -47,6 +48,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity private String importAlertTitle; private String importAlertMessage; private DataFormat importDataFormat; + private String exportPassword; @Override protected void onCreate(Bundle savedInstanceState) @@ -88,7 +90,28 @@ public class ImportExportActivity extends CatimaAppCompatActivity @Override public void onClick(View v) { - chooseFileWithIntent(intentCreateDocumentAction, CHOOSE_EXPORT_LOCATION); + AlertDialog.Builder builder = new AlertDialog.Builder(ImportExportActivity.this); + builder.setTitle(R.string.exportPassword); + + FrameLayout container = new FrameLayout(ImportExportActivity.this); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + params.leftMargin = 50; + params.rightMargin = 50; + + final EditText input = new EditText(ImportExportActivity.this); + input.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + input.setLayoutParams(params); + input.setHint(R.string.exportPasswordHint); + + container.addView(input); + builder.setView(container); + builder.setPositiveButton(R.string.ok, (dialogInterface, i) -> { + exportPassword = input.getText().toString(); + chooseFileWithIntent(intentCreateDocumentAction, CHOOSE_EXPORT_LOCATION); + }); + builder.setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.cancel()); + builder.show(); + } }); @@ -211,7 +234,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity importExporter.execute(); } - private void startExport(final OutputStream target, final Uri targetUri, final boolean closeWhenDone) + private void startExport(final OutputStream target, final Uri targetUri,char[] password, final boolean closeWhenDone) { ImportExportTask.TaskCompleteListener listener = new ImportExportTask.TaskCompleteListener() { @@ -229,7 +252,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity }; importExporter = new ImportExportTask(ImportExportActivity.this, - DataFormat.Catima, target, listener); + DataFormat.Catima, target,password, listener); importExporter.execute(); } @@ -407,6 +430,7 @@ public class ImportExportActivity extends CatimaAppCompatActivity { if (requestCode == CHOOSE_EXPORT_LOCATION) { + OutputStream writer; if (uri.getScheme() != null) { @@ -416,9 +440,8 @@ public class ImportExportActivity extends CatimaAppCompatActivity { writer = new FileOutputStream(new File(uri.toString())); } - Log.e(TAG, "Starting file export with: " + uri.toString()); - startExport(writer, uri, true); + startExport(writer, uri,exportPassword.toCharArray(),true); } else { diff --git a/app/src/main/java/protect/card_locker/ImportExportTask.java b/app/src/main/java/protect/card_locker/ImportExportTask.java index 1538185c3..df2d26e0d 100644 --- a/app/src/main/java/protect/card_locker/ImportExportTask.java +++ b/app/src/main/java/protect/card_locker/ImportExportTask.java @@ -35,7 +35,7 @@ class ImportExportTask extends AsyncTask /** * Constructor which will setup a task for exporting to the given file */ - ImportExportTask(Activity activity, DataFormat format, OutputStream output, + ImportExportTask(Activity activity, DataFormat format, OutputStream output,char[] password, TaskCompleteListener listener) { super(); @@ -43,6 +43,7 @@ class ImportExportTask extends AsyncTask this.doImport = false; this.format = format; this.outputStream = output; + this.password = password; this.listener = listener; } @@ -70,14 +71,14 @@ class ImportExportTask extends AsyncTask return importResult; } - private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db) + private ImportExportResult performExport(Context context, OutputStream stream, DBHelper db,char[] password) { ImportExportResult result = ImportExportResult.GenericFailure; try { OutputStreamWriter writer = new OutputStreamWriter(stream, StandardCharsets.UTF_8); - result = MultiFormatExporter.exportData(context, db, stream, format); + result = MultiFormatExporter.exportData(context, db, stream, format,password); writer.close(); } catch (IOException e) @@ -118,7 +119,7 @@ class ImportExportTask extends AsyncTask } else { - result = performExport(activity.getApplicationContext(), outputStream, db); + result = performExport(activity.getApplicationContext(), outputStream, db,password); } return result; diff --git a/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java b/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java index 4afe49841..119443bba 100644 --- a/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CatimaExporter.java @@ -6,6 +6,7 @@ import android.graphics.Bitmap; import net.lingala.zip4j.io.outputstream.ZipOutputStream; import net.lingala.zip4j.model.ZipParameters; +import net.lingala.zip4j.model.enums.EncryptionMethod; import net.lingala.zip4j.util.InternalZipConstants; import org.apache.commons.csv.CSVFormat; @@ -30,14 +31,21 @@ import protect.card_locker.Utils; */ public class CatimaExporter implements Exporter { - public void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException + public void exportData(Context context, DBHelper db, OutputStream output,char[] password) throws IOException, InterruptedException { // Necessary vars int readLen; byte[] readBuffer = new byte[InternalZipConstants.BUFF_SIZE]; // Create zip output stream - ZipOutputStream zipOutputStream = new ZipOutputStream(output); + ZipOutputStream zipOutputStream; + + if(password!=null && password.length>0){ + zipOutputStream = new ZipOutputStream(output,password); + } + else{ + zipOutputStream = new ZipOutputStream(output); + } // Generate CSV ByteArrayOutputStream catimaOutputStream = new ByteArrayOutputStream(); @@ -45,8 +53,7 @@ public class CatimaExporter implements Exporter writeCSV(db, catimaOutputStreamWriter); // Add CSV to zip file - ZipParameters csvZipParameters = new ZipParameters(); - csvZipParameters.setFileNameInZip("catima.csv"); + ZipParameters csvZipParameters = createZipParameters("catima.csv",password); zipOutputStream.putNextEntry(csvZipParameters); InputStream csvInputStream = new ByteArrayInputStream(catimaOutputStream.toByteArray()); while ((readLen = csvInputStream.read(readBuffer)) != -1) { @@ -71,8 +78,7 @@ public class CatimaExporter implements Exporter // If it exists, add to the .zip file Bitmap image = Utils.retrieveCardImage(context, card.id, front); if (image != null) { - ZipParameters imageZipParameters = new ZipParameters(); - imageZipParameters.setFileNameInZip(Utils.getCardImageFileName(card.id, front)); + ZipParameters imageZipParameters = createZipParameters(Utils.getCardImageFileName(card.id, front),password); zipOutputStream.putNextEntry(imageZipParameters); InputStream imageInputStream = new ByteArrayInputStream(Utils.bitmapToByteArray(image)); while ((readLen = imageInputStream.read(readBuffer)) != -1) { @@ -86,6 +92,16 @@ public class CatimaExporter implements Exporter zipOutputStream.close(); } + private ZipParameters createZipParameters(String fileName, char[] password){ + ZipParameters zipParameters = new ZipParameters(); + zipParameters.setFileNameInZip(fileName); + if(password!=null && password.length>0){ + zipParameters.setEncryptFiles(true); + zipParameters.setEncryptionMethod(EncryptionMethod.AES); + } + return zipParameters; + } + private void writeCSV(DBHelper db, OutputStreamWriter output) throws IOException, InterruptedException { CSVPrinter printer = new CSVPrinter(output, CSVFormat.RFC4180); diff --git a/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java index ce1273373..0c42f5210 100644 --- a/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java +++ b/app/src/main/java/protect/card_locker/importexport/CatimaImporter.java @@ -48,7 +48,7 @@ public class CatimaImporter implements Importer bufferedInputStream.mark(100); // First, check if this is a zip file - ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream); + ZipInputStream zipInputStream = new ZipInputStream(bufferedInputStream,password); boolean isZipFile = false; diff --git a/app/src/main/java/protect/card_locker/importexport/Exporter.java b/app/src/main/java/protect/card_locker/importexport/Exporter.java index 6f70f6d80..ad4928030 100644 --- a/app/src/main/java/protect/card_locker/importexport/Exporter.java +++ b/app/src/main/java/protect/card_locker/importexport/Exporter.java @@ -17,5 +17,5 @@ public interface Exporter * Export the database to the output stream in a given format. * @throws IOException */ - void exportData(Context context, DBHelper db, OutputStream output) throws IOException, InterruptedException; + void exportData(Context context, DBHelper db, OutputStream output,char[] password) throws IOException, InterruptedException; } diff --git a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java index 8e95c49c4..b45cf1eda 100644 --- a/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java +++ b/app/src/main/java/protect/card_locker/importexport/MultiFormatExporter.java @@ -22,7 +22,7 @@ public class MultiFormatExporter * another ImportExportResult otherwise. If not Success, partial data may have been * written to the output stream, and it should be discarded. */ - public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format) + public static ImportExportResult exportData(Context context, DBHelper db, OutputStream output, DataFormat format,char[] password) { Exporter exporter = null; @@ -40,7 +40,7 @@ public class MultiFormatExporter { try { - exporter.exportData(context, db, output); + exporter.exportData(context, db, output,password); return ImportExportResult.Success; } catch(IOException e) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index aa689bccb..4e14f6b22 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -196,6 +196,8 @@ Yes No Please enter the password + Set a password to protect your export (optional) + Enter password Could not generate sharable URL. Please report this. Turn flashlight on Turn flashlight off @@ -243,4 +245,4 @@ Rate this app on Google Play Report Error - \ No newline at end of file + diff --git a/app/src/test/java/protect/card_locker/ImportExportTest.java b/app/src/test/java/protect/card_locker/ImportExportTest.java index 41374d496..230faef6f 100644 --- a/app/src/test/java/protect/card_locker/ImportExportTest.java +++ b/app/src/test/java/protect/card_locker/ImportExportTest.java @@ -334,7 +334,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -354,6 +354,39 @@ public class ImportExportTest TestHelpers.getEmptyDb(activity); } + public void multipleCardsExportImportPasswordProtected() throws IOException + { + final int NUM_CARDS = 10; + List passwords = Arrays.asList(null, "123456789".toCharArray()); + for(char[] password : passwords){ + addLoyaltyCards(NUM_CARDS); + + ByteArrayOutputStream outData = new ByteArrayOutputStream(); + OutputStreamWriter outStream = new OutputStreamWriter(outData); + + // Export data to CSV format + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,password); + assertEquals(ImportExportResult.Success, result); + outStream.close(); + + TestHelpers.getEmptyDb(activity); + + ByteArrayInputStream inData = new ByteArrayInputStream(outData.toByteArray()); + + // Import the CSV data + result = MultiFormatImporter.importData(activity.getApplicationContext(), db, inData, DataFormat.Catima, password); + assertEquals(ImportExportResult.Success, result); + + assertEquals(NUM_CARDS, db.getLoyaltyCardCount()); + + checkLoyaltyCards(); + + // Clear the database for the next format under test + TestHelpers.getEmptyDb(activity); + } + + } + @Test public void multipleCardsExportImportSomeStarred() throws IOException { @@ -365,7 +398,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -438,7 +471,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -482,7 +515,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export into CSV data - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null); assertEquals(ImportExportResult.Success, result); outStream.close(); @@ -513,7 +546,7 @@ public class ImportExportTest OutputStreamWriter outStream = new OutputStreamWriter(outData); // Export data to CSV format - ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima); + ImportExportResult result = MultiFormatExporter.exportData(activity.getApplicationContext(), db, outData, DataFormat.Catima,null); assertEquals(ImportExportResult.Success, result); TestHelpers.getEmptyDb(activity); @@ -560,8 +593,9 @@ public class ImportExportTest TestTaskCompleteListener listener = new TestTaskCompleteListener(); // Export to the file + final String password = "123456789"; FileOutputStream fileOutputStream = new FileOutputStream(exportFile); - ImportExportTask task = new ImportExportTask(activity, DataFormat.Catima, fileOutputStream, listener); + ImportExportTask task = new ImportExportTask(activity, DataFormat.Catima, fileOutputStream,password.toCharArray(), listener); task.execute(); // Actually run the task to completion @@ -579,7 +613,7 @@ public class ImportExportTest FileInputStream fileStream = new FileInputStream(exportFile); - task = new ImportExportTask(activity, DataFormat.Catima, fileStream, null, listener); + task = new ImportExportTask(activity, DataFormat.Catima, fileStream, password.toCharArray(), listener); task.execute(); // Actually run the task to completion @@ -901,7 +935,7 @@ public class ImportExportTest // Export everything ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStream, DataFormat.Catima); + MultiFormatExporter.exportData(activity.getApplicationContext(), db, outputStream, DataFormat.Catima,null); // Wipe database TestHelpers.getEmptyDb(activity);