Переглянути джерело

merge upstream for jsoncpp include fix

Missingmew 5 роки тому
батько
коміт
6d5dc8e762

+ 1 - 1
cli/CMakeLists.txt

@@ -16,6 +16,6 @@ find_package(Threads)
 find_package(Boost 1.67 REQUIRED COMPONENTS system program_options)
 
 
-include_directories(${Boost_INCLUDE_DIR} include)
+include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDEDIR} include)
 
 target_link_libraries(ccats-cli PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} ${READLINE_LIBRARIES} ${JSONCPP_LIBRARIES})

+ 12 - 13
cli/src/commands.cpp

@@ -1,19 +1,18 @@
 #include "../include/commands.hpp"
 
-CMD
-    commands[]{
-        {CMD_HELP, "help", "show help"},
-        {CMD_STATUS, "status", "query status of IP"},
-        {CMD_DISCONNECT, "disconnect", "disconnect from IP"},
+CMD commands[]{
+    {CMD_HELP, "help", "show help"},
+    {CMD_STATUS, "status", "query status of IP"},
+    {CMD_DISCONNECT, "disconnect", "disconnect from IP"},
 
-        /* TODO
-        {CMD_PUT	, "put", "upload file to IP and add to queue"},
-        {CMD_REMOVE	, "remove", "remove file from IP and queue (stops xfer
-        if required)"}, {CMD_GET	, "get", "retrieve file from IP"},
-        {CMD_SETUP	, "setup", "configure server at IP"},
-        {CMD_LOG	, "log", "show log from IP"}
-        */
-    };
+    /* TODO
+    {CMD_PUT	, "put", "upload file to IP and add to queue"},
+    {CMD_REMOVE	, "remove", "remove file from IP and queue (stops xfer
+    if required)"}, {CMD_GET	, "get", "retrieve file from IP"},
+    {CMD_SETUP	, "setup", "configure server at IP"},
+    {CMD_LOG	, "log", "show log from IP"}
+    */
+};
 
 COMMANDID getCmdIdFromString(const char *str) {
   COMMANDID ret = CMD_UNKNOWN;

+ 1 - 1
cli/src/useriomanager.cpp

@@ -2,7 +2,7 @@
 #include "../include/commands.hpp"
 
 #include <iostream>
-#include <jsoncpp/json/json.h>
+#include <json/json.h>
 
 #include <readline/history.h>
 #include <readline/readline.h>

+ 2 - 2
daemon/CMakeLists.txt

@@ -5,7 +5,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
 
 project(ccats)
 
-add_executable(ccats src/main.cpp src/Sniffer.cpp src/Server.cpp)
+add_executable(ccats src/main.cpp src/Sniffer.cpp src/Server.cpp src/base64.cpp)
 
 # use pkg-config to fix building on debian unstable
 find_package(PkgConfig REQUIRED)
@@ -17,5 +17,5 @@ find_package(Threads REQUIRED)
 find_package(Boost 1.67 REQUIRED COMPONENTS system)
 # find_package(libtins 4.2 REQUIRED)
 
-include_directories(${Boost_INCLUDE_DIR})
+include_directories(${Boost_INCLUDE_DIR} ${JSONCPP_INCLUDEDIR})
 target_link_libraries(ccats PRIVATE ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} ${TINS_LIBRARIES} ${PCAP_LIBRARIES} ${JSONCPP_LIBRARIES})

+ 21 - 1
daemon/include/Server.h

@@ -1,6 +1,8 @@
 #ifndef SERVER_H
 #define SERVER_H
 
+#include <fstream>
+
 #include <boost/asio.hpp>
 #include <boost/bind.hpp>
 #include <boost/enable_shared_from_this.hpp>
@@ -22,16 +24,34 @@ private:
 
   const std::string protocolVersion = "0.1";
 
+  const std::string fileDirectory = "./files/";
+
   /**
    * max buffer length
    */
-  enum { max_length = 1024 };
+  enum { max_length = 1024, max_data_length = 512 };
 
   /**
    * data buffer
    */
   char data[max_length];
 
+  /**
+   * file stream for get command
+   */
+  std::ifstream getFile;
+
+  /**
+   * file stream for put command
+   */
+  std::ofstream putFile;
+
+  /**
+   * file stream for put command
+   * (used to delete the file if the upload is canceled)
+   */
+  std::string putFileName;
+
 public:
   /**
    * Pointer to a con_handler.

+ 26 - 0
daemon/include/base64.h

@@ -0,0 +1,26 @@
+#ifndef BASE64_H
+#define BASE64_H
+
+#include <string>
+
+namespace base64 {
+/**
+ * Decodes base64 encoded strings.
+ *
+ * @param val base64 encoded string
+ *
+ * @return normal string
+ */
+std::string decode(const std::string &val);
+
+/**
+ * Encodes base64 encoded strings.
+ *
+ * @param val normal string
+ *
+ * @return base64 encoded string
+ */
+std::string encode(const std::string &val);
+} // namespace base64
+
+#endif

+ 196 - 43
daemon/src/Server.cpp

@@ -1,6 +1,8 @@
 #include "../include/Server.h"
+#include "../include/base64.h"
+
 #include <iostream>
-#include <jsoncpp/json/json.h>
+#include <json/json.h>
 
 using namespace boost::asio;
 using ip::tcp;
@@ -67,17 +69,17 @@ void con_handler::handle_read_version(const boost::system::error_code &err,
       answer["accept"] = true;
       const std::string answerString = Json::writeString(stringBuilder, answer);
 
-      // send answer
-      sock.async_write_some(buffer(answerString, max_length),
-                            boost::bind(&con_handler::handle_write,
-                                        shared_from_this(), placeholders::error,
-                                        placeholders::bytes_transferred));
-
       // read next data
       sock.async_read_some(buffer(data, max_length),
                            boost::bind(&con_handler::handle_read_login,
                                        shared_from_this(), placeholders::error,
                                        placeholders::bytes_transferred));
+
+      // send answer
+      sock.async_write_some(buffer(answerString, max_length),
+                            boost::bind(&con_handler::handle_write,
+                                        shared_from_this(), placeholders::error,
+                                        placeholders::bytes_transferred));
     } else {
       answer["accept"] = false;
       const std::string answerString = Json::writeString(stringBuilder, answer);
@@ -123,17 +125,17 @@ void con_handler::handle_read_login(const boost::system::error_code &err,
       answer["accept"] = true;
       const std::string answerString = Json::writeString(stringBuilder, answer);
 
-      // send answer
-      sock.async_write_some(buffer(answerString, max_length),
-                            boost::bind(&con_handler::handle_write,
-                                        shared_from_this(), placeholders::error,
-                                        placeholders::bytes_transferred));
-
       // read next data
       sock.async_read_some(buffer(data, max_length),
                            boost::bind(&con_handler::handle_read_command,
                                        shared_from_this(), placeholders::error,
                                        placeholders::bytes_transferred));
+
+      // send answer
+      sock.async_write_some(buffer(answerString, max_length),
+                            boost::bind(&con_handler::handle_write,
+                                        shared_from_this(), placeholders::error,
+                                        placeholders::bytes_transferred));
     } else {
       answer["accept"] = false;
       const std::string answerString = Json::writeString(stringBuilder, answer);
@@ -174,10 +176,27 @@ void con_handler::handle_read_command(const boost::system::error_code &err,
 
     // check command
     if (root["command"].compare("status") == 0) {
+      // read next data
+      sock.async_read_some(buffer(data, max_length),
+                           boost::bind(&con_handler::handle_read_command,
+                                       shared_from_this(), placeholders::error,
+                                       placeholders::bytes_transferred));
+
       answer["command"] = "status";
 
       // TODO answer a real status message
-      answer["response"] = "This is a status message :)";
+      std::string response;
+      if (this->getFile.is_open() && this->putFile.is_open()) {
+        response = "download and upload running";
+      } else if (this->putFile.is_open()) {
+        response = "upload running";
+      } else if (this->getFile.is_open()) {
+        response = "download running";
+      } else {
+        response = "ok";
+      }
+
+      answer["response"] = response;
       const std::string answerString = Json::writeString(stringBuilder, answer);
 
       // send answer
@@ -185,13 +204,13 @@ void con_handler::handle_read_command(const boost::system::error_code &err,
                             boost::bind(&con_handler::handle_write,
                                         shared_from_this(), placeholders::error,
                                         placeholders::bytes_transferred));
-
+    } else if (root["command"].compare("list") == 0) {
       // read next data
       sock.async_read_some(buffer(data, max_length),
                            boost::bind(&con_handler::handle_read_command,
                                        shared_from_this(), placeholders::error,
                                        placeholders::bytes_transferred));
-    } else if (root["command"].compare("list") == 0) {
+
       answer["command"] = "list";
 
       // TODO look for real data
@@ -209,47 +228,181 @@ void con_handler::handle_read_command(const boost::system::error_code &err,
                                         shared_from_this(), placeholders::error,
                                         placeholders::bytes_transferred));
 
+    } else if (root["command"].compare("put") == 0) {
       // read next data
       sock.async_read_some(buffer(data, max_length),
                            boost::bind(&con_handler::handle_read_command,
                                        shared_from_this(), placeholders::error,
                                        placeholders::bytes_transferred));
-    } else if (root["command"].compare("put") == 0) {
-      answer["command"] = "put";
-
-      // TODO establish real connection and receive file
-      answer["accept"] = false;
 
-      const std::string answerString = Json::writeString(stringBuilder, answer);
-      // send answer
-      sock.async_write_some(buffer(answerString, max_length),
-                            boost::bind(&con_handler::handle_write,
-                                        shared_from_this(), placeholders::error,
-                                        placeholders::bytes_transferred));
+      answer["command"] = "put";
 
+      if (this->putFile.is_open() && root["cancel"].asBool() == true) {
+        // cancel upload
+        this->putFile.close();
+        std::remove(this->putFileName.c_str());
+
+      } else if (this->putFile.is_open()) {
+        // upload chunks
+
+        // decode base64 string
+        const std::string data = base64::decode(root["data"].asString());
+
+        this->putFile << data;
+
+        // close file if put sends remaining = 0
+        if (root["remaining"].asInt() == 0) {
+          this->putFile.close();
+        }
+
+      } else {
+        // start upload
+        const std::string filename = root["file"].asString();
+        if (filename.find("/") != std::string::npos) {
+          // slashes in file names are illegal
+          answer["accept"] = false;
+
+          const std::string answerString =
+              Json::writeString(stringBuilder, answer);
+          // send answer
+          sock.async_write_some(buffer(answerString, max_length),
+                                boost::bind(&con_handler::handle_write,
+                                            shared_from_this(),
+                                            placeholders::error,
+                                            placeholders::bytes_transferred));
+        } else {
+          // build file path
+          this->putFileName = this->fileDirectory;
+          this->putFileName.append(filename);
+
+          // open file and test if it already exists
+          this->putFile.open(this->putFileName,
+                             std::ios::app | std::ios::binary);
+          if (this->putFile.tellp() != std::ios::beg) {
+            // file already exists
+            this->putFile.close();
+            answer["accept"] = false;
+
+            const std::string answerString =
+                Json::writeString(stringBuilder, answer);
+            // send answer
+            sock.async_write_some(buffer(answerString, max_length),
+                                  boost::bind(&con_handler::handle_write,
+                                              shared_from_this(),
+                                              placeholders::error,
+                                              placeholders::bytes_transferred));
+          } else {
+            // accept file
+            answer["accept"] = true;
+
+            const std::string answerString =
+                Json::writeString(stringBuilder, answer);
+            // send answer
+            sock.async_write_some(buffer(answerString, max_length),
+                                  boost::bind(&con_handler::handle_write,
+                                              shared_from_this(),
+                                              placeholders::error,
+                                              placeholders::bytes_transferred));
+          }
+        }
+      }
+    } else if (root["command"].compare("get") == 0) {
       // read next data
       sock.async_read_some(buffer(data, max_length),
                            boost::bind(&con_handler::handle_read_command,
                                        shared_from_this(), placeholders::error,
                                        placeholders::bytes_transferred));
-    } else if (root["command"].compare("get") == 0) {
-      answer["command"] = "get";
-
-      // TODO establish real connection and send file
-      answer["accept"] = false;
 
-      const std::string answerString = Json::writeString(stringBuilder, answer);
-      // send answer
-      sock.async_write_some(buffer(answerString, max_length),
-                            boost::bind(&con_handler::handle_write,
-                                        shared_from_this(), placeholders::error,
-                                        placeholders::bytes_transferred));
+      answer["command"] = "get";
 
-      // read next data
-      sock.async_read_some(buffer(data, max_length),
-                           boost::bind(&con_handler::handle_read_command,
-                                       shared_from_this(), placeholders::error,
-                                       placeholders::bytes_transferred));
+      // a get request is already being progressed
+      if (this->getFile.is_open()) {
+        answer["accept"] = false;
+
+        const std::string answerString =
+            Json::writeString(stringBuilder, answer);
+        // send answer
+        sock.async_write_some(
+            buffer(answerString, max_length),
+            boost::bind(&con_handler::handle_write, shared_from_this(),
+                        placeholders::error, placeholders::bytes_transferred));
+      } else {
+        // open file
+        const std::string filename = root["file"].asString();
+        if (filename.find("/") != std::string::npos) {
+          // slashes in file names are illegal
+          answer["accept"] = false;
+
+          const std::string answerString =
+              Json::writeString(stringBuilder, answer);
+          // send answer
+          sock.async_write_some(buffer(answerString, max_length),
+                                boost::bind(&con_handler::handle_write,
+                                            shared_from_this(),
+                                            placeholders::error,
+                                            placeholders::bytes_transferred));
+        } else {
+          std::string file = this->fileDirectory;
+          file.append(filename);
+
+          this->getFile = std::ifstream(file, std::ios::ate | std::ios::binary);
+
+          if (this->getFile.is_open() == 0) {
+            // file does not exist or cannot be opened
+            answer["accept"] = false;
+
+            const std::string answerString =
+                Json::writeString(stringBuilder, answer);
+            // send answer
+            sock.async_write_some(buffer(answerString, max_length),
+                                  boost::bind(&con_handler::handle_write,
+                                              shared_from_this(),
+                                              placeholders::error,
+                                              placeholders::bytes_transferred));
+          } else {
+            answer["accept"] = true;
+
+            const std::string answerString =
+                Json::writeString(stringBuilder, answer);
+
+            sock.async_write_some(buffer(answerString, max_length),
+                                  boost::bind(&con_handler::handle_write,
+                                              shared_from_this(),
+                                              placeholders::error,
+                                              placeholders::bytes_transferred));
+
+            answer = Json::Value();
+            answer["command"] = "get";
+
+            // get size of file
+            size_t size = this->getFile.tellg();
+            this->getFile.seekg(std::ios::beg);
+
+            char fileBuffer[max_data_length + 1];
+            while (size_t read =
+                       this->getFile.readsome(fileBuffer, max_data_length)) {
+              fileBuffer[read] = 0;
+              size -= read;
+              int remaining = size / max_data_length +
+                              (size % max_data_length == 0 ? 0 : 1);
+              answer["remaining"] = remaining;
+              answer["cancel"] = false;
+              answer["data"] = base64::encode(fileBuffer);
+
+              const std::string answerString =
+                  Json::writeString(stringBuilder, answer);
+
+              sock.async_write_some(
+                  buffer(answerString, max_length),
+                  boost::bind(&con_handler::handle_write, shared_from_this(),
+                              placeholders::error,
+                              placeholders::bytes_transferred));
+            }
+
+            this->getFile.close();
+          }
+        }
+      }
     } else if (root["command"].compare("close") == 0) {
       answer["command"] = "close";
       answer["response"] = "bye";

+ 23 - 0
daemon/src/base64.cpp

@@ -0,0 +1,23 @@
+#include "../include/base64.h"
+
+#include <boost/algorithm/string.hpp>
+#include <boost/archive/iterators/base64_from_binary.hpp>
+#include <boost/archive/iterators/binary_from_base64.hpp>
+#include <boost/archive/iterators/transform_width.hpp>
+
+std::string base64::decode(const std::string &val) {
+  using namespace boost::archive::iterators;
+  using It =
+      transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
+  return boost::algorithm::trim_right_copy_if(
+      std::string(It(std::begin(val)), It(std::end(val))),
+      [](char c) { return c == '\0'; });
+}
+
+std::string base64::encode(const std::string &val) {
+  using namespace boost::archive::iterators;
+  using It =
+      base64_from_binary<transform_width<std::string::const_iterator, 6, 8>>;
+  auto tmp = std::string(It(std::begin(val)), It(std::end(val)));
+  return tmp.append((3 - val.size() % 3) % 3, '=');
+}

+ 0 - 0
gui/include/.gitkeep


+ 53 - 0
gui/src/FooterForm.ui.qml

@@ -0,0 +1,53 @@
+import QtQuick 2.4
+import QtQuick.Controls 2.4
+import QtQuick.Layouts 1.3
+
+Page {
+    width: 1280
+    height: 200
+
+    Connections {
+        target: _qmlHandler
+        onLog: {
+            footerLog.append(logText)
+            footerFlickable.contentY = footerLog.height - footerFlickable.height
+        }
+    }
+
+    Rectangle {
+        anchors.fill: parent
+        color: "#404040"
+
+        ColumnLayout {
+            anchors.fill: parent
+
+            Text {
+                id: footerStatusText
+                height: 30
+                color: "#ffffff"
+                text: qsTr("Status: Connected to foobar")
+                font.pixelSize: 23
+                Layout.preferredHeight: 30
+                Layout.preferredWidth: parent.width
+            }
+
+            Flickable {
+                id: footerFlickable
+                flickableDirection: Flickable.VerticalFlick
+                Layout.preferredHeight: 170
+                Layout.preferredWidth: parent.width
+
+                TextArea.flickable: TextArea {
+                    selectByMouse: true
+                    id: footerLog
+                    wrapMode: TextArea.Wrap
+                    text: qsTr("Log goes here\nNew Line works as well")
+                    font.pointSize: 15
+                }
+
+                ScrollBar.vertical: ScrollBar {
+                }
+            }
+        }
+    }
+}

+ 20 - 0
gui/src/HelpForm.ui.qml

@@ -0,0 +1,20 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+
+Page {
+    width: 1280
+    height: 470
+
+    Text {
+        id: helpPlaceholder
+        x: 220
+        y: 135
+        width: 840
+        height: 200
+        color: "#ffffff"
+        text: qsTr("Tons of useful information goes here")
+        horizontalAlignment: Text.AlignHCenter
+        verticalAlignment: Text.AlignVCenter
+        font.pixelSize: 40
+    }
+}

+ 65 - 0
gui/src/IpPopupForm.ui.qml

@@ -0,0 +1,65 @@
+import QtQuick 2.4
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.3
+
+Popup {
+    id: popup
+    height: 200
+    dim: true
+    clip: false
+    width: 400
+    modal: true
+    focus: true
+    closePolicy: Popup.NoAutoClose
+
+    ColumnLayout {
+        anchors.fill: parent
+
+        Text {
+            Layout.alignment: Qt.AlignCenter
+            id: popupText
+            color: "#ffffff"
+            text: qsTr("Enter the IP to connect:")
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            font.pixelSize: 20
+        }
+
+        TextField {
+            Layout.alignment: Qt.AlignCenter
+            id: popupIpInput
+            selectByMouse: true
+            text: qsTr("")
+            placeholderText: "Enter IP"
+            horizontalAlignment: Text.AlignHCenter
+            validator: RegExpValidator {
+                regExp: /^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$/
+            }
+        }
+
+        Text {
+            id: popupStatusText
+            color: "#df3f3f"
+            text: qsTr("Text field for status")
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            Layout.alignment: Qt.AlignCenter
+            font.pixelSize: 20
+        }
+
+        Button {
+            Layout.alignment: Qt.AlignCenter
+            id: popupConnectButton
+            text: qsTr("Connect")
+            enabled: popupIpInput.acceptableInput
+            font.pointSize: 16
+            // @disable-check M223
+            onClicked: {
+                // @disable-check M222
+                _qmlHandler.onIpPopupEnterIp(popupIpInput.text)
+                // @disable-check M222
+                popup.close()
+            }
+        }
+    }
+}

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

@@ -0,0 +1,73 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+import QtQuick.Layouts 1.3
+
+Page {
+    width: 1280
+    height: 470
+
+    font.capitalization: Font.MixedCase
+
+    Connections {
+        target: _qmlHandler
+        onMessage: {
+            messagesLog.append(msg)
+            messagesFlickable.contentY = messagesLog.height - messagesFlickable.height
+        }
+    }
+
+    ColumnLayout {
+        anchors.fill: parent
+
+        Flickable {
+            id: messagesFlickable
+            flickableDirection: Flickable.VerticalFlick
+            Layout.preferredHeight: 400
+            Layout.preferredWidth: parent.width
+
+            TextArea.flickable: TextArea {
+                id: messagesLog
+                selectByMouse: true
+                wrapMode: TextArea.Wrap
+                text: qsTr("Messages go here\nNew Line works as well")
+                font.pointSize: 15
+            }
+
+            ScrollBar.vertical: ScrollBar {
+            }
+        }
+
+        RowLayout {
+            Layout.preferredHeight: 70
+            Layout.preferredWidth: parent.width
+
+            TextField {
+                id: messagesInputField
+                selectByMouse: true
+                Layout.preferredWidth: 1060
+                Layout.preferredHeight: parent.height
+                placeholderText: "Enter message..."
+                text: qsTr("")
+                font.pixelSize: 20
+                Keys.onReturnPressed: messagesSendButton.activate()
+                Keys.onEnterPressed: messagesSendButton.activate()
+            }
+
+            Button {
+                id: messagesSendButton
+                Layout.preferredWidth: 180
+                Layout.preferredHeight: parent.height
+                text: qsTr("Send")
+
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M222
+                    messagesSendButton.activate()
+                }
+
+                enabled: messagesInputField.text != ""
+                function activate() {if(messagesInputField.text != "") {_qmlHandler.onMessagesSendButton(messagesInputField.text); messagesInputField.text = ""}}
+            }
+        }
+    }
+}

+ 7 - 19
gui/src/ReceivingForm.ui.qml

@@ -3,26 +3,14 @@ import QtQuick.Controls 2.5
 
 Page {
     width: 1280
-    height: 670
-    title: "Receiving"
+    height: 470
 
-    Label {
-        color: "#000000"
-        text: "This is where the UI for receiving files goes."
-        font.pointSize: 30
-        anchors.verticalCenterOffset: 1
-        anchors.horizontalCenterOffset: 1
-        z: 3
-        anchors.centerIn: parent
-    }
-
-    Rectangle {
-        id: rectangle
-        x: 0
-        y: 0
-        width: 1280
-        height: 670
+    Text {
+        id: receivingPlaceholder
+        x: 154
+        y: 212
         color: "#ffffff"
-        z: 2
+        text: qsTr("This will be implemented with the actual functionality")
+        font.pixelSize: 40
     }
 }

+ 51 - 19
gui/src/SendingForm.ui.qml

@@ -4,47 +4,79 @@ import QtQuick.Dialogs 1.0
 
 Page {
     width: 1280
-    height: 670
+    height: 470
+    font.capitalization: Font.MixedCase
 
     title: qsTr("Sending")
 
     Connections {
-      target: _qmlHandler
-      onSetFileUrlText: {
-        fileUrlText.text = signalText
-      }
+        target: _qmlHandler
+        onSendingSetFileUrlText: {
+            sendingSelectedFileText.text = signalText
+        }
     }
 
     Button {
-        id: selectFileButton
-        x: 510
-        y: 291
+        id: sendingClearFileButton
+        x: 900
+        y: 40
+        width: 260
+        height: 90
+        text: qsTr("Clear Selection")
+        font.pointSize: 16
+    }
+
+    Button {
+        id: sendingSelectFileButton
+        x: 120
+        y: 40
         width: 260
-        height: 89
+        height: 90
         text: qsTr("Select File")
         font.pointSize: 16
-        onClicked: {fileDialog.open()}
+        // @disable-check M223
+        onClicked: {
+            // @disable-check M222
+            sendingFileDialog.open()
+        }
     }
 
     Text {
-        id: fileUrlText
+        id: sendingSelectedFileText
         x: 54
-        y: 427
+        y: 186
         width: 1172
         height: 52
         color: "#ffffff"
-        text: qsTr("")
+        text: qsTr("Selected File: None")
         verticalAlignment: Text.AlignVCenter
         horizontalAlignment: Text.AlignHCenter
         font.pixelSize: 23
     }
 
     FileDialog {
-      id: fileDialog
-      title: "Please choose a file"
-      folder: shortcuts.home
-      onAccepted: {
-        _qmlHandler.onSelectFile(fileDialog.fileUrl)
-      }
+        id: sendingFileDialog
+        title: "Please choose a file"
+        folder: shortcuts.home
+        // @disable-check M223
+        onAccepted: {
+            // @disable-check M222
+            _qmlHandler.onSendingSelectFileButton(fileDialog.fileUrl)
+        }
+    }
+
+    Button {
+        id: sendingSendFileButton
+        x: 510
+        y: 304
+        width: 260
+        height: 90
+        text: qsTr("Send File")
+        font.pointSize: 16
+        // @disable-check M223
+        onClicked: {
+            // @disable-check M222
+            _qmlHandler.onSendingSendFileButton()
+        }
     }
 }

+ 71 - 0
gui/src/SettingsForm.ui.qml

@@ -0,0 +1,71 @@
+import QtQuick 2.12
+import QtQuick.Controls 2.5
+
+Page {
+    width: 1280
+    height: 470
+
+    font.capitalization: Font.MixedCase
+
+    ComboBox {
+        id: settingsCovertMethodPicker
+        x: 328
+        y: 59
+        width: 285
+        height: 48
+
+        model: ListModel {
+            ListElement {
+                text: "Method 1"
+            }
+            ListElement {
+                text: "Method 2"
+            }
+            ListElement {
+                text: "Method 3"
+            }
+        }
+    }
+
+    Text {
+        id: settingsCovertMethodText
+        x: 56
+        y: 71
+        color: "#ffffff"
+        text: qsTr("Covert Channel Method:")
+        font.pixelSize: 20
+    }
+
+    Button {
+        id: settingsSwitchServerButton
+        x: 549
+        y: 343
+        width: 182
+        height: 71
+        text: qsTr("Switch Server")
+        font.pointSize: 15
+        // @disable-check M223
+        onClicked: {
+            // @disable-check M222
+            _qmlHandler.onSettingsSwitchServerButton()
+        }
+    }
+
+    Text {
+        id: settingsSaveIpText
+        x: 56
+        y: 154
+        color: "#ffffff"
+        text: qsTr("Save last IP:")
+        font.pixelSize: 20
+    }
+
+    Switch {
+        id: settingsSaveIpSwitch
+        x: 328
+        y: 142
+        text: qsTr("")
+        checked: false
+        display: AbstractButton.IconOnly
+    }
+}

+ 83 - 0
gui/src/SwitchIpPopupForm.ui.qml

@@ -0,0 +1,83 @@
+import QtQuick 2.4
+import QtQuick.Controls 2.3
+import QtQuick.Layouts 1.3
+
+Popup {
+    id: popup
+    height: 200
+    dim: true
+    clip: false
+    width: 400
+    modal: true
+    focus: true
+    closePolicy: Popup.NoAutoClose
+
+    ColumnLayout {
+        anchors.fill: parent
+
+        Text {
+            Layout.alignment: Qt.AlignCenter
+            id: switchText
+            color: "#ffffff"
+            text: qsTr("Enter the IP to connect:")
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            font.pixelSize: 20
+        }
+
+        TextField {
+            Layout.alignment: Qt.AlignCenter
+            id: switchIpInput
+            selectByMouse: true
+            text: qsTr("")
+            placeholderText: "Enter IP"
+            horizontalAlignment: Text.AlignHCenter
+            validator: RegExpValidator {
+                regExp: /^(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))\.(\d|[1-9]\d|1\d\d|2([0-4]\d|5[0-5]))$/
+            }
+        }
+
+        Text {
+            id: switchStatusText
+            color: "#df3f3f"
+            text: qsTr("Text field for status")
+            horizontalAlignment: Text.AlignHCenter
+            verticalAlignment: Text.AlignVCenter
+            Layout.alignment: Qt.AlignCenter
+            font.pixelSize: 20
+        }
+
+        RowLayout {
+            Layout.alignment: Qt.AlignCenter
+
+            Button {
+                Layout.alignment: Qt.AlignLeft
+                id: switchConnectButton
+                text: qsTr("Connect")
+                rightPadding: 8
+                padding: 12
+                enabled: switchIpInput.acceptableInput
+                font.pointSize: 16
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M222
+                    _qmlHandler.onSwitchPopupEnterIp(popupIpInput.text)
+                    // @disable-check M222
+                    popup.close()
+                }
+            }
+
+            Button {
+                Layout.alignment: Qt.AlignRight
+                id: switchCancelButton
+                text: qsTr("Cancel")
+                font.pointSize: 16
+                // @disable-check M223
+                onClicked: {
+                    // @disable-check M222
+                    popup.close()
+                }
+            }
+        }
+    }
+}

+ 1 - 1
gui/src/main.cpp

@@ -40,7 +40,7 @@ int main(int argc, char *argv[]) {
 
     // Set the path to the CLI - pass argument h (help) for now
     // TODO: Change hardcoded path
-    execl("../../cli/build/ccats-cli", "ccats-cli", "c", "127.0.0.1",
+    execl("../../cli/build/ccats-cli", "ccats-cli", "127.0.0.1", "--machine",
           (char *)NULL);
 
     exit(1);

+ 67 - 34
gui/src/main.qml

@@ -8,50 +8,83 @@ ApplicationWindow {
     height: 720
     title: qsTr("Covert Channel - Control Panel")
 
-    header: ToolBar {
-        contentHeight: 50
+    Connections {
+        target: _qmlHandler
+        onSettingsOpenSwitchServerPopup: {
+            switchDialog.open()
+        }
+    }
+
+    SwipeView {
+        id: swipeView
+        anchors.fill: parent
+        currentIndex: header.currentIndex
+
+        SendingForm {
+
+        }
+
+        ReceivingForm {
+
+        }
+
+        MessagesForm {
 
-        ToolButton {
-            id: toolButton
-            text: stackView.depth > 1 ? "\u25C0" : "\u2630"
-            font.pixelSize: Qt.application.font.pixelSize * 1.6
-            onClicked: {
-                if (stackView.depth > 1) {
-                    stackView.pop()
-                } else {
-                    drawer.open()
-                }
-            }
         }
 
-        Label {
-            text: stackView.currentItem.title
-            anchors.centerIn: parent
+        SettingsForm {
+
+        }
+
+        HelpForm {
+
         }
     }
 
-    Drawer {
-        id: drawer
-        width: window.width * 0.66
-        height: 50
+    header: TabBar {
+        id: header
+        currentIndex: swipeView.currentIndex
+        contentHeight: 50
+
+        TabButton {
+            text: qsTr("Sending")
+        }
+
+        TabButton {
+            text: qsTr("Receiving")
+        }
+
+        TabButton {
+            text: qsTr("Messages")
+        }
 
-        Column {
-            anchors.fill: parent
+        TabButton {
+            text: qsTr("Settings")
+        }
 
-            ItemDelegate {
-                text: qsTr("Receiving")
-                width: parent.width
-                onClicked: {
-                    stackView.push("ReceivingForm.ui.qml")
-                    drawer.close()
-                }
-            }
+        TabButton {
+            text: qsTr("Help")
         }
     }
 
-    StackView {
-        id: stackView
-        initialItem: "SendingForm.ui.qml"
-        anchors.fill: parent
+    footer: FooterForm {
+
+    }
+
+    IpPopupForm {
+        id: ipDialog
+        x: Math.round((parent.width - width) / 2)
+        y: Math.round((parent.height - height) / 2)
+    }
+
+    SwitchIpPopupForm {
+        id: switchDialog
+        x: Math.round((parent.width - width) / 2)
+        y: Math.round((parent.height - height) / 2)
+    }
+
+    Component.onCompleted: {
+      swipeView.interactive = false
+      ipDialog.open()
     }
 }

+ 6 - 0
gui/src/qml.qrc

@@ -3,6 +3,12 @@
         <file>main.qml</file>
         <file>SendingForm.ui.qml</file>
         <file>ReceivingForm.ui.qml</file>
+        <file>SettingsForm.ui.qml</file>
+        <file>MessagesForm.ui.qml</file>
         <file>qtquickcontrols2.conf</file>
+        <file>IpPopupForm.ui.qml</file>
+        <file>HelpForm.ui.qml</file>
+        <file>FooterForm.ui.qml</file>
+        <file>SwitchIpPopupForm.ui.qml</file>
     </qresource>
 </RCC>

+ 27 - 8
gui/src/qmlhandler.cpp

@@ -1,15 +1,34 @@
 #include "qmlhandler.h"
-#include <string>
-
-using namespace std;
-
-string fileUrl;
 
 QMLHandler::QMLHandler(QObject *parent) : QObject(parent) {}
 
-void QMLHandler::onSelectFile(QUrl url) {
+// Sending
+void QMLHandler::onSendingSelectFileButton(QUrl url) {
   qInfo() << "File Selected: " << url.toString();
-  emit setFileUrlText("Selected File: " + url.toString());
+  emit sendingSetFileUrlText("Selected File: " + url.toString());
+}
+
+void QMLHandler::onSendingSendFileButton() {
+  qInfo() << "Sending File";
+  emit log("Sending File");
+}
+
+// Receiving
+
+// Messages
+void QMLHandler::onMessagesSendButton(QString msg) { emit message(msg); }
+
+// Settings
+void QMLHandler::onSettingsSwitchServerButton() {
+  emit settingsOpenSwitchServerPopup();
+}
+
+// Ip Popup
+void QMLHandler::onIpPopupEnterIp(QString ip) {
+  qInfo() << "Connecting to " << ip;
+}
 
-  fileUrl = url.toString().toStdString();
+// Switch Popup
+void QMLHandler::onSwitchPopupEnterIp(QString ip) {
+  qInfo() << "Switching to " << ip;
 }

+ 38 - 5
gui/src/qmlhandler.h

@@ -1,5 +1,6 @@
-#ifndef CCATS_GUI_QMLHANDLER_H
-#define CCATS_GUI_QMLHANDLER_H
+#ifndef QMLHANDLER_H
+#define QMLHANDLER_H
+
 
 #include <QDebug>
 #include <QObject>
@@ -12,10 +13,42 @@ public:
   explicit QMLHandler(QObject *parent = 0);
 
 signals:
-  void setFileUrlText(QString signalText);
+  // Sending
+  void sendingSetFileUrlText(QString signalText);
+
+  // Receiving
+
+  // Messages
+  void message(QString msg);
+
+  // Settings
+  void settingsOpenSwitchServerPopup();
+
+  // Ip Popup
+
+  // Switch Popup
+
+  // Footer
+  void log(QString logText);
 
 public slots:
-  void onSelectFile(QUrl url);
+  // Sending
+  void onSendingSelectFileButton(QUrl url);
+  void onSendingSendFileButton();
+
+  // Receiving
+
+  // Messages
+  void onMessagesSendButton(QString msg);
+
+  // Settings
+  void onSettingsSwitchServerButton();
+
+  // Ip Popup
+  void onIpPopupEnterIp(QString ip);
+
+  // Switch Popup
+  void onSwitchPopupEnterIp(QString ip);
 };
 
-#endif // CCATS_GUI_QMLHANDLER_H
+#endif // QMLHANDLER_H

+ 1 - 4
gui/src/qtquickcontrols2.conf

@@ -7,7 +7,4 @@ Style=Material
 
 [Material]
 Theme=Dark
-;Accent=BlueGrey
-;Primary=BlueGray
-;Foreground=Brown
-;Background=Grey
+Accent=Blue