2021-07-08 05:41:34 +00:00
|
|
|
#include "rename_object.hpp"
|
|
|
|
#include "functions.hpp"
|
|
|
|
#include <climits>
|
|
|
|
#include <iostream>
|
|
|
|
#include <memory>
|
|
|
|
#include <restbed>
|
|
|
|
#include <sstream>
|
|
|
|
#include <rapidjson/document.h>
|
|
|
|
#include <rapidjson/stringbuffer.h>
|
|
|
|
#include <rapidjson/writer.h>
|
|
|
|
#include <string>
|
|
|
|
#include <chrono>
|
|
|
|
|
|
|
|
#include "jwt.hpp"
|
|
|
|
#include "filesystem/filesystem.hpp"
|
|
|
|
#include "config/config.hpp"
|
2022-02-26 10:33:17 +00:00
|
|
|
#include "fileobject.hpp"
|
2021-07-08 05:41:34 +00:00
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::vector< RenameLibrary > libraries{};
|
2021-07-08 05:41:34 +00:00
|
|
|
Configuration cfg;
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void sendResponse( const std::string &response, int status_code,
|
|
|
|
const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
session->close( status_code, response,
|
|
|
|
{ { "Content-Length", std::to_string( response.length() ) },
|
|
|
|
{ "Access-Control-Allow-Origin", "*" } } );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void performPostFunc(
|
|
|
|
const std::shared_ptr< restbed::Session > &session,
|
|
|
|
const std::function< void(
|
|
|
|
const std::shared_ptr< restbed::Session >,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > & ) > &callback ) {
|
|
|
|
const auto request = session->get_request();
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
int content_length = request->get_header( "Content-Length", 0 );
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
session->fetch(
|
|
|
|
content_length,
|
|
|
|
[callback]( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
const restbed::Bytes &body ) {
|
|
|
|
rapidjson::Document doc;
|
|
|
|
doc.Parse( reinterpret_cast< const char * >( body.data() ),
|
|
|
|
body.size() );
|
|
|
|
if ( doc.HasParseError() ) {
|
|
|
|
sendResponse( "ERROR: Invalid body!", 401, session );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
callback( session, doc );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
bool verifyLogin( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( doc.FindMember( "token" ) == doc.MemberEnd() ||
|
|
|
|
!doc["token"].IsString() ) {
|
|
|
|
sendResponse( "ERROR: Invalid token!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return false;
|
|
|
|
}
|
2021-07-09 16:28:16 +00:00
|
|
|
const auto *token = doc["token"].GetString();
|
2022-03-11 23:25:10 +00:00
|
|
|
auto res = verifyJWT( token );
|
|
|
|
if ( !res ) {
|
|
|
|
sendResponse( "ERROR: Invalid token!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::vector< std::pair< RenameLibrary *, size_t > > getLibraries() {
|
|
|
|
std::vector< std::pair< RenameLibrary *, size_t > > result{};
|
|
|
|
for ( size_t i = 0; i < libraries.size(); i++ ) {
|
|
|
|
result.emplace_back( &libraries[i], i );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-02-27 18:24:35 +00:00
|
|
|
std::string getLibrariesJson() {
|
|
|
|
auto libraries = getLibraries();
|
2021-07-08 05:41:34 +00:00
|
|
|
std::ostringstream result;
|
2022-02-27 18:24:35 +00:00
|
|
|
result << "{\n \"libraries\": [\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !libraries.empty() ) {
|
|
|
|
for ( const auto &library : libraries ) {
|
2022-02-27 18:24:35 +00:00
|
|
|
result << " {\n \"id\": " << library.second << ",\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
result << " \"name\": \"" << safeJson( library.first->getName() )
|
|
|
|
<< "\"\n,";
|
|
|
|
result << " \"multiple_files\": "
|
|
|
|
<< ( library.first->canRenameMultipleFiles() ? "true"
|
|
|
|
: "false" )
|
|
|
|
<< "\n },\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
result.seekp( -2, std::ios_base::end );
|
2021-07-08 05:41:34 +00:00
|
|
|
result << "\n";
|
|
|
|
}
|
2021-07-10 20:48:31 +00:00
|
|
|
result << " ]\n}";
|
|
|
|
auto res = result.str();
|
|
|
|
return res;
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-02-27 18:24:35 +00:00
|
|
|
void getLibrariesRest( const std::shared_ptr< restbed::Session > &session ) {
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( getLibrariesJson(), restbed::OK, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::vector< RenameObject > getOptions( const RenameObject &search ) {
|
2022-02-27 19:56:20 +00:00
|
|
|
auto library_id = search.getLibraryId();
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( library_id >= libraries.size() ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return {};
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
auto result = libraries[library_id].getOptions( search );
|
|
|
|
for ( auto &res : result ) {
|
|
|
|
res.setLibraryId( library_id );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string getOptionsJson( const RenameObject &search ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
std::ostringstream res;
|
2021-07-10 20:48:31 +00:00
|
|
|
res << "{\n \"options\": [\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
auto options = getOptions( search );
|
|
|
|
if ( !options.empty() ) {
|
|
|
|
for ( auto &option : options ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
res << option.toJson();
|
|
|
|
res << ",\n";
|
|
|
|
}
|
|
|
|
res.seekp( -2, std::ios_base::end );
|
|
|
|
res << "\n";
|
|
|
|
}
|
2021-07-10 20:48:31 +00:00
|
|
|
res << " ]\n}";
|
|
|
|
auto result = res.str();
|
|
|
|
return result;
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getOptionsRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( doc.HasParseError() ) {
|
|
|
|
sendResponse( "ERROR: Invalid body!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !verifyLogin( session, doc ) ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "info" ) == doc.MemberEnd() ||
|
|
|
|
!doc["info"].IsObject() ) {
|
|
|
|
sendResponse( "ERROR: Invalid search!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
RenameObject search;
|
|
|
|
rapidjson::StringBuffer sb;
|
2022-03-11 23:25:10 +00:00
|
|
|
rapidjson::Writer< rapidjson::StringBuffer > writer( sb );
|
|
|
|
doc["info"].Accept( writer );
|
2021-07-08 05:41:34 +00:00
|
|
|
std::string s = sb.GetString();
|
2022-03-11 23:25:10 +00:00
|
|
|
search.fromJson( s );
|
|
|
|
if ( search.getPresentedName().empty() ) {
|
|
|
|
sendResponse( "Empty search", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( getOptionsJson( search ), 200, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::vector< std::unordered_map< std::string, std::string > >
|
|
|
|
getCustomKeys( size_t library_id ) {
|
|
|
|
if ( library_id >= libraries.size() ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return {};
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
return libraries[library_id].getCustomKeys();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector< std::pair< string, string > >
|
|
|
|
getFieldOptions( size_t library_id, const std::string &field ) {
|
|
|
|
if ( library_id >= libraries.size() ) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
return libraries[library_id].getCustomKeyOptions( field );
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string getFieldDefault( size_t library_id, const std::string &field ) {
|
|
|
|
if ( library_id >= libraries.size() ) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
return libraries[library_id].getCustomKeyDefault( field );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string getCustomKeysJson( size_t library_id ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
std::ostringstream res;
|
2022-03-06 15:40:15 +00:00
|
|
|
res << "{\n \"custom_keys\": [\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
auto custom_keys = getCustomKeys( library_id );
|
|
|
|
if ( !custom_keys.empty() ) {
|
|
|
|
for ( auto &key : custom_keys ) {
|
|
|
|
res << " {\n";
|
|
|
|
res << " \"name\": \"" << safeJson( key["name"] ) << "\",\n";
|
|
|
|
res << " \"type\": \"" << safeJson( key["type"] ) << "\",\n";
|
2022-03-12 14:05:34 +00:00
|
|
|
res << " \"input\": " << safeJson( key["input"] ) << "\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
res << " },\n";
|
|
|
|
}
|
|
|
|
res.seekp( -2, std::ios_base::end );
|
|
|
|
res << "\n";
|
|
|
|
}
|
|
|
|
res << " ]\n}";
|
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string getFieldOptionsJson( size_t library_id, const std::string &field ) {
|
|
|
|
std::ostringstream res;
|
|
|
|
res << "{\n \"options\": [\n";
|
|
|
|
auto options = getFieldOptions( library_id, field );
|
|
|
|
if ( !options.empty() ) {
|
|
|
|
for ( auto &option : options ) {
|
2022-03-06 15:40:15 +00:00
|
|
|
res << " {\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
// TODO replace first/second with a proper name, create option
|
|
|
|
// struct or something
|
|
|
|
res << " \"code\": \"" << safeJson( option.first ) << "\",\n";
|
|
|
|
res << " \"name\": \"" << safeJson( option.second ) << "\"\n";
|
2022-03-06 15:40:15 +00:00
|
|
|
res << " },\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
res.seekp( -2, std::ios_base::end );
|
|
|
|
res << "\n";
|
|
|
|
}
|
2022-03-06 15:40:15 +00:00
|
|
|
res << " ]\n}";
|
2021-07-08 05:41:34 +00:00
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string getFieldDefaultJson( size_t library_id, const std::string &field ) {
|
|
|
|
std::ostringstream res;
|
|
|
|
res << "{\n \"default\": \"" << getFieldDefault( library_id, field )
|
|
|
|
<< "\"}";
|
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
void getCustomKeysRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( doc.FindMember( "library_id" ) == doc.MemberEnd() ||
|
|
|
|
!doc["library_id"].IsUint64() ) {
|
|
|
|
sendResponse( "ERROR: Invalid library_id!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-02-27 19:56:20 +00:00
|
|
|
size_t library_id = doc["library_id"].GetUint64();
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( getCustomKeysJson( library_id ), 200, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getFieldOptionsRest(
|
|
|
|
const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( doc.FindMember( "library_id" ) == doc.MemberEnd() ||
|
|
|
|
!doc["library_id"].IsUint64() ) {
|
|
|
|
sendResponse( "ERROR: Invalid library_id!", 401, session );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( doc.FindMember( "field" ) == doc.MemberEnd() ||
|
|
|
|
!doc["field"].IsString() ) {
|
|
|
|
sendResponse( "ERROR: Invalid field!", 401, session );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
auto library_id = doc["library_id"].GetUint64();
|
|
|
|
auto field = doc["field"].GetString();
|
|
|
|
sendResponse( getFieldOptionsJson( library_id, field ), 200, session );
|
|
|
|
}
|
|
|
|
|
|
|
|
void getFieldDefaultRest(
|
|
|
|
const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( doc.FindMember( "library_id" ) == doc.MemberEnd() ||
|
|
|
|
!doc["library_id"].IsUint64() ) {
|
|
|
|
sendResponse( "ERROR: Invalid library_id!", 401, session );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( doc.FindMember( "field" ) == doc.MemberEnd() ||
|
|
|
|
!doc["field"].IsString() ) {
|
|
|
|
sendResponse( "ERROR: Invalid field!", 401, session );
|
|
|
|
return;
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
auto library_id = doc["library_id"].GetUint64();
|
|
|
|
auto field = doc["field"].GetString();
|
|
|
|
sendResponse( getFieldDefaultJson( library_id, field ), 200, session );
|
|
|
|
}
|
|
|
|
|
|
|
|
std::pair< bool, std::string > renamePath( std::string path,
|
|
|
|
const RenameObject &renamer ) {
|
|
|
|
if ( renamer.getLibraryId() >= libraries.size() ) {
|
|
|
|
return { false, "Invalid library id" };
|
|
|
|
}
|
|
|
|
if ( path[0] != '/' ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
path = cfg.getSourcePath() + "/" + path;
|
|
|
|
}
|
2021-07-11 12:43:51 +00:00
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
auto canon_path = FSLib::canonical( path );
|
|
|
|
if ( canon_path.substr( 0, cfg.getSourcePath().length() ) !=
|
|
|
|
cfg.getSourcePath() ) {
|
|
|
|
return { false, "Invalid path" };
|
2021-07-11 12:43:51 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !FSLib::exists( path ) ) {
|
|
|
|
return { false, "Source doesn't exist" };
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
return { libraries[renamer.getLibraryId()].renamePath( path, renamer ),
|
|
|
|
"Library error" };
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string renamePathJson( const std::string &path,
|
|
|
|
const RenameObject &renamer ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
std::ostringstream res;
|
|
|
|
res << "{\n \"success\": ";
|
2022-03-11 23:25:10 +00:00
|
|
|
auto rename_result = renamePath( path, renamer );
|
|
|
|
if ( rename_result.first ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
res << "true";
|
|
|
|
} else {
|
|
|
|
res << "false";
|
|
|
|
}
|
2022-02-26 10:33:17 +00:00
|
|
|
res << ",\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !rename_result.first ) {
|
|
|
|
res << " \"error\": \"" << safeJson( rename_result.second ) << "\"\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
res << "}";
|
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void renamePathRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( !verifyLogin( session, doc ) ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "path" ) == doc.MemberEnd() ||
|
|
|
|
!doc["path"].IsString() ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
// TODO validate path, also validate against config
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( "ERROR: Invalid path!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "info" ) == doc.MemberEnd() ||
|
|
|
|
!doc["info"].IsObject() ) {
|
|
|
|
sendResponse( "ERROR: Invalid info!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::string path = doc["path"].GetString();
|
|
|
|
RenameObject renamer;
|
|
|
|
rapidjson::StringBuffer sb;
|
2022-03-11 23:25:10 +00:00
|
|
|
rapidjson::Writer< rapidjson::StringBuffer > writer( sb );
|
|
|
|
doc["info"].Accept( writer );
|
2021-07-08 05:41:34 +00:00
|
|
|
std::string s = sb.GetString();
|
2022-03-11 23:25:10 +00:00
|
|
|
renamer.fromJson( s );
|
|
|
|
sendResponse( renamePathJson( path, renamer ), 200, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string getFilesJson() {
|
|
|
|
std::ostringstream res;
|
2021-07-10 20:48:31 +00:00
|
|
|
res << "{\n \"files\": [\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
auto files = getFilesInSource( cfg.getSourcePath() );
|
|
|
|
if ( !files.empty() ) {
|
|
|
|
for ( const auto &file : files ) {
|
|
|
|
res << " {\n \"path\": \"" << safeJson( file.getPath() )
|
|
|
|
<< "\",\n";
|
2022-02-26 10:33:17 +00:00
|
|
|
res << " \"depth\": " << file.getDepth() << ",\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
res << " \"type\": \""
|
|
|
|
<< ( file.getFileType() == TYPE_FILE ? "file" : "directory" )
|
|
|
|
<< "\"\n },\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
res.seekp( -2, std::ios_base::end );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2021-07-10 20:48:31 +00:00
|
|
|
res << "\n ]\n}";
|
2021-07-08 05:41:34 +00:00
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getFilesRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( !verifyLogin( session, doc ) ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( getFilesJson(), restbed::OK, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::vector< std::pair< uint64_t, std::string > > getTargets() {
|
|
|
|
std::vector< std::pair< uint64_t, std::string > > result;
|
2021-07-08 05:41:34 +00:00
|
|
|
const auto &targets = cfg.getTargetPaths();
|
2022-03-11 23:25:10 +00:00
|
|
|
for ( uint64_t i = 0; i < targets.size(); i++ ) {
|
|
|
|
result.emplace_back( i, targets[i].second );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string getTargetsJson() {
|
|
|
|
std::ostringstream res;
|
2021-07-10 20:48:31 +00:00
|
|
|
res << "{\n \"targets\": [\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
auto targets = getTargets();
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !targets.empty() ) {
|
|
|
|
for ( const auto &target : targets ) {
|
|
|
|
res << " {\n"
|
|
|
|
<< " \"id\": " << target.first << ",\n";
|
|
|
|
res << " \"name\": \"" << safeJson( target.second )
|
|
|
|
<< "\"\n },\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
res.seekp( -2, std::ios_base::end );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2021-07-10 20:48:31 +00:00
|
|
|
res << "\n ]\n}";
|
2021-07-08 05:41:34 +00:00
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getTargetsRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( !verifyLogin( session, doc ) ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( getTargetsJson(), restbed::OK, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string getTargetDirectoriesJson( uint64_t id ) {
|
|
|
|
if ( id >= cfg.getTargetPaths().size() ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return "";
|
|
|
|
}
|
|
|
|
std::ostringstream res;
|
2021-07-10 20:48:31 +00:00
|
|
|
res << "{\n \"target_directories\": [\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
auto dirs = getTargetDirectories( cfg.getTargetPaths()[id].first );
|
|
|
|
if ( !dirs.empty() ) {
|
|
|
|
for ( const auto &dir : dirs ) {
|
|
|
|
res << " \"" << safeJson( dir.getPath() ) << "\",\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
res.seekp( -2, std::ios_base::end );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2021-07-10 20:48:31 +00:00
|
|
|
res << "\n ]\n}";
|
2021-07-08 05:41:34 +00:00
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getTargetDirectoriesRest(
|
|
|
|
const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( !verifyLogin( session, doc ) ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
uint64_t id = 0;
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "path_id" ) == doc.MemberEnd() ||
|
|
|
|
!doc["path_id"].IsUint64() ||
|
|
|
|
( id = doc["path_id"].GetUint64() ) >= cfg.getTargetPaths().size() ) {
|
|
|
|
sendResponse( "ERROR: Invalid path_id!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( getTargetDirectoriesJson( id ), restbed::OK, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::pair< bool, std::string > move( std::string path, uint64_t target_id,
|
|
|
|
const std::string &containing_dir ) {
|
|
|
|
if ( path[0] != '/' ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
path = cfg.getSourcePath() + "/" + path;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
auto canon_path = FSLib::canonical( path );
|
|
|
|
if ( canon_path.substr( 0, cfg.getSourcePath().length() ) !=
|
|
|
|
cfg.getSourcePath() ) {
|
|
|
|
return { false, "Invalid path" };
|
2021-07-11 12:43:51 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( target_id >= cfg.getTargetPaths().size() ) {
|
|
|
|
return { false, "Invalid target_id" };
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !FSLib::exists( path ) ) {
|
|
|
|
return { false, "Source doesn't exist" };
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2021-07-11 12:43:51 +00:00
|
|
|
auto target_start = cfg.getTargetPaths()[target_id].first;
|
|
|
|
auto target_dir = target_start + FSLib::dir_divisor + containing_dir;
|
2022-03-11 23:25:10 +00:00
|
|
|
auto target_canon = FSLib::canonical( target_dir );
|
|
|
|
if ( target_canon.substr( 0, target_start.length() ) != target_start &&
|
|
|
|
!target_canon.empty() ) {
|
|
|
|
return { false, "Invalid target" };
|
2021-07-11 12:43:51 +00:00
|
|
|
}
|
|
|
|
// might result in needless false positives, but better be safe than sorry
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( target_canon.empty() &&
|
|
|
|
target_dir.find( ".." ) != std::string::npos ) {
|
|
|
|
return { false, "Invalid target" };
|
2021-07-11 12:43:51 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !FSLib::exists( target_dir ) ) {
|
|
|
|
FSLib::createDirectoryFull( target_dir );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
return { FSLib::rename( path, target_dir + FSLib::dir_divisor +
|
|
|
|
FSLib::getFileName( path ) ),
|
|
|
|
"Library error" };
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string moveJson( const std::string &path, uint64_t target_id,
|
|
|
|
const std::string &containing_dir ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
std::ostringstream res;
|
|
|
|
res << "{\n \"success\": ";
|
2022-03-11 23:25:10 +00:00
|
|
|
auto move_result = move( path, target_id, containing_dir );
|
|
|
|
if ( move_result.first ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
res << "true";
|
|
|
|
} else {
|
|
|
|
res << "false";
|
|
|
|
}
|
2022-02-26 10:33:17 +00:00
|
|
|
res << ",\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !move_result.first ) {
|
|
|
|
res << " \"error\": \"" << safeJson( move_result.second ) << "\"\n";
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
res << "}";
|
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void moveRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( !verifyLogin( session, doc ) ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-07-09 16:28:16 +00:00
|
|
|
std::string containing_dir;
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "path" ) == doc.MemberEnd() ||
|
|
|
|
!doc["path"].IsString() ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
// TODO validate path, also validate against config
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( "ERROR: Invalid path!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
uint64_t id = 0;
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "target_id" ) == doc.MemberEnd() ||
|
|
|
|
!doc["target_id"].IsUint64() ||
|
|
|
|
( id = doc["target_id"].GetUint64() ) >=
|
|
|
|
cfg.getTargetPaths().size() ) {
|
|
|
|
sendResponse( "ERROR: Invalid target_id!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "containing_dir" ) != doc.MemberEnd() &&
|
|
|
|
doc["containing_dir"].IsString() ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
containing_dir = doc["containing_dir"].GetString();
|
|
|
|
}
|
|
|
|
std::string path = doc["path"].GetString();
|
|
|
|
// TODO correct response code
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( moveJson( path, id, containing_dir ), 200, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::pair< bool, std::string > remove( std::string path ) {
|
|
|
|
if ( path[0] != '/' ) {
|
2021-07-09 16:28:16 +00:00
|
|
|
path = cfg.getSourcePath() + "/" + path;
|
|
|
|
}
|
2021-07-11 12:43:51 +00:00
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
auto canon_path = FSLib::canonical( path );
|
|
|
|
if ( canon_path.substr( 0, cfg.getSourcePath().length() ) !=
|
|
|
|
cfg.getSourcePath() ) {
|
|
|
|
return { false, "Invalid path" };
|
2021-07-11 12:43:51 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !FSLib::exists( path ) ) {
|
|
|
|
return { false, "Source doesn't exist" };
|
2021-07-09 16:28:16 +00:00
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
return { FSLib::deleteFile( path ), "Library error" };
|
2021-07-09 16:28:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string removeJson( const std::string &path ) {
|
2021-07-09 16:28:16 +00:00
|
|
|
std::ostringstream res;
|
|
|
|
res << "{\n \"success\": ";
|
2022-03-11 23:25:10 +00:00
|
|
|
auto remove_result = remove( path );
|
|
|
|
if ( remove_result.first ) {
|
2021-07-09 16:28:16 +00:00
|
|
|
res << "true";
|
|
|
|
} else {
|
|
|
|
res << "false";
|
|
|
|
}
|
2022-02-26 10:33:17 +00:00
|
|
|
res << ",\n";
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( !remove_result.first ) {
|
|
|
|
res << " \"error\": \"" << safeJson( remove_result.second ) << "\"\n";
|
2021-07-09 16:28:16 +00:00
|
|
|
}
|
|
|
|
res << "}";
|
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void removeRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( !verifyLogin( session, doc ) ) {
|
2021-07-09 16:28:16 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "path" ) == doc.MemberEnd() ||
|
|
|
|
!doc["path"].IsString() ) {
|
2021-07-09 16:28:16 +00:00
|
|
|
// TODO validate path, also validate against config
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( "ERROR: Invalid path!", 401, session );
|
2021-07-09 16:28:16 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
std::string path = doc["path"].GetString();
|
|
|
|
// TODO correct response code
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( removeJson( path ), 200, session );
|
2021-07-09 16:28:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
std::string loginJson( const std::string &user ) {
|
2021-07-10 20:48:31 +00:00
|
|
|
std::ostringstream res;
|
2022-03-11 23:25:10 +00:00
|
|
|
res << "{\n \"token\": \"" << createLoginToken( user ) << "\"\n}";
|
2021-07-10 20:48:31 +00:00
|
|
|
return res.str();
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void loginRest( const std::shared_ptr< restbed::Session > &session,
|
|
|
|
rapidjson::GenericDocument< rapidjson::UTF8<> > &doc ) {
|
|
|
|
if ( doc.FindMember( "user" ) == doc.MemberEnd() ||
|
|
|
|
!doc["user"].IsString() ) {
|
|
|
|
sendResponse( "ERROR: Invalid user!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( doc.FindMember( "password" ) == doc.MemberEnd() ||
|
|
|
|
!doc["password"].IsString() ) {
|
|
|
|
sendResponse( "ERROR: Invalid password!", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
return;
|
|
|
|
}
|
2021-07-09 16:28:16 +00:00
|
|
|
const auto *user = doc["user"].GetString();
|
|
|
|
const auto *password = doc["password"].GetString();
|
2021-07-08 05:41:34 +00:00
|
|
|
bool valid = false;
|
2022-03-11 23:25:10 +00:00
|
|
|
for ( const auto &user_cfg : cfg.getUsers() ) {
|
|
|
|
if ( user_cfg.first == user ) {
|
2021-07-08 05:41:34 +00:00
|
|
|
valid = user_cfg.second == password;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2022-03-11 23:25:10 +00:00
|
|
|
if ( valid ) {
|
|
|
|
sendResponse( loginJson( user ), 200, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
} else {
|
2022-03-11 23:25:10 +00:00
|
|
|
sendResponse( "Invalid user/password", 401, session );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getOptionsCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, getOptionsRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getCustomKeysCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, getCustomKeysRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getFieldOptionsCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, getFieldOptionsRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getFieldDefaultCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, getFieldDefaultRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void renameCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, renamePathRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getFilesCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, getFilesRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getTargetsCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, getTargetsRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void getTargetDirectoriesCall(
|
|
|
|
const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, getTargetDirectoriesRest );
|
2021-07-09 16:28:16 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void moveCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, moveRest );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
void removeCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, removeRest );
|
|
|
|
}
|
|
|
|
|
|
|
|
void loginCall( const std::shared_ptr< restbed::Session > &session ) {
|
|
|
|
performPostFunc( session, loginRest );
|
|
|
|
}
|
2021-07-08 05:41:34 +00:00
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
int main( int argc, char **argv ) {
|
|
|
|
cfg.readConfiguration( "/etc/renameserver/main.cfg" );
|
|
|
|
|
|
|
|
libraries = getLibraries( cfg.getLibraries() );
|
|
|
|
for ( auto &library : libraries ) {
|
|
|
|
library.init( library.config );
|
2021-07-08 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
restbed::Service service;
|
|
|
|
|
2022-02-27 18:24:35 +00:00
|
|
|
auto get_libraries = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
get_libraries->set_path( "/get_libraries" );
|
2022-02-27 18:24:35 +00:00
|
|
|
get_libraries->set_method_handler( "GET", getLibrariesRest );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( get_libraries );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto search = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
search->set_path( "/search" );
|
2021-07-08 05:41:34 +00:00
|
|
|
search->set_method_handler( "POST", getOptionsCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( search );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto custom_fields = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
custom_fields->set_path( "/get_custom_fields" );
|
2021-07-08 05:41:34 +00:00
|
|
|
custom_fields->set_method_handler( "POST", getCustomKeysCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( custom_fields );
|
|
|
|
|
|
|
|
auto field_options = std::make_shared< restbed::Resource >();
|
|
|
|
field_options->set_path( "/get_field_options" );
|
|
|
|
field_options->set_method_handler( "POST", getFieldOptionsCall );
|
|
|
|
service.publish( field_options );
|
|
|
|
|
|
|
|
auto field_default = std::make_shared< restbed::Resource >();
|
|
|
|
field_default->set_path( "/get_field_default" );
|
|
|
|
field_default->set_method_handler( "POST", getFieldDefaultCall );
|
|
|
|
service.publish( field_default );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto rename_path = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
rename_path->set_path( "/rename" );
|
2021-07-08 05:41:34 +00:00
|
|
|
rename_path->set_method_handler( "POST", renameCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( rename_path );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto get_files = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
get_files->set_path( "/get_files" );
|
2021-07-08 05:41:34 +00:00
|
|
|
get_files->set_method_handler( "POST", getFilesCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( get_files );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto get_targets = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
get_targets->set_path( "/get_targets" );
|
2021-07-08 05:41:34 +00:00
|
|
|
get_targets->set_method_handler( "POST", getTargetsCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( get_targets );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto get_target_directories = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
get_target_directories->set_path( "/get_target_directories" );
|
|
|
|
get_target_directories->set_method_handler( "POST",
|
|
|
|
getTargetDirectoriesCall );
|
|
|
|
service.publish( get_target_directories );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto move = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
move->set_path( "/move" );
|
2021-07-08 05:41:34 +00:00
|
|
|
move->set_method_handler( "POST", moveCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( move );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
2021-07-09 16:28:16 +00:00
|
|
|
auto remove = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
remove->set_path( "/remove" );
|
2021-07-09 16:28:16 +00:00
|
|
|
remove->set_method_handler( "POST", removeCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( remove );
|
2021-07-09 16:28:16 +00:00
|
|
|
|
2021-07-08 05:41:34 +00:00
|
|
|
auto login = std::make_shared< restbed::Resource >();
|
2022-03-11 23:25:10 +00:00
|
|
|
login->set_path( "/login" );
|
2021-07-08 05:41:34 +00:00
|
|
|
login->set_method_handler( "POST", loginCall );
|
2022-03-11 23:25:10 +00:00
|
|
|
service.publish( login );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
auto settings = std::make_shared< restbed::Settings >();
|
2022-03-11 23:25:10 +00:00
|
|
|
settings->set_port( 1984 );
|
2021-07-08 05:41:34 +00:00
|
|
|
settings->set_default_header( "Connection", "close" );
|
|
|
|
|
2022-03-11 23:25:10 +00:00
|
|
|
service.start( settings );
|
2021-07-08 05:41:34 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|