浏览代码

Merge branch 'cleanup-gui' of git.rwth-aachen.de:tobias.wach/ccats into cleanup-cli

Missingmew 5 年之前
父节点
当前提交
2d312a55ba

+ 128 - 32
Client-Server Protocol.md

@@ -1,11 +1,14 @@
 # Client-Server Protocol
 
-Protocol version: "0.1"
+Protocol version: <b>"0.2"</b>
+
+Every json message must be minimized and be followed by a newline. This rule makes receiving and parsing the messages easier because you can read until a newline comes and you will parse every json message seperately because you won't read multiple json messages from the buffer at a time.
 
 ## 1. Start a connection
 
-To start a connection you have to check the versions and verify the login.
+To start a connection you have to check the versions and verify the login or signup.
 Only if step 1.1 and 1.2 have been accomplished a usable connection is negotiated.
+You can close the connection by sending a cancel message.
 
 ### 1.1 Version check
 Client:
@@ -26,23 +29,66 @@ Server:
 
 If `accept` is `true` the connection is valid. Else the connection will be terminated after the server answered.
 
-### 1.2 Login
+
+## 1.2 Login / Signup
+
+The client is supposed to always send every field used for the login requests: `login`, `user`, `pass` and `cancel`.
+The server is supposed to always send every field used for the login answer: `accept` and `error`.
+
+### 1.2.1 Login
 Client:
 ```
 {
+	"login": true,
 	"user": string,
-	"pass": string
+	"pass": string,
+	"cancel": false
 }
 ```
 
 Server:
 ```
 {
-	"accept": bool
+	"accept": bool,
+	"error": string
+}
+```
+
+If `accept` is `true` the connection is valid and the user is logged in. Else `error` has an error string and the connection will be terminated after the server answered.
+
+### 1.2.2 Signup
+Client:
+```
+{
+	"login": false,
+	"user": string,
+	"pass": string,
+	"cancel": false
+}
+```
+
+Server:
+```
+{
+	"accept": bool,
+	"error": string
+}
+```
+
+If `accept` is `true` the connection is valid and the user is logged in and signed up. Else `error` has an error string and the connection will be terminated after the server answered.
+
+### 1.2.3 Cancel login
+Client:
+```
+{
+	"login": bool,
+	"user": string,
+	"pass": string,
+	"cancel": true
 }
 ```
 
-If `accept` is `true` the connection is valid and the user is logged in. Else the connection will be terminated after the server answered.
+After the cancel message was sent the connection will be closed.
 
 
 ## 2. Sending commands
@@ -67,31 +113,60 @@ Server:
 ```
 
 ### 2.2 List command
+List is split in two commands. `list` to request a list download and `listdata` to download the list.
+
+#### 2.2.1 list - request file list download
 Client:
 ```
 {
 	"command": "list"
 }
 ```
+Server:
+```
+{
+	"command": "list",
+	"accept": bool,
+	"items": int,
+	"chunks": int,
+	"error": string
+}
+```
 
+#### 2.2.2 listdata - download file list
+Client:
+```
+{
+	"command": "listdata",
+	"chunk": int,
+	"cancel": bool
+}
+```
 Server:
 ```
 {
-	"command": "put",
+	"command": "listdata",
+	"remaining": int,
+	"cancel": bool,
 	"names": string[],
-	"remaining": int
+	"error": string
 }
 ```
-The server loops this until all names have been sent and `remaining` hits `0`.
+The client has to request every chunk until all names have been sent and `remaining` hits `0`.
 
 
 ### 2.3 Put command
+
+Put is split in two commands. `put` to request a file upload and `putdata` to upload the data.
+
+#### 2.3.1 put - request upload
 Client:
 ```
 {
 	"command": "put",
 	"file": string,
-	"size": int
+	"size": int,
+	"chunks": int
 }
 ```
 
@@ -99,15 +174,20 @@ Server:
 ```
 {
 	"command": "put",
-	"accept": bool
+	"file": string,
+	"accept": bool,
+	"error": string
 }
 ```
-If `accept` is `true` the connection is valid and the client can start sending the file. Else the put request was rejected and is hereby canceled.
-<br /><br />
+If `accept` is `true` the connection is valid and the client can start sending the file. Else the put request was rejected and is hereby canceled. `error` should contain an error string if the request was rejected.
+
+
+#### 2.3.2 putdata - upload data
 Client:
 ```
 {
-	"command": "put",
+	"command": "putdata",
+	"file": string,
 	"data": string,
 	"remaining": int,
 	"cancel": bool
@@ -120,14 +200,21 @@ If `cancel` is `true` the file transfer will be canceled.
 Server:
 ```
 {
-	"command": "put",
+	"command": "putdata",
+	"file": string,
 	"recieved": int,
-	"cancel": bool
+	"cancel": bool,
+	"error": string
 }
 ```
-If `cancel` is `false` then cancel the upload of the file.
+If `cancel` is `true` then the upload of the file is canceled and an error message should be in `error`.
 
 ### 2.4 Get command
+
+Get is split in two commands. `get` to request a file download and `getdata` to download the data.
+
+#### 2.4.1 get - request download
+
 Client:
 ```
 {
@@ -140,33 +227,42 @@ Server:
 ```
 {
 	"command": "get",
-	"accept": bool
+	"file": string,
+	"accept": bool,
+	"chunks": int,
+	"error": string
 }
 ```
-If `accept` is `true` the connection is valid and the server can start sending the file. Else the get request was rejected and is hereby canceled.
-<br /><br />
-Server:
+If `accept` is `true` the connection is valid and the server can start sending the file. Else the get request was rejected and is hereby canceled. `error` should contain an error string if the request was rejected. `chunks` is the number of chunks of the file.
+
+#### 2.4.2 getdata - download data
+
+Client:
 ```
 {
-	"command": "get",
-	"data": string,
-	"remaining": int,
+	"command": "getdata",
+	"file": string,
+	"chunk": int,
 	"cancel": bool
 }
 ```
-`data` is a base64 string and contains a piece of the file. The server will loop this until the file is completely sent and the client has to send a received message for every chunk.
-`remaining` is a counter how many chunks will follow and the transfer is over after it hits `0`.
-If `cancel` is `true` the file transfer will be canceled.
+If `cancel` is `true` then cancel the download of the file.
 <br /><br />
-Client:
+Server:
 ```
 {
-	"command": "get",
-	"recieved": int,
-	"cancel": bool
+	"command": "getdata",
+	"file": string,
+	"data": string,
+	"remaining": int,
+	"cancel": bool,
+	"error": string
 }
 ```
-If `cancel` is `false` then cancel the download of the file.
+`data` is a base64 string and contains a piece of the file. The client must request every chunk.
+`remaining` is a counter how many chunks will follow and the transfer is over after it hits `0`.
+If `cancel` is `true` then the download of the file is canceled and an error message should be in `error`.
+
 
 ### TODO
 

+ 0 - 1
daemon/CMakeLists.txt

@@ -1,7 +1,6 @@
 cmake_minimum_required(VERSION 2.8)
 
 set(CMAKE_CXX_STANDARD 11)
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
 project(ccats)
 

+ 64 - 0
daemon/include/UserManager.h

@@ -0,0 +1,64 @@
+#ifndef USER_MANAGER_H
+#define USER_MANAGER_H
+
+#include <fstream>
+#include <iostream>
+#include <map>
+#include <sstream>
+#include <string>
+#include <vector>
+
+/**
+ * @Class UserManager
+ *
+ * Handle differnt users with passwords and store them
+ */
+class UserManager {
+public:
+  /**
+   * Makes shure that there is a user storage.
+   * If not. It creates a new one
+   */
+  static void init();
+
+  /**
+   * Checks if a given user with password is valid
+   *
+   * @param user the name of the users
+   * @param pw password in plain text
+   */
+  static bool isAllowed(const std::string &user, const std::string &pw);
+
+  /**
+   * Add a new user and write to storage.
+   *
+   * @param user the name of the users
+   * @param pw password in plain text
+   */
+  static void addUser(const std::string &name, const std::string &pw);
+
+  /**
+   * Delete a new user and delete from storage.
+   *
+   * @param user the name of the users
+   * @param pw password in plain text
+   */
+  static void deleteUser(const std::string &name, const std::string &pw);
+
+private:
+  /**
+   * Read data from file and create map.
+   *
+   * @param user_map pointer to the map
+   */
+  static void readFromFile(std::map<std::string, std::string> &user_map);
+
+  /**
+   * Write data to afile
+   *
+   * @param user_map pointer to the map
+   */
+  static void writeToFile(std::map<std::string, std::string> &user_map);
+};
+
+#endif

+ 1 - 4
daemon/src/CMakeLists.txt

@@ -1,11 +1,8 @@
 cmake_minimum_required(VERSION 2.8)
 
-set(CMAKE_CXX_STANDARD 11)
 set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
-project(ccats)
-
-add_executable(ccats src/main.cpp src/Sniffer.cpp src/Server.cpp src/base64.cpp src/JsonCommander.cpp src/FileManager.cpp)
+add_executable(ccats src/main.cpp src/Sniffer.cpp src/Server.cpp src/base64.cpp src/JsonCommander.cpp src/FileManager.cpp src/UserManager.cpp)
 
 # use pkg-config to fix building on debian unstable
 find_package(PkgConfig REQUIRED)

+ 4 - 3
daemon/src/JsonCommander.cpp

@@ -1,4 +1,5 @@
 #include "../include/JsonCommander.h"
+#include "../include/UserManager.h"
 #include "../include/base64.h"
 
 JsonCommander::JsonCommander(FileManager &fileManager)
@@ -335,9 +336,9 @@ JsonCommander::Response JsonCommander::checkLogin(const Json::Value &message) {
     response.json["accept"] = false;
     response.json["error"] = "invalid login request";
   } else if (message["login"].asBool() &&
-             message["user"].asString().compare("user") == 0 &&
-             message["pass"].asString().compare("pass") == 0) {
-    // TODO replace with real credentials check
+             UserManager::isAllowed(message["user"].asString(),
+                                    message["pass"].asString())) {
+    // credential check
     response.action = send;
     response.json["accept"] = true;
     response.json["error"] = "";

+ 1 - 0
daemon/src/Server.cpp

@@ -1,4 +1,5 @@
 #include "../include/Server.h"
+#include "../include/base64.h"
 
 #include <iostream>
 

+ 77 - 0
daemon/src/UserManager.cpp

@@ -0,0 +1,77 @@
+#include "../include/UserManager.h"
+
+// TODO passwords are stored and checked in plain text
+// TODO read userStorage file location from config
+
+void UserManager::init() {
+  std::ifstream ifile("userStorage.txt");
+  if (!ifile) {
+    // create new file if userStorage does not exist
+    std::ofstream file;
+    file.open("userStorage.txt");
+    file << "cat;tac\n";
+    file.close();
+  }
+  ifile.close();
+}
+
+bool UserManager::isAllowed(const std::string &name, const std::string &pw) {
+  std::map<std::string, std::string> user_map;
+  readFromFile(user_map);
+  auto it = user_map.find(name);
+  // check if user exists and pw is equal
+  if (it != user_map.end() && (it->second.compare(pw) == 0)) {
+    return true;
+  }
+  return false;
+}
+
+void UserManager::addUser(const std::string &name, const std::string &pw) {
+  std::map<std::string, std::string> user_map;
+  readFromFile(user_map);
+  auto it = user_map.find(name);
+  // if user exists, do nothing
+  if (it != user_map.end()) {
+    return;
+  }
+  user_map.insert(std::pair<std::string, std::string>(name, pw));
+  writeToFile(user_map);
+}
+
+void UserManager::deleteUser(const std::string &name, const std::string &pw) {
+  // TODO check pw before delete
+  std::map<std::string, std::string> user_map;
+  readFromFile(user_map);
+  auto it = user_map.find(name);
+  if (it == user_map.end()) {
+    return;
+  }
+  user_map.erase(it);
+  writeToFile(user_map);
+}
+
+// read content from file into given map
+void UserManager::readFromFile(std::map<std::string, std::string> &user_map) {
+  std::ifstream ifile("userStorage.txt");
+  std::string line;
+  while (getline(ifile, line)) {
+    std::stringstream ss(line);
+    std::string segment;
+    std::vector<std::string> v;
+    while (std::getline(ss, segment, ';')) {
+      v.push_back(segment);
+    }
+    user_map.insert(std::pair<std::string, std::string>(v.at(0), v.at(1)));
+  }
+  ifile.close();
+}
+
+// write content from map to file
+void UserManager::writeToFile(std::map<std::string, std::string> &user_map) {
+  std::ofstream file;
+  file.open("userStorage.txt");
+  for (auto const &x : user_map) {
+    file << x.first << ";" << x.second << std::endl;
+  }
+  file.close();
+}

+ 5 - 0
daemon/src/main.cpp

@@ -3,6 +3,7 @@
 
 #include "../include/Server.h"
 #include "../include/Sniffer.h"
+#include "../include/UserManager.h"
 
 using namespace std;
 
@@ -12,6 +13,10 @@ int main(int argc, char *argv[]) {
     return 0;
   }
 
+  // check if userStorage is add specified location
+  // if not create one
+  UserManager::init();
+
   const string interface = argv[1];
   Sniffer *sniffer = new Sniffer(interface);
   thread snifferThread(&Sniffer::startSniffing, sniffer);

+ 2 - 5
daemon/test/CMakeLists.txt

@@ -1,9 +1,6 @@
 cmake_minimum_required(VERSION 2.8)
 
-set(CMAKE_CXX_STANDARD 11)
-set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
-
-project(ccats)
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/test)
 
 # use pkg-config to fix building on debian unstable
 find_package(PkgConfig REQUIRED)
@@ -23,7 +20,7 @@ find_package(GTest REQUIRED)
 include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDEDIR} ${GMOCK_INCLUDE_DIR} ${GTEST_INCLUDE_DIR})
 
 # Add test cpp file
-add_executable(jsonCommanderTest test/JsonCommanderTest.cpp src/JsonCommander.cpp src/FileManager.cpp src/base64.cpp)
+add_executable(jsonCommanderTest test/JsonCommanderTest.cpp src/JsonCommander.cpp src/FileManager.cpp src/base64.cpp src/UserManager.cpp)
 target_link_libraries(jsonCommanderTest ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} ${JSONCPP_LIBRARIES} ${GMOCK_LIBRARIES} ${GTEST_LIBRARY} ${GTEST_MAIN_LIBRARY})
 
 add_test(

+ 2 - 1
gui/src/FooterForm.ui.qml

@@ -11,7 +11,7 @@ Page {
     Connections {
         target: _qmlHandler
         onLog: {
-            footerLog.append(logText)
+            footerLog.append(new Date().toLocaleTimeString(Qt.locale("de_DE"), "[hh:mm:ss]\n") + logText)
             footerFlickable.contentY = footerLog.height - footerFlickable.height
         }
 
@@ -63,6 +63,7 @@ Page {
                 TextArea.flickable: TextArea {
                     selectByMouse: true
                     id: footerLog
+                    readOnly: true
                     wrapMode: TextArea.Wrap
                     text: qsTr("")
                     font.pointSize: 15

+ 1 - 0
gui/src/MessagesForm.ui.qml

@@ -27,6 +27,7 @@ Page {
 
             TextArea.flickable: TextArea {
                 id: messagesLog
+                readOnly: true
                 selectByMouse: true
                 wrapMode: TextArea.Wrap
                 font.pointSize: 15

+ 45 - 1
gui/src/ReceivingForm.ui.qml

@@ -8,9 +8,40 @@ Page {
 
     font.capitalization: Font.MixedCase
 
+    Connections {
+        target: _qmlHandler
+        onReceivingListFile: {
+            receivingFileList.append(fileName)
+            receivingFlickable.contentY = receivingFileList.height - receivingFlickable.height
+        }
+
+        onReceivingClearFileList: {
+            receivingFileList.text = ""
+        }
+    }
+
     ColumnLayout {
         anchors.fill: parent
 
+        Flickable {
+            id: receivingFlickable
+            flickableDirection: Flickable.VerticalFlick
+            Layout.preferredHeight: 400
+            Layout.preferredWidth: parent.width
+
+            TextArea.flickable: TextArea {
+                selectByMouse: true
+                readOnly: true
+                id: receivingFileList
+                wrapMode: TextArea.Wrap
+                text: qsTr("")
+                font.pointSize: 15
+            }
+
+            ScrollBar.vertical: ScrollBar {
+            }
+        }
+
         RowLayout {
             Layout.preferredWidth: parent.width
             Layout.preferredHeight: 70
@@ -18,13 +49,26 @@ Page {
             TextField {
                 id: receivingFileNameField
                 selectByMouse: true
-                Layout.preferredWidth: 1060
+                Layout.preferredWidth: 880
                 Layout.preferredHeight: parent.height
                 placeholderText: "Enter File Name to download..."
                 text: qsTr("")
                 font.pixelSize: 20
             }
 
+            Button {
+                id: receivingListFilesButton
+                Layout.preferredWidth: 180
+                Layout.preferredHeight: parent.height
+                text: qsTr("List Files")
+
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M223
+                    _qmlHandler.onReceivingListFilesButton()
+                }
+            }
+
             Button {
                 id: receivingGetFileButton
                 Layout.preferredWidth: 180

+ 9 - 0
gui/src/SendingForm.ui.qml

@@ -17,6 +17,9 @@ Page {
         onSendingEnableSendButton: {
             sendingSendFileButton.enabled = true
         }
+        onSendingDisableSendButton: {
+            sendingSendFileButton.enabled = false
+        }
     }
 
     Button {
@@ -26,7 +29,13 @@ Page {
         width: 260
         height: 90
         text: qsTr("Clear Selection")
+        enabled: sendingSendFileButton.enabled
         font.pointSize: 16
+        // @disable-check M223
+        onClicked: {
+            // @disable-check M222
+            _qmlHandler.onSendingClearSelectionButton()
+        }
     }
 
     Button {

+ 8 - 0
gui/src/main.cpp

@@ -29,14 +29,22 @@ int main(int argc, char *argv[]) {
 
   QMLHandler qmlHandler;
 
+  // Set the context for the window, so that the qml files can be connected to
+  // the qmlHandler
   engine.rootContext()->setContextProperty("_qmlHandler", &qmlHandler);
 
+  // Load the main window
   QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
 
   QObject *object = component.create();
 
+  // Run the main window and save the return of the application in an integer
+  // ret This is blocking, which means it will block the main program until the
+  // window is closed
   int ret = app.exec();
 
+  // If we land here, the window has been closed. Properly disconnect from the
+  // server now
   qmlHandler.onExit();
 
   return ret;

+ 60 - 9
gui/src/qmlhandler.cpp

@@ -20,39 +20,67 @@ using namespace std;
 
 int inpipefd[2];
 int outpipefd[2];
-char buf[1024];
+char buf[1025];
 QUrl sendFileUrl = QUrl("");
 
 bool programActive = true;
 
+QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {}
+
 void QMLHandler::onExit() {
   write(outpipefd[1], "disconnect\n", strlen("disconnect\n"));
 }
 
+// This method gets a string and tries to read it as Json
+// If it fails to do so, return and do nothing, else handle the content
 void QMLHandler::handleJSON(string buffer) {
   Json::Value root;
   Json::CharReaderBuilder builder;
   Json::CharReader *reader = builder.newCharReader();
   string jsonError;
 
+  // Try to parse the string as Json and store the result of the pasring in a
+  // boolean
   bool parsingSuccessful = reader->parse(
       buffer.c_str(), buffer.c_str() + buffer.size(), &root, &jsonError);
 
+  // If the string is not correct Json, return
   if (!parsingSuccessful) {
     return;
   }
+
   const Json::Value command = root["command"];
   string cmd = command.asString();
 
-  if (cmd == "status") {
+  if (cmd.compare("status")) {
     emit footerSetStatus(root["response"].asString().c_str());
   }
 
-  else if (cmd == "close") {
+  else if (cmd.compare("close")) {
     programActive = false;
   }
+
+  else if (cmd.compare("listdata")) {
+    emit receivingClearFileList();
+    // Get the array of file Names
+    auto fileNames = root["names"];
+    for (int i = 0; i < fileNames.size(); i++) {
+      emit receivingListFile(
+          QString::fromStdString(fileNames[i].asString().c_str()));
+    }
+  }
+
+  else if (cmd.compare("version")) {
+    // TODO: Change hardcoded login details to input values
+    write(outpipefd[1], "user\n", strlen("user\n"));
+    write(outpipefd[1], "pass\n", strlen("pass\n"));
+  }
 }
 
+// This method is a loop which runs in a seperate thread.
+// If will read the Input Pipe, which containts the string that get printed
+// on stdout by the CLI/Server, and calls handleJSON with the read content
+// one it reads a newline.
 void QMLHandler::readPipeLoop() {
   unsigned int readOffset = 0;
   unsigned int pollCount = 0;
@@ -66,18 +94,22 @@ void QMLHandler::readPipeLoop() {
     if (inPipeStatus.revents & POLLIN) {
       readOffset += read(inpipefd[0], buf + readOffset, 1);
 
+      if(buf[readOffset-1] == '\n') {
+        pollCount = 10;
+      } else {
+        pollCount = 0;
+      }
+
       pollCount = 0;
 
     } else {
       pollCount++;
     }
 
-    if (pollCount > 9 && buf[0]) {
-      buf[1023] = 0;
-      buf[strlen(buf)] = 0;
+    if (pollCount > 9 && readOffset > 0) {
+      buf[strnlen(buf, 1024)] = 0;
 
-      string cleanBuffer = buf + strcspn(buf, "\n") + 1;
-      string receivedData = cleanBuffer.substr(0, cleanBuffer.size() - 1);
+      string receivedData = buf;
 
       emit log(QString::fromStdString(receivedData));
       qInfo() << QString::fromStdString(receivedData);
@@ -86,10 +118,18 @@ void QMLHandler::readPipeLoop() {
       pollCount = 0;
       readOffset = 0;
     }
+
+    // Fixme
+    if (readOffset >= 1024) {
+      qInfo() << "Fixme: QMLHandler::readPipeLoop() readOffset too high!!!";
+      readOffset = 0;
+      pollCount = 0;
+      memset(buf, 0, 1025);
+    }
   }
 }
 
-QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {}
+// ### QML Handlers ###
 
 // Sending
 void QMLHandler::onSendingSelectFileButton(QUrl url) {
@@ -105,7 +145,18 @@ void QMLHandler::onSendingSendFileButton() {
         strlen(command.toUtf8().constData()));
 }
 
+void QMLHandler::onSendingClearSelectionButton() {
+  sendFileUrl = QUrl("");
+  emit log("Cleared Selection");
+  emit sendingSetFileUrlText("Selected File: None");
+  emit sendingDisableSendButton();
+}
+
 // Receiving
+void QMLHandler::onReceivingListFilesButton() {
+  write(outpipefd[1], "list\n", strlen("list\n"));
+}
+
 void QMLHandler::onReceivingGetFileButton(QString fileName) {
   QString command = "get " + fileName + "\n";
   write(outpipefd[1], command.toUtf8().constData(),

+ 9 - 0
gui/src/qmlhandler.h

@@ -18,12 +18,17 @@ public:
   explicit QMLHandler(QObject *parent = 0);
   void onExit();
 
+
+// C++ -> QML
 signals:
   // Sending
   void sendingSetFileUrlText(QString signalText);
   void sendingEnableSendButton();
+  void sendingDisableSendButton();
 
   // Receiving
+  void receivingClearFileList();
+  void receivingListFile(QString fileName);
 
   // Messages
   void message(QString msg);
@@ -39,12 +44,16 @@ signals:
   void log(QString logText);
   void footerSetStatus(QString status);
 
+
+// QML -> C++
 public slots:
   // Sending
   void onSendingSelectFileButton(QUrl url);
   void onSendingSendFileButton();
+  void onSendingClearSelectionButton();
 
   // Receiving
+  void onReceivingListFilesButton();
   void onReceivingGetFileButton(QString fileName);
 
   // Messages