Quellcode durchsuchen

Merge branch '9-us09-file-transfer-client-server'

Resolve "US09.2:  File Transfer Client Server (Server side)"

Closes #9

See merge request tobias.wach/ccats!8
anon vor 4 Jahren
Ursprung
Commit
eac5a2b5c9
5 geänderte Dateien mit 266 neuen und 44 gelöschten Zeilen
  1. 1 1
      daemon/CMakeLists.txt
  2. 21 1
      daemon/include/Server.h
  3. 26 0
      daemon/include/base64.h
  4. 195 42
      daemon/src/Server.cpp
  5. 23 0
      daemon/src/base64.cpp

+ 1 - 1
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)

+ 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

+ 195 - 42
daemon/src/Server.cpp

@@ -1,4 +1,6 @@
 #include "../include/Server.h"
+#include "../include/base64.h"
+
 #include <iostream>
 #include <json/json.h>
 
@@ -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, '=');
+}