Bladeren bron

Merge branch 'us45-2-extendedlist-cli' into 'develop'

US45.2: Extendedlist (CLI)

See merge request tobias.wach/ccats!87
Sander, Paul 4 jaren geleden
bovenliggende
commit
888cc54ab5

+ 51 - 1
Client-Server Protocol.md

@@ -274,7 +274,7 @@ If `cancel` is `true` then the download of the file is canceled and an error mes
 
 ### 2.5 Head command
 
-The `head` command requests the first 4 bytes of `file` from the server for further inspection by the client.
+The `head` command requests the first 32 bytes of `file` from the server for further inspection by the client. If the file is smaller than 32 bytes, the first 4 bytes are sent instead. If the file is even too small for this, `data` stays empty, and an error is sent.
 
 Client:
 ```
@@ -442,6 +442,56 @@ Server:
 	"error": string
 }
 ```
+### 2.12 Extendedlist command
+Extendedlist is split in two commands. `extendedlist` to request a list download and `extendedlistdata` to download the list.
+
+#### 2.12.1 extendedlist - request file list download
+Client:
+```
+{
+	"command": "extendedlist"
+}
+```
+Server:
+```
+{
+	"command": "extendedlist",
+	"accept": bool,
+	"items": int,
+	"chunks": int,
+	"error": string
+}
+```
+`chunks` is the number of chunks that will be sent.
+
+#### 2.12.2 extendedlistdata - download file list
+Client:
+```
+{
+	"command": "extendedlistdata",
+	"chunk": int,
+	"cancel": bool
+}
+```
+Here `chunk` is the number of remaining chunks. (Thus, when requesting the first chunk the number of chunks - 1 is expected.)
+<br /><br />
+Server:
+```
+{
+	"command": "extendedlistdata",
+	"remaining": int,
+	"cancel": bool,
+	"files": {
+        "name": string,
+        "head": string,
+        "size": float
+    }[],
+	"error": string
+}
+```
+The client has to request every chunk until all names have been sent and `remaining` hits `0`. <br />
+The list contains the name of a file and the `head` as specified by the `data` field of the head command (section 2.5), and its `size` in kByte.
+
 
 ## 3. Close connection
 

+ 24 - 1
GUI-CLI Protocol.md

@@ -308,7 +308,7 @@ Server:
 }
 ```
 
-### 2.11 Dequeue command
+### 2.10 Dequeue command
 
 To remove a file from the queue for sending with the covert channel, the use the `dequeue` command.
 
@@ -327,6 +327,29 @@ Server:
 }
 ```
 
+### 2.11 Extendedlist command
+
+GUI:
+```
+write: "extendedlist"
+```
+CLI:
+```
+{
+	"command": "extendedlist",
+	"accept": bool,
+	"files": {
+        "name": string,
+        "encrypted": string,
+        "size": float
+    }[],
+	"error": string
+}
+```
+
+The list contains the `name` of a file and its `size` in kByte. <br />
+The `encrypted` field contains information wether the file is `unencrypted`, `decryptable` or `undecryptable`.
+
 ## 3. Disconnecting and exiting
 
 ### 3.1. Disconnect

+ 1 - 0
cli/include/batchioman.h

@@ -61,6 +61,7 @@ private:
 	std::string printPut(Json::Value root);
 	std::string printGet(Json::Value root);
 	std::string printList(Json::Value root);
+	std::string printExtendedlist(Json::Value root);
 	std::string printVersion(Json::Value root);
 	std::string printLogin(Json::Value root);
 	std::string printSignup(Json::Value root);

+ 6 - 1
cli/include/cmdman.h

@@ -113,8 +113,10 @@ private:
 	CmdRet cmdPut(vector<string> args);
 	const string descGet = "retrieve file from server";
 	CmdRet cmdGet(vector<string> args);
-	const string descList = "list files available on server";
+	const string descList = "list names of files available on server";
 	CmdRet cmdList(vector<string> args);
+	const string descExtendedlist = "list files available on server with further information";
+	CmdRet cmdExtendedlist(vector<string> args);
 	const string descHead = "request the first four bytes of a file from the server";
 	CmdRet cmdHead(vector<string> args);
 	const string descDeletefile = "delete a file from the server";
@@ -150,6 +152,7 @@ private:
 	CmdRet cmdPutdata(vector<string> args);
 	CmdRet cmdGetdata(vector<string> args);
 	CmdRet cmdListdata(vector<string> args);
+	CmdRet cmdExtendedlistdata(vector<string> args);
 
 	/**
 	 * Method prototypes for handling json responses
@@ -161,9 +164,11 @@ private:
 	CmdRet handlePut(Json::Value);
 	CmdRet handleGet(Json::Value);
 	CmdRet handleList(Json::Value);
+	CmdRet handleExtendedlist(Json::Value);
 	CmdRet handlePutdata(Json::Value);
 	CmdRet handleGetdata(Json::Value);
 	CmdRet handleListdata(Json::Value);
+	CmdRet handleExtendedlistdata(Json::Value);
 	CmdRet handleVersion(Json::Value);
 	CmdRet handleLogin(Json::Value);
 	CmdRet handleSignup(Json::Value);

+ 15 - 5
cli/include/fileman.h

@@ -2,6 +2,7 @@
 #define FILEMAN_H
 
 #include <fstream>
+#include <json/json.h>
 #include <openssl/conf.h>
 #include <openssl/err.h>
 #include <openssl/evp.h>
@@ -28,7 +29,7 @@ private:
 	 */
 	std::vector<std::vector<char>> putdata;
 	std::fstream getfile;
-	std::vector<std::string> listdata;
+	std::vector<Json::Value> listdata;
 	std::string getpath, getname, putpath, putname;
 	const unsigned int max_read_len = 4096;
 	int putsize;
@@ -46,6 +47,8 @@ private:
 	bool cryptoreadyd;
 	bool pendingerr;
 
+	bool extendedListing;
+
 	unsigned char iv[12];  // 96bits
 	unsigned char tag[16]; // 128bits
 	unsigned char key[32]; // 256bits
@@ -65,6 +68,9 @@ private:
 	void writeEnc(const std::vector<char> data);
 	const char signature[4] = {'C', 'C', 'A', 'T'};
 
+	enum decryptability { plaintext, decryptable, undecryptable };
+	decryptability isDecryptable(const std::vector<char> data);
+
 public:
 	/**
 	 * Constructor and Destructor
@@ -75,11 +81,15 @@ public:
 	/**
 	 * Query internal state
 	 *
-	 * Return true if the corresponding action is being performed, false otherwise
+	 * Return true if the corresponding action is being performed, false otherwise.
+	 * isListing returns true if either a list from the "list" or "extendedlist" is built.
+	 * isListingExtended corresponds to the "extendedlist" command, isListingSimple to "list".
 	 */
 	virtual bool isGetting();
 	virtual bool isPutting();
 	virtual bool isListing();
+	virtual bool isListingSimple();
+	virtual bool isListingExtended();
 	virtual bool isEncrypted();
 
 	/**
@@ -89,7 +99,7 @@ public:
 	 */
 	virtual bool openPut(const std::string &path);
 	virtual bool openGet(const std::string &path);
-	virtual bool openList();
+	virtual bool openList(bool extended);
 
 	/**
 	 * Open file and read a hex string from it as key and initialize the IV
@@ -147,8 +157,8 @@ public:
 	/**
 	 * read and write emulating methods for list
 	 */
-	virtual void putListData(std::vector<std::string> names);
-	virtual std::vector<std::string> getListData();
+	virtual void putListData(std::vector<Json::Value> names);
+	virtual std::vector<Json::Value> getListData();
 
 	/**
 	 * Query internal state, requesting the corresponding size

+ 1 - 0
cli/include/userioman.h

@@ -44,6 +44,7 @@ private:
 	void printPut(Json::Value root);
 	void printGet(Json::Value root);
 	void printList(Json::Value root);
+	void printExtendedlist(Json::Value root);
 	void printVersion(Json::Value root);
 	void printLogin(Json::Value root);
 	void printSignup(Json::Value root);

+ 42 - 0
cli/src/batchioman.cpp

@@ -20,6 +20,7 @@ BatchIoMan::BatchIoMan(bool usessl, bool beverbose, std::string batchpath) : IoM
 	printmap["put"] = &BatchIoMan::printPut;
 	printmap["get"] = &BatchIoMan::printGet;
 	printmap["list"] = &BatchIoMan::printList;
+	printmap["extendedlist"] = &BatchIoMan::printExtendedlist;
 	printmap["version"] = &BatchIoMan::printVersion;
 	printmap["login"] = &BatchIoMan::printLogin;
 	printmap["signup"] = &BatchIoMan::printSignup;
@@ -418,6 +419,47 @@ std::string BatchIoMan::printList(Json::Value root) {
 	return ret;
 }
 
+std::string BatchIoMan::printExtendedlist(Json::Value root) {
+	std::string ret;
+	if (!root["accept"].asBool()) {
+		ret = "Listing files failed: " + root["error"].asString();
+	} else {
+		if (!root["files"].empty()) {
+			ret = "Files stored on server: \n";
+			/*
+			 * EXAMPLE:
+			 * size (kBytes)  decryptable  file
+			 *       9000.01  yes          foo.txt
+			 *         42.00  no           baz.html
+			 *          0.02  plaintext    bar.zip
+			 */
+			ret += "size (kBytes)  decryptable  file\n";
+			for (Json::Value val : root["files"]) {
+				float size = val["size"].asFloat();
+				std::string encrypted = val["encrypted"].asString();
+				std::string decryptable;
+				if (encrypted == "decryptable") {
+					decryptable = "yes        ";
+				} else if (encrypted == "undecryptable") {
+					decryptable = "no         ";
+				} else {
+					decryptable = "plaintext  ";
+				}
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string file = val["name"].asString();
+
+				char sizeString[44];
+				std::snprintf(sizeString, 44, "%13.2f  ", size);
+
+				ret += std::string(sizeString) + decryptable + file + "\n";
+			}
+		} else {
+			std::cout << "No files stored on server." << std::endl;
+		}
+	}
+	return ret;
+}
+
 std::string BatchIoMan::printVersion(Json::Value root) {
 	if (!root["accept"].asBool()) {
 		return std::string("Version check failed. Server reports ") + root["serverversion"].asString() + " but client is " + root["clientversion"].asString();

+ 131 - 8
cli/src/cmdman.cpp

@@ -24,12 +24,14 @@ CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
 	execmap["put"] = &CmdMan::cmdPut;
 	execmap["get"] = &CmdMan::cmdGet;
 	execmap["list"] = &CmdMan::cmdList;
+	execmap["extendedlist"] = &CmdMan::cmdExtendedlist;
 	execmap["version"] = &CmdMan::cmdVersion;
 	execmap["login"] = &CmdMan::cmdLogin;
 	execmap["signup"] = &CmdMan::cmdSignup;
 	execmap["putdata"] = &CmdMan::cmdPutdata;
 	execmap["getdata"] = &CmdMan::cmdGetdata;
 	execmap["listdata"] = &CmdMan::cmdListdata;
+	execmap["extendedlistdata"] = &CmdMan::cmdExtendedlistdata;
 	execmap["head"] = &CmdMan::cmdHead;
 	execmap["deletefile"] = &CmdMan::cmdDeletefile;
 	execmap["deleteme"] = &CmdMan::cmdDeleteme;
@@ -49,6 +51,7 @@ CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
 	helpmap["put"] = descPut;
 	helpmap["get"] = descGet;
 	helpmap["list"] = descList;
+	helpmap["extendedlist"] = descExtendedlist;
 	helpmap["head"] = descHead;
 	helpmap["login"] = descLogin;
 	helpmap["signup"] = descSignup;
@@ -71,10 +74,12 @@ CmdMan::CmdMan(FileMan &fm, void (*dpf)(string)) : fileman(fm) {
 	handlemap["putdata"] = &CmdMan::handlePutdata;
 	handlemap["getdata"] = &CmdMan::handleGetdata;
 	handlemap["list"] = &CmdMan::handleList;
+	handlemap["extendedlist"] = &CmdMan::handleExtendedlist;
 	handlemap["version"] = &CmdMan::handleVersion;
 	handlemap["login"] = &CmdMan::handleLogin;
 	handlemap["signup"] = &CmdMan::handleSignup;
 	handlemap["listdata"] = &CmdMan::handleListdata;
+	handlemap["extendedlistdata"] = &CmdMan::handleExtendedlistdata;
 	handlemap["head"] = &CmdMan::handleHead;
 	handlemap["deletefile"] = &CmdMan::handleDeletefile;
 	handlemap["deleteme"] = &CmdMan::handleDeleteme;
@@ -268,7 +273,7 @@ CmdMan::CmdRet CmdMan::cmdList(vector<string> args) {
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
 
-	bool opened = fileman.openList();
+	bool opened = fileman.openList(false);
 	root["command"] = "list";
 	if (opened) {
 		retval.type = send;
@@ -287,7 +292,7 @@ CmdMan::CmdRet CmdMan::cmdListdata(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
 	Json::Value root;
-	if (!fileman.isListing()) {
+	if (!fileman.isListingSimple()) {
 		root["command"] = "list";
 		root["error"] = "Client cannot handle input (received command \"listdata\").";
 		retval.type = error;
@@ -302,6 +307,45 @@ CmdMan::CmdRet CmdMan::cmdListdata(vector<string> args) {
 	return retval;
 }
 
+CmdMan::CmdRet CmdMan::cmdExtendedlist(vector<string> args) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	Json::Value root;
+
+	bool opened = fileman.openList(true);
+	root["command"] = "extendedlist";
+	if (opened) {
+		retval.type = send;
+	} else {
+		retval.type = error;
+		root["accept"] = false;
+		root["names"] = "";
+		root["error"] = "cannot list, already listing";
+	}
+
+	retval.msg = root;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdExtendedlistdata(vector<string> args) {
+	CmdRet retval;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	Json::Value root;
+	if (!fileman.isListingExtended()) {
+		root["command"] = "extendedlist";
+		root["error"] = "Client cannot handle input (received command \"listdata\").";
+		retval.type = error;
+	} else {
+		root["command"] = "extendedlistdata";
+		root["chunk"] = fileman.getListRemainingChunks();
+		root["cancel"] = false;
+		retval.type = send;
+	}
+
+	retval.msg = root;
+	return retval;
+}
+
 CmdMan::CmdRet CmdMan::cmdHead(vector<string> args) {
 	CmdRet retval;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
@@ -868,7 +912,7 @@ CmdMan::CmdRet CmdMan::handleList(Json::Value root) {
 		output["accept"] = false;
 		output["error"] = "There are no files stored on the server.";
 		fileman.closeList();
-	} else if (!fileman.isListing()) {
+	} else if (!fileman.isListingSimple()) {
 		retval.type = error;
 		output["accept"] = false;
 		output["error"] = "Server responds to list message which was never sent.";
@@ -887,7 +931,7 @@ CmdMan::CmdRet CmdMan::handleListdata(Json::Value root) {
 	CmdRet retval;
 	Json::Value output, arr;
 	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
-	vector<string> toadd;
+	vector<Json::Value> toadd;
 
 	output["command"] = "list";
 	output["names"] = "";
@@ -897,7 +941,7 @@ CmdMan::CmdRet CmdMan::handleListdata(Json::Value root) {
 		retval.type = error;
 		output["error"] = "Server reports: " + root["error"].asString();
 		fileman.cancelList();
-	} else if (!fileman.isListing()) {
+	} else if (!fileman.isListingSimple()) {
 		retval.type = error;
 		output["error"] = "Server responds to list message which was never sent.";
 	} else if (root["remaining"].asInt() != fileman.getListRemainingChunks()) {
@@ -910,14 +954,14 @@ CmdMan::CmdRet CmdMan::handleListdata(Json::Value root) {
 	} else {
 		output["accept"] = true;
 		for (Json::Value i : root["names"])
-			toadd.push_back(i.asString());
+			toadd.push_back(i);
 		fileman.putListData(toadd);
 		// loaded successfully
 		if (root["remaining"] <= 0) {
 			// everything sent
 			retval.type = print;
-			for (string s : fileman.getListData())
-				arr.append(s);
+			for (Json::Value s : fileman.getListData())
+				arr.append(s.asString());
 			output["names"] = arr;
 			fileman.closeList();
 		} else {
@@ -929,6 +973,85 @@ CmdMan::CmdRet CmdMan::handleListdata(Json::Value root) {
 	return retval;
 }
 
+CmdMan::CmdRet CmdMan::handleExtendedlist(Json::Value root) {
+	CmdRet retval;
+	Json::Value output, files; // LOCALOUTPUT
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+
+	output["command"] = "extendedlist";
+	output["files"] = files;
+
+	if (!root["accept"].asBool()) {
+		retval.type = error;
+		output["accept"] = false;
+		output["error"] = "Server reports: " + root["error"].asString();
+		fileman.cancelList();
+	} else if (root["items"].asInt() == 0) {
+		retval.type = print;
+		output["accept"] = false;
+		output["error"] = "There are no files stored on the server.";
+		fileman.closeList();
+	} else if (!fileman.isListingExtended()) {
+		retval.type = error;
+		output["accept"] = false;
+		output["error"] = "Server responds to list message which was never sent.";
+	} else {
+		fileman.setListChunks(root["chunks"].asInt());
+		retval.type = send;
+		output["accept"] = true;
+		retval.nextcommand = "extendedlistdata";
+	}
+
+	retval.msg = output;
+	return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleExtendedlistdata(Json::Value root) {
+	CmdRet retval;
+	Json::Value output, arr, files;
+	DEBUGPRINT(string(__PRETTY_FUNCTION__) + " begin");
+	vector<Json::Value> toadd;
+
+	output["command"] = "extendedlist";
+	output["files"] = files;
+	output["accept"] = false;
+
+	if (root["cancel"].asBool()) {
+		retval.type = error;
+		output["error"] = "Server reports: " + root["error"].asString();
+		fileman.cancelList();
+	} else if (!fileman.isListingExtended()) {
+		retval.type = error;
+		output["error"] = "Server responds to list message which was never sent.";
+	} else if (root["remaining"].asInt() != fileman.getListRemainingChunks()) {
+		// the passed number of recieved chunks should equal the number of sent chunks
+		retval.type = error;
+		output["error"] = std::string("Server reports number of "
+		                              "remaining chunks as ") +
+		                  std::to_string(root["remaining"].asInt()) + " but actual number is " + std::to_string(fileman.getListRemainingChunks());
+		fileman.cancelList();
+	} else {
+		output["accept"] = true;
+		for (Json::Value i : root["files"])
+			toadd.push_back(i);
+		fileman.putListData(toadd);
+		// loaded successfully
+		if (root["remaining"] <= 0) {
+			// everything sent
+			retval.type = print;
+			for (Json::Value s : fileman.getListData())
+				arr.append(s);
+			output["files"] = arr;
+			fileman.closeList();
+		} else {
+			retval.type = send;
+			retval.nextcommand = "extendedlistdata";
+		}
+	}
+	retval.msg = output;
+	return retval;
+}
+
 CmdMan::CmdRet CmdMan::handleVersion(Json::Value root) {
 	CmdRet retval;
 	Json::Value output; // LOCALOUTPUT

+ 73 - 11
cli/src/fileman.cpp

@@ -31,6 +31,8 @@ bool FileMan::isGetting() { return getfile.is_open(); }
 bool FileMan::isPutting() { return isputting; }
 
 bool FileMan::isListing() { return islisting; }
+bool FileMan::isListingSimple() { return islisting && !extendedListing; }
+bool FileMan::isListingExtended() { return islisting && extendedListing; }
 
 bool FileMan::isEncrypted() { return keyenabled; }
 
@@ -145,29 +147,30 @@ bool FileMan::openGet(const string &path) {
 	return true;
 }
 
-bool FileMan::openList() {
+bool FileMan::openList(bool extended) {
 	if (isListing()) {
 		return false;
 	}
-	listdata = vector<string>();
+	listdata = vector<Json::Value>();
+	extendedListing = extended;
 	islisting = true;
 	return true;
 }
 
 bool FileMan::openKey(const string &path) {
 	std::ifstream keyfile;
-	std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+	//~ std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
 	if (isPutting() || isGetting())
 		return false; // do not enable key mid-operation
 	if (keyenabled)
 		closeKey();
 	keyfile.open(path, std::ios::ate | std::ios::binary);
-	std::cerr << __PRETTY_FUNCTION__ << " open keyfile" << std::endl;
+	//~ std::cerr << __PRETTY_FUNCTION__ << " open keyfile" << std::endl;
 	if (keyfile.is_open()) {
-		std::cerr << __PRETTY_FUNCTION__ << " keyfile open " << keyfile.tellg() << " " << sizeof(key) << std::endl;
+		//~ std::cerr << __PRETTY_FUNCTION__ << " keyfile open " << keyfile.tellg() << " " << sizeof(key) << std::endl;
 		if (keyfile.tellg() == sizeof(key)) {
 			keyfile.seekg(0);
-			std::cerr << "keyfile is at " << keyfile.tellg() << std::endl;
+			//~ std::cerr << "keyfile is at " << keyfile.tellg() << std::endl;
 			keyfile.read((char *)key, sizeof(key));
 			keyfile.close();
 			keyenabled = true;
@@ -242,12 +245,34 @@ void FileMan::writeBase64(string data) {
 	keyenabled ? writeEnc(decode) : writeGet(decode);
 }
 
-void FileMan::putListData(vector<string> names) {
-	listdata.insert(listdata.end(), names.begin(), names.end());
+void FileMan::putListData(vector<Json::Value> files) {
+	if (!extendedListing) {
+		listdata.insert(listdata.end(), files.begin(), files.end());
+	} else {
+		for (Json::Value file : files) {
+
+			Json::Value fileInfo;
+			fileInfo["name"] = file["name"];
+			fileInfo["size"] = file["size"];
+
+			string headString = file["head"].asString();
+			std::cerr << "checking decryptability of file " << file["name"].asString() << std::endl;
+			decryptability decr = isDecryptable(base64::decodeVector(headString));
+			if (decr == FileMan::undecryptable) {
+				fileInfo["encrypted"] = "undecryptable";
+			} else if (decr == FileMan::decryptable) {
+				fileInfo["encrypted"] = "decryptable";
+			} else {
+				fileInfo["encrypted"] = "unencrypted";
+			}
+
+			listdata.push_back(fileInfo);
+		}
+	}
 	listchunksRemaining--;
 }
 
-vector<string> FileMan::getListData() { return listdata; }
+vector<Json::Value> FileMan::getListData() { return listdata; }
 
 int FileMan::getGetChunks() { return getchunks; }
 
@@ -371,7 +396,7 @@ void FileMan::deinitCryptoD() {
 }
 
 void FileMan::writeEnc(const vector<char> data) {
-	std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+	//~ std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
 	writeGet(data);
 	if (getchunksRemaining < 0) {
 		// loaded everything, try to decrypt
@@ -385,7 +410,6 @@ void FileMan::writeEnc(const vector<char> data) {
 			return;
 		}
 		size -= (sizeof(iv) + sizeof(tag));
-		std::cerr << __PRETTY_FUNCTION__ << " size is " << size << std::endl;
 		cipher = new unsigned char[size];
 		plain = new unsigned char[size];
 		getfile.seekg(0);
@@ -428,3 +452,41 @@ void FileMan::writeEnc(const vector<char> data) {
 		delete[] plain;
 	}
 }
+
+FileMan::decryptability FileMan::isDecryptable(const vector<char> data) {
+	unsigned char plain[sizeof(signature)] = {0}, *cipher;
+	int plainlen;
+	// check if signature matches in plaintext
+	if (memcmp(data.data(), signature, sizeof(signature))) {
+		// it does not
+		if (data.size() >= (sizeof(signature) + sizeof(iv) + sizeof(tag)) && keyenabled) {
+			// enough data, key is enabled
+			memcpy(iv, data.data(), sizeof(iv));
+			memcpy(tag, data.data() + sizeof(iv), sizeof(tag));
+			cipher = (unsigned char *)(data.data() + sizeof(iv) + sizeof(tag));
+
+			if (initCryptoD()) {
+				// crypto is ready
+				if (!EVP_DecryptUpdate(cryptctxd, plain, &plainlen, cipher, sizeof(signature))) {
+					setOpensslError();
+					std::cerr << __PRETTY_FUNCTION__ << " failed to update " << getOpensslError() << std::endl;
+				}
+
+				deinitCryptoD();
+
+				// check again if signature matches
+				if (memcmp(plain, signature, sizeof(signature))) {
+					// still no dice
+					return undecryptable;
+				}
+
+				// yup, got good data
+				return decryptable;
+			}
+		}
+		// no key enabled or decryption failed, assume undecryptable
+		return undecryptable;
+	}
+	// it does, assume plaintext
+	return plaintext;
+}

+ 40 - 0
cli/src/userioman.cpp

@@ -21,6 +21,7 @@ UserIoMan::UserIoMan(bool usessl, bool beverbose) : IoMan(usessl) {
 	printmap["put"] = &UserIoMan::printPut;
 	printmap["get"] = &UserIoMan::printGet;
 	printmap["list"] = &UserIoMan::printList;
+	printmap["extendedlist"] = &UserIoMan::printExtendedlist;
 	printmap["version"] = &UserIoMan::printVersion;
 	printmap["login"] = &UserIoMan::printLogin;
 	printmap["signup"] = &UserIoMan::printSignup;
@@ -212,6 +213,45 @@ void UserIoMan::printList(Json::Value root) {
 	}
 }
 
+void UserIoMan::printExtendedlist(Json::Value root) {
+	if (!root["accept"].asBool()) {
+		std::cout << "Listing files failed: " << root["error"].asString() << std::endl;
+	} else {
+		if (!root["files"].empty()) {
+			std::cout << "Files stored on server: " << std::endl;
+			/*
+			 * EXAMPLE:
+			 * size (kBytes)  decryptable  file
+			 *       9000.01  yes          foo.txt
+			 *         42.00  no           baz.html
+			 *          0.02  plaintext    bar.zip
+			 */
+			std::cout << "size (kBytes)  decryptable  file" << std::endl;
+			for (Json::Value val : root["files"]) {
+				float size = val["size"].asFloat();
+				std::string encrypted = val["encrypted"].asString();
+				std::string decryptable;
+				if (encrypted == "decryptable") {
+					decryptable = "\033[32myes\033[0m        "; // "yes" in green
+				} else if (encrypted == "undecryptable") {
+					decryptable = "\033[31mno\033[0m         "; // "no" in red
+				} else {
+					decryptable = "plaintext  ";
+				}
+				std::string progress = std::to_string(val["progress"].asInt());
+				std::string file = val["name"].asString();
+
+				char sizeString[44];
+				std::snprintf(sizeString, 44, "%13.2f  ", size);
+
+				std::cout << sizeString << decryptable << file << std::endl;
+			}
+		} else {
+			std::cout << "No files stored on server." << std::endl;
+		}
+	}
+}
+
 void UserIoMan::printVersion(Json::Value root) {
 	if (!root["accept"].asBool()) {
 		std::cout << "Version check failed. Server reports " << root["serverversion"].asString() << " but client is " << root["clientversion"].asString()

+ 374 - 19
cli/test/cmdman_test.cpp

@@ -1657,7 +1657,7 @@ TEST(testList, Positive) {
 	root["items"] = 92;
 	root["chunks"] = 2;
 
-	EXPECT_CALL(fm, openList).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, openList(false)).WillOnce(testing::Return(true));
 
 	EXPECT_CALL(fm, cancelList).Times(0); // not called
 
@@ -1667,7 +1667,7 @@ TEST(testList, Positive) {
 	EXPECT_EQ(retvalCmd.type, CmdMan::send);
 	EXPECT_EQ(retvalCmd.msg["command"].asString(), "list");
 
-	EXPECT_CALL(fm, isListing()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, isListingSimple()).WillOnce(testing::Return(true));
 	EXPECT_CALL(fm, setListChunks(2));
 
 	retvalHdl = cm.handle(root);
@@ -1703,7 +1703,7 @@ TEST(testListdata, Positive_ChunksRemaining) {
 	root["names"] = namesArr;
 
 	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(3));
-	EXPECT_CALL(fm, isListing).Times(2).WillRepeatedly(testing::Return(true));
+	EXPECT_CALL(fm, isListingSimple).Times(2).WillRepeatedly(testing::Return(true));
 
 	EXPECT_CALL(fm, cancelList).Times(0); // not called
 
@@ -1715,7 +1715,7 @@ TEST(testListdata, Positive_ChunksRemaining) {
 	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 3);
 	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
 
-	vector<string> vec{"blue.txt", "red.pdf", "green.sh", "yellow.tgz"};
+	vector<Json::Value> vec{Json::Value("blue.txt"), Json::Value("red.pdf"), Json::Value("green.sh"), Json::Value("yellow.tgz")};
 	EXPECT_CALL(fm, putListData(vec));
 
 	retvalHdl = cm.handle(root);
@@ -1747,7 +1747,7 @@ TEST(testListdata, Positive_LastChunk) {
 	root["names"] = namesArr;
 
 	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(0));
-	EXPECT_CALL(fm, isListing).Times(2).WillRepeatedly(testing::Return(true));
+	EXPECT_CALL(fm, isListingSimple).Times(2).WillRepeatedly(testing::Return(true));
 
 	EXPECT_CALL(fm, cancelList).Times(0); // not called
 
@@ -1759,10 +1759,10 @@ TEST(testListdata, Positive_LastChunk) {
 	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 0);
 	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
 
-	vector<string> vec{"magenta.vcd"};
+	vector<Json::Value> vec{Json::Value("magenta.vcd")};
 	EXPECT_CALL(fm, putListData(vec));
 	EXPECT_CALL(fm, closeList);
-	vector<string> allFiles{"blue.txt", "red.pdf", "green.sh", "yellow.tgz", "cyan.exe", "white", "black", "magenta.vcd"};
+	vector<Json::Value> allFiles{"blue.txt", "red.pdf", "green.sh", "yellow.tgz", "cyan.exe", "white", "black", "magenta.vcd"};
 	EXPECT_CALL(fm, getListData).WillOnce(testing::Return(allFiles));
 
 	retvalHdl = cm.handle(root);
@@ -1774,7 +1774,8 @@ TEST(testListdata, Positive_LastChunk) {
 	EXPECT_EQ(retvalHdl.type, CmdMan::print);
 	EXPECT_EQ(retvalHdl.msg["command"].asString(), "list");
 	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
-	EXPECT_EQ(retval_msg_names, allFiles);
+	vector<string> allFilesStringVec{"blue.txt", "red.pdf", "green.sh", "yellow.tgz", "cyan.exe", "white", "black", "magenta.vcd"};
+	EXPECT_EQ(retval_msg_names, allFilesStringVec);
 
 	EXPECT_TRUE(cm.isLoggedIn());
 }
@@ -1798,7 +1799,7 @@ TEST(testList, Negative_ListEmpty) {
 	root["items"] = 0;
 
 	// stick into cmdman and check things
-	EXPECT_CALL(fm, openList).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, openList(false)).WillOnce(testing::Return(true));
 	retvalCmd = cm.execute(cmd, args);
 
 	EXPECT_EQ(retvalCmd.type, CmdMan::send);
@@ -1823,8 +1824,6 @@ TEST(testListdata, Negative) {
 	/* tests the handling of server reply only,
 	 * initial request assumed as correct (tested in other tests) */
 
-	std::string cmd;
-	std::vector<std::string> args;
 	Json::Value root;
 	CmdMan::CmdRet retvalHdl;
 
@@ -1852,14 +1851,13 @@ TEST(testList, Negative_AlreadyListing) {
 
 	std::string cmd;
 	std::vector<std::string> args;
-	Json::Value root;
 	CmdMan::CmdRet retvalCmd;
 
 	// prepare cmd/args
 	cmd = "list";
 	args = {};
 
-	EXPECT_CALL(fm, openList).WillOnce(testing::Return(false));
+	EXPECT_CALL(fm, openList(false)).WillOnce(testing::Return(false));
 
 	EXPECT_CALL(fm, cancelList).Times(0); // not called
 
@@ -1879,8 +1877,6 @@ TEST(testList, Negative_NotListing_ServerRequest) {
 	CmdManForTest cm(fm, dummyDebugPrint);
 	cm.initLoggedIn();
 
-	std::string cmd;
-	std::vector<std::string> args;
 	Json::Value root, namesArr;
 	CmdMan::CmdRet retvalHdl;
 
@@ -1895,7 +1891,7 @@ TEST(testList, Negative_NotListing_ServerRequest) {
 	EXPECT_CALL(fm, putListData).Times(0);
 	EXPECT_CALL(fm, closeList).Times(0);
 	EXPECT_CALL(fm, getListData).Times(0);
-	EXPECT_CALL(fm, isListing).WillOnce(testing::Return(false));
+	EXPECT_CALL(fm, isListingSimple).WillOnce(testing::Return(false));
 
 	retvalHdl = cm.handle(root);
 
@@ -1920,7 +1916,7 @@ TEST(testListdata, Negative_NotListing_UserRequest) {
 	cmd = "listdata";
 	args = {};
 
-	EXPECT_CALL(fm, isListing).WillOnce(testing::Return(false));
+	EXPECT_CALL(fm, isListingSimple).WillOnce(testing::Return(false));
 
 	// stick into cmdman and check things
 	retvalCmd = cm.execute(cmd, args);
@@ -1949,6 +1945,7 @@ TEST(testListdata, Negative_NotListing_ServerRequest) {
 	root["names"] = namesArr;
 
 	EXPECT_CALL(fm, cancelList).Times(0);
+	EXPECT_CALL(fm, isListingSimple).WillOnce(testing::Return(false));
 
 	retvalHdl = cm.handle(root);
 
@@ -1960,6 +1957,364 @@ TEST(testListdata, Negative_NotListing_ServerRequest) {
 	EXPECT_TRUE(cm.isLoggedIn());
 }
 
+/* =====================================
+ * tests for extendedlist[data]
+ */
+
+TEST(testExtendedlist, Positive) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "extendedlist";
+	args = {};
+	root["command"] = "extendedlist";
+	root["accept"] = true;
+	root["items"] = 92;
+	root["chunks"] = 2;
+
+	EXPECT_CALL(fm, openList(true)).WillOnce(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+
+	EXPECT_CALL(fm, isListingExtended()).WillOnce(testing::Return(true));
+	EXPECT_CALL(fm, setListChunks(2));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::send);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.nextcommand, "extendedlistdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlistdata, Positive_ChunksRemaining) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, f1, f2, namesArr;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	f1["name"] = "blue.txt";
+	f1["size"] = 42.0f;
+	f1["head"] = "some head 1";
+	files.append(f1);
+	f2["name"] = "green.pdf";
+	f2["size"] = 9.0f;
+	f2["head"] = "some head 2";
+	files.append(f2);
+
+	cmd = "extendedlistdata";
+	args = {};
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 3;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(3));
+	EXPECT_CALL(fm, isListingExtended).Times(2).WillRepeatedly(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlistdata");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 3);
+	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
+
+	vector<Json::Value> vec{f1, f2};
+	EXPECT_CALL(fm, putListData(vec));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::send);
+	EXPECT_EQ(retvalHdl.nextcommand, "extendedlistdata");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlistdata, Positive_LastChunk) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, f1, f2, f3, inputfile;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare file json/root/cmd/args
+
+	inputfile["name"] = "magenta.vcd";
+	inputfile["size"] = 9000.001f;
+	inputfile["head"] = "some head 3";
+	files.append(inputfile);
+
+	cmd = "extendedlistdata";
+	args = {};
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, getListRemainingChunks).Times(2).WillRepeatedly(testing::Return(0));
+	EXPECT_CALL(fm, isListingExtended).Times(2).WillRepeatedly(testing::Return(true));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlistdata");
+	EXPECT_EQ(retvalCmd.msg["chunk"].asInt(), 0);
+	EXPECT_EQ(retvalCmd.msg["cancel"].asBool(), false);
+
+	vector<Json::Value> vec{inputfile};
+	EXPECT_CALL(fm, putListData(vec));
+	EXPECT_CALL(fm, closeList);
+	// output of fileman
+	f1["name"] = "blue.txt";
+	f1["size"] = 42.0f;
+	f1["encrypted"] = "unencrypted";
+	f2["name"] = "green.pdf";
+	f2["size"] = 9.0f;
+	f2["encrypted"] = "decryptable";
+	f3["name"] = "magenta.vcd";
+	f3["size"] = 9000.001f;
+	f3["encrypted"] = "undecryptable";
+	vector<Json::Value> allFiles{f1, f2, f3};
+	EXPECT_CALL(fm, getListData).WillOnce(testing::Return(allFiles));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_TRUE(retvalHdl.msg["accept"].asBool());
+	EXPECT_EQ(retvalHdl.msg["files"][0]["name"].asString(), "blue.txt");
+	EXPECT_EQ(retvalHdl.msg["files"][0]["encrypted"].asString(), "unencrypted");
+	EXPECT_EQ(retvalHdl.msg["files"][0]["size"].asFloat(), 42.0f);
+	EXPECT_EQ(retvalHdl.msg["files"][1]["name"].asString(), "green.pdf");
+	EXPECT_EQ(retvalHdl.msg["files"][1]["encrypted"].asString(), "decryptable");
+	EXPECT_EQ(retvalHdl.msg["files"][1]["size"].asFloat(), 9.0f);
+	EXPECT_EQ(retvalHdl.msg["files"][2]["name"].asString(), "magenta.vcd");
+	EXPECT_EQ(retvalHdl.msg["files"][2]["encrypted"].asString(), "undecryptable");
+	EXPECT_EQ(retvalHdl.msg["files"][2]["size"].asFloat(), 9000.001f);
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlist, Negative_ListEmpty) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	cmd = "extendedlist";
+	args = {};
+	root["command"] = "extendedlist";
+	root["accept"] = true;
+	root["items"] = 0;
+
+	// stick into cmdman and check things
+	EXPECT_CALL(fm, openList(true)).WillOnce(testing::Return(true));
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::send);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+
+	EXPECT_CALL(fm, closeList);
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::print);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("There are no files stored on the server."));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlistdata, Negative) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	/* tests the handling of server reply only,
+	 * initial request assumed as correct (tested in other tests) */
+
+	Json::Value root;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root/cmd/args
+	root["command"] = "extendedlistdata";
+	root["cancel"] = true;
+	root["error"] = "some foobar";
+
+	EXPECT_CALL(fm, cancelList);
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_THAT(retvalHdl.msg["error"].asString(), testing::HasSubstr("some foobar"));
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlist, Negative_AlreadyListing) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare cmd/args
+	cmd = "extendedlist";
+	args = {};
+
+	EXPECT_CALL(fm, openList(true)).WillOnce(testing::Return(false));
+
+	EXPECT_CALL(fm, cancelList).Times(0); // not called
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalCmd.msg["accept"].asBool());
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedlist, Negative_NotListing_ServerRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, inputfile;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	inputfile["name"] = "magenta.vcd";
+	inputfile["size"] = 9000.001f;
+	inputfile["head"] = "some head 3";
+	files.append(inputfile);
+
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, cancelList).Times(0);
+	EXPECT_CALL(fm, putListData).Times(0);
+	EXPECT_CALL(fm, closeList).Times(0);
+	EXPECT_CALL(fm, getListData).Times(0);
+	EXPECT_CALL(fm, isListingExtended).WillOnce(testing::Return(false));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
+TEST(testExtendedistdata, Negative_NotListing_UserRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	CmdMan::CmdRet retvalCmd;
+
+	// prepare root/cmd/args
+	cmd = "extendedlistdata";
+	args = {};
+
+	EXPECT_CALL(fm, isListingExtended).WillOnce(testing::Return(false));
+
+	// stick into cmdman and check things
+	retvalCmd = cm.execute(cmd, args);
+
+	EXPECT_EQ(retvalCmd.type, CmdMan::error);
+	EXPECT_EQ(retvalCmd.msg["command"].asString(), "extendedlist");
+	EXPECT_NE(retvalCmd.msg["error"].asString(), "");
+	EXPECT_EQ(retvalCmd.msg["accept"].asBool(), false);
+}
+
+TEST(testExtendedlistdata, Negative_NotListing_ServerRequest) {
+	FileManMock fm;
+	CmdManForTest cm(fm, dummyDebugPrint);
+	cm.initLoggedIn();
+
+	std::string cmd;
+	std::vector<std::string> args;
+	Json::Value root, files, inputfile;
+	CmdMan::CmdRet retvalHdl;
+
+	// prepare root
+	inputfile["name"] = "magenta.vcd";
+	inputfile["size"] = 9000.001f;
+	inputfile["head"] = "some head 3";
+	files.append(inputfile);
+
+	root["command"] = "extendedlistdata";
+	root["cancel"] = false;
+	root["remaining"] = 0;
+	root["files"] = files;
+
+	EXPECT_CALL(fm, cancelList).Times(0);
+	EXPECT_CALL(fm, isListingExtended).WillOnce(testing::Return(false));
+
+	retvalHdl = cm.handle(root);
+
+	EXPECT_EQ(retvalHdl.type, CmdMan::error);
+	EXPECT_EQ(retvalHdl.msg["command"].asString(), "extendedlist");
+	EXPECT_FALSE(retvalHdl.msg["accept"].asBool());
+	EXPECT_NE(retvalHdl.msg["error"].asString(), "");
+
+	EXPECT_TRUE(cm.isLoggedIn());
+}
+
 /* =====================================
  * tests for deleteme
  */
@@ -2812,8 +3167,8 @@ TEST(testCommandsWithRequiredLogin, NotLoggedInOrNotConnected) {
 	CmdMan::CmdRet retval1, retval2, retval3;
 
 	// prepare cmd/args
-	std::vector<std::string> cmds = {"put",  "get",        "list",     "putdata", "getdata",  "listdata",
-	                                 "head", "deletefile", "deleteme", "keyfile", "closekey", "extendedstatus"};
+	std::vector<std::string> cmds = {"put",  "get",        "list",     "extendedlist", "putdata",  "getdata",       "listdata", "extendedlistdata",
+	                                 "head", "deletefile", "deleteme", "keyfile",      "closekey", "extendedstatus"};
 	// as every command works fine with too many args, we will simply pass three to all of them
 	std::vector<std::string> args = {"arg1", "arg2", "arg3"};
 

+ 20 - 0
cli/test/cryptotest_gcm.c

@@ -57,6 +57,16 @@ int main(int argc, char **argv) {
 	}
 
 	if (mode) {
+		printf("using iv ");
+		for (unsigned i = 0; i < sizeof(iv); i++)
+			printf("%02x ", iv[i]);
+		printf("\n");
+
+		printf("using tag ");
+		for (unsigned i = 0; i < sizeof(tag); i++)
+			printf("%02x ", tag[i]);
+		printf("\n");
+
 		fseek(f, 0, SEEK_END);
 		insize = ftell(f) + 4;
 		fseek(f, 0, SEEK_SET);
@@ -83,6 +93,16 @@ int main(int argc, char **argv) {
 		fread(cipher, insize, 1, f);
 		fclose(f);
 
+		printf("using iv ");
+		for (unsigned i = 0; i < sizeof(iv); i++)
+			printf("%02x ", iv[i]);
+		printf("\n");
+
+		printf("using tag ");
+		for (unsigned i = 0; i < sizeof(tag); i++)
+			printf("%02x ", tag[i]);
+		printf("\n");
+
 		decryptedtext_len = gcm_decrypt(cipher, insize, tag, key, iv, sizeof(iv), plain);
 		if (decryptedtext_len < 0) {
 			printf("decrypt failed\n");

+ 5 - 3
cli/test/fileman_mock.h

@@ -9,10 +9,12 @@ public:
 	MOCK_METHOD(bool, isGetting, (), (override));
 	MOCK_METHOD(bool, isPutting, (), (override));
 	MOCK_METHOD(bool, isListing, (), (override));
+	MOCK_METHOD(bool, isListingSimple, (), (override));
+	MOCK_METHOD(bool, isListingExtended, (), (override));
 
 	MOCK_METHOD(bool, openPut, (const std::string &path), (override));
 	MOCK_METHOD(bool, openGet, (const std::string &path), (override));
-	MOCK_METHOD(bool, openList, (), (override));
+	MOCK_METHOD(bool, openList, (bool extended), (override));
 
 	MOCK_METHOD(void, closePut, (), (override));
 	MOCK_METHOD(void, closeGet, (), (override));
@@ -32,8 +34,8 @@ public:
 	MOCK_METHOD(std::string, readBase64, (), (override));
 	MOCK_METHOD(void, writeBase64, (std::string data), (override));
 
-	MOCK_METHOD(void, putListData, (std::vector<std::string> names), (override));
-	MOCK_METHOD(std::vector<std::string>, getListData, (), (override));
+	MOCK_METHOD(void, putListData, (std::vector<Json::Value> names), (override));
+	MOCK_METHOD(std::vector<Json::Value>, getListData, (), (override));
 
 	MOCK_METHOD(int, getPutChunks, (), (override));
 	MOCK_METHOD(int, getGetChunks, (), (override));