/*
* Copyright 2009-2010 Marcel Zumstein, Oxinia GmbH
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.blync.client;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import javax.microedition.io.*;
import javax.microedition.io.file.*;
import org.blync.client.mail.MailMainScreen;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.engines.DESedeEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
/**
*
* @author Marcel
*/
public class DataAccess {
private static DataAccess instance;
private BufferedBlockCipher cipher = null;
private static final int KEY_LENGTH = 24;
private static final int BUFFER_SIZE = 2048;
private static int ENCRYPTED_BUFFER_SIZE = BUFFER_SIZE;
private static String baseDir;
private static String mailBaseDir;
private static String inboxDir;
private static String sentDir;
private static String draftsDir;
private static String attachmentDir;
private static String tempDir;
private static String contactsDir;
private static String appointmentDir;
private static String taskDir;
private LogScreen logger = LogScreen.getInstance();
private String userName;
private String password;
private boolean cryptoInitialized = false;
private boolean debug = true; // TODO: remove
// data types
public static final int BASE = 0;
public static final int ATTACHMENT = 1;
public static final int MAIL = 2;
// public static final int INBOX = 3;
// public static final int SENT = 4;
// public static final int DRAFTS = 5;
public static final int CONTACT = 6;
public static final int APPOINTMENT = 7;
public static final int TASK = 8;
public static final int TEMP = 9;
public static final String FILE_USER_NAME = "user";
public static final String FILE_LOGIN = "login";
private DataAccess() {
initDirectories();
}
/**
* Get the singleton instance
*/
public static DataAccess getInstance() {
if (instance == null) {
instance = new DataAccess();
}
return instance;
}
/*
public Image loadImage(String fileName) {
ByteArrayOutputStream oStream = new ByteArrayOutputStream();
loadData(ATTACHMENT, fileName, oStream);
byte[] bytes = oStream.toByteArray();
return Image.createImage(bytes, 0, bytes.length);
}
*/
/**
* Get the content of a file as text
*/
public String loadText(int type, String fileName) {
return loadText(type, fileName, true);
}
/**
* Get the content of a file as text
*/
public String loadText(int type, String fileName, boolean encrypted) {
ByteArrayOutputStream oStream = new ByteArrayOutputStream();
loadData(type, fileName, oStream, encrypted);
byte[] bytes = oStream.toByteArray();
return new String(bytes);
}
/**
* Save a string to a file
*/
public void saveText(int type, String fileName, String text) {
saveText(type, fileName, text, true);
}
/**
* Save a string to a file
*/
public void saveText(int type, String fileName, String text, boolean encrypted) {
InputStream iStream = new ByteArrayInputStream(text.getBytes());
saveData(type, fileName, iStream, encrypted);
}
/**
* Move a mail from one folder to another.
*/
/* public void moveMail(String folder, String fileName, String targetFolder) {
String mail = loadText(DataAccess.MAIL, folder + "/" + fileName, false);
if (mail.length() > 0) {
saveText(DataAccess.MAIL, targetFolder + "/" + fileName, mail, false);
deleteFile(MAIL, folder + "/" + fileName);
}
} */
/**
* Load data via an output stream
*/
public void loadData(int type, String fileName, OutputStream oStream) {
loadData(type, fileName, oStream, true);
}
/**
* Load data via an output stream
*/
public void loadData(int type, String fileName, OutputStream oStream, boolean encrypted) {
loadData(getDir(type) + fileName, oStream, encrypted);
}
private void loadData(String path, OutputStream oStream, boolean encrypted) {
try {
FileConnection fc = (FileConnection)
Connector.open("file:///" + path, Connector.READ);
if (fc.exists()) {
DataInputStream fis = fc.openDataInputStream();
byte[] encBuffer = new byte[ENCRYPTED_BUFFER_SIZE];
// int totalEnc = 0;
// int totalDec = 0;
int length;
while ((length = fis.read(encBuffer)) != -1) {
if (length < ENCRYPTED_BUFFER_SIZE) {
int extraLength = fis.read(encBuffer, length, ENCRYPTED_BUFFER_SIZE - length);
if (extraLength != -1) {
length += extraLength;
}
}
if (encrypted && !debug) {
CryptoResult decryptResult = performDecrypt(getKey(), encBuffer, length);
oStream.write(decryptResult.getData(), 0, decryptResult.getLength());
}
else {
oStream.write(encBuffer, 0, length);
}
// totalEnc += length;
// totalDec += decryptResult.getLength();
}
// System.out.println("Encyrpted length: " + totalEnc);
// System.out.println("Decyrpted length: " + totalDec);
fis.close();
fc.close();
}
}
catch (Exception e) {
log("loadData", e.toString());
}
}
/**
* Save data via an input stream
*/
public void saveData(int type, String fileName, InputStream iStream) {
saveData(type, fileName, iStream, true);
}
/**
* Save data via an input stream
*/
public void saveData(int type, String fileName, InputStream iStream, boolean encrypted) {
saveData(getDir(type) + fileName, iStream, encrypted);
}
private void saveData(String path, InputStream iStream, boolean encrypted) {
try {
initPath(path);
FileConnection fc = (FileConnection)
Connector.open("file:///" + path);
if (!fc.exists()) {
fc.create();
}
fc.truncate(0); // overwrite all
OutputStream fos = fc.openOutputStream();
int length;
byte[] buffer = new byte[BUFFER_SIZE];
while ((length = iStream.read(buffer, 0, BUFFER_SIZE)) != -1) {
if (encrypted && !debug) {
CryptoResult encryptResult = performEncrypt(getKey(), buffer, length);
fos.write(encryptResult.getData(), 0, encryptResult.getLength());
}
else {
fos.write(buffer, 0, length);
}
}
fos.close();
fc.close();
}
catch (IOException e) {
log("saveData", e.toString());
log("saveData", "file:///" + path);
}
}
public boolean createFolder(int type, String folderName) {
try {
initDirectory(getDir(type) + folderName);
return true;
}
catch (IOException e) {
log("createFolder", e.toString());
return false;
}
}
public boolean renameFolder(int type, String sourceName, String targetName) {
try {
FileConnection fc = (FileConnection) Connector.open("file:///" + getDir(type) + sourceName);
fc.rename(targetName);
fc.close();
return true;
}
catch (IOException e) {
log("renameFolder", e.toString());
return false;
}
}
/**
* Create an unencoded copy of a file in a temporary directory
*/
public void decodeFile(int type, String fileName) {
try {
initPath(getDir(TEMP) + fileName);
FileConnection codedFc = (FileConnection)
Connector.open("file:///" + getDir(type) + fileName, Connector.READ);
FileConnection decodedFc = (FileConnection)
Connector.open("file:///" + getDir(TEMP) + fileName);
if (!decodedFc.exists()) {
decodedFc.create();
}
DataInputStream fis = codedFc.openDataInputStream();
OutputStream fos = decodedFc.openOutputStream();
byte[] encBuffer = new byte[ENCRYPTED_BUFFER_SIZE];
int length;
while ((length = fis.read(encBuffer)) != -1) {
if (length < ENCRYPTED_BUFFER_SIZE) {
int extraLength = fis.read(encBuffer, length, ENCRYPTED_BUFFER_SIZE - length);
if (extraLength != -1) {
length += extraLength;
}
}
CryptoResult decryptResult = performDecrypt(getKey(), encBuffer, length);
fos.write(decryptResult.getData(), 0, decryptResult.getLength());
}
fis.close();
fos.close();
codedFc.close();
decodedFc.close();
}
catch (Exception e) {
log("decodeFile", e.toString());
}
}
/**
* Will delete an empty subdirectory; returns false if subdirectory is not empty.
*/
public boolean deleteFolder(int type, String directoryName) {
try {
FileConnection dir = (FileConnection)
Connector.open("file:///" + getDir(type) + directoryName);
if (dir.exists()) {
Enumeration enumDir = dir.list();
if (enumDir.hasMoreElements()) {
return false; // directory not emtpy
}
dir.delete();
}
}
catch (IOException e) {
log("deleteFolder", e.toString());
return false;
}
return true;
}
private void deleteDirectory(String directoryName, boolean recursive, boolean deleteRootFolder, boolean deleteSubFolders) {
try {
FileConnection dir = (FileConnection)
Connector.open("file:///" + directoryName);
if (dir.exists()) {
Enumeration enumDir = dir.list();
while (enumDir.hasMoreElements()) {
String fileName = (String)enumDir.nextElement();
FileConnection fc = (FileConnection)
Connector.open("file:///" + directoryName + fileName);
if (fc.isDirectory()) {
if (recursive) {
deleteDirectory(directoryName + fileName, true, deleteSubFolders, deleteSubFolders);
}
}
else {
fc.delete();
}
fc.close();
}
if (deleteRootFolder) {
dir.delete();
}
}
dir.close();
}
catch (IOException e) {
log("deleteDirectory", e.toString());
}
}
/**
* Delete a file from the file system
*/
public void deleteFile(int type, String fileName) {
try {
if (type == MAIL/*type == INBOX || type == SENT || type == DRAFTS*/) {
deleteDirectory(getDir(ATTACHMENT) + fileName + "/", true, true, true);
}
FileConnection fc = (FileConnection)
Connector.open("file:///" + getDir(type) + fileName);
if (fc.exists()) {
fc.delete();
}
fc.close();
}
catch (IOException e) {
log("deleteFile", e.toString());
}
}
/**
* Check whether a file exists on the file system
*/
public boolean fileExists(int type, String fileName) {
boolean exists = false;
try {
FileConnection fc = (FileConnection)
Connector.open("file:///" + getDir(type) + fileName);
exists = fc.exists();
fc.close();
}
catch (IOException e) {
log("fileExists", e.toString());
}
return exists;
}
/**
* Delete all content
*/
public void deleteAll() {
deleteDirectory(getDir(MAIL), true, false, false);
deleteDirectory(getDir(APPOINTMENT), false, false, false);
deleteDirectory(getDir(TASK), false, false, false);
deleteDirectory(getDir(CONTACT), false, false, false);
deleteDirectory(getDir(ATTACHMENT), true, false, true);
deleteDirectory(getDir(BASE), false, false, false);
deleteTempDir();
}
public void deleteTempDir() {
deleteDirectory(getDir(TEMP), true, false, true);
}
/**
* Make sure data directories exist
*/
private void initDirectories() {
String dataDir = Resources.get(Resources.DATA_DIRECTORY);
try {
/* FileConnection fc = (FileConnection) Connector.open("file:///" + dataDir);
if (!fc.exists()) {
fc.mkdir();
if (!fc.exists()) {
throw new Exception("Data directory " + dataDir + " not found");
}
} */
baseDir = dataDir + "SyncClient/";
mailBaseDir = baseDir + getDirName(MAIL);
inboxDir = mailBaseDir + MailMainScreen.INBOX_NAME + "/";
sentDir = mailBaseDir + MailMainScreen.SENT_NAME + "/";
draftsDir = mailBaseDir + MailMainScreen.DRAFTS_NAME + "/";
attachmentDir = baseDir + getDirName(ATTACHMENT);
tempDir = baseDir + getDirName(TEMP);
contactsDir = baseDir + getDirName(CONTACT);
appointmentDir = baseDir + getDirName(APPOINTMENT);
taskDir = baseDir + getDirName(TASK);
initDirectory(baseDir);
initDirectory(mailBaseDir);
initDirectory(inboxDir);
initDirectory(sentDir);
initDirectory(draftsDir);
initDirectory(attachmentDir);
initDirectory(appointmentDir);
initDirectory(taskDir);
initDirectory(contactsDir);
initDirectory(tempDir);
}
catch (IOException e) {
log("initDirectory", e.toString());
}
catch (Exception e) {
log("initDirectory", e.toString());
}
}
/**
* make sure directory exists
*/
private void initDirectory(String dirPath) throws IOException {
FileConnection fc = (FileConnection) Connector.open("file:///" + dirPath);
if (!fc.exists()) {
fc.mkdir();
}
fc.close();
}
/**
* make sure all directories in path exist
*/
private void initPath(String path) throws IOException {
int parsePos = 1;
int dirSeparator = path.indexOf('/', parsePos);
while (dirSeparator != -1) {
initDirectory(path.substring(0, dirSeparator + 1));
parsePos = dirSeparator + 1;
dirSeparator = path.indexOf('/', parsePos);
}
}
/**
* Get a list of all existing files of a specific type
*/
public Enumeration listDirectory(int type) {
Enumeration enumDir = null;
try {
FileConnection fc = (FileConnection)
Connector.open("file:///" + getDir(type));
if (fc.exists()) {
enumDir = fc.list();
}
}
catch (IOException e) {
log("loadList", e.toString());
}
catch (Exception e) {
log("loadList", e.toString());
}
return enumDir;
}
/**
* Get the subdirectory name for a specific type
*/
public static String getDirName(int type) {
switch (type) {
case MAIL:
return "Mails/";
case CONTACT:
return "Contacts/";
case APPOINTMENT:
return "Appointments/";
case TASK:
return "Tasks/";
case ATTACHMENT:
return "Attachments/";
case TEMP:
return "Temp/";
case BASE:
default:
return "";
}
}
/**
* Get the full path for a specific type
*/
private String getDir(int type) {
switch (type) {
case BASE:
return baseDir;
case MAIL:
return mailBaseDir;
/* case INBOX:
return inboxDir;
case SENT:
return sentDir;
case DRAFTS:
return draftsDir; */
case CONTACT:
return contactsDir;
case APPOINTMENT:
return appointmentDir;
case TASK:
return taskDir;
case ATTACHMENT:
return attachmentDir;
case TEMP:
return tempDir;
default:
return "";
}
}
/*
public boolean isDirectory(String fileName) {
boolean isDirectory = false;
try {
FileConnection fc = (FileConnection)
Connector.open("file:///" + attachmentDir + fileName, Connector.READ);
isDirectory = fc.isDirectory();
}
catch (IOException e) {
log("isDirectory", e.toString());
}
return isDirectory;
}
*/
public String getStoredUserName() {
return loadText(BASE, FILE_USER_NAME, false);
}
/**
* Checks the password for the user account
*/
public boolean isPasswordOk(String userName, String password) {
this.password = password;
String login = loadText(BASE, FILE_LOGIN);
boolean ok = login.equals("ok");
if (ok) {
setLogin(userName, password);
}
else {
this.password = "";
}
return ok;
}
public void setLogin(String userName, String password) {
this.userName = userName;
this.password = password;
saveText(BASE, FILE_USER_NAME, userName, false);
saveText(BASE, FILE_LOGIN, "ok", true);
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
private BufferedBlockCipher getCipher() {
if (cipher == null) {
BlockCipher engine = new DESedeEngine();
cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(engine));
String name = cipher.getUnderlyingCipher().getAlgorithmName();
// System.out.println("Using " + name);
}
return cipher;
}
private void initCrypto() {
if (!cryptoInitialized) {
cipher = getCipher();
cipher.init(true, new KeyParameter(getKey()));
ENCRYPTED_BUFFER_SIZE = cipher.getOutputSize(BUFFER_SIZE);
cryptoInitialized = true;
}
}
private final CryptoResult performEncrypt(byte[] key, byte[] plainText, int length)
{
initCrypto();
cipher = getCipher();
cipher.init(true, new KeyParameter(key));
byte[] encText = new byte[cipher.getOutputSize(length)];
int oLen = cipher.processBytes(plainText, 0, length, encText, 0);
try
{
oLen += cipher.doFinal(encText, oLen);
}
catch (CryptoException ce)
{
log("performEncrypt", ce.toString());
}
// p(encText, length);
return new CryptoResult(encText, oLen);
}
private final CryptoResult performDecrypt(byte[] key, byte[] cipherText, int length)
{
initCrypto();
cipher = getCipher();
cipher.init(false, new KeyParameter(key));
// System.out.println("Encrypted length: " + length);
byte[] plainText = new byte[cipher.getOutputSize(length)];
// System.out.println("Buffer length: " + plainText.length);
int oLen = cipher.processBytes(cipherText, 0, length, plainText, 0);
try {
oLen += cipher.doFinal(plainText, oLen);
}
catch (CryptoException ce) { /*
System.out.println("cipher");
System.out.println(new String(cipherText));
System.out.println("plain");
System.out.println(new String(plainText)); */
log("performDecrypt", ce.toString());
}
// System.out.println("Decrypted length: " + oLen);
return new CryptoResult(plainText, oLen);
}
private byte[] getKey() {
if (password == null || password.length() == 0) {
throw new java.lang.Error("No password");
}
byte[] pwBytes = password.getBytes();
byte[] key = new byte[KEY_LENGTH];
int pwLength = java.lang.Math.min(KEY_LENGTH, pwBytes.length);
for (int i = 0; i < pwLength; i++) {
key[i] = pwBytes[i];
}
for (int i = pwLength; i < KEY_LENGTH; i++) {
key[i] = 0;
}
return key;
}
private void log(String function, String entry) {
if (logger != null) {
logger.log(function, entry);
}
}
/**
* Helper class for decyrpting data
*/
private class CryptoResult {
private byte[] data;
private int length;
public CryptoResult(byte[] data, int length) {
this.data = data;
this.length = length;
}
public byte[] getData() {
return data;
}
public int getLength() {
return length;
}
}
}