Merge pull request #1039 from CristianAUnisa/export-to-html

Export vault to HTML
This commit is contained in:
Alexander Bakker 2022-12-04 23:01:25 +01:00 committed by GitHub
commit bebda569de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 146 additions and 2 deletions

View file

@ -122,6 +122,8 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
// intentional fallthrough
case CODE_EXPORT_PLAIN:
// intentional fallthrough
case CODE_EXPORT_HTML:
// intentional fallthrough
case CODE_EXPORT_GOOGLE_URI:
onExportResult(requestCode, resultCode, data);
break;
@ -361,6 +363,8 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
private static int getExportRequestCode(int spinnerPos, boolean encrypt) {
if (spinnerPos == 0) {
return encrypt ? CODE_EXPORT : CODE_EXPORT_PLAIN;
} else if (spinnerPos == 1) {
return CODE_EXPORT_HTML;
}
return CODE_EXPORT_GOOGLE_URI;
@ -370,13 +374,20 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
if (spinnerPos == 0) {
String filename = encrypt ? VaultRepository.FILENAME_PREFIX_EXPORT : VaultRepository.FILENAME_PREFIX_EXPORT_PLAIN;
return new VaultBackupManager.FileInfo(filename);
} else if (spinnerPos == 1) {
return new VaultBackupManager.FileInfo(VaultRepository.FILENAME_PREFIX_EXPORT_HTML, "html");
}
return new VaultBackupManager.FileInfo(VaultRepository.FILENAME_PREFIX_EXPORT_URI, "txt");
}
private static String getExportMimeType(int requestCode) {
return requestCode == CODE_EXPORT_GOOGLE_URI ? "text/plain" : "application/json";
if (requestCode == CODE_EXPORT_GOOGLE_URI) {
return "text/plain";
} else if (requestCode == CODE_EXPORT_HTML) {
return "text/html";
}
return "application/json";
}
private File getExportCacheDir() throws IOException {
@ -444,6 +455,10 @@ public class ImportExportPreferencesFragment extends PreferencesFragment {
cb.exportVault((stream) -> _vaultManager.getVault().exportGoogleUris(stream, filter));
_prefs.setIsPlaintextBackupWarningNeeded(true);
break;
case CODE_EXPORT_HTML:
cb.exportVault((stream) -> _vaultManager.getVault().exportHtml(stream, filter));
_prefs.setIsPlaintextBackupWarningNeeded(true);
break;
}
}

View file

@ -28,7 +28,8 @@ public abstract class PreferencesFragment extends PreferenceFragmentCompat {
public static final int CODE_EXPORT = 5;
public static final int CODE_EXPORT_PLAIN = 6;
public static final int CODE_EXPORT_GOOGLE_URI = 7;
public static final int CODE_BACKUPS = 8;
public static final int CODE_EXPORT_HTML = 8;
public static final int CODE_BACKUPS = 9;
private Intent _result;

View file

@ -1,17 +1,28 @@
package com.beemdevelopment.aegis.vault;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.util.Base64;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.AtomicFile;
import com.beemdevelopment.aegis.R;
import com.beemdevelopment.aegis.encoding.Base32;
import com.beemdevelopment.aegis.helpers.QrCodeHelper;
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
import com.beemdevelopment.aegis.otp.HotpInfo;
import com.beemdevelopment.aegis.otp.OtpInfo;
import com.beemdevelopment.aegis.util.IOUtils;
import com.google.common.html.HtmlEscapers;
import com.google.zxing.WriterException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
@ -21,6 +32,7 @@ import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.text.Collator;
import java.util.Collection;
import java.util.Objects;
import java.util.TreeSet;
import java.util.UUID;
@ -29,6 +41,7 @@ public class VaultRepository {
public static final String FILENAME_PREFIX_EXPORT = "aegis-export";
public static final String FILENAME_PREFIX_EXPORT_PLAIN = "aegis-export-plain";
public static final String FILENAME_PREFIX_EXPORT_URI = "aegis-export-uri";
public static final String FILENAME_PREFIX_EXPORT_HTML = "aegis-export-html";
@NonNull
private final Vault _vault;
@ -197,6 +210,92 @@ public class VaultRepository {
}
}
/**
* Exports the vault by serializing the list of entries to an HTML file containing the Issuer,
* Username and QR Code and writing it to the given OutputStream.
*/
public void exportHtml(OutputStream outStream, @Nullable Vault.EntryFilter filter) throws VaultRepositoryException {
try {
PrintStream printStream = new PrintStream(outStream, false, StandardCharsets.UTF_8.name());
printStream.print("<html><head><title>");
printStream.print(_context.getString(R.string.export_html_title));
printStream.print("</title></head><body>");
printStream.print("<h1>");
printStream.print(_context.getString(R.string.export_html_title));
printStream.print("</h1>");
printStream.print("<table>");
printStream.print("<tr>");
printStream.print("<th>Issuer</th>");
printStream.print("<th>Username</th>");
printStream.print("<th>Type</th>");
printStream.print("<th>QR Code</th>");
printStream.print("<th>UUID</th>");
printStream.print("<th>Note</th>");
printStream.print("<th>Favorite</th>");
printStream.print("<th>Algo</th>");
printStream.print("<th>Digits</th>");
printStream.print("<th>Secret</th>");
printStream.print("<th>Counter</th>");
printStream.print("</tr>");
for (VaultEntry entry : getEntries()) {
if (filter == null || filter.includeEntry(entry)) {
printStream.print("<tr>");
GoogleAuthInfo info = new GoogleAuthInfo(entry.getInfo(), entry.getName(), entry.getIssuer());
OtpInfo otpInfo = info.getOtpInfo();
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(info.getIssuer()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getName()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(otpInfo.getType()));
printStream.print("</td>");
Bitmap bm = QrCodeHelper.encodeToBitmap(info.getUri().toString(),256, 256, Color.WHITE);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] b = baos.toByteArray();
String encodedImage = Base64.encodeToString(b, Base64.DEFAULT);
printStream.print("<td class='qr'><img src=\"data:image/png;base64,");
printStream.print(encodedImage);
printStream.print("\"/></td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getUUID().toString()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.getNote()));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(entry.isFavorite() ? "true" : "false"));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(otpInfo.getAlgorithm(false)));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(Integer.toString(otpInfo.getDigits())));
printStream.print("</td>");
printStream.print("<td>");
printStream.print(HtmlEscapers.htmlEscaper().escape(Base32.encode(otpInfo.getSecret())));
printStream.print("</td>");
printStream.print("<td>");
if (Objects.equals(otpInfo.getTypeId(), HotpInfo.ID)) {
printStream.print(HtmlEscapers.htmlEscaper().escape(Long.toString(((HotpInfo) otpInfo).getCounter())));
} else {
printStream.print("-");
}
printStream.print("</td>");
printStream.print("</tr>");
}
};
printStream.print("</table></body>");
printStream.print("<style>table,td,th{border:1px solid #000;border-collapse:collapse;text-align:center}td:not(.qr),th{padding:1em}</style>");
printStream.print("</html>");
printStream.flush();
} catch (WriterException | IOException e) {
throw new VaultRepositoryException(e);
}
}
public void addEntry(VaultEntry entry) {
_vault.getEntries().add(entry);
}